Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
700cbcec28
|
@@ -28,7 +28,7 @@ struct NotificationExtensionState: HeadlessDamusState {
|
|||||||
self.settings = UserSettingsStore()
|
self.settings = UserSettingsStore()
|
||||||
|
|
||||||
self.contacts = Contacts(our_pubkey: keypair.pubkey)
|
self.contacts = Contacts(our_pubkey: keypair.pubkey)
|
||||||
self.mutelist_manager = MutelistManager(user_keypair: keypair)
|
self.mutelist_manager = MutelistManager()
|
||||||
self.keypair = keypair
|
self.keypair = keypair
|
||||||
self.profiles = Profiles(ndb: ndb)
|
self.profiles = Profiles(ndb: ndb)
|
||||||
self.zaps = Zaps(our_pubkey: keypair.pubkey)
|
self.zaps = Zaps(our_pubkey: keypair.pubkey)
|
||||||
|
|||||||
@@ -40,32 +40,15 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show notification details that match mute list.
|
guard should_display_notification(state: state, event: nostr_event) else {
|
||||||
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
|
|
||||||
if state.mutelist_manager.is_event_muted(nostr_event) {
|
|
||||||
// We cannot really suppress muted notifications until we have the notification supression entitlement.
|
|
||||||
// The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details
|
|
||||||
let content = UNMutableNotificationContent()
|
|
||||||
content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted")
|
|
||||||
content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it")
|
|
||||||
content.sound = UNNotificationSound.default
|
|
||||||
contentHandler(content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard should_display_notification(state: state, event: nostr_event, mode: .push) else {
|
|
||||||
// We should not display notification for this event. Suppress notification.
|
// We should not display notification for this event. Suppress notification.
|
||||||
// contentHandler(UNNotificationContent())
|
contentHandler(UNNotificationContent())
|
||||||
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
|
||||||
contentHandler(request.content)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else {
|
guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else {
|
||||||
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
||||||
// contentHandler(UNNotificationContent())
|
contentHandler(UNNotificationContent())
|
||||||
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
|
||||||
contentHandler(request.content)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataTypes</key>
|
|
||||||
<array/>
|
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>1C8F.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>C617.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyCollectedDataTypes</key>
|
|
||||||
<array/>
|
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>1C8F.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
|
||||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
||||||
<array>
|
|
||||||
<string>C617.1</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
+107
-135
@@ -20,7 +20,11 @@
|
|||||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
|
||||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
||||||
|
3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5E47C42A4A6CF400C0D090 /* Trie.swift */; };
|
||||||
|
3A5E47C72A4A76C800C0D090 /* TrieTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5E47C62A4A76C800C0D090 /* TrieTests.swift */; };
|
||||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
|
||||||
|
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */; };
|
||||||
|
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */; };
|
||||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||||
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
||||||
@@ -94,7 +98,6 @@
|
|||||||
4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||||
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
||||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
||||||
4C2D34412BDAF1B300F9FB44 /* NIP10Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */; };
|
|
||||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
|
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
|
||||||
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
|
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
|
||||||
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; };
|
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; };
|
||||||
@@ -132,6 +135,7 @@
|
|||||||
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
||||||
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
||||||
4C363A9A28283854006E126D /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; };
|
4C363A9A28283854006E126D /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; };
|
||||||
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9B282838B9006E126D /* EventRef.swift */; };
|
||||||
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9D2828A822006E126D /* ReplyTests.swift */; };
|
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9D2828A822006E126D /* ReplyTests.swift */; };
|
||||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9F2828A8DD006E126D /* LikeTests.swift */; };
|
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9F2828A8DD006E126D /* LikeTests.swift */; };
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
||||||
@@ -170,7 +174,6 @@
|
|||||||
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */; };
|
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */; };
|
||||||
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
|
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
|
||||||
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
|
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
|
||||||
4C45E5022BED4D000025A428 /* ThreadReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C45E5012BED4D000025A428 /* ThreadReply.swift */; };
|
|
||||||
4C463CBF2B960B96008A8C36 /* PurpleBackdrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C463CBE2B960B96008A8C36 /* PurpleBackdrop.swift */; };
|
4C463CBF2B960B96008A8C36 /* PurpleBackdrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C463CBE2B960B96008A8C36 /* PurpleBackdrop.swift */; };
|
||||||
4C4793012A993CDA00489948 /* mdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793002A993B9A00489948 /* mdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
4C4793012A993CDA00489948 /* mdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793002A993B9A00489948 /* mdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
||||||
4C4793042A993DC000489948 /* midl.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793032A993DB900489948 /* midl.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
4C4793042A993DC000489948 /* midl.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C4793032A993DB900489948 /* midl.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
||||||
@@ -244,7 +247,6 @@
|
|||||||
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
||||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
||||||
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C45E5012BED4D000025A428 /* ThreadReply.swift */; };
|
|
||||||
4C9054852A6AEAA000811EEC /* NdbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054842A6AEAA000811EEC /* NdbTests.swift */; };
|
4C9054852A6AEAA000811EEC /* NdbTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054842A6AEAA000811EEC /* NdbTests.swift */; };
|
||||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||||
@@ -280,6 +282,7 @@
|
|||||||
4CA927632A290EB10098A105 /* EventTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927622A290EB10098A105 /* EventTop.swift */; };
|
4CA927632A290EB10098A105 /* EventTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927622A290EB10098A105 /* EventTop.swift */; };
|
||||||
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927642A290F1A0098A105 /* TimeDot.swift */; };
|
4CA927652A290F1A0098A105 /* TimeDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927642A290F1A0098A105 /* TimeDot.swift */; };
|
||||||
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927662A290F8B0098A105 /* RelativeTime.swift */; };
|
4CA927672A290F8B0098A105 /* RelativeTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927662A290F8B0098A105 /* RelativeTime.swift */; };
|
||||||
|
4CA9276A2A290FC00098A105 /* ContextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA927692A290FC00098A105 /* ContextButton.swift */; };
|
||||||
4CA9276C2A2910D10098A105 /* ReplyPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276B2A2910D10098A105 /* ReplyPart.swift */; };
|
4CA9276C2A2910D10098A105 /* ReplyPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA9276B2A2910D10098A105 /* ReplyPart.swift */; };
|
||||||
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
||||||
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
||||||
@@ -316,6 +319,7 @@
|
|||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
|
||||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; };
|
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; };
|
||||||
|
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
|
||||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
|
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
|
||||||
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||||
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; };
|
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; };
|
||||||
@@ -460,8 +464,6 @@
|
|||||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
||||||
D72E12782BEED22500F4F781 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12772BEED22400F4F781 /* Array.swift */; };
|
|
||||||
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
|
|
||||||
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
||||||
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
|
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
|
||||||
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
|
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
|
||||||
@@ -484,7 +486,6 @@
|
|||||||
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
|
||||||
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
|
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
|
||||||
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
|
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
|
||||||
D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; };
|
|
||||||
D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; };
|
D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; };
|
||||||
D76874F32AE3632B00FB0F68 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; };
|
D76874F32AE3632B00FB0F68 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; };
|
||||||
D77BFA0B2AE3051200621634 /* ProfileActionSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */; };
|
D77BFA0B2AE3051200621634 /* ProfileActionSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */; };
|
||||||
@@ -552,6 +553,7 @@
|
|||||||
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
||||||
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
||||||
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
||||||
|
D7CCFC0E2B0587C300323D86 /* EventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9B282838B9006E126D /* EventRef.swift */; };
|
||||||
D7CCFC0F2B0587F600323D86 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
D7CCFC0F2B0587F600323D86 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
||||||
D7CCFC102B05880F00323D86 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
D7CCFC102B05880F00323D86 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
||||||
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; };
|
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; };
|
||||||
@@ -611,7 +613,6 @@
|
|||||||
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; };
|
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; };
|
||||||
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
||||||
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
||||||
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
|
||||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||||
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
@@ -637,9 +638,6 @@
|
|||||||
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
|
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
|
||||||
D7EDED342B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
|
D7EDED342B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
|
||||||
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||||
D7FB14222BE5970000398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */; };
|
|
||||||
D7FB14252BE5A9A800398331 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */; };
|
|
||||||
D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */; };
|
|
||||||
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
|
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
|
||||||
E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; };
|
E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; };
|
||||||
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
|
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
|
||||||
@@ -741,14 +739,13 @@
|
|||||||
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesView.swift; sourceTree = "<group>"; };
|
3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesView.swift; sourceTree = "<group>"; };
|
||||||
3A47CB772BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
|
||||||
3A47CB782BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
|
||||||
3A47CB792BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
|
||||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
||||||
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A5CAE1F298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A5CAE1F298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
|
3A5E47C42A4A6CF400C0D090 /* Trie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
|
||||||
|
3A5E47C62A4A76C800C0D090 /* TrieTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrieTests.swift; sourceTree = "<group>"; };
|
||||||
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
@@ -762,6 +759,8 @@
|
|||||||
3A8624DA299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A8624DA299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A8624DB299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A8624DB299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtil.swift; sourceTree = "<group>"; };
|
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtil.swift; sourceTree = "<group>"; };
|
||||||
|
3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSearchCache.swift; sourceTree = "<group>"; };
|
||||||
|
3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearchCacheTests.swift; sourceTree = "<group>"; };
|
||||||
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
@@ -771,9 +770,6 @@
|
|||||||
3A96D41A298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3A96D41A298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3A96D41B298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A96D41B298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A96D41C298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A96D41C298DA94500388A2A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A994C4C2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
|
||||||
3A994C4D2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
|
||||||
3A994C4E2BE5B9370019F632 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
|
||||||
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
||||||
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
||||||
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -885,7 +881,6 @@
|
|||||||
4C2B10272A7B0F5C008AA43E /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
4C2B10272A7B0F5C008AA43E /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
||||||
4C2B7BF12A71B6540049DEE7 /* Id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
|
4C2B7BF12A71B6540049DEE7 /* Id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
|
||||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
||||||
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP10Tests.swift; sourceTree = "<group>"; };
|
|
||||||
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||||
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
|
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
|
||||||
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; };
|
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; };
|
||||||
@@ -921,6 +916,7 @@
|
|||||||
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
||||||
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
||||||
4C363A9928283854006E126D /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
4C363A9928283854006E126D /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
||||||
|
4C363A9B282838B9006E126D /* EventRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventRef.swift; sourceTree = "<group>"; };
|
||||||
4C363A9D2828A822006E126D /* ReplyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyTests.swift; sourceTree = "<group>"; };
|
4C363A9D2828A822006E126D /* ReplyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyTests.swift; sourceTree = "<group>"; };
|
||||||
4C363A9F2828A8DD006E126D /* LikeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeTests.swift; sourceTree = "<group>"; };
|
4C363A9F2828A8DD006E126D /* LikeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeTests.swift; sourceTree = "<group>"; };
|
||||||
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||||
@@ -988,7 +984,6 @@
|
|||||||
4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicesView.swift; sourceTree = "<group>"; };
|
4C3EA67C28FFBBA200C48A62 /* InvoicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicesView.swift; sourceTree = "<group>"; };
|
||||||
4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceView.swift; sourceTree = "<group>"; };
|
4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceView.swift; sourceTree = "<group>"; };
|
||||||
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
|
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
|
||||||
4C45E5012BED4D000025A428 /* ThreadReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadReply.swift; sourceTree = "<group>"; };
|
|
||||||
4C463CBE2B960B96008A8C36 /* PurpleBackdrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleBackdrop.swift; sourceTree = "<group>"; };
|
4C463CBE2B960B96008A8C36 /* PurpleBackdrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleBackdrop.swift; sourceTree = "<group>"; };
|
||||||
4C478E242A9932C100489948 /* Ndb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ndb.swift; sourceTree = "<group>"; };
|
4C478E242A9932C100489948 /* Ndb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ndb.swift; sourceTree = "<group>"; };
|
||||||
4C478E262A99353500489948 /* threadpool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = threadpool.h; sourceTree = "<group>"; };
|
4C478E262A99353500489948 /* threadpool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = threadpool.h; sourceTree = "<group>"; };
|
||||||
@@ -1197,6 +1192,7 @@
|
|||||||
4CA927622A290EB10098A105 /* EventTop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTop.swift; sourceTree = "<group>"; };
|
4CA927622A290EB10098A105 /* EventTop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTop.swift; sourceTree = "<group>"; };
|
||||||
4CA927642A290F1A0098A105 /* TimeDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeDot.swift; sourceTree = "<group>"; };
|
4CA927642A290F1A0098A105 /* TimeDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeDot.swift; sourceTree = "<group>"; };
|
||||||
4CA927662A290F8B0098A105 /* RelativeTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTime.swift; sourceTree = "<group>"; };
|
4CA927662A290F8B0098A105 /* RelativeTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelativeTime.swift; sourceTree = "<group>"; };
|
||||||
|
4CA927692A290FC00098A105 /* ContextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextButton.swift; sourceTree = "<group>"; };
|
||||||
4CA9276B2A2910D10098A105 /* ReplyPart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyPart.swift; sourceTree = "<group>"; };
|
4CA9276B2A2910D10098A105 /* ReplyPart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyPart.swift; sourceTree = "<group>"; };
|
||||||
4CA9276D2A2A5D110098A105 /* wasm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wasm.h; sourceTree = "<group>"; };
|
4CA9276D2A2A5D110098A105 /* wasm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wasm.h; sourceTree = "<group>"; };
|
||||||
4CA9276E2A2A5D110098A105 /* wasm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wasm.c; sourceTree = "<group>"; };
|
4CA9276E2A2A5D110098A105 /* wasm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wasm.c; sourceTree = "<group>"; };
|
||||||
@@ -1240,6 +1236,7 @@
|
|||||||
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
||||||
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
|
||||||
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; };
|
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; };
|
||||||
|
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
|
||||||
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
|
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
|
||||||
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
||||||
4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; };
|
4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1386,8 +1383,6 @@
|
|||||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
||||||
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
||||||
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
||||||
D72E12772BEED22400F4F781 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
|
||||||
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
|
|
||||||
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
||||||
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
|
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
|
||||||
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
|
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1401,7 +1396,6 @@
|
|||||||
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
|
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
|
||||||
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
|
||||||
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
|
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
|
||||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = "<group>"; };
|
|
||||||
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
|
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
|
||||||
D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZapLinkView.swift; sourceTree = "<group>"; };
|
D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZapLinkView.swift; sourceTree = "<group>"; };
|
||||||
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionSheetView.swift; sourceTree = "<group>"; };
|
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionSheetView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1430,16 +1424,12 @@
|
|||||||
D7CB5D5E2B11770C00AD4105 /* FollowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowState.swift; sourceTree = "<group>"; };
|
D7CB5D5E2B11770C00AD4105 /* FollowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowState.swift; sourceTree = "<group>"; };
|
||||||
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
||||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
||||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
|
|
||||||
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
||||||
D7EDED202B117DCA0018B19C /* SequenceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceUtils.swift; sourceTree = "<group>"; };
|
D7EDED202B117DCA0018B19C /* SequenceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceUtils.swift; sourceTree = "<group>"; };
|
||||||
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
|
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
|
||||||
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
|
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
|
||||||
D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
|
||||||
D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
|
||||||
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAidSettingsView.swift; sourceTree = "<group>"; };
|
|
||||||
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; };
|
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; };
|
||||||
E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
|
E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
|
||||||
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
|
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -1613,6 +1603,7 @@
|
|||||||
4C363A93282704FA006E126D /* Post.swift */,
|
4C363A93282704FA006E126D /* Post.swift */,
|
||||||
4C363A952827096D006E126D /* PostBlock.swift */,
|
4C363A952827096D006E126D /* PostBlock.swift */,
|
||||||
4C363A9928283854006E126D /* Reply.swift */,
|
4C363A9928283854006E126D /* Reply.swift */,
|
||||||
|
4C363A9B282838B9006E126D /* EventRef.swift */,
|
||||||
4C363AA328296DEE006E126D /* SearchModel.swift */,
|
4C363AA328296DEE006E126D /* SearchModel.swift */,
|
||||||
0E8A4BB62AE4359200065E81 /* NostrFilter+Hashable.swift */,
|
0E8A4BB62AE4359200065E81 /* NostrFilter+Hashable.swift */,
|
||||||
4C3AC79A28306D7B00E1F516 /* Contacts.swift */,
|
4C3AC79A28306D7B00E1F516 /* Contacts.swift */,
|
||||||
@@ -1640,6 +1631,8 @@
|
|||||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */,
|
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */,
|
||||||
4C7D09772A0B0CC900943473 /* WalletModel.swift */,
|
4C7D09772A0B0CC900943473 /* WalletModel.swift */,
|
||||||
3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */,
|
3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */,
|
||||||
|
3A5E47C42A4A6CF400C0D090 /* Trie.swift */,
|
||||||
|
3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */,
|
||||||
D723C38D2AB8D83400065664 /* ContentFilters.swift */,
|
D723C38D2AB8D83400065664 /* ContentFilters.swift */,
|
||||||
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */,
|
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */,
|
||||||
D7CB5D3D2B116DAD00AD4105 /* NotificationsManager.swift */,
|
D7CB5D3D2B116DAD00AD4105 /* NotificationsManager.swift */,
|
||||||
@@ -1655,7 +1648,6 @@
|
|||||||
D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */,
|
D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */,
|
||||||
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
||||||
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
||||||
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
|
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1728,7 +1720,6 @@
|
|||||||
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
4C1A9A2629DDE31900516EAC /* TranslationSettingsView.swift */,
|
||||||
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
|
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
|
||||||
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
|
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
|
||||||
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */,
|
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1784,14 +1775,6 @@
|
|||||||
path = flatbuffers;
|
path = flatbuffers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
4C45E5002BED4CE10025A428 /* NIP10 */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4C45E5012BED4D000025A428 /* ThreadReply.swift */,
|
|
||||||
);
|
|
||||||
path = NIP10;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
4C478E2A2A9935D300489948 /* bindings */ = {
|
4C478E2A2A9935D300489948 /* bindings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -2301,6 +2284,7 @@
|
|||||||
4CA927622A290EB10098A105 /* EventTop.swift */,
|
4CA927622A290EB10098A105 /* EventTop.swift */,
|
||||||
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
||||||
4CA927662A290F8B0098A105 /* RelativeTime.swift */,
|
4CA927662A290F8B0098A105 /* RelativeTime.swift */,
|
||||||
|
4CA927692A290FC00098A105 /* ContextButton.swift */,
|
||||||
4CA9276B2A2910D10098A105 /* ReplyPart.swift */,
|
4CA9276B2A2910D10098A105 /* ReplyPart.swift */,
|
||||||
5C7389B02B6EFA7100781E0A /* ProxyView.swift */,
|
5C7389B02B6EFA7100781E0A /* ProxyView.swift */,
|
||||||
);
|
);
|
||||||
@@ -2411,6 +2395,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */,
|
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */,
|
||||||
|
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */,
|
||||||
4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */,
|
4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */,
|
||||||
);
|
);
|
||||||
path = Search;
|
path = Search;
|
||||||
@@ -2459,7 +2444,6 @@
|
|||||||
4CE6DEDA27F7A08100C66700 = {
|
4CE6DEDA27F7A08100C66700 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D7FB14212BE5970000398331 /* PrivacyInfo.xcprivacy */,
|
|
||||||
4C32B9362A9AD44700DC3548 /* flatbuffers */,
|
4C32B9362A9AD44700DC3548 /* flatbuffers */,
|
||||||
4C9054862A6AEB4500811EEC /* nostrdb */,
|
4C9054862A6AEB4500811EEC /* nostrdb */,
|
||||||
4C19AE4A2A5CEF7C00C90DB7 /* nostrscript */,
|
4C19AE4A2A5CEF7C00C90DB7 /* nostrscript */,
|
||||||
@@ -2490,7 +2474,6 @@
|
|||||||
4CE6DEE527F7A08100C66700 /* damus */ = {
|
4CE6DEE527F7A08100C66700 /* damus */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C45E5002BED4CE10025A428 /* NIP10 */,
|
|
||||||
4C1D4FB32A7967990024F453 /* build-git-hash.txt */,
|
4C1D4FB32A7967990024F453 /* build-git-hash.txt */,
|
||||||
4CA3529C2A76AE47003BB08B /* Notify */,
|
4CA3529C2A76AE47003BB08B /* Notify */,
|
||||||
4CC14FEC2A73FC9A007AEB17 /* Types */,
|
4CC14FEC2A73FC9A007AEB17 /* Types */,
|
||||||
@@ -2549,6 +2532,8 @@
|
|||||||
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
|
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
|
||||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
|
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */,
|
||||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */,
|
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */,
|
||||||
|
3A5E47C62A4A76C800C0D090 /* TrieTests.swift */,
|
||||||
|
3A90B1822A4EA3C600000D94 /* UserSearchCacheTests.swift */,
|
||||||
4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */,
|
4C4F14A62A2A61A30045A0B9 /* NostrScriptTests.swift */,
|
||||||
4C19AE542A5D977400C90DB7 /* HashtagTests.swift */,
|
4C19AE542A5D977400C90DB7 /* HashtagTests.swift */,
|
||||||
3AAC7A012A60FE72002B50DF /* LocalizationUtilTests.swift */,
|
3AAC7A012A60FE72002B50DF /* LocalizationUtilTests.swift */,
|
||||||
@@ -2564,9 +2549,6 @@
|
|||||||
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
|
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
|
||||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
|
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
|
||||||
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
||||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */,
|
|
||||||
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
|
||||||
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */,
|
|
||||||
);
|
);
|
||||||
path = damusTests;
|
path = damusTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2691,7 +2673,6 @@
|
|||||||
children = (
|
children = (
|
||||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
||||||
D72E12772BEED22400F4F781 /* Array.swift */,
|
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2768,7 +2749,6 @@
|
|||||||
D79C4C182AFEB061003A41B4 /* Info.plist */,
|
D79C4C182AFEB061003A41B4 /* Info.plist */,
|
||||||
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */,
|
D70A3B162B02DCE5008BD568 /* NotificationFormatter.swift */,
|
||||||
D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */,
|
D74AAFC42B1538DE006CF0F4 /* NotificationExtensionState.swift */,
|
||||||
D7FB14242BE5A9A800398331 /* PrivacyInfo.xcprivacy */,
|
|
||||||
);
|
);
|
||||||
path = DamusNotificationService;
|
path = DamusNotificationService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2951,7 +2931,6 @@
|
|||||||
"es-419",
|
"es-419",
|
||||||
"es-ES",
|
"es-ES",
|
||||||
fa,
|
fa,
|
||||||
fi,
|
|
||||||
fr,
|
fr,
|
||||||
"hu-HU",
|
"hu-HU",
|
||||||
id,
|
id,
|
||||||
@@ -2966,7 +2945,6 @@
|
|||||||
ru,
|
ru,
|
||||||
"sv-SE",
|
"sv-SE",
|
||||||
sw,
|
sw,
|
||||||
th,
|
|
||||||
"tr-TR",
|
"tr-TR",
|
||||||
uk,
|
uk,
|
||||||
vi,
|
vi,
|
||||||
@@ -3001,7 +2979,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4C1D4FB42A7967990024F453 /* build-git-hash.txt in Resources */,
|
4C1D4FB42A7967990024F453 /* build-git-hash.txt in Resources */,
|
||||||
D7FB14222BE5970000398331 /* PrivacyInfo.xcprivacy in Resources */,
|
|
||||||
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */,
|
3ACB685F297633BC00C46468 /* Localizable.strings in Resources */,
|
||||||
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
|
4CE6DEEE27F7A08200C66700 /* Preview Assets.xcassets in Resources */,
|
||||||
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */,
|
3ACB685C297633BC00C46468 /* InfoPlist.strings in Resources */,
|
||||||
@@ -3035,7 +3012,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D7FB14252BE5A9A800398331 /* PrivacyInfo.xcprivacy in Resources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -3097,6 +3073,7 @@
|
|||||||
4C32B9572A9AD44700DC3548 /* Root.swift in Sources */,
|
4C32B9572A9AD44700DC3548 /* Root.swift in Sources */,
|
||||||
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
|
||||||
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */,
|
504323A72A34915F006AE6DC /* RelayModel.swift in Sources */,
|
||||||
|
4CA9276A2A290FC00098A105 /* ContextButton.swift in Sources */,
|
||||||
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
|
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
|
||||||
4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */,
|
4C32B9542A9AD44700DC3548 /* FlatBuffersUtils.swift in Sources */,
|
||||||
D7EDED1C2B1178FE0018B19C /* NoteContent.swift in Sources */,
|
D7EDED1C2B1178FE0018B19C /* NoteContent.swift in Sources */,
|
||||||
@@ -3127,7 +3104,6 @@
|
|||||||
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
||||||
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
||||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
|
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
|
||||||
D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */,
|
|
||||||
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
|
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
|
||||||
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||||
@@ -3172,6 +3148,7 @@
|
|||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
D74AAFCF2B155D8C006CF0F4 /* ZapDataModel.swift in Sources */,
|
||||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
||||||
|
3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */,
|
||||||
D7100C562B76F8E600C59298 /* PurpleViewPrimitives.swift in Sources */,
|
D7100C562B76F8E600C59298 /* PurpleViewPrimitives.swift in Sources */,
|
||||||
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */,
|
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */,
|
||||||
D7EDED2E2B128E8A0018B19C /* CollectionExtension.swift in Sources */,
|
D7EDED2E2B128E8A0018B19C /* CollectionExtension.swift in Sources */,
|
||||||
@@ -3226,7 +3203,6 @@
|
|||||||
4C86F7C62A76C51100EC0817 /* AttachedWalletNotify.swift in Sources */,
|
4C86F7C62A76C51100EC0817 /* AttachedWalletNotify.swift in Sources */,
|
||||||
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
||||||
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
|
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
|
||||||
4C45E5022BED4D000025A428 /* ThreadReply.swift in Sources */,
|
|
||||||
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */,
|
D74AAFD42B155ECB006CF0F4 /* Zaps+.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */,
|
4C7D09592A05BEAD00943473 /* KeyboardVisible.swift in Sources */,
|
||||||
@@ -3247,7 +3223,6 @@
|
|||||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||||
D72E12782BEED22500F4F781 /* Array.swift in Sources */,
|
|
||||||
4C198DF529F88D2E004C165C /* ImageMetadata.swift in Sources */,
|
4C198DF529F88D2E004C165C /* ImageMetadata.swift in Sources */,
|
||||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
@@ -3272,6 +3247,7 @@
|
|||||||
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */,
|
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */,
|
||||||
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
||||||
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
|
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
|
||||||
|
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */,
|
||||||
4C463CBF2B960B96008A8C36 /* PurpleBackdrop.swift in Sources */,
|
4C463CBF2B960B96008A8C36 /* PurpleBackdrop.swift in Sources */,
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
||||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
||||||
@@ -3281,7 +3257,6 @@
|
|||||||
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
||||||
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
||||||
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */,
|
|
||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
||||||
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
||||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||||
@@ -3344,6 +3319,7 @@
|
|||||||
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
|
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
|
||||||
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
|
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
|
||||||
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
||||||
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
||||||
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */,
|
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */,
|
||||||
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
|
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
|
||||||
@@ -3391,6 +3367,7 @@
|
|||||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
||||||
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
|
||||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
|
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
|
||||||
|
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */,
|
||||||
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */,
|
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */,
|
||||||
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
|
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
|
||||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||||
@@ -3533,8 +3510,8 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4C2D34412BDAF1B300F9FB44 /* NIP10Tests.swift in Sources */,
|
|
||||||
4C684A572A7FFAE6005E6031 /* UrlTests.swift in Sources */,
|
4C684A572A7FFAE6005E6031 /* UrlTests.swift in Sources */,
|
||||||
|
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
|
||||||
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
|
||||||
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */,
|
||||||
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */,
|
||||||
@@ -3546,7 +3523,6 @@
|
|||||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
||||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
||||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
||||||
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */,
|
|
||||||
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
||||||
@@ -3564,6 +3540,7 @@
|
|||||||
75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */,
|
75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */,
|
||||||
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */,
|
E0E024112B7C19C20075735D /* TranslationTests.swift in Sources */,
|
||||||
F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */,
|
F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */,
|
||||||
|
3A5E47C72A4A76C800C0D090 /* TrieTests.swift in Sources */,
|
||||||
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */,
|
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */,
|
||||||
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
|
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
|
||||||
@@ -3572,7 +3549,6 @@
|
|||||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
|
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
|
||||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
|
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
|
||||||
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
|
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
|
||||||
D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */,
|
|
||||||
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */,
|
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */,
|
||||||
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
|
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
|
||||||
4C684A552A7E91FE005E6031 /* LongPostTests.swift in Sources */,
|
4C684A552A7E91FE005E6031 /* LongPostTests.swift in Sources */,
|
||||||
@@ -3593,10 +3569,10 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4C8FA7242BED58A900798A6A /* ThreadReply.swift in Sources */,
|
|
||||||
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
|
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
|
||||||
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
|
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
|
||||||
D7CB5D552B11758A00AD4105 /* UnmuteThreadNotify.swift in Sources */,
|
D7CB5D552B11758A00AD4105 /* UnmuteThreadNotify.swift in Sources */,
|
||||||
|
D7CCFC0E2B0587C300323D86 /* EventRef.swift in Sources */,
|
||||||
D7CCFC192B058A3F00323D86 /* Block.swift in Sources */,
|
D7CCFC192B058A3F00323D86 /* Block.swift in Sources */,
|
||||||
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */,
|
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */,
|
||||||
D798D2202B08592000234419 /* NdbTagIterator.swift in Sources */,
|
D798D2202B08592000234419 /* NdbTagIterator.swift in Sources */,
|
||||||
@@ -3754,37 +3730,35 @@
|
|||||||
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
|
3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
3AC524F0298C000B00693EBF /* ar */,
|
|
||||||
3AA5E70729B9E84A002701ED /* bg */,
|
|
||||||
3A8624DB299E82BE00BD8BE9 /* cs */,
|
|
||||||
3AB5B86C2986D8A3006599D2 /* de */,
|
|
||||||
3A25EF152992DA5D008ABE69 /* el-GR */,
|
|
||||||
3A2B8B0A296A8982009CC16D /* en-US */,
|
|
||||||
3A5C4575296A879E0032D398 /* es-419 */,
|
3A5C4575296A879E0032D398 /* es-419 */,
|
||||||
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
3A2B8B0A296A8982009CC16D /* en-US */,
|
||||||
3AD5662C29BD2F5300BF77C5 /* fa */,
|
|
||||||
3A47CB792BDA05A200728A7C /* fi */,
|
|
||||||
3A821C4029E819D500B4BCA7 /* fr */,
|
|
||||||
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
|
||||||
3A41E55B299D52BE001FA465 /* id */,
|
|
||||||
3A929C22297F2CF80090925E /* it-IT */,
|
|
||||||
3A66D929299472FA008B44F4 /* ja */,
|
|
||||||
3AD5663229C0DA4B00BF77C5 /* ko */,
|
|
||||||
3A185A06297F2C3800F4BDC0 /* lv-LV */,
|
|
||||||
3A96D41C298DA94500388A2A /* nl */,
|
|
||||||
3A93342B29884CA600D6A8F3 /* pl-PL */,
|
|
||||||
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
|
||||||
3AF6336A29884C6B0005672A /* pt-PT */,
|
|
||||||
3A827A1A299FC69D00C4D171 /* ru */,
|
|
||||||
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
|
||||||
3ABACEC02A5B3ED10037A847 /* sw */,
|
|
||||||
3A994C4C2BE5B9370019F632 /* th */,
|
|
||||||
3AEB8005297CCEA900713A25 /* tr-TR */,
|
3AEB8005297CCEA900713A25 /* tr-TR */,
|
||||||
3AA5E70429B682B3002701ED /* uk */,
|
3A185A06297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A325AC629C9E0B8002BE7ED /* vi */,
|
3A929C22297F2CF80090925E /* it-IT */,
|
||||||
|
3AB5B86C2986D8A3006599D2 /* de */,
|
||||||
|
3AF6336A29884C6B0005672A /* pt-PT */,
|
||||||
|
3A93342B29884CA600D6A8F3 /* pl-PL */,
|
||||||
|
3AC524F0298C000B00693EBF /* ar */,
|
||||||
|
3A96D41C298DA94500388A2A /* nl */,
|
||||||
3A5CAE1F298DC0DB00B5334F /* zh-CN */,
|
3A5CAE1F298DC0DB00B5334F /* zh-CN */,
|
||||||
|
3A25EF152992DA5D008ABE69 /* el-GR */,
|
||||||
|
3A66D929299472FA008B44F4 /* ja */,
|
||||||
|
3A41E55B299D52BE001FA465 /* id */,
|
||||||
|
3A8624DB299E82BE00BD8BE9 /* cs */,
|
||||||
|
3A827A1A299FC69D00C4D171 /* ru */,
|
||||||
3A3040FB29A91F03008A0F29 /* zh-HK */,
|
3A3040FB29A91F03008A0F29 /* zh-HK */,
|
||||||
3A3040FD29A91F31008A0F29 /* zh-TW */,
|
3A3040FD29A91F31008A0F29 /* zh-TW */,
|
||||||
|
3AA5E70429B682B3002701ED /* uk */,
|
||||||
|
3AA5E70729B9E84A002701ED /* bg */,
|
||||||
|
3AD5662C29BD2F5300BF77C5 /* fa */,
|
||||||
|
3AD5663229C0DA4B00BF77C5 /* ko */,
|
||||||
|
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
||||||
|
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
||||||
|
3A325AC629C9E0B8002BE7ED /* vi */,
|
||||||
|
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
||||||
|
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C4029E819D500B4BCA7 /* fr */,
|
||||||
|
3ABACEC02A5B3ED10037A847 /* sw */,
|
||||||
);
|
);
|
||||||
name = Localizable.stringsdict;
|
name = Localizable.stringsdict;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3792,36 +3766,34 @@
|
|||||||
3ACB685A297633BC00C46468 /* InfoPlist.strings */ = {
|
3ACB685A297633BC00C46468 /* InfoPlist.strings */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
3AC524EE298C000B00693EBF /* ar */,
|
|
||||||
3AA5E70529B9E83E002701ED /* bg */,
|
|
||||||
3A8624D9299E82BE00BD8BE9 /* cs */,
|
|
||||||
3AB5B86A2986D8A3006599D2 /* de */,
|
|
||||||
3A25EF132992DA5D008ABE69 /* el-GR */,
|
|
||||||
3ACB685B297633BC00C46468 /* es-419 */,
|
3ACB685B297633BC00C46468 /* es-419 */,
|
||||||
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
|
||||||
3AD5662B29BD2F5300BF77C5 /* fa */,
|
|
||||||
3A47CB772BDA05A200728A7C /* fi */,
|
|
||||||
3A821C3F29E819D500B4BCA7 /* fr */,
|
|
||||||
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
|
||||||
3A41E559299D52BE001FA465 /* id */,
|
|
||||||
3A929C20297F2CF80090925E /* it-IT */,
|
|
||||||
3A66D927299472FA008B44F4 /* ja */,
|
|
||||||
3AD5663329C0DA4B00BF77C5 /* ko */,
|
|
||||||
3A96D41A298DA94500388A2A /* nl */,
|
|
||||||
3A185A04297F2C3800F4BDC0 /* lv-LV */,
|
|
||||||
3A93342929884CA600D6A8F3 /* pl-PL */,
|
|
||||||
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
|
||||||
3AF6336829884C6B0005672A /* pt-PT */,
|
|
||||||
3A827A18299FC69D00C4D171 /* ru */,
|
|
||||||
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
|
||||||
3ABACEBF2A5B3ED10037A847 /* sw */,
|
|
||||||
3A994C4D2BE5B9370019F632 /* th */,
|
|
||||||
3AEB8003297CCEA800713A25 /* tr-TR */,
|
3AEB8003297CCEA800713A25 /* tr-TR */,
|
||||||
3AA5E70329B682AD002701ED /* uk */,
|
3A185A04297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A325AC529C9E0B8002BE7ED /* vi */,
|
3A929C20297F2CF80090925E /* it-IT */,
|
||||||
|
3AB5B86A2986D8A3006599D2 /* de */,
|
||||||
|
3AF6336829884C6B0005672A /* pt-PT */,
|
||||||
|
3A93342929884CA600D6A8F3 /* pl-PL */,
|
||||||
|
3AC524EE298C000B00693EBF /* ar */,
|
||||||
|
3A96D41A298DA94500388A2A /* nl */,
|
||||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */,
|
3A5CAE1D298DC0DB00B5334F /* zh-CN */,
|
||||||
|
3A25EF132992DA5D008ABE69 /* el-GR */,
|
||||||
|
3A66D927299472FA008B44F4 /* ja */,
|
||||||
|
3A41E559299D52BE001FA465 /* id */,
|
||||||
|
3A8624D9299E82BE00BD8BE9 /* cs */,
|
||||||
|
3A827A18299FC69D00C4D171 /* ru */,
|
||||||
3A3040F929A91ED6008A0F29 /* zh-HK */,
|
3A3040F929A91ED6008A0F29 /* zh-HK */,
|
||||||
3A3040FC29A91F31008A0F29 /* zh-TW */,
|
3A3040FC29A91F31008A0F29 /* zh-TW */,
|
||||||
|
3AA5E70329B682AD002701ED /* uk */,
|
||||||
|
3AA5E70529B9E83E002701ED /* bg */,
|
||||||
|
3AD5662B29BD2F5300BF77C5 /* fa */,
|
||||||
|
3AD5663329C0DA4B00BF77C5 /* ko */,
|
||||||
|
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
||||||
|
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
||||||
|
3A325AC529C9E0B8002BE7ED /* vi */,
|
||||||
|
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
||||||
|
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C3F29E819D500B4BCA7 /* fr */,
|
||||||
|
3ABACEBF2A5B3ED10037A847 /* sw */,
|
||||||
);
|
);
|
||||||
name = InfoPlist.strings;
|
name = InfoPlist.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3829,37 +3801,35 @@
|
|||||||
3ACB685D297633BC00C46468 /* Localizable.strings */ = {
|
3ACB685D297633BC00C46468 /* Localizable.strings */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
3AC524EF298C000B00693EBF /* ar */,
|
|
||||||
3AA5E70629B9E844002701ED /* bg */,
|
|
||||||
3A8624DA299E82BE00BD8BE9 /* cs */,
|
|
||||||
3AB5B86B2986D8A3006599D2 /* de */,
|
|
||||||
3A25EF142992DA5D008ABE69 /* el-GR */,
|
|
||||||
3A3040FF29AB02D1008A0F29 /* en-US */,
|
|
||||||
3ACB685E297633BC00C46468 /* es-419 */,
|
3ACB685E297633BC00C46468 /* es-419 */,
|
||||||
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
|
||||||
3AD5662D29BD2F5300BF77C5 /* fa */,
|
|
||||||
3A47CB782BDA05A200728A7C /* fi */,
|
|
||||||
3A821C3E29E819D500B4BCA7 /* fr */,
|
|
||||||
3A41E55A299D52BE001FA465 /* id */,
|
|
||||||
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
|
||||||
3A929C21297F2CF80090925E /* it-IT */,
|
|
||||||
3A66D928299472FA008B44F4 /* ja */,
|
|
||||||
3AD5663129C0DA4B00BF77C5 /* ko */,
|
|
||||||
3A185A05297F2C3800F4BDC0 /* lv-LV */,
|
|
||||||
3A96D41B298DA94500388A2A /* nl */,
|
|
||||||
3A93342A29884CA600D6A8F3 /* pl-PL */,
|
|
||||||
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
|
||||||
3AF6336929884C6B0005672A /* pt-PT */,
|
|
||||||
3A827A19299FC69D00C4D171 /* ru */,
|
|
||||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
|
||||||
3ABACEC12A5B3ED10037A847 /* sw */,
|
|
||||||
3A994C4E2BE5B9370019F632 /* th */,
|
|
||||||
3AEB8004297CCEA800713A25 /* tr-TR */,
|
3AEB8004297CCEA800713A25 /* tr-TR */,
|
||||||
3AA5E70229B682A5002701ED /* uk */,
|
3A185A05297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A325AC429C9E0B8002BE7ED /* vi */,
|
3A929C21297F2CF80090925E /* it-IT */,
|
||||||
|
3AB5B86B2986D8A3006599D2 /* de */,
|
||||||
|
3AF6336929884C6B0005672A /* pt-PT */,
|
||||||
|
3A93342A29884CA600D6A8F3 /* pl-PL */,
|
||||||
|
3AC524EF298C000B00693EBF /* ar */,
|
||||||
|
3A96D41B298DA94500388A2A /* nl */,
|
||||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */,
|
3A5CAE1E298DC0DB00B5334F /* zh-CN */,
|
||||||
|
3A25EF142992DA5D008ABE69 /* el-GR */,
|
||||||
|
3A66D928299472FA008B44F4 /* ja */,
|
||||||
|
3A41E55A299D52BE001FA465 /* id */,
|
||||||
|
3A8624DA299E82BE00BD8BE9 /* cs */,
|
||||||
|
3A827A19299FC69D00C4D171 /* ru */,
|
||||||
3A3040FA29A91EFC008A0F29 /* zh-HK */,
|
3A3040FA29A91EFC008A0F29 /* zh-HK */,
|
||||||
3A3040FE29A91F31008A0F29 /* zh-TW */,
|
3A3040FE29A91F31008A0F29 /* zh-TW */,
|
||||||
|
3A3040FF29AB02D1008A0F29 /* en-US */,
|
||||||
|
3AA5E70229B682A5002701ED /* uk */,
|
||||||
|
3AA5E70629B9E844002701ED /* bg */,
|
||||||
|
3AD5662D29BD2F5300BF77C5 /* fa */,
|
||||||
|
3AD5663129C0DA4B00BF77C5 /* ko */,
|
||||||
|
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
||||||
|
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
||||||
|
3A325AC429C9E0B8002BE7ED /* vi */,
|
||||||
|
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
||||||
|
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C3E29E819D500B4BCA7 /* fr */,
|
||||||
|
3ABACEC12A5B3ED10037A847 /* sw */,
|
||||||
);
|
);
|
||||||
name = Localizable.strings;
|
name = Localizable.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3902,7 +3872,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@@ -3923,7 +3893,7 @@
|
|||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||||
MARKETING_VERSION = 1.9;
|
MARKETING_VERSION = 1.8;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
@@ -3969,7 +3939,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
@@ -3985,7 +3955,7 @@
|
|||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||||
MARKETING_VERSION = 1.9;
|
MARKETING_VERSION = 1.8;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -4031,6 +4001,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 1.9;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
@@ -4080,6 +4051,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
|
MARKETING_VERSION = 1.9;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"originHash" : "c627e27ffbf9762282eabbfa1118e0c13a337c2492a58f81531aa396bcf2d440",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "gsplayer",
|
"identity" : "gsplayer",
|
||||||
@@ -62,5 +63,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 2
|
"version" : 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ struct SearchHeaderView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var SearchText: Text {
|
var SearchText: Text {
|
||||||
Text(described.description)
|
Text(verbatim: described.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -83,9 +83,9 @@ struct SingleCharacterAvatar: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NonImageAvatar {
|
NonImageAvatar {
|
||||||
Text(character)
|
Text(verbatim: character)
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
.mask(Text(character)
|
.mask(Text(verbatim: character)
|
||||||
.font(.largeTitle.bold()))
|
.font(.largeTitle.bold()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ struct UserStatusSheet: View {
|
|||||||
|
|
||||||
Picker(NSLocalizedString("Duration", comment: "Label for profile status expiration duration picker."), selection: $duration) {
|
Picker(NSLocalizedString("Duration", comment: "Label for profile status expiration duration picker."), selection: $duration) {
|
||||||
ForEach(StatusDuration.allCases, id: \.self) { d in
|
ForEach(StatusDuration.allCases, id: \.self) { d in
|
||||||
Text(d.description)
|
Text(verbatim: d.description)
|
||||||
.tag(d)
|
.tag(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ struct SupporterBadge: View {
|
|||||||
.frame(width:size, height:size)
|
.frame(width:size, height:size)
|
||||||
.foregroundStyle(GoldGradient)
|
.foregroundStyle(GoldGradient)
|
||||||
if self.style == .full {
|
if self.style == .full {
|
||||||
let date = format_date(date: purple_account.created_at, time_style: .none)
|
Text(verbatim: format_date(date: purple_account.created_at, time_style: .none))
|
||||||
Text(date)
|
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-41
@@ -59,57 +59,66 @@ func parse_note_content(content: NoteContent) -> Blocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpret_event_refs(tags: TagsSequence) -> ThreadReply? {
|
func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef] {
|
||||||
// migration is long over, lets just do this to fix tests
|
|
||||||
return interpret_event_refs_ndb(tags: tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
func interpret_event_refs_ndb(tags: TagsSequence) -> ThreadReply? {
|
|
||||||
if tags.count == 0 {
|
if tags.count == 0 {
|
||||||
return nil
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// build a set of indices for each event mention
|
||||||
|
let mention_indices = build_mention_indices(blocks, type: .e)
|
||||||
|
|
||||||
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
/// simpler case with no mentions
|
||||||
|
if mention_indices.count == 0 {
|
||||||
|
return interp_event_refs_without_mentions_ndb(tags.note.referenced_noterefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices)
|
||||||
}
|
}
|
||||||
|
|
||||||
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> ThreadReply? {
|
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [EventRef] {
|
||||||
|
|
||||||
|
var count = 0
|
||||||
|
var evrefs: [EventRef] = []
|
||||||
var first: Bool = true
|
var first: Bool = true
|
||||||
var root_id: NoteRef? = nil
|
var first_ref: NoteRef? = nil
|
||||||
var reply_id: NoteRef? = nil
|
|
||||||
var mention: NoteRef? = nil
|
|
||||||
var any_marker: Bool = false
|
|
||||||
|
|
||||||
for ref in ev_tags {
|
for ref in ev_tags {
|
||||||
if let marker = ref.marker {
|
if first {
|
||||||
any_marker = true
|
first_ref = ref
|
||||||
switch marker {
|
evrefs.append(.thread_id(ref))
|
||||||
case .root: root_id = ref
|
first = false
|
||||||
case .reply: reply_id = ref
|
} else {
|
||||||
case .mention: mention = ref
|
|
||||||
}
|
evrefs.append(.reply(ref))
|
||||||
// deprecated form, only activate if we don't have any markers set
|
}
|
||||||
} else if !any_marker {
|
count += 1
|
||||||
if first {
|
}
|
||||||
root_id = ref
|
|
||||||
first = false
|
if let first_ref, count == 1 {
|
||||||
|
let r = first_ref
|
||||||
|
return [.reply_to_root(r)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return evrefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_with_mentions_ndb(tags: TagsSequence, mention_indices: Set<Int>) -> [EventRef] {
|
||||||
|
var mentions: [EventRef] = []
|
||||||
|
var ev_refs: [NoteRef] = []
|
||||||
|
var i: Int = 0
|
||||||
|
|
||||||
|
for tag in tags {
|
||||||
|
if let note_id = NoteRef.from_tag(tag: tag) {
|
||||||
|
if mention_indices.contains(i) {
|
||||||
|
mentions.append(.mention(.noteref(note_id, index: i)))
|
||||||
} else {
|
} else {
|
||||||
reply_id = ref
|
ev_refs.append(note_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// If either reply or root_id is blank while the other is not, then this is
|
var replies = interp_event_refs_without_mentions(ev_refs)
|
||||||
// considered reply-to-root. We should always have a root and reply tag, if they
|
replies.append(contentsOf: mentions)
|
||||||
// are equal this is reply-to-root
|
return replies
|
||||||
if reply_id == nil && root_id != nil {
|
|
||||||
reply_id = root_id
|
|
||||||
} else if root_id == nil && reply_id != nil {
|
|
||||||
root_id = reply_id
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let reply_id, let root_id else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ThreadReply(root: root_id, reply: reply_id, mention: mention.map { m in .noteref(m) })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ struct ContentView: View {
|
|||||||
active_sheet = .onboardingSuggestions
|
active_sheet = .onboardingSuggestions
|
||||||
hasSeenOnboardingSuggestions = true
|
hasSeenOnboardingSuggestions = true
|
||||||
}
|
}
|
||||||
self.appDelegate?.state = damus_state
|
self.appDelegate?.settings = damus_state?.settings
|
||||||
}
|
}
|
||||||
.sheet(item: $active_sheet) { item in
|
.sheet(item: $active_sheet) { item in
|
||||||
switch item {
|
switch item {
|
||||||
@@ -679,7 +679,10 @@ struct ContentView: View {
|
|||||||
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
||||||
|
|
||||||
let settings = UserSettingsStore.globally_load_for(pubkey: pubkey)
|
// dumb stuff needed for property wrappers
|
||||||
|
UserSettingsStore.pubkey = pubkey
|
||||||
|
let settings = UserSettingsStore()
|
||||||
|
UserSettingsStore.shared = settings
|
||||||
|
|
||||||
let new_relay_filters = load_relay_filters(pubkey) == nil
|
let new_relay_filters = load_relay_filters(pubkey) == nil
|
||||||
for relay in bootstrap_relays {
|
for relay in bootstrap_relays {
|
||||||
@@ -699,7 +702,7 @@ struct ContentView: View {
|
|||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
boosts: EventCounter(our_pubkey: pubkey),
|
boosts: EventCounter(our_pubkey: pubkey),
|
||||||
contacts: Contacts(our_pubkey: pubkey),
|
contacts: Contacts(our_pubkey: pubkey),
|
||||||
mutelist_manager: MutelistManager(user_keypair: keypair),
|
mutelist_manager: MutelistManager(),
|
||||||
profiles: Profiles(ndb: ndb),
|
profiles: Profiles(ndb: ndb),
|
||||||
dms: home.dms,
|
dms: home.dms,
|
||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
@@ -832,12 +835,6 @@ func save_last_event(_ ev: NostrEvent, timeline: Timeline) {
|
|||||||
UserDefaults.standard.set(String(ev.created_at), forKey: "last_\(str)_time")
|
UserDefaults.standard.set(String(ev.created_at), forKey: "last_\(str)_time")
|
||||||
}
|
}
|
||||||
|
|
||||||
func save_last_event(_ ev_id: NoteId, created_at: UInt32, timeline: Timeline) {
|
|
||||||
let str = timeline.rawValue
|
|
||||||
UserDefaults.standard.set(ev_id.hex(), forKey: "last_\(str)")
|
|
||||||
UserDefaults.standard.set(String(created_at), forKey: "last_\(str)_time")
|
|
||||||
}
|
|
||||||
|
|
||||||
func update_filters_with_since(last_of_kind: [UInt32: NostrEvent], filters: [NostrFilter]) -> [NostrFilter] {
|
func update_filters_with_since(last_of_kind: [UInt32: NostrEvent], filters: [NostrFilter]) -> [NostrFilter] {
|
||||||
|
|
||||||
return filters.map { filter in
|
return filters.map { filter in
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
class Contacts {
|
class Contacts {
|
||||||
private var friends: Set<Pubkey> = Set()
|
private var friends: Set<Pubkey> = Set()
|
||||||
private var friend_of_friends: Set<Pubkey> = Set()
|
private var friend_of_friends: Set<Pubkey> = Set()
|
||||||
@@ -14,13 +15,7 @@ class Contacts {
|
|||||||
private var pubkey_to_our_friends = [Pubkey : Set<Pubkey>]()
|
private var pubkey_to_our_friends = [Pubkey : Set<Pubkey>]()
|
||||||
|
|
||||||
let our_pubkey: Pubkey
|
let our_pubkey: Pubkey
|
||||||
var delegate: ContactsDelegate? = nil
|
var event: NostrEvent?
|
||||||
var event: NostrEvent? {
|
|
||||||
didSet {
|
|
||||||
guard let event else { return }
|
|
||||||
self.delegate?.latest_contact_event_changed(new_event: event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(our_pubkey: Pubkey) {
|
init(our_pubkey: Pubkey) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
@@ -93,8 +88,3 @@ class Contacts {
|
|||||||
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
|
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delegate protocol for `Contacts`. Use this to listen to significant updates from a `Contacts` instance
|
|
||||||
protocol ContactsDelegate {
|
|
||||||
func latest_contact_event_changed(new_event: NostrEvent)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ enum FilterState : Int {
|
|||||||
func filter(ev: NostrEvent) -> Bool {
|
func filter(ev: NostrEvent) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .posts:
|
case .posts:
|
||||||
return ev.known_kind == .boost || !ev.is_reply()
|
return ev.known_kind == .boost || !ev.is_reply(.empty)
|
||||||
case .posts_and_replies:
|
case .posts_and_replies:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class DamusState: HeadlessDamusState {
|
|||||||
let video: VideoController
|
let video: VideoController
|
||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
var purple: DamusPurple
|
var purple: DamusPurple
|
||||||
var push_notification_client: PushNotificationClient
|
|
||||||
|
|
||||||
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
@@ -69,7 +68,6 @@ class DamusState: HeadlessDamusState {
|
|||||||
keypair: keypair
|
keypair: keypair
|
||||||
)
|
)
|
||||||
self.quote_reposts = quote_reposts
|
self.quote_reposts = quote_reposts
|
||||||
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -114,7 +112,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
likes: EventCounter(our_pubkey: empty_pub),
|
likes: EventCounter(our_pubkey: empty_pub),
|
||||||
boosts: EventCounter(our_pubkey: empty_pub),
|
boosts: EventCounter(our_pubkey: empty_pub),
|
||||||
contacts: Contacts(our_pubkey: empty_pub),
|
contacts: Contacts(our_pubkey: empty_pub),
|
||||||
mutelist_manager: MutelistManager(user_keypair: kp),
|
mutelist_manager: MutelistManager(),
|
||||||
profiles: Profiles(ndb: .empty),
|
profiles: Profiles(ndb: .empty),
|
||||||
dms: DirectMessagesModel(our_pubkey: empty_pub),
|
dms: DirectMessagesModel(our_pubkey: empty_pub),
|
||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
//
|
||||||
|
// EventRef.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-08.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum EventRef: Equatable {
|
||||||
|
case mention(Mention<NoteRef>)
|
||||||
|
case thread_id(NoteRef)
|
||||||
|
case reply(NoteRef)
|
||||||
|
case reply_to_root(NoteRef)
|
||||||
|
|
||||||
|
var is_mention: NoteRef? {
|
||||||
|
if case .mention(let m) = self { return m.ref }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_direct_reply: NoteRef? {
|
||||||
|
switch self {
|
||||||
|
case .mention:
|
||||||
|
return nil
|
||||||
|
case .thread_id:
|
||||||
|
return nil
|
||||||
|
case .reply(let refid):
|
||||||
|
return refid
|
||||||
|
case .reply_to_root(let refid):
|
||||||
|
return refid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_thread_id: NoteRef? {
|
||||||
|
switch self {
|
||||||
|
case .mention:
|
||||||
|
return nil
|
||||||
|
case .thread_id(let referencedId):
|
||||||
|
return referencedId
|
||||||
|
case .reply:
|
||||||
|
return nil
|
||||||
|
case .reply_to_root(let referencedId):
|
||||||
|
return referencedId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_reply: NoteRef? {
|
||||||
|
switch self {
|
||||||
|
case .mention:
|
||||||
|
return nil
|
||||||
|
case .thread_id:
|
||||||
|
return nil
|
||||||
|
case .reply(let refid):
|
||||||
|
return refid
|
||||||
|
case .reply_to_root(let refid):
|
||||||
|
return refid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
||||||
|
return blocks.reduce(into: []) { acc, block in
|
||||||
|
switch block {
|
||||||
|
case .mention(let m):
|
||||||
|
if m.ref.key == type, let idx = m.index {
|
||||||
|
acc.insert(idx)
|
||||||
|
}
|
||||||
|
case .relay:
|
||||||
|
return
|
||||||
|
case .text:
|
||||||
|
return
|
||||||
|
case .hashtag:
|
||||||
|
return
|
||||||
|
case .url:
|
||||||
|
return
|
||||||
|
case .invoice:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] {
|
||||||
|
if refs.count == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if refs.count == 1 {
|
||||||
|
return [.reply_to_root(refs[0])]
|
||||||
|
}
|
||||||
|
|
||||||
|
var evrefs: [EventRef] = []
|
||||||
|
var first: Bool = true
|
||||||
|
for ref in refs {
|
||||||
|
if first {
|
||||||
|
evrefs.append(.thread_id(ref))
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
evrefs.append(.reply(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return evrefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set<Int>) -> [EventRef] {
|
||||||
|
var mentions: [EventRef] = []
|
||||||
|
var ev_refs: [NoteRef] = []
|
||||||
|
var i: Int = 0
|
||||||
|
|
||||||
|
for tag in tags {
|
||||||
|
if let ref = NoteRef.from_tag(tag: tag) {
|
||||||
|
if mention_indices.contains(i) {
|
||||||
|
let mention = Mention<NoteRef>(index: i, ref: ref)
|
||||||
|
mentions.append(.mention(mention))
|
||||||
|
} else {
|
||||||
|
ev_refs.append(ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var replies = interp_event_refs_without_mentions(ev_refs)
|
||||||
|
replies.append(contentsOf: mentions)
|
||||||
|
return replies
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] {
|
||||||
|
if tags.count == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build a set of indices for each event mention
|
||||||
|
let mention_indices = build_mention_indices(blocks, type: .e)
|
||||||
|
|
||||||
|
/// simpler case with no mentions
|
||||||
|
if mention_indices.count == 0 {
|
||||||
|
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func event_is_reply(_ refs: [EventRef]) -> Bool {
|
||||||
|
return refs.contains { evref in
|
||||||
|
return evref.is_reply != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,19 +41,11 @@ enum HomeResubFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomeModel: ContactsDelegate {
|
class HomeModel {
|
||||||
// The maximum amount of contacts placed on a home feed subscription filter.
|
|
||||||
// If the user has more contacts, chunking or other techniques will be used to avoid sending huge filters
|
|
||||||
let MAX_CONTACTS_ON_FILTER = 500
|
|
||||||
|
|
||||||
// Don't trigger a user notification for events older than a certain age
|
// Don't trigger a user notification for events older than a certain age
|
||||||
static let event_max_age_for_notification: TimeInterval = EVENT_MAX_AGE_FOR_NOTIFICATION
|
static let event_max_age_for_notification: TimeInterval = EVENT_MAX_AGE_FOR_NOTIFICATION
|
||||||
|
|
||||||
var damus_state: DamusState {
|
var damus_state: DamusState
|
||||||
didSet {
|
|
||||||
self.load_our_stuff_from_damus_state()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NDBTODO: let's get rid of this entirely, let nostrdb handle it
|
// NDBTODO: let's get rid of this entirely, let nostrdb handle it
|
||||||
var has_event: [String: Set<NoteId>] = [:]
|
var has_event: [String: Set<NoteId>] = [:]
|
||||||
@@ -116,32 +108,6 @@ class HomeModel: ContactsDelegate {
|
|||||||
self.should_debounce_dms = false
|
self.should_debounce_dms = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Loading items from DamusState
|
|
||||||
|
|
||||||
/// This is called whenever DamusState gets set. This function is used to load or setup anything we need from the new DamusState
|
|
||||||
func load_our_stuff_from_damus_state() {
|
|
||||||
self.load_latest_contact_event_from_damus_state()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This loads the latest contact event we have on file from NostrDB. This should be called as soon as we get the new DamusState
|
|
||||||
/// Loading the latest contact list event into our `Contacts` instance from storage is important to avoid getting into weird states when the network is unreliable or when relays delete such information
|
|
||||||
func load_latest_contact_event_from_damus_state() {
|
|
||||||
guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return }
|
|
||||||
guard let latest_contact_event_id = NoteId(hex: latest_contact_event_id_hex) else { return }
|
|
||||||
guard let latest_contact_event: NdbNote = damus_state.ndb.lookup_note( latest_contact_event_id)?.unsafeUnownedValue?.to_owned() else { return }
|
|
||||||
process_contact_event(state: damus_state, ev: latest_contact_event)
|
|
||||||
damus_state.contacts.delegate = self
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - ContactsDelegate functions
|
|
||||||
|
|
||||||
func latest_contact_event_changed(new_event: NostrEvent) {
|
|
||||||
// When the latest user contact event has changed, save its ID so we know exactly where to find it next time
|
|
||||||
damus_state.settings.latest_contact_event_id_hex = new_event.id.hex()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Nostr event and subscription handling
|
|
||||||
|
|
||||||
func resubscribe(_ resubbing: Resubscribe) {
|
func resubscribe(_ resubbing: Resubscribe) {
|
||||||
if self.should_debounce_dms {
|
if self.should_debounce_dms {
|
||||||
@@ -313,14 +279,9 @@ class HomeModel: ContactsDelegate {
|
|||||||
@MainActor
|
@MainActor
|
||||||
func handle_damus_app_notification(_ notification: DamusAppNotification) async {
|
func handle_damus_app_notification(_ notification: DamusAppNotification) async {
|
||||||
if self.notifications.insert_app_notification(notification: notification) {
|
if self.notifications.insert_app_notification(notification: notification) {
|
||||||
let last_notification = get_last_event(.notifications)
|
// If we successfully inserted a new Damus App notification, switch ON the Damus App notification bit on our NewsEventsBits
|
||||||
if last_notification == nil || last_notification!.created_at < notification.last_event_at {
|
// This will cause the bell icon on the tab bar to display the purple dot indicating there is an unread notification
|
||||||
save_last_event(NoteId.empty, created_at: notification.last_event_at, timeline: .notifications)
|
self.notification_status.new_events = NewEventsBits(rawValue: self.notification_status.new_events.rawValue | NewEventsBits.damus_app_notifications.rawValue)
|
||||||
// If we successfully inserted a new Damus App notification, switch ON the Damus App notification bit on our NewsEventsBits
|
|
||||||
// This will cause the bell icon on the tab bar to display the purple dot indicating there is an unread notification
|
|
||||||
self.notification_status.new_events = NewEventsBits(rawValue: self.notification_status.new_events.rawValue | NewEventsBits.damus_app_notifications.rawValue)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,8 +510,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
notifications_filter.limit = 500
|
notifications_filter.limit = 500
|
||||||
|
|
||||||
var notifications_filters = [notifications_filter]
|
var notifications_filters = [notifications_filter]
|
||||||
let contacts_filter_chunks = contacts_filter.chunked(on: .authors, into: MAX_CONTACTS_ON_FILTER)
|
var contacts_filters = [contacts_filter, our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
|
||||||
var contacts_filters = contacts_filter_chunks + [our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
|
|
||||||
var dms_filters = [dms_filter, our_dms_filter]
|
var dms_filters = [dms_filter, our_dms_filter]
|
||||||
let last_of_kind = get_last_of_kind(relay_id: relay_id)
|
let last_of_kind = get_last_of_kind(relay_id: relay_id)
|
||||||
|
|
||||||
@@ -603,7 +563,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
home_filter.authors = friends
|
home_filter.authors = friends
|
||||||
home_filter.limit = 500
|
home_filter.limit = 500
|
||||||
|
|
||||||
var home_filters = home_filter.chunked(on: .authors, into: MAX_CONTACTS_ON_FILTER)
|
var home_filters = [home_filter]
|
||||||
|
|
||||||
let followed_hashtags = Array(damus_state.contacts.get_followed_hashtags())
|
let followed_hashtags = Array(damus_state.contacts.get_followed_hashtags())
|
||||||
if followed_hashtags.count != 0 {
|
if followed_hashtags.count != 0 {
|
||||||
@@ -733,7 +693,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
||||||
notification_status.new_events = notifs
|
notification_status.new_events = notifs
|
||||||
|
|
||||||
guard should_display_notification(state: damus_state, event: ev, mode: .local),
|
guard should_display_notification(state: damus_state, event: ev),
|
||||||
let notification_object = generate_local_notification_object(from: ev, state: damus_state)
|
let notification_object = generate_local_notification_object(from: ev, state: damus_state)
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
@@ -1153,8 +1113,8 @@ func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func should_show_event(state: DamusState, ev: NostrEvent) -> Bool {
|
func should_show_event(state: DamusState, ev: NostrEvent, keypair: Keypair? = nil) -> Bool {
|
||||||
let event_muted = state.mutelist_manager.is_event_muted(ev)
|
let event_muted = state.mutelist_manager.is_event_muted(ev, keypair: keypair)
|
||||||
if event_muted {
|
if event_muted {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,32 +49,6 @@ enum MediaUpload {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var mime_type: String {
|
|
||||||
switch self.file_extension {
|
|
||||||
case "jpg", "jpeg":
|
|
||||||
return "image/jpg"
|
|
||||||
case "png":
|
|
||||||
return "image/png"
|
|
||||||
case "gif":
|
|
||||||
return "image/gif"
|
|
||||||
case "tiff", "tif":
|
|
||||||
return "image/tiff"
|
|
||||||
case "mp4":
|
|
||||||
return "video/mp4"
|
|
||||||
case "ogg":
|
|
||||||
return "video/ogg"
|
|
||||||
case "webm":
|
|
||||||
return "video/webm"
|
|
||||||
default:
|
|
||||||
switch self {
|
|
||||||
case .image:
|
|
||||||
return "image/jpg"
|
|
||||||
case .video:
|
|
||||||
return "video/mp4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ struct LongformEvent {
|
|||||||
var image: URL? = nil
|
var image: URL? = nil
|
||||||
var summary: String? = nil
|
var summary: String? = nil
|
||||||
var published_at: Date? = nil
|
var published_at: Date? = nil
|
||||||
var labels: [String]? = nil
|
|
||||||
|
|
||||||
static func parse(from ev: NostrEvent) -> LongformEvent {
|
static func parse(from ev: NostrEvent) -> LongformEvent {
|
||||||
var longform = LongformEvent(event: ev)
|
var longform = LongformEvent(event: ev)
|
||||||
@@ -27,10 +26,6 @@ struct LongformEvent {
|
|||||||
case "summary": longform.summary = tag[1].string()
|
case "summary": longform.summary = tag[1].string()
|
||||||
case "published_at":
|
case "published_at":
|
||||||
longform.published_at = Double(tag[1].string()).map { d in Date(timeIntervalSince1970: d) }
|
longform.published_at = Double(tag[1].string()).map { d in Date(timeIntervalSince1970: d) }
|
||||||
case "t":
|
|
||||||
if (longform.labels?.append(tag[1].string())) == nil {
|
|
||||||
longform.labels = [tag[1].string()]
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,8 +292,9 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
|
func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
|
||||||
|
let tags = post.references.map({ r in r.tag }) + post.tags
|
||||||
let post_blocks = parse_post_blocks(content: post.content)
|
let post_blocks = parse_post_blocks(content: post.content)
|
||||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: post.tags)
|
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
||||||
let content = post_tags.blocks
|
let content = post_tags.blocks
|
||||||
.map(\.asString)
|
.map(\.asString)
|
||||||
.joined(separator: "")
|
.joined(separator: "")
|
||||||
|
|||||||
@@ -8,27 +8,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class MutelistManager {
|
class MutelistManager {
|
||||||
let user_keypair: Keypair
|
|
||||||
private(set) var event: NostrEvent? = nil
|
private(set) var event: NostrEvent? = nil
|
||||||
|
|
||||||
var users: Set<MuteItem> = [] {
|
var users: Set<MuteItem> = []
|
||||||
didSet { self.reset_cache() }
|
var hashtags: Set<MuteItem> = []
|
||||||
}
|
var threads: Set<MuteItem> = []
|
||||||
var hashtags: Set<MuteItem> = [] {
|
var words: Set<MuteItem> = []
|
||||||
didSet { self.reset_cache() }
|
|
||||||
}
|
|
||||||
var threads: Set<MuteItem> = [] {
|
|
||||||
didSet { self.reset_cache() }
|
|
||||||
}
|
|
||||||
var words: Set<MuteItem> = [] {
|
|
||||||
didSet { self.reset_cache() }
|
|
||||||
}
|
|
||||||
|
|
||||||
var muted_notes_cache: [NoteId: EventMuteStatus] = [:]
|
|
||||||
|
|
||||||
init(user_keypair: Keypair) {
|
|
||||||
self.user_keypair = user_keypair
|
|
||||||
}
|
|
||||||
|
|
||||||
func refresh_sets() {
|
func refresh_sets() {
|
||||||
guard let referenced_mute_items = event?.referenced_mute_items else { return }
|
guard let referenced_mute_items = event?.referenced_mute_items else { return }
|
||||||
@@ -56,10 +41,6 @@ class MutelistManager {
|
|||||||
threads = new_threads
|
threads = new_threads
|
||||||
words = new_words
|
words = new_words
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset_cache() {
|
|
||||||
self.muted_notes_cache = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func is_muted(_ item: MuteItem) -> Bool {
|
func is_muted(_ item: MuteItem) -> Bool {
|
||||||
switch item {
|
switch item {
|
||||||
@@ -74,8 +55,8 @@ class MutelistManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_event_muted(_ ev: NostrEvent) -> Bool {
|
func is_event_muted(_ ev: NostrEvent, keypair: Keypair? = nil) -> Bool {
|
||||||
return self.event_muted_reason(ev) != nil
|
return event_muted_reason(ev, keypair: keypair) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func set_mutelist(_ ev: NostrEvent) {
|
func set_mutelist(_ ev: NostrEvent) {
|
||||||
@@ -133,27 +114,15 @@ class MutelistManager {
|
|||||||
threads.remove(item)
|
threads.remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
|
|
||||||
if let cached_mute_status = self.muted_notes_cache[ev.id] {
|
|
||||||
return cached_mute_status.mute_reason()
|
|
||||||
}
|
|
||||||
if let reason = self.compute_event_muted_reason(ev) {
|
|
||||||
self.muted_notes_cache[ev.id] = .muted(reason: reason)
|
|
||||||
return reason
|
|
||||||
}
|
|
||||||
self.muted_notes_cache[ev.id] = .not_muted
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Check if an event is muted given a collection of ``MutedItem``.
|
/// Check if an event is muted given a collection of ``MutedItem``.
|
||||||
///
|
///
|
||||||
/// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for.
|
/// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for.
|
||||||
/// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted.
|
/// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted.
|
||||||
func compute_event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
|
func event_muted_reason(_ ev: NostrEvent, keypair: Keypair? = nil) -> MuteItem? {
|
||||||
// Events from the current user should not be muted.
|
// Events from the current user should not be muted.
|
||||||
guard self.user_keypair.pubkey != ev.pubkey else { return nil }
|
guard keypair?.pubkey != ev.pubkey else { return nil }
|
||||||
|
|
||||||
// Check if user is muted
|
// Check if user is muted
|
||||||
let check_user_item = MuteItem.user(ev.pubkey, nil)
|
let check_user_item = MuteItem.user(ev.pubkey, nil)
|
||||||
@@ -178,7 +147,7 @@ class MutelistManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if word is muted
|
// Check if word is muted
|
||||||
if let content: String = ev.maybe_get_content(self.user_keypair)?.lowercased() {
|
if let keypair, let content: String = ev.maybe_get_content(keypair)?.lowercased() {
|
||||||
for word in words {
|
for word in words {
|
||||||
if case .word(let string, _) = word {
|
if case .word(let string, _) = word {
|
||||||
if content.contains(string.lowercased()) {
|
if content.contains(string.lowercased()) {
|
||||||
@@ -190,18 +159,4 @@ class MutelistManager {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EventMuteStatus {
|
|
||||||
case muted(reason: MuteItem)
|
|
||||||
case not_muted
|
|
||||||
|
|
||||||
func mute_reason() -> MuteItem? {
|
|
||||||
switch self {
|
|
||||||
case .muted(reason: let reason):
|
|
||||||
return reason
|
|
||||||
case .not_muted:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import UIKit
|
|||||||
let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60
|
let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60
|
||||||
|
|
||||||
func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) {
|
func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) {
|
||||||
guard should_display_notification(state: state, event: ev, mode: .local) else {
|
guard should_display_notification(state: state, event: ev) else {
|
||||||
// We should not display notification. Exit.
|
// We should not display notification. Exit.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -25,12 +25,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent)
|
|||||||
create_local_notification(profiles: state.profiles, notify: local_notification)
|
create_local_notification(profiles: state.profiles, notify: local_notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) -> Bool {
|
func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent) -> Bool {
|
||||||
// Do not show notification if it's coming from a mode different from the one selected by our user
|
|
||||||
guard state.settings.notifications_mode == mode else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ev.known_kind == nil {
|
if ev.known_kind == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -42,7 +37,7 @@ func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't show notifications that match mute list.
|
// Don't show notifications that match mute list.
|
||||||
if state.mutelist_manager.is_event_muted(ev) {
|
if state.mutelist_manager.is_event_muted(ev, keypair: state.keypair) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import Foundation
|
|||||||
struct NostrPost {
|
struct NostrPost {
|
||||||
let kind: NostrKind
|
let kind: NostrKind
|
||||||
let content: String
|
let content: String
|
||||||
|
let references: [RefId]
|
||||||
let tags: [[String]]
|
let tags: [[String]]
|
||||||
|
|
||||||
init(content: String, kind: NostrKind = .text, tags: [[String]] = []) {
|
init(content: String, references: [RefId], kind: NostrKind = .text, tags: [[String]] = []) {
|
||||||
self.content = content
|
self.content = content
|
||||||
|
self.references = references
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
//
|
|
||||||
// PushNotificationClient.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2024-05-17.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct PushNotificationClient {
|
|
||||||
let keypair: Keypair
|
|
||||||
let settings: UserSettingsStore
|
|
||||||
private(set) var device_token: Data? = nil
|
|
||||||
|
|
||||||
mutating func set_device_token(new_device_token: Data) async throws {
|
|
||||||
self.device_token = new_device_token
|
|
||||||
if settings.enable_experimental_push_notifications && settings.notifications_mode == .push {
|
|
||||||
try await self.send_token()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func send_token() async throws {
|
|
||||||
guard let device_token else { return }
|
|
||||||
// Send the device token and pubkey to the server
|
|
||||||
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
|
|
||||||
|
|
||||||
Log.info("Sending device token to server: %s", for: .push_notifications, token)
|
|
||||||
|
|
||||||
let pubkey = self.keypair.pubkey
|
|
||||||
|
|
||||||
// Send those as JSON to the server
|
|
||||||
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
|
|
||||||
|
|
||||||
// create post request
|
|
||||||
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_RECEIVER_TEST_URL : Constants.DEVICE_TOKEN_RECEIVER_PRODUCTION_URL
|
|
||||||
let json_data = try JSONSerialization.data(withJSONObject: json)
|
|
||||||
|
|
||||||
|
|
||||||
let (data, response) = try await make_nip98_authenticated_request(
|
|
||||||
method: .post,
|
|
||||||
url: url,
|
|
||||||
payload: json_data,
|
|
||||||
payload_type: .json,
|
|
||||||
auth_keypair: self.keypair
|
|
||||||
)
|
|
||||||
|
|
||||||
if let httpResponse = response as? HTTPURLResponse {
|
|
||||||
switch httpResponse.statusCode {
|
|
||||||
case 200:
|
|
||||||
Log.info("Sent device token to Damus push notification server successfully", for: .push_notifications)
|
|
||||||
default:
|
|
||||||
Log.error("Error in sending device_token to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
|
|
||||||
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func revoke_token() async throws {
|
|
||||||
guard let device_token else { return }
|
|
||||||
// Send the device token and pubkey to the server
|
|
||||||
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
|
|
||||||
|
|
||||||
Log.info("Revoking device token from server: %s", for: .push_notifications, token)
|
|
||||||
|
|
||||||
let pubkey = self.keypair.pubkey
|
|
||||||
|
|
||||||
// Send those as JSON to the server
|
|
||||||
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
|
|
||||||
|
|
||||||
// create post request
|
|
||||||
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_REVOKER_TEST_URL : Constants.DEVICE_TOKEN_REVOKER_PRODUCTION_URL
|
|
||||||
let json_data = try JSONSerialization.data(withJSONObject: json)
|
|
||||||
|
|
||||||
|
|
||||||
let (data, response) = try await make_nip98_authenticated_request(
|
|
||||||
method: .post,
|
|
||||||
url: url,
|
|
||||||
payload: json_data,
|
|
||||||
payload_type: .json,
|
|
||||||
auth_keypair: self.keypair
|
|
||||||
)
|
|
||||||
|
|
||||||
if let httpResponse = response as? HTTPURLResponse {
|
|
||||||
switch httpResponse.statusCode {
|
|
||||||
case 200:
|
|
||||||
Log.info("Sent device token removal request to Damus push notification server successfully", for: .push_notifications)
|
|
||||||
default:
|
|
||||||
Log.error("Error in sending device_token removal to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
|
|
||||||
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Helper structures
|
|
||||||
|
|
||||||
extension PushNotificationClient {
|
|
||||||
enum ClientError: Error {
|
|
||||||
case http_response_error(status_code: Int, response: Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,7 +60,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply(damus_state.keypair)
|
||||||
{
|
{
|
||||||
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
|
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ThreadModel: ObservableObject {
|
|||||||
var event_filter = NostrFilter()
|
var event_filter = NostrFilter()
|
||||||
var ref_events = NostrFilter()
|
var ref_events = NostrFilter()
|
||||||
|
|
||||||
let thread_id = event.thread_id()
|
let thread_id = event.thread_id(keypair: .empty)
|
||||||
|
|
||||||
ref_events.referenced_ids = [thread_id, event.id]
|
ref_events.referenced_ids = [thread_id, event.id]
|
||||||
ref_events.kinds = [.text]
|
ref_events.kinds = [.text]
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
//
|
||||||
|
// Trie.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 6/26/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Tree data structure of all the substring permutations of a collection of strings optimized for searching for values of type V.
|
||||||
|
///
|
||||||
|
/// Each node in the tree can have child nodes.
|
||||||
|
/// Each node represents a single character in substrings, and each of its child nodes represent the subsequent character in those substrings.
|
||||||
|
///
|
||||||
|
/// A node that has no children mean that there are no substrings with any additional characters beyond the branch of letters leading up to that node.
|
||||||
|
///
|
||||||
|
/// A node that has values mean that there are strings that end in the character represented by the node and contain the substring represented by the branch of letters leading up to that node.
|
||||||
|
///
|
||||||
|
/// https://en.wikipedia.org/wiki/Trie
|
||||||
|
class Trie<V: Hashable> {
|
||||||
|
private var children: [Character : Trie] = [:]
|
||||||
|
|
||||||
|
/// Separate exact matches from strict substrings so that exact matches appear first in returned results.
|
||||||
|
private var exactMatchValues = Set<V>()
|
||||||
|
private var substringMatchValues = Set<V>()
|
||||||
|
|
||||||
|
private var parent: Trie? = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Trie {
|
||||||
|
var hasChildren: Bool {
|
||||||
|
return !self.children.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasValues: Bool {
|
||||||
|
return !self.exactMatchValues.isEmpty || !self.substringMatchValues.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the branch that matches the specified key and returns the values from all of its descendant nodes.
|
||||||
|
func find(key: String) -> [V] {
|
||||||
|
var currentNode = self
|
||||||
|
|
||||||
|
// Find branch with matching prefix.
|
||||||
|
for char in key {
|
||||||
|
if let child = currentNode.children[char] {
|
||||||
|
currentNode = child
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform breadth-first search from matching branch and collect values from all descendants.
|
||||||
|
var substringMatches = Set<V>(currentNode.substringMatchValues)
|
||||||
|
var queue = Array(currentNode.children.values)
|
||||||
|
|
||||||
|
while !queue.isEmpty {
|
||||||
|
let node = queue.removeFirst()
|
||||||
|
substringMatches.formUnion(node.exactMatchValues)
|
||||||
|
substringMatches.formUnion(node.substringMatchValues)
|
||||||
|
queue.append(contentsOf: node.children.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize exact matches to be returned first, and then remove exact matches from the set of partial substring matches that are appended afterward.
|
||||||
|
return Array(currentNode.exactMatchValues) + (substringMatches.subtracting(currentNode.exactMatchValues))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts value of type V into this trie for the specified key. This function stores all substring endings of the key, not only the key itself.
|
||||||
|
/// Runtime performance is O(n^2) and storage cost is O(n), where n is the number of characters in the key.
|
||||||
|
func insert(key: String, value: V) {
|
||||||
|
// Create root branches for each character of the key to enable substring searches instead of only just prefix searches.
|
||||||
|
// Hence the nested loop.
|
||||||
|
for i in 0..<key.count {
|
||||||
|
var currentNode = self
|
||||||
|
|
||||||
|
// Find branch with matching prefix.
|
||||||
|
for char in key[key.index(key.startIndex, offsetBy: i)...] {
|
||||||
|
if let child = currentNode.children[char] {
|
||||||
|
currentNode = child
|
||||||
|
} else {
|
||||||
|
let child = Trie()
|
||||||
|
child.parent = currentNode
|
||||||
|
currentNode.children[char] = child
|
||||||
|
currentNode = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
currentNode.exactMatchValues.insert(value)
|
||||||
|
} else {
|
||||||
|
currentNode.substringMatchValues.insert(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes value of type V from this trie for the specified key.
|
||||||
|
func remove(key: String, value: V) {
|
||||||
|
for i in 0..<key.count {
|
||||||
|
var currentNode = self
|
||||||
|
|
||||||
|
var foundLeafNode = true
|
||||||
|
|
||||||
|
// Find branch with matching prefix.
|
||||||
|
for j in i..<key.count {
|
||||||
|
let char = key[key.index(key.startIndex, offsetBy: j)]
|
||||||
|
|
||||||
|
if let child = currentNode.children[char] {
|
||||||
|
currentNode = child
|
||||||
|
} else {
|
||||||
|
foundLeafNode = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundLeafNode {
|
||||||
|
currentNode.exactMatchValues.remove(value)
|
||||||
|
currentNode.substringMatchValues.remove(value)
|
||||||
|
|
||||||
|
// Clean up the tree if this leaf node no longer holds values or children.
|
||||||
|
for j in (i..<key.count).reversed() {
|
||||||
|
if let parent = currentNode.parent, !currentNode.hasValues && !currentNode.hasChildren {
|
||||||
|
currentNode = parent
|
||||||
|
let char = key[key.index(key.startIndex, offsetBy: j)]
|
||||||
|
currentNode.children.removeValue(forKey: char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// UserSearchCache.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 6/27/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Cache of searchable users by name, display_name, NIP-05 identifier, or own contact list petname.
|
||||||
|
/// Optimized for fast searches of substrings by using a Trie.
|
||||||
|
/// Optimal for performing user searches that could be initiated by typing quickly on a keyboard into a text input field.
|
||||||
|
|
||||||
|
// TODO: replace with lmdb (the b tree should handle this just fine ?)
|
||||||
|
// we just need a name to profile index
|
||||||
|
class UserSearchCache {
|
||||||
|
private let trie = Trie<Pubkey>()
|
||||||
|
|
||||||
|
func search(key: String) -> [Pubkey] {
|
||||||
|
let results = trie.find(key: key)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the differences between an old profile, if it exists, and a new profile, and updates the user search cache accordingly.
|
||||||
|
@MainActor
|
||||||
|
func updateProfile(id: Pubkey, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) {
|
||||||
|
// Remove searchable keys tied to the old profile if they differ from the new profile
|
||||||
|
// to keep the trie clean without empty nodes while avoiding excessive graph searching.
|
||||||
|
if let oldProfile {
|
||||||
|
if let oldName = oldProfile.name, newProfile.name?.caseInsensitiveCompare(oldName) != .orderedSame {
|
||||||
|
trie.remove(key: oldName.lowercased(), value: id)
|
||||||
|
}
|
||||||
|
if let oldDisplayName = oldProfile.display_name, newProfile.display_name?.caseInsensitiveCompare(oldDisplayName) != .orderedSame {
|
||||||
|
trie.remove(key: oldDisplayName.lowercased(), value: id)
|
||||||
|
}
|
||||||
|
if let oldNip05 = oldProfile.nip05, newProfile.nip05?.caseInsensitiveCompare(oldNip05) != .orderedSame {
|
||||||
|
trie.remove(key: oldNip05.lowercased(), value: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addProfile(id: id, profiles: profiles, profile: newProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a profile to the user search cache.
|
||||||
|
@MainActor
|
||||||
|
private func addProfile(id: Pubkey, profiles: Profiles, profile: Profile) {
|
||||||
|
// Searchable by name.
|
||||||
|
if let name = profile.name {
|
||||||
|
trie.insert(key: name.lowercased(), value: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searchable by display name.
|
||||||
|
if let displayName = profile.display_name {
|
||||||
|
trie.insert(key: displayName.lowercased(), value: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searchable by NIP-05 identifier.
|
||||||
|
if let nip05 = profiles.is_validated(id) {
|
||||||
|
trie.insert(key: "\(nip05.username.lowercased())@\(nip05.host.lowercased())", value: id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the diffences between an old contacts event and a new contacts event for our own user, and updates the search cache accordingly.
|
||||||
|
func updateOwnContactsPetnames(id: Pubkey, oldEvent: NostrEvent?, newEvent: NostrEvent) {
|
||||||
|
guard newEvent.known_kind == .contacts && newEvent.pubkey == id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var petnames: [Pubkey: String] = [:]
|
||||||
|
for tag in newEvent.tags {
|
||||||
|
guard tag.count > 3,
|
||||||
|
let chr = tag[0].single_char, chr == "p",
|
||||||
|
let id = tag[1].id()
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let pubkey = Pubkey(id)
|
||||||
|
|
||||||
|
petnames[pubkey] = tag[3].string()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the diff with the old contacts list, if it exists,
|
||||||
|
// mark the ones that are the same to not be removed from the user search cache,
|
||||||
|
// and remove the old ones that are different from the user search cache.
|
||||||
|
if let oldEvent, oldEvent.known_kind == .contacts, oldEvent.pubkey == id {
|
||||||
|
for tag in oldEvent.tags {
|
||||||
|
guard tag.count >= 4,
|
||||||
|
tag[0].matches_char("p"),
|
||||||
|
let id = tag[1].id()
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let pubkey = Pubkey(id)
|
||||||
|
|
||||||
|
let oldPetname = tag[3].string()
|
||||||
|
|
||||||
|
if let newPetname = petnames[pubkey] {
|
||||||
|
if newPetname.caseInsensitiveCompare(oldPetname) == .orderedSame {
|
||||||
|
petnames.removeValue(forKey: pubkey)
|
||||||
|
} else {
|
||||||
|
trie.remove(key: oldPetname, value: pubkey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trie.remove(key: oldPetname, value: pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new petnames to the user search cache.
|
||||||
|
for (pubkey, petname) in petnames {
|
||||||
|
trie.insert(key: petname, value: pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,14 +96,6 @@ class UserSettingsStore: ObservableObject {
|
|||||||
static var shared: UserSettingsStore? = nil
|
static var shared: UserSettingsStore? = nil
|
||||||
static var bool_options = Set<String>()
|
static var bool_options = Set<String>()
|
||||||
|
|
||||||
static func globally_load_for(pubkey: Pubkey) -> UserSettingsStore {
|
|
||||||
// dumb stuff needed for property wrappers
|
|
||||||
UserSettingsStore.pubkey = pubkey
|
|
||||||
let settings = UserSettingsStore()
|
|
||||||
UserSettingsStore.shared = settings
|
|
||||||
return settings
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
|
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
|
||||||
var default_wallet: Wallet
|
var default_wallet: Wallet
|
||||||
|
|
||||||
@@ -155,9 +147,6 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "like_notification", default_value: true)
|
@Setting(key: "like_notification", default_value: true)
|
||||||
var like_notification: Bool
|
var like_notification: Bool
|
||||||
|
|
||||||
@StringSetting(key: "notifications_mode", default_value: .local)
|
|
||||||
var notifications_mode: NotificationsMode
|
|
||||||
|
|
||||||
@Setting(key: "notification_only_from_following", default_value: false)
|
@Setting(key: "notification_only_from_following", default_value: false)
|
||||||
var notification_only_from_following: Bool
|
var notification_only_from_following: Bool
|
||||||
|
|
||||||
@@ -323,42 +312,6 @@ class UserSettingsStore: ObservableObject {
|
|||||||
return internal_winetranslate_api_key != nil
|
return internal_winetranslate_api_key != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Internal, hidden settings
|
|
||||||
|
|
||||||
@Setting(key: "latest_contact_event_id", default_value: nil)
|
|
||||||
var latest_contact_event_id_hex: String?
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Helper types
|
|
||||||
|
|
||||||
enum NotificationsMode: String, CaseIterable, Identifiable, StringCodable, Equatable {
|
|
||||||
var id: String { self.rawValue }
|
|
||||||
|
|
||||||
func to_string() -> String {
|
|
||||||
return rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(from string: String) {
|
|
||||||
guard let notifications_mode = NotificationsMode(rawValue: string) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self = notifications_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
func text_description() -> String {
|
|
||||||
switch self {
|
|
||||||
case .local:
|
|
||||||
NSLocalizedString("Local", comment: "Option for notification mode setting: Local notification mode")
|
|
||||||
case .push:
|
|
||||||
NSLocalizedString("Push", comment: "Option for notification mode setting: Push notification mode")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case local
|
|
||||||
case push
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pk_setting_key(_ pubkey: Pubkey, key: String) -> String {
|
func pk_setting_key(_ pubkey: Pubkey, key: String) -> String {
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
//
|
|
||||||
// VideoCache.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Daniel D'Aquino on 2024-04-01.
|
|
||||||
//
|
|
||||||
import Foundation
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
// Default expiry time of only 1 day to prevent using too much storage
|
|
||||||
fileprivate let DEFAULT_EXPIRY_TIME: TimeInterval = 60*60*24
|
|
||||||
// Default cache directory is in the system-provided caches directory, so that the operating system can delete files when it needs storage space
|
|
||||||
// (https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
|
|
||||||
fileprivate let DEFAULT_CACHE_DIRECTORY_PATH: URL? = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("video_cache")
|
|
||||||
|
|
||||||
struct VideoCache {
|
|
||||||
private let cache_url: URL
|
|
||||||
private let expiry_time: TimeInterval
|
|
||||||
static let standard: VideoCache? = try? VideoCache()
|
|
||||||
|
|
||||||
init?(cache_url: URL? = nil, expiry_time: TimeInterval = DEFAULT_EXPIRY_TIME) throws {
|
|
||||||
guard let cache_url_to_apply = cache_url ?? DEFAULT_CACHE_DIRECTORY_PATH else { return nil }
|
|
||||||
self.cache_url = cache_url_to_apply
|
|
||||||
self.expiry_time = expiry_time
|
|
||||||
|
|
||||||
// Create the cache directory if it doesn't exist
|
|
||||||
do {
|
|
||||||
try FileManager.default.createDirectory(at: self.cache_url, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
} catch {
|
|
||||||
Log.error("Could not create cache directory: %s", for: .storage, error.localizedDescription)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks for a cached video and returns its URL if available, otherwise downloads and caches the video.
|
|
||||||
func maybe_cached_url_for(video_url: URL) throws -> URL {
|
|
||||||
let cached_url = url_to_cached_url(url: video_url)
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: cached_url.path) {
|
|
||||||
// Check if the cached video has expired
|
|
||||||
let file_attributes = try FileManager.default.attributesOfItem(atPath: cached_url.path)
|
|
||||||
if let modification_date = file_attributes[.modificationDate] as? Date, Date().timeIntervalSince(modification_date) <= expiry_time {
|
|
||||||
// Video is not expired
|
|
||||||
return cached_url
|
|
||||||
} else {
|
|
||||||
Task {
|
|
||||||
// Video is expired, delete and re-download on the background
|
|
||||||
try FileManager.default.removeItem(at: cached_url)
|
|
||||||
return try await download_and_cache_video(from: video_url)
|
|
||||||
}
|
|
||||||
return video_url
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Task {
|
|
||||||
// Video is not cached, download and cache on the background
|
|
||||||
return try await download_and_cache_video(from: video_url)
|
|
||||||
}
|
|
||||||
return video_url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downloads video content using URLSession and caches it to disk.
|
|
||||||
private func download_and_cache_video(from url: URL) async throws -> URL {
|
|
||||||
let (data, response) = try await URLSession.shared.data(from: url)
|
|
||||||
|
|
||||||
guard let http_response = response as? HTTPURLResponse,
|
|
||||||
200..<300 ~= http_response.statusCode else {
|
|
||||||
throw URLError(.badServerResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
let destination_url = url_to_cached_url(url: url)
|
|
||||||
|
|
||||||
try data.write(to: destination_url)
|
|
||||||
return destination_url
|
|
||||||
}
|
|
||||||
|
|
||||||
func url_to_cached_url(url: URL) -> URL {
|
|
||||||
let hashed_url = hash_url(url)
|
|
||||||
let file_extension = url.pathExtension
|
|
||||||
return self.cache_url.appendingPathComponent(hashed_url + "." + file_extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all cached videos older than the expiry time.
|
|
||||||
func periodic_purge(completion: ((Error?) -> Void)? = nil) {
|
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
Log.info("Starting periodic video cache purge", for: .storage)
|
|
||||||
let file_manager = FileManager.default
|
|
||||||
do {
|
|
||||||
let cached_files = try file_manager.contentsOfDirectory(at: self.cache_url, includingPropertiesForKeys: [.contentModificationDateKey], options: .skipsHiddenFiles)
|
|
||||||
|
|
||||||
for file in cached_files {
|
|
||||||
let attributes = try file.resourceValues(forKeys: [.contentModificationDateKey])
|
|
||||||
if let modification_date = attributes.contentModificationDate, Date().timeIntervalSince(modification_date) > self.expiry_time {
|
|
||||||
try file_manager.removeItem(at: file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion?(nil)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion?(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hashes the URL using SHA-256
|
|
||||||
private func hash_url(_ url: URL) -> String {
|
|
||||||
let data = Data(url.absoluteString.utf8)
|
|
||||||
let hashed_data = SHA256.hash(data: data)
|
|
||||||
return hashed_data.compactMap { String(format: "%02x", $0) }.joined()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
//
|
|
||||||
// ThreadReply.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2024-05-09.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
|
|
||||||
struct ThreadReply {
|
|
||||||
let root: NoteRef
|
|
||||||
let reply: NoteRef
|
|
||||||
let mention: Mention<NoteRef>?
|
|
||||||
|
|
||||||
var is_reply_to_root: Bool {
|
|
||||||
return root.id == reply.id
|
|
||||||
}
|
|
||||||
|
|
||||||
init(root: NoteRef, reply: NoteRef, mention: Mention<NoteRef>?) {
|
|
||||||
self.root = root
|
|
||||||
self.reply = reply
|
|
||||||
self.mention = mention
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(tags: TagsSequence) {
|
|
||||||
guard let tr = interpret_event_refs_ndb(tags: tags) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self = tr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,68 +54,4 @@ struct NostrFilter: Codable, Equatable {
|
|||||||
public static func filter_hashtag(_ htags: [String]) -> NostrFilter {
|
public static func filter_hashtag(_ htags: [String]) -> NostrFilter {
|
||||||
NostrFilter(hashtag: htags.map { $0.lowercased() })
|
NostrFilter(hashtag: htags.map { $0.lowercased() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Splits the filter on a given filter path/axis into chunked filters
|
|
||||||
///
|
|
||||||
/// - Parameter path: The path where chunking should be done
|
|
||||||
/// - Parameter chunk_size: The maximum size of each chunk.
|
|
||||||
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
|
||||||
func chunked(on path: ChunkPath, into chunk_size: Int) -> [Self] {
|
|
||||||
let chunked_slices = self.get_slice(from: path).chunked(into: chunk_size)
|
|
||||||
var chunked_filters: [NostrFilter] = []
|
|
||||||
for chunked_slice in chunked_slices {
|
|
||||||
var chunked_filter = self
|
|
||||||
chunked_filter.apply_slice(chunked_slice)
|
|
||||||
chunked_filters.append(chunked_filter)
|
|
||||||
}
|
|
||||||
return chunked_filters
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a slice from a NostrFilter on a given path/axis
|
|
||||||
///
|
|
||||||
/// - Parameter path: The path where chunking should be done
|
|
||||||
/// - Parameter chunk_size: The maximum size of each chunk.
|
|
||||||
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
|
||||||
func get_slice(from path: ChunkPath) -> Slice {
|
|
||||||
switch path {
|
|
||||||
case .pubkeys:
|
|
||||||
return .pubkeys(self.pubkeys)
|
|
||||||
case .authors:
|
|
||||||
return .authors(self.authors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Overrides one member/axis of a NostrFilter using a specific slice
|
|
||||||
/// - Parameter slice: The slice to be applied on this NostrFilter
|
|
||||||
mutating func apply_slice(_ slice: Slice) {
|
|
||||||
switch slice {
|
|
||||||
case .pubkeys(let pubkeys):
|
|
||||||
self.pubkeys = pubkeys
|
|
||||||
case .authors(let authors):
|
|
||||||
self.authors = authors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A path to one of the axes of a NostrFilter.
|
|
||||||
enum ChunkPath {
|
|
||||||
case pubkeys
|
|
||||||
case authors
|
|
||||||
// Other paths/axes not supported yet
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the value of a single axis of a NostrFilter
|
|
||||||
enum Slice {
|
|
||||||
case pubkeys([Pubkey]?)
|
|
||||||
case authors([Pubkey]?)
|
|
||||||
|
|
||||||
func chunked(into chunk_size: Int) -> [Slice] {
|
|
||||||
switch self {
|
|
||||||
case .pubkeys(let array):
|
|
||||||
return (array ?? []).chunked(into: chunk_size).map({ .pubkeys($0) })
|
|
||||||
case .authors(let array):
|
|
||||||
return (array ?? []).chunked(into: chunk_size).map({ .authors($0) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,23 +226,19 @@ class RelayPool {
|
|||||||
print("queueing request for \(relay)")
|
print("queueing request for \(relay)")
|
||||||
request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral))
|
request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral))
|
||||||
}
|
}
|
||||||
|
|
||||||
func send_raw_to_local_ndb(_ req: NostrRequestType) {
|
|
||||||
// send to local relay (nostrdb)
|
|
||||||
switch req {
|
|
||||||
case .typical(let r):
|
|
||||||
if case .event = r, let rstr = make_nostr_req(r) {
|
|
||||||
let _ = ndb.process_client_event(rstr)
|
|
||||||
}
|
|
||||||
case .custom(let string):
|
|
||||||
let _ = ndb.process_client_event(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func send_raw(_ req: NostrRequestType, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) {
|
func send_raw(_ req: NostrRequestType, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) {
|
||||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||||
|
|
||||||
self.send_raw_to_local_ndb(req)
|
// send to local relay (nostrdb)
|
||||||
|
switch req {
|
||||||
|
case .typical(let r):
|
||||||
|
if case .event = r, let rstr = make_nostr_req(r) {
|
||||||
|
let _ = ndb.process_client_event(rstr)
|
||||||
|
}
|
||||||
|
case .custom(let string):
|
||||||
|
let _ = ndb.process_client_event(string)
|
||||||
|
}
|
||||||
|
|
||||||
for relay in relays {
|
for relay in relays {
|
||||||
if req.is_read && !(relay.descriptor.info.read ?? true) {
|
if req.is_read && !(relay.descriptor.info.read ?? true) {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ var test_damus_state: DamusState = ({
|
|||||||
likes: .init(our_pubkey: our_pubkey),
|
likes: .init(our_pubkey: our_pubkey),
|
||||||
boosts: .init(our_pubkey: our_pubkey),
|
boosts: .init(our_pubkey: our_pubkey),
|
||||||
contacts: .init(our_pubkey: our_pubkey),
|
contacts: .init(our_pubkey: our_pubkey),
|
||||||
mutelist_manager: MutelistManager(user_keypair: test_keypair),
|
mutelist_manager: MutelistManager(),
|
||||||
profiles: .init(ndb: ndb),
|
profiles: .init(ndb: ndb),
|
||||||
dms: .init(our_pubkey: our_pubkey),
|
dms: .init(our_pubkey: our_pubkey),
|
||||||
previews: .init(),
|
previews: .init(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class CompatibleText: Equatable {
|
|||||||
return AnyView(
|
return AnyView(
|
||||||
VStack {
|
VStack {
|
||||||
Image("warning")
|
Image("warning")
|
||||||
Text("This note contains too many items and cannot be rendered", comment: "Error message indicating that a note is too big and cannot be rendered")
|
Text(NSLocalizedString("This note contains too many items and cannot be rendered", comment: "Error message indicating that a note is too big and cannot be rendered"))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ class Constants {
|
|||||||
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
||||||
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info")!
|
||||||
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
||||||
static let DEVICE_TOKEN_REVOKER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info/remove")!
|
|
||||||
static let DEVICE_TOKEN_REVOKER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info/remove")!
|
|
||||||
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
||||||
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class EventCache {
|
|||||||
var ev = event
|
var ev = event
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
guard let direct_reply = ev.direct_replies(),
|
guard let direct_reply = ev.direct_replies(keypair).last,
|
||||||
let next_ev = lookup(direct_reply), next_ev != ev
|
let next_ev = lookup(direct_reply), next_ev != ev
|
||||||
else {
|
else {
|
||||||
break
|
break
|
||||||
@@ -183,7 +183,7 @@ class EventCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add_replies(ev: NostrEvent, keypair: Keypair) {
|
func add_replies(ev: NostrEvent, keypair: Keypair) {
|
||||||
if let reply = ev.direct_replies() {
|
for reply in ev.direct_replies(keypair) {
|
||||||
replies.add(id: reply, reply_id: ev.id)
|
replies.add(id: reply, reply_id: ev.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,16 +218,7 @@ class EventCache {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
func lookup(_ evid: NoteId) -> NostrEvent? {
|
func lookup(_ evid: NoteId) -> NostrEvent? {
|
||||||
if let ev = events[evid] {
|
return events[evid]
|
||||||
return ev
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ev = self.ndb.lookup_note(evid)?.unsafeUnownedValue?.to_owned() {
|
|
||||||
events[ev.id] = ev
|
|
||||||
return ev
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(_ ev: NostrEvent) {
|
func insert(_ ev: NostrEvent) {
|
||||||
@@ -248,11 +239,6 @@ func should_translate(event: NostrEvent, our_keypair: Keypair, settings: UserSet
|
|||||||
guard settings.can_translate else {
|
guard settings.can_translate else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't translate reposts, longform, etc
|
|
||||||
if event.kind != 1 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not translate self-authored notes if logged in with a private key
|
// Do not translate self-authored notes if logged in with a private key
|
||||||
// as we can assume the user can understand their own notes.
|
// as we can assume the user can understand their own notes.
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// Array.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2024-05-10.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension Array {
|
|
||||||
/// Splits the array into chunks of the specified size.
|
|
||||||
/// - Parameter size: The maximum size of each chunk.
|
|
||||||
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
|
||||||
func chunked(into size: Int) -> [[Element]] {
|
|
||||||
guard size > 0 else { return [self] }
|
|
||||||
return stride(from: 0, to: count, by: size).map {
|
|
||||||
Array(self[$0..<Swift.min($0 + size, count)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array where Element: Equatable {
|
|
||||||
mutating func removeAll(equalTo item: Element) {
|
|
||||||
self.removeAll(where: { $0 == item })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ func processImage(image: UIImage) -> URL? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func processImage(source: CGImageSource, fileExtension: String) -> URL? {
|
fileprivate func processImage(source: CGImageSource, fileExtension: String) -> URL? {
|
||||||
let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: fileExtension)
|
let destinationURL = createMediaURL(fileExtension: fileExtension)
|
||||||
|
|
||||||
guard let destination = removeGPSDataFromImage(source: source, url: destinationURL) else { return nil }
|
guard let destination = removeGPSDataFromImage(source: source, url: destinationURL) else { return nil }
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ func processVideo(videoURL: URL) -> URL? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
|
fileprivate func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
|
||||||
let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: videoURL.pathExtension)
|
let destinationURL = createMediaURL(fileExtension: videoURL.pathExtension)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try FileManager.default.copyItem(at: videoURL, to: destinationURL)
|
try FileManager.default.copyItem(at: videoURL, to: destinationURL)
|
||||||
@@ -57,7 +57,7 @@ fileprivate func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a temporary URL with a unique filename
|
/// Generate a temporary URL with a unique filename
|
||||||
func generateUniqueTemporaryMediaURL(fileExtension: String) -> URL {
|
fileprivate func createMediaURL(fileExtension: String) -> URL {
|
||||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
let uniqueMediaName = "\(UUID().uuidString).\(fileExtension)"
|
let uniqueMediaName = "\(UUID().uuidString).\(fileExtension)"
|
||||||
let temporaryMediaURL = temporaryDirectoryURL.appendingPathComponent(uniqueMediaName)
|
let temporaryMediaURL = temporaryDirectoryURL.appendingPathComponent(uniqueMediaName)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ enum LogCategory: String {
|
|||||||
case storage
|
case storage
|
||||||
case push_notifications
|
case push_notifications
|
||||||
case damus_purple
|
case damus_purple
|
||||||
case image_uploading
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Damus structured logger
|
/// Damus structured logger
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func load_bootstrap_relays(pubkey: Pubkey) -> [RelayURL] {
|
|||||||
|
|
||||||
let relay_urls = relays.compactMap({ RelayURL($0) })
|
let relay_urls = relays.compactMap({ RelayURL($0) })
|
||||||
|
|
||||||
let loaded_relays = Array(Set(relay_urls))
|
let loaded_relays = Array(Set(relay_urls + get_default_bootstrap_relays()))
|
||||||
print("Loading custom bootstrap relays: \(loaded_relays)")
|
print("Loading custom bootstrap relays: \(loaded_relays)")
|
||||||
return loaded_relays
|
return loaded_relays
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ReplyCounter {
|
|||||||
|
|
||||||
counted.insert(event.id)
|
counted.insert(event.id)
|
||||||
|
|
||||||
if let reply = event.direct_replies() {
|
for reply in event.direct_replies(keypair) {
|
||||||
if event.pubkey == our_pubkey {
|
if event.pubkey == our_pubkey {
|
||||||
self.our_replies[reply] = event
|
self.our_replies[reply] = event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ enum Route: Hashable {
|
|||||||
case ReactionsSettings(settings: UserSettingsStore)
|
case ReactionsSettings(settings: UserSettingsStore)
|
||||||
case SearchSettings(settings: UserSettingsStore)
|
case SearchSettings(settings: UserSettingsStore)
|
||||||
case DeveloperSettings(settings: UserSettingsStore)
|
case DeveloperSettings(settings: UserSettingsStore)
|
||||||
case FirstAidSettings(settings: UserSettingsStore)
|
|
||||||
case Thread(thread: ThreadModel)
|
case Thread(thread: ThreadModel)
|
||||||
case Reposts(reposts: EventsModel)
|
case Reposts(reposts: EventsModel)
|
||||||
case QuoteReposts(quotes: EventsModel)
|
case QuoteReposts(quotes: EventsModel)
|
||||||
@@ -79,7 +78,7 @@ enum Route: Hashable {
|
|||||||
case .AppearanceSettings(let settings):
|
case .AppearanceSettings(let settings):
|
||||||
AppearanceSettingsView(damus_state: damusState, settings: settings)
|
AppearanceSettingsView(damus_state: damusState, settings: settings)
|
||||||
case .NotificationSettings(let settings):
|
case .NotificationSettings(let settings):
|
||||||
NotificationSettingsView(damus_state: damusState, settings: settings)
|
NotificationSettingsView(settings: settings)
|
||||||
case .ZapSettings(let settings):
|
case .ZapSettings(let settings):
|
||||||
ZapSettingsView(settings: settings)
|
ZapSettingsView(settings: settings)
|
||||||
case .TranslationSettings(let settings):
|
case .TranslationSettings(let settings):
|
||||||
@@ -90,8 +89,6 @@ enum Route: Hashable {
|
|||||||
SearchSettingsView(settings: settings)
|
SearchSettingsView(settings: settings)
|
||||||
case .DeveloperSettings(let settings):
|
case .DeveloperSettings(let settings):
|
||||||
DeveloperSettingsView(settings: settings)
|
DeveloperSettingsView(settings: settings)
|
||||||
case .FirstAidSettings(settings: let settings):
|
|
||||||
FirstAidSettingsView(damus_state: damusState, settings: settings)
|
|
||||||
case .Thread(let thread):
|
case .Thread(let thread):
|
||||||
ThreadView(state: damusState, thread: thread)
|
ThreadView(state: damusState, thread: thread)
|
||||||
case .Reposts(let reposts):
|
case .Reposts(let reposts):
|
||||||
@@ -178,8 +175,6 @@ enum Route: Hashable {
|
|||||||
hasher.combine("searchSettings")
|
hasher.combine("searchSettings")
|
||||||
case .DeveloperSettings:
|
case .DeveloperSettings:
|
||||||
hasher.combine("developerSettings")
|
hasher.combine("developerSettings")
|
||||||
case .FirstAidSettings:
|
|
||||||
hasher.combine("firstAidSettings")
|
|
||||||
case .Thread(let threadModel):
|
case .Thread(let threadModel):
|
||||||
hasher.combine("thread")
|
hasher.combine("thread")
|
||||||
hasher.combine(threadModel.event.id)
|
hasher.combine(threadModel.event.id)
|
||||||
|
|||||||
@@ -411,6 +411,9 @@ func get_zap_description(_ ev: NostrEvent, inv_desc: InvoiceDescription) -> Stri
|
|||||||
guard let data = desc.data(using: .utf8) else {
|
guard let data = desc.data(using: .utf8) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
guard sha256(data) == deschash else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return desc
|
return desc
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ struct ShareActionButton: View {
|
|||||||
.frame(width: 55.0, height: 55.0)
|
.frame(width: 55.0, height: 55.0)
|
||||||
}
|
}
|
||||||
.frame(height: 25)
|
.frame(height: 25)
|
||||||
Text(text)
|
Text(verbatim: text)
|
||||||
.foregroundColor(col)
|
.foregroundColor(col)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ struct AddRelayView: View {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Add relay", comment: "Button to add a relay.")
|
Text(verbatim: "Add relay")
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ enum ImageUploadResult {
|
|||||||
|
|
||||||
fileprivate func create_upload_body(mediaData: Data, boundary: String, mediaUploader: MediaUploader, mediaToUpload: MediaUpload) -> Data {
|
fileprivate func create_upload_body(mediaData: Data, boundary: String, mediaUploader: MediaUploader, mediaToUpload: MediaUpload) -> Data {
|
||||||
let body = NSMutableData();
|
let body = NSMutableData();
|
||||||
let contentType = mediaToUpload.mime_type
|
let contentType = mediaToUpload.is_image ? "image/jpg" : "video/mp4"
|
||||||
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
||||||
body.appendString(string: "--\(boundary)\r\n")
|
body.appendString(string: "--\(boundary)\r\n")
|
||||||
body.appendString(string: "Content-Disposition: form-data; name=\(mediaUploader.nameParam); filename=\(mediaToUpload.genericFileName)\r\n")
|
body.appendString(string: "Content-Disposition: form-data; name=\(mediaUploader.nameParam); filename=\(mediaToUpload.genericFileName)\r\n")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct BookmarksView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 32.0, height: 32.0)
|
.frame(width: 32.0, height: 32.0)
|
||||||
Text("You have no bookmarks yet, add them in the context menu", comment: "Text indicating that there are no bookmarks to be viewed")
|
Text(NSLocalizedString("You have no bookmarks yet, add them in the context menu", comment: "Text indicating that there are no bookmarks to be viewed"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ struct GradientFollowButton: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
follow_state = perform_follow_btn_action(follow_state, target: target)
|
follow_state = perform_follow_btn_action(follow_state, target: target)
|
||||||
}) {
|
}) {
|
||||||
let followButtonText = follow_btn_txt(follow_state, follows_you: follows_you)
|
Text(follow_btn_txt(follow_state, follows_you: follows_you))
|
||||||
Text(followButtonText)
|
|
||||||
.foregroundColor(follow_state == .unfollows ? .white : grayTextColor)
|
.foregroundColor(follow_state == .unfollows ? .white : grayTextColor)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ struct ConfigView: View {
|
|||||||
NavigationLink(value: Route.DeveloperSettings(settings: settings)) {
|
NavigationLink(value: Route.DeveloperSettings(settings: settings)) {
|
||||||
IconLabel(NSLocalizedString("Developer", comment: "Section header for developer settings"), img_name: "magic-stick2.fill", color: DamusColors.adaptableBlack)
|
IconLabel(NSLocalizedString("Developer", comment: "Section header for developer settings"), img_name: "magic-stick2.fill", color: DamusColors.adaptableBlack)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink(value: Route.FirstAidSettings(settings: settings)) {
|
|
||||||
IconLabel(NSLocalizedString("First Aid", comment: "Section header for first aid tools and settings"), img_name: "help2", color: .red)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
|
||||||
@@ -88,7 +84,7 @@ struct ConfigView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state.is_privkey_user {
|
if state.is_privkey_user {
|
||||||
Section(header: Text("Permanently Delete Account", comment: "Section title for deleting the user")) {
|
Section(header: Text(NSLocalizedString("Permanently Delete Account", comment: "Section title for deleting the user"))) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
delete_account_warning = true
|
delete_account_warning = true
|
||||||
}, label: {
|
}, label: {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct CreateAccountView: View {
|
|||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
EditPictureControl(uploader: .nostrBuild, pubkey: account.pubkey, image_url: $account.profile_image , uploadObserver: profileUploadObserver, callback: uploadedProfilePicture)
|
||||||
|
|
||||||
Text("Public Key", comment: "Label to indicate the public key of the account.")
|
Text(NSLocalizedString("Public Key", comment: "Label to indicate the public key of the account."))
|
||||||
.bold()
|
.bold()
|
||||||
.padding()
|
.padding()
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct DMChatView: View, KeyboardReadable {
|
|||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
ForEach(Array(zip(dms.events, dms.events.indices)).filter { should_show_event(state: damus_state, ev: $0.0)}, id: \.0.id) { (ev, ind) in
|
ForEach(Array(zip(dms.events, dms.events.indices)).filter { should_show_event(state: damus_state, ev: $0.0, keypair: damus_state.keypair)}, id: \.0.id) { (ev, ind) in
|
||||||
DMView(event: dms.events[ind], damus_state: damus_state)
|
DMView(event: dms.events[ind], damus_state: damus_state)
|
||||||
.contextMenu{MenuItems(damus_state: damus_state, event: ev, target_pubkey: ev.pubkey, profileModel: ProfileModel(pubkey: ev.pubkey, damus: damus_state))}
|
.contextMenu{MenuItems(damus_state: damus_state, event: ev, target_pubkey: ev.pubkey, profileModel: ProfileModel(pubkey: ev.pubkey, damus: damus_state))}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ struct DirectMessagesView: View {
|
|||||||
|
|
||||||
func MaybeEvent(_ model: DirectMessageModel) -> some View {
|
func MaybeEvent(_ model: DirectMessageModel) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if let ev = model.events.last(where: { should_show_event(state: damus_state, ev: $0) }) {
|
if let ev = model.events.last(where: { should_show_event(state: damus_state, ev: $0, keypair: damus_state.keypair) }) {
|
||||||
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
self.model.set_active_dm_model(model)
|
self.model.set_active_dm_model(model)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// ContextButton.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-06-01.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContextButton: View {
|
||||||
|
var body: some View {
|
||||||
|
Text(verbatim: /*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContextButton_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ContextButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ struct ProxyView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
let protocolLogo = get_protocol_image(protocolName: proxy.protocolName)
|
let protocolLogo = get_protocol_image(protocolName: proxy.protocolName)
|
||||||
if protocolLogo.isEmpty {
|
if protocolLogo.isEmpty {
|
||||||
Text(proxy.protocolName)
|
Text("\(proxy.protocolName)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
} else {
|
} else {
|
||||||
Image(protocolLogo)
|
Image(protocolLogo)
|
||||||
|
|||||||
@@ -13,10 +13,18 @@ struct ReplyPart: View {
|
|||||||
let keypair: Keypair
|
let keypair: Keypair
|
||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
|
|
||||||
|
var replying_to: NostrEvent? {
|
||||||
|
guard let note_ref = event.event_refs(keypair).first(where: { evref in evref.is_direct_reply != nil })?.is_direct_reply else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.lookup(note_ref.note_id)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let reply_ref = event.thread_reply()?.reply {
|
if event_is_reply(event.event_refs(keypair)) {
|
||||||
ReplyDescription(event: event, replying_to: events.lookup(reply_ref.note_id), ndb: ndb)
|
ReplyDescription(event: event, replying_to: replying_to, ndb: ndb)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ struct EventBody: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if event.known_kind == .longform {
|
if event.known_kind == .longform {
|
||||||
LongformPreviewBody(state: damus_state, ev: event, options: options, header: true)
|
LongformPreviewBody(state: damus_state, ev: event, options: options)
|
||||||
|
|
||||||
// truncated longform bodies are just the preview
|
// truncated longform bodies are just the preview
|
||||||
if !options.contains(.truncate_content) {
|
if !options.contains(.truncate_content) {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ struct MenuItems: View {
|
|||||||
if event.known_kind != .dm {
|
if event.known_kind != .dm {
|
||||||
MuteDurationMenu { duration in
|
MuteDurationMenu { duration in
|
||||||
if let full_keypair = self.damus_state.keypair.to_full(),
|
if let full_keypair = self.damus_state.keypair.to_full(),
|
||||||
let new_mutelist_ev = toggle_from_mutelist(keypair: full_keypair, prev: damus_state.mutelist_manager.event, to_toggle: .thread(event.thread_id(), duration?.date_from_now)) {
|
let new_mutelist_ev = toggle_from_mutelist(keypair: full_keypair, prev: damus_state.mutelist_manager.event, to_toggle: .thread(event.thread_id(keypair: damus_state.keypair), duration?.date_from_now)) {
|
||||||
damus_state.mutelist_manager.set_mutelist(new_mutelist_ev)
|
damus_state.mutelist_manager.set_mutelist(new_mutelist_ev)
|
||||||
damus_state.postbox.send(new_mutelist_ev)
|
damus_state.postbox.send(new_mutelist_ev)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,31 +6,25 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Kingfisher
|
|
||||||
|
|
||||||
struct LongformPreviewBody: View {
|
struct LongformPreviewBody: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
let event: LongformEvent
|
let event: LongformEvent
|
||||||
let options: EventViewOptions
|
let options: EventViewOptions
|
||||||
let header: Bool
|
|
||||||
@State var blur_images: Bool = true
|
|
||||||
|
|
||||||
@ObservedObject var artifacts: NoteArtifactsModel
|
@ObservedObject var artifacts: NoteArtifactsModel
|
||||||
|
|
||||||
init(state: DamusState, ev: LongformEvent, options: EventViewOptions, header: Bool) {
|
init(state: DamusState, ev: LongformEvent, options: EventViewOptions) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.event = ev
|
self.event = ev
|
||||||
self.options = options
|
self.options = options
|
||||||
self.header = header
|
|
||||||
|
|
||||||
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.event.id).artifacts_model)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(state: DamusState, ev: NostrEvent, options: EventViewOptions, header: Bool) {
|
init(state: DamusState, ev: NostrEvent, options: EventViewOptions) {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.event = LongformEvent.parse(from: ev)
|
self.event = LongformEvent.parse(from: ev)
|
||||||
self.options = options
|
self.options = options
|
||||||
self.header = header
|
|
||||||
|
|
||||||
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
self._artifacts = ObservedObject(wrappedValue: state.events.get_cache_data(ev.id).artifacts_model)
|
||||||
}
|
}
|
||||||
@@ -39,67 +33,6 @@ struct LongformPreviewBody: View {
|
|||||||
let wordCount = pluralizedString(key: "word_count", count: words)
|
let wordCount = pluralizedString(key: "word_count", count: words)
|
||||||
return Text(wordCount)
|
return Text(wordCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
var truncate: Bool {
|
|
||||||
return options.contains(.truncate_content)
|
|
||||||
}
|
|
||||||
|
|
||||||
var truncate_very_short: Bool {
|
|
||||||
return options.contains(.truncate_content_very_short)
|
|
||||||
}
|
|
||||||
|
|
||||||
func truncatedText(content: CompatibleText) -> some View {
|
|
||||||
Group {
|
|
||||||
if truncate_very_short {
|
|
||||||
TruncatedText(text: content, maxChars: 140)
|
|
||||||
.font(header ? .body : .caption)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
}
|
|
||||||
else if truncate {
|
|
||||||
TruncatedText(text: content)
|
|
||||||
.font(header ? .body : .caption)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
} else {
|
|
||||||
content.text
|
|
||||||
.font(header ? .body : .caption)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Placeholder(url: URL) -> some View {
|
|
||||||
Group {
|
|
||||||
if let meta = state.events.lookup_img_metadata(url: url),
|
|
||||||
case .processed(let blurhash) = meta.state {
|
|
||||||
Image(uiImage: blurhash)
|
|
||||||
.resizable()
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
|
|
||||||
} else {
|
|
||||||
DamusColors.adaptableWhite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func titleImage(url: URL) -> some View {
|
|
||||||
KFAnimatedImage(url)
|
|
||||||
.callbackQueue(.dispatch(.global(qos:.background)))
|
|
||||||
.backgroundDecode(true)
|
|
||||||
.imageContext(.note, disable_animation: state.settings.disable_animation)
|
|
||||||
.image_fade(duration: 0.25)
|
|
||||||
.cancelOnDisappear(true)
|
|
||||||
.configure { view in
|
|
||||||
view.framePreloadCount = 3
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
Placeholder(url: url)
|
|
||||||
}
|
|
||||||
.aspectRatio(contentMode: .fill)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: header ? .infinity : 150)
|
|
||||||
.cornerRadius(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
@@ -113,71 +46,23 @@ struct LongformPreviewBody: View {
|
|||||||
|
|
||||||
var Main: some View {
|
var Main: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
if let url = event.image {
|
if let title = event.title {
|
||||||
if (self.options.contains(.no_media)) {
|
Text(title)
|
||||||
EmptyView()
|
.font(.title)
|
||||||
} else if !blur_images || (!blur_images && !state.settings.media_previews) {
|
} else {
|
||||||
titleImage(url: url)
|
Text("Untitled", comment: "Text indicating that the long-form note title is untitled.")
|
||||||
} else if blur_images || (blur_images && !state.settings.media_previews) {
|
.font(.title)
|
||||||
ZStack {
|
|
||||||
titleImage(url: url)
|
|
||||||
Blur()
|
|
||||||
.onTapGesture {
|
|
||||||
blur_images = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(event.title ?? "Untitled")
|
|
||||||
.font(header ? .title : .headline)
|
|
||||||
.padding(.horizontal, 10)
|
|
||||||
.padding(.top, 5)
|
|
||||||
|
|
||||||
if let summary = event.summary {
|
|
||||||
truncatedText(content: CompatibleText(stringLiteral: summary))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let labels = event.labels {
|
|
||||||
ScrollView(.horizontal) {
|
|
||||||
HStack {
|
|
||||||
ForEach(labels, id: \.self) { label in
|
|
||||||
Text(label)
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
|
|
||||||
.background(DamusColors.neutral1)
|
|
||||||
.cornerRadius(20)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 20)
|
|
||||||
.stroke(DamusColors.neutral3, lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scrollIndicators(.hidden)
|
|
||||||
.padding(10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text(event.summary ?? "")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
if case .loaded(let arts) = artifacts.state,
|
if case .loaded(let arts) = artifacts.state,
|
||||||
case .longform(let longform) = arts
|
case .longform(let longform) = arts
|
||||||
{
|
{
|
||||||
Words(longform.words).font(.footnote)
|
Words(longform.words).font(.footnote)
|
||||||
.padding([.horizontal, .bottom], 10)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.background(DamusColors.neutral3)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 10)
|
|
||||||
.stroke(DamusColors.neutral1, lineWidth: 1)
|
|
||||||
)
|
|
||||||
.padding(.top, 10)
|
|
||||||
.onAppear {
|
|
||||||
blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event.event, our_pubkey: state.pubkey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +79,7 @@ struct LongformPreview: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
EventShell(state: state, event: event.event, options: options) {
|
EventShell(state: state, event: event.event, options: options) {
|
||||||
LongformPreviewBody(state: state, ev: event, options: options, header: false)
|
LongformPreviewBody(state: state, ev: event, options: options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ struct SelectedEventView: View {
|
|||||||
|
|
||||||
@StateObject var bar: ActionBarModel
|
@StateObject var bar: ActionBarModel
|
||||||
|
|
||||||
|
var replying_to: NostrEvent? {
|
||||||
|
guard let note_ref = event.event_refs(damus.keypair).first(where: { evref in evref.is_direct_reply != nil })?.is_direct_reply else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return damus.events.lookup(note_ref.note_id)
|
||||||
|
}
|
||||||
|
|
||||||
init(damus: DamusState, event: NostrEvent, size: EventViewKind) {
|
init(damus: DamusState, event: NostrEvent, size: EventViewKind) {
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
self.event = event
|
self.event = event
|
||||||
@@ -40,8 +48,8 @@ struct SelectedEventView: View {
|
|||||||
.minimumScaleFactor(0.75)
|
.minimumScaleFactor(0.75)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if let reply_ref = event.thread_reply()?.reply {
|
if event_is_reply(event.event_refs(damus.keypair)) {
|
||||||
ReplyDescription(event: event, replying_to: damus.events.lookup(reply_ref.note_id), ndb: damus.ndb)
|
ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ struct FullScreenCarouselView_Previews: PreviewProvider {
|
|||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(verbatim: "Some content")
|
Text("Some content")
|
||||||
.padding()
|
.padding()
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
|||||||
@@ -36,29 +36,7 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
result.itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { (item, error) in
|
result.itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { (item, error) in
|
||||||
guard let url = item as? URL else { return }
|
guard let url = item as? URL else { return }
|
||||||
|
|
||||||
if(url.pathExtension == "gif") {
|
if canGetSourceTypeFromUrl(url: url) {
|
||||||
// GIFs do not natively support location metadata (See https://superuser.com/a/556320 and https://www.w3.org/Graphics/GIF/spec-gif89a.txt)
|
|
||||||
// It is better to avoid any GPS data processing at all, as it can cause the image to be converted to JPEG.
|
|
||||||
// Therefore, we should load the file directtly and deliver it as "already processed".
|
|
||||||
|
|
||||||
// Load the data for the GIF image
|
|
||||||
// - Don't load it as an UIImage since that can only get exported into JPEG/PNG
|
|
||||||
// - Don't load it as a file representation because it gets deleted before the upload can occur
|
|
||||||
_ = result.itemProvider.loadDataRepresentation(for: .gif, completionHandler: { imageData, error in
|
|
||||||
guard let imageData else { return }
|
|
||||||
let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: "gif")
|
|
||||||
do {
|
|
||||||
try imageData.write(to: destinationURL)
|
|
||||||
Task {
|
|
||||||
await self.chooseMedia(.processed_image(destinationURL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Log.error("Failed to write GIF image data from Photo picker into a local copy", for: .image_uploading)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else if canGetSourceTypeFromUrl(url: url) {
|
|
||||||
// Media was not taken from camera
|
// Media was not taken from camera
|
||||||
self.attemptAcquireResourceAndChooseMedia(
|
self.attemptAcquireResourceAndChooseMedia(
|
||||||
url: url,
|
url: url,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ struct MuteDurationMenu<T: View>: View {
|
|||||||
Button {
|
Button {
|
||||||
action(duration)
|
action(duration)
|
||||||
} label: {
|
} label: {
|
||||||
Text(duration.title)
|
Text("\(duration.title)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
@@ -30,6 +30,6 @@ struct MuteDurationMenu<T: View>: View {
|
|||||||
MuteDurationMenu { _ in
|
MuteDurationMenu { _ in
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
Text(verbatim: "Mute hashtag")
|
Text("Mute hashtag")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ struct MutelistView: View {
|
|||||||
Section(NSLocalizedString("Hashtags", comment: "Section header title for a list of hashtags that are muted.")) {
|
Section(NSLocalizedString("Hashtags", comment: "Section header title for a list of hashtags that are muted.")) {
|
||||||
ForEach(hashtags, id: \.self) { item in
|
ForEach(hashtags, id: \.self) { item in
|
||||||
if case let MuteItem.hashtag(hashtag, _) = item {
|
if case let MuteItem.hashtag(hashtag, _) = item {
|
||||||
Text(verbatim: "#\(hashtag.hashtag)")
|
Text("#\(hashtag.hashtag)")
|
||||||
.id(hashtag.hashtag)
|
.id(hashtag.hashtag)
|
||||||
.swipeActions {
|
.swipeActions {
|
||||||
RemoveAction(item: .hashtag(hashtag, nil))
|
RemoveAction(item: .hashtag(hashtag, nil))
|
||||||
@@ -76,7 +76,7 @@ struct MutelistView: View {
|
|||||||
Section(NSLocalizedString("Words", comment: "Section header title for a list of words that are muted.")) {
|
Section(NSLocalizedString("Words", comment: "Section header title for a list of words that are muted.")) {
|
||||||
ForEach(words, id: \.self) { item in
|
ForEach(words, id: \.self) { item in
|
||||||
if case let MuteItem.word(word, _) = item {
|
if case let MuteItem.word(word, _) = item {
|
||||||
Text(word)
|
Text("\(word)")
|
||||||
.id(word)
|
.id(word)
|
||||||
.swipeActions {
|
.swipeActions {
|
||||||
RemoveAction(item: .word(word, nil))
|
RemoveAction(item: .word(word, nil))
|
||||||
@@ -94,7 +94,7 @@ struct MutelistView: View {
|
|||||||
RemoveAction(item: .thread(note_id, nil))
|
RemoveAction(item: .thread(note_id, nil))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("Error retrieving muted event", comment: "Text for an item that application failed to retrieve the muted event for.")
|
Text(NSLocalizedString("Error retrieving muted event", comment: "Text for an item that application failed to retrieve the muted event for."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ struct DamusAppNotificationView: View {
|
|||||||
.shadow(radius: 5, y: 5)
|
.shadow(radius: 5, y: 5)
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
HStack(alignment: .center, spacing: 3) {
|
HStack(alignment: .center, spacing: 3) {
|
||||||
Text("Damus", comment: "Name of the app for the title of an internal notification")
|
Text(NSLocalizedString("Damus", comment: "Name of the app for the title of an internal notification"))
|
||||||
.font(.body.weight(.bold))
|
.font(.body.weight(.bold))
|
||||||
Text(verbatim: "·")
|
Text("·")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Text(relative_date)
|
Text(relative_date)
|
||||||
.font(.system(size: 16))
|
.font(.system(size: 16))
|
||||||
@@ -49,7 +49,7 @@ struct DamusAppNotificationView: View {
|
|||||||
Image("check-circle.fill")
|
Image("check-circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 15, height: 15)
|
.frame(width: 15, height: 15)
|
||||||
Text("Internal app notification", comment: "Badge indicating that a notification is an official internal app notification")
|
Text(NSLocalizedString("Internal app notification", comment: "Badge indicating that a notification is an official internal app notification"))
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ struct EventGroupView: View {
|
|||||||
return VStack(alignment: .center) {
|
return VStack(alignment: .center) {
|
||||||
Image("zap.fill")
|
Image("zap.fill")
|
||||||
.foregroundColor(.orange)
|
.foregroundColor(.orange)
|
||||||
Text(fmt)
|
Text(verbatim: fmt)
|
||||||
.foregroundColor(Color.orange)
|
.foregroundColor(Color.orange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ struct OnboardingSuggestionsView: View {
|
|||||||
.navigationBarItems(leading: Button(action: {
|
.navigationBarItems(leading: Button(action: {
|
||||||
self.next_page()
|
self.next_page()
|
||||||
}, label: {
|
}, label: {
|
||||||
Text("Skip", comment: "Button to dismiss the suggested users screen")
|
Text(NSLocalizedString("Skip", comment: "Button to dismiss the suggested users screen"))
|
||||||
.font(.subheadline.weight(.semibold))
|
.font(.subheadline.weight(.semibold))
|
||||||
}))
|
}))
|
||||||
.tag(0)
|
.tag(0)
|
||||||
@@ -48,7 +48,7 @@ struct OnboardingSuggestionsView: View {
|
|||||||
AnyView(
|
AnyView(
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "sparkles")
|
Image(systemName: "sparkles")
|
||||||
Text("Add your first post", comment: "Prompt given to the user during onboarding, suggesting them to write their first post")
|
Text(NSLocalizedString("Add your first post", comment: "Prompt given to the user during onboarding, suggesting them to write their first post"))
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
@@ -97,7 +97,7 @@ fileprivate struct SuggestedUsersPageView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
self.next_page()
|
self.next_page()
|
||||||
}) {
|
}) {
|
||||||
Text("Continue", comment: "Button to dismiss suggested users view and continue to the main app")
|
Text(NSLocalizedString("Continue", comment: "Button to dismiss suggested users view and continue to the main app"))
|
||||||
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
|
||||||
}
|
}
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
|
|||||||
+11
-54
@@ -92,24 +92,13 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func send_post() {
|
func send_post() {
|
||||||
// don't add duplicate pubkeys but retain order
|
let refs = references.filter { ref in
|
||||||
var pkset = Set<Pubkey>()
|
if case .pubkey(let pk) = ref, filtered_pubkeys.contains(pk) {
|
||||||
|
return false
|
||||||
// we only want pubkeys really
|
|
||||||
let pks = references.reduce(into: Array<Pubkey>()) { acc, ref in
|
|
||||||
guard case .pubkey(let pk) = ref else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
if pkset.contains(pk) || filtered_pubkeys.contains(pk) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pkset.insert(pk)
|
|
||||||
acc.append(pk)
|
|
||||||
}
|
}
|
||||||
|
let new_post = build_post(state: damus_state, post: self.post, action: action, uploadedMedias: uploadedMedias, references: refs)
|
||||||
let new_post = build_post(state: damus_state, post: self.post, action: action, uploadedMedias: uploadedMedias, pubkeys: pks)
|
|
||||||
|
|
||||||
notify(.post(.post(new_post)))
|
notify(.post(.post(new_post)))
|
||||||
|
|
||||||
@@ -279,7 +268,7 @@ struct PostView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
self.cancel()
|
self.cancel()
|
||||||
}, label: {
|
}, label: {
|
||||||
Text("Cancel", comment: "Button to cancel out of posting a note.")
|
Text(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note."))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
})
|
})
|
||||||
.buttonStyle(NeutralButtonStyle())
|
.buttonStyle(NeutralButtonStyle())
|
||||||
@@ -615,24 +604,7 @@ private func isAlphanumeric(_ char: Character) -> Bool {
|
|||||||
return char.isLetter || char.isNumber
|
return char.isLetter || char.isNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
func nip10_reply_tags(replying_to: NostrEvent, keypair: Keypair) -> [[String]] {
|
func build_post(state: DamusState, post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [RefId]) -> NostrPost {
|
||||||
guard let nip10 = replying_to.thread_reply() else {
|
|
||||||
// we're replying to a post that isn't in a thread,
|
|
||||||
// just add a single reply-to-root tag
|
|
||||||
return [["e", replying_to.id.hex(), "", "root"]]
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise use the root tag from the parent's nip10 reply and include the note
|
|
||||||
// that we are replying to's note id.
|
|
||||||
let tags = [
|
|
||||||
["e", nip10.root.note_id.hex(), nip10.root.relay ?? "", "root"],
|
|
||||||
["e", replying_to.id.hex(), "", "reply"]
|
|
||||||
]
|
|
||||||
|
|
||||||
return tags
|
|
||||||
}
|
|
||||||
|
|
||||||
func build_post(state: DamusState, post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], pubkeys: [Pubkey]) -> NostrPost {
|
|
||||||
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
||||||
if let link = attributes[.link] as? String {
|
if let link = attributes[.link] as? String {
|
||||||
let nextCharIndex = range.upperBound
|
let nextCharIndex = range.upperBound
|
||||||
@@ -662,35 +634,20 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post
|
|||||||
|
|
||||||
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
||||||
|
|
||||||
|
var tags = uploadedMedias.compactMap { $0.metadata?.to_tag() }
|
||||||
|
|
||||||
if !imagesString.isEmpty {
|
if !imagesString.isEmpty {
|
||||||
content.append(" " + imagesString + " ")
|
content.append(" " + imagesString + " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tags: [[String]] = []
|
if case .quoting(let ev) = action {
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .replying_to(let replying_to):
|
|
||||||
// start off with the reply tags
|
|
||||||
tags = nip10_reply_tags(replying_to: replying_to, keypair: state.keypair)
|
|
||||||
|
|
||||||
case .quoting(let ev):
|
|
||||||
content.append(" nostr:" + bech32_note_id(ev.id))
|
content.append(" nostr:" + bech32_note_id(ev.id))
|
||||||
|
|
||||||
if let quoted_ev = state.events.lookup(ev.id) {
|
if let quoted_ev = state.events.lookup(ev.id) {
|
||||||
tags.append(["p", quoted_ev.pubkey.hex()])
|
tags.append(["p", quoted_ev.pubkey.hex()])
|
||||||
}
|
}
|
||||||
case .posting(let postTarget):
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// include pubkeys
|
|
||||||
tags += pubkeys.map { pk in
|
|
||||||
["p", pk.hex()]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// append additional tags
|
return NostrPost(content: content, references: references, kind: .text, tags: tags)
|
||||||
tags += uploadedMedias.compactMap { $0.metadata?.to_tag() }
|
|
||||||
|
|
||||||
return NostrPost(content: content, kind: .text, tags: tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,17 @@ struct UserSearch: View {
|
|||||||
|
|
||||||
var users: [Pubkey] {
|
var users: [Pubkey] {
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return [] }
|
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return [] }
|
||||||
return search_profiles(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
|
return search_profiles(profiles: damus_state.profiles, search: search, txn: txn).sorted { a, b in
|
||||||
|
let aFriendTypePriority = get_friend_type(contacts: damus_state.contacts, pubkey: a)?.priority ?? 0
|
||||||
|
let bFriendTypePriority = get_friend_type(contacts: damus_state.contacts, pubkey: b)?.priority ?? 0
|
||||||
|
|
||||||
|
if aFriendTypePriority > bFriendTypePriority {
|
||||||
|
// `a` should be sorted before `b`
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func on_user_tapped(pk: Pubkey) {
|
func on_user_tapped(pk: Pubkey) {
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ struct ProfileView: View {
|
|||||||
MuteDurationMenu { duration in
|
MuteDurationMenu { duration in
|
||||||
notify(.mute(.user(profile.pubkey, duration?.date_from_now)))
|
notify(.mute(.user(profile.pubkey, duration?.date_from_now)))
|
||||||
} label: {
|
} label: {
|
||||||
Text("Mute", comment: "Button to mute a profile.")
|
Text(NSLocalizedString("Mute", comment: "Button to mute a profile."))
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ struct ProfileActionSheetView: View {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.buttonStyle(NeutralButtonShape.circle.style)
|
.buttonStyle(NeutralButtonShape.circle.style)
|
||||||
Text("Message", comment: "Button label that allows the user to start a direct message conversation with the user shown on-screen")
|
Text(NSLocalizedString("Message", comment: "Button label that allows the user to start a direct message conversation with the user shown on-screen"))
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ struct ProfileActionSheetView: View {
|
|||||||
label: {
|
label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("View full profile", comment: "A button label that allows the user to see the full profile of the profile they are previewing")
|
Text(NSLocalizedString("View full profile", comment: "A button label that allows the user to see the full profile of the profile they are previewing"))
|
||||||
Image(systemName: "arrow.up.right")
|
Image(systemName: "arrow.up.right")
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@@ -305,9 +305,9 @@ fileprivate struct ProfileActionSheetZapButton: View {
|
|||||||
})
|
})
|
||||||
.alert(isPresented: $show_error_alert) {
|
.alert(isPresented: $show_error_alert) {
|
||||||
Alert(
|
Alert(
|
||||||
title: Text("Zap failed", comment: "Title of an alert indicating that a zap action failed"),
|
title: Text(NSLocalizedString("Zap failed", comment: "Title of an alert indicating that a zap action failed")),
|
||||||
message: Text(zap_state.error_message() ?? ""),
|
message: Text(zap_state.error_message() ?? ""),
|
||||||
dismissButton: .default(Text("OK", comment: "Button label to dismiss an error dialog"))
|
dismissButton: .default(Text(NSLocalizedString("OK", comment: "Button label to dismiss an error dialog")))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onChange(of: zap_state) { new_zap_state in
|
.onChange(of: zap_state) { new_zap_state in
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ struct PubkeyView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(DamusColors.green)
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
Text("Copied", comment: "Label indicating that a user's key was copied.")
|
Text(NSLocalizedString("Copied", comment: "Label indicating that a user's key was copied."))
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.layoutPriority(1)
|
.layoutPriority(1)
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(DamusColors.green)
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ struct DamusPurpleAccountView: View {
|
|||||||
// TODO: Generalize this view instead of setting up dividers and paddings manually
|
// TODO: Generalize this view instead of setting up dividers and paddings manually
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Expiry date", comment: "Label for Purple subscription expiry date")
|
Text(NSLocalizedString("Expiry date", comment: "Label for Purple subscription expiry date"))
|
||||||
Spacer()
|
Spacer()
|
||||||
let formattedDate = DateFormatter.localizedString(from: account.expiry, dateStyle: .short, timeStyle: .none)
|
Text(DateFormatter.localizedString(from: account.expiry, dateStyle: .short, timeStyle: .none))
|
||||||
Text(formattedDate)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
@@ -44,10 +43,9 @@ struct DamusPurpleAccountView: View {
|
|||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Account creation", comment: "Label for Purple account creation date")
|
Text(NSLocalizedString("Account creation", comment: "Label for Purple account creation date"))
|
||||||
Spacer()
|
Spacer()
|
||||||
let formattedDate = DateFormatter.localizedString(from: account.created_at, dateStyle: .short, timeStyle: .none)
|
Text(DateFormatter.localizedString(from: account.created_at, dateStyle: .short, timeStyle: .none))
|
||||||
Text(formattedDate)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ struct DamusPurpleAccountView: View {
|
|||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Subscriber number", comment: "Label for Purple account subscriber number")
|
Text(NSLocalizedString("Subscriber number", comment: "Label for Purple account subscriber number"))
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(verbatim: "#\(account.subscriber_number)")
|
Text(verbatim: "#\(account.subscriber_number)")
|
||||||
}
|
}
|
||||||
@@ -92,7 +90,7 @@ struct DamusPurpleAccountView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 15, height: 15)
|
.frame(width: 15, height: 15)
|
||||||
|
|
||||||
Text("Active account", comment: "Badge indicating user has an active Damus Purple account")
|
Text(NSLocalizedString("Active account", comment: "Badge indicating user has an active Damus Purple account"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
@@ -109,7 +107,7 @@ struct DamusPurpleAccountView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 15, height: 15)
|
.frame(width: 15, height: 15)
|
||||||
|
|
||||||
Text("Expired account", comment: "Badge indicating user has an expired Damus Purple account")
|
Text(NSLocalizedString("Expired account", comment: "Badge indicating user has an expired Damus Purple account"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ struct DamusPurpleTranslationSetupView: View {
|
|||||||
.opacity(start ? 1.0 : 0.0)
|
.opacity(start ? 1.0 : 0.0)
|
||||||
.animation(.content(), value: start)
|
.animation(.content(), value: start)
|
||||||
|
|
||||||
Text("You unlocked", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple" )
|
Text(NSLocalizedString("You unlocked", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple" ))
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
@@ -95,7 +95,7 @@ struct DamusPurpleTranslationSetupView: View {
|
|||||||
.opacity(start ? 1.0 : 0.0)
|
.opacity(start ? 1.0 : 0.0)
|
||||||
.animation(Animation.snappy(duration: 2).delay(0), value: start)
|
.animation(Animation.snappy(duration: 2).delay(0), value: start)
|
||||||
|
|
||||||
Text("Automatic translations", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple")
|
Text(NSLocalizedString("Automatic translations", comment: "Part 1 of 2 in message 'You unlocked automatic translations' the user gets when they sign up for Damus Purple"))
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
@@ -110,7 +110,7 @@ struct DamusPurpleTranslationSetupView: View {
|
|||||||
.animation(.content(), value: start)
|
.animation(.content(), value: start)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
Text("As part of your Damus Purple membership, you get complimentary and automated translations. Would you like to enable Damus Purple translations?\n\nTip: You can always change this later in Settings → Translations", comment: "Message notifying the user that they get auto-translations as part of their service")
|
Text(NSLocalizedString("As part of your Damus Purple membership, you get complimentary and automated translations. Would you like to enable Damus Purple translations?\n\nTip: You can always change this later in Settings → Translations", comment: "Message notifying the user that they get auto-translations as part of their service"))
|
||||||
.lineSpacing(5)
|
.lineSpacing(5)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundStyle(.white.opacity(0.8))
|
.foregroundStyle(.white.opacity(0.8))
|
||||||
@@ -125,7 +125,7 @@ struct DamusPurpleTranslationSetupView: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Enable Purple auto-translations", comment: "Label for button that allows users to enable Damus Purple translations")
|
Text(NSLocalizedString("Enable Purple auto-translations", comment: "Label for button that allows users to enable Damus Purple translations"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -139,7 +139,7 @@ struct DamusPurpleTranslationSetupView: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("No, thanks", comment: "Label for button that allows users to reject enabling Damus Purple translations")
|
Text(NSLocalizedString("No, thanks", comment: "Label for button that allows users to reject enabling Damus Purple translations"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ struct DamusPurpleVerifyNpubView: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Verify my npub", comment: "Button label to verify the user's npub for the purpose of Purple subscription checkout")
|
Text(NSLocalizedString("Verify my npub", comment: "Button label to verify the user's npub for the purpose of Purple subscription checkout"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -61,7 +61,7 @@ struct DamusPurpleVerifyNpubView: View {
|
|||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Text("Verified!", comment: "Instructions after the user has verified their npub for Damus Purple purchase checkout")
|
Text(NSLocalizedString("Verified!", comment: "Instructions after the user has verified their npub for Damus Purple purchase checkout"))
|
||||||
.frame(height: subtitle_height)
|
.frame(height: subtitle_height)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
@@ -71,7 +71,7 @@ struct DamusPurpleVerifyNpubView: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Continue", comment: "Prompt to user to continue")
|
Text(NSLocalizedString("Continue", comment: "Prompt to user to continue"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ struct DamusPurpleView: View, DamusPurpleStoreKitManagerDelegate {
|
|||||||
if let account_uuid {
|
if let account_uuid {
|
||||||
DamusPurpleView.IAPProductStateView(products: products, purchased: purchased, account_uuid: account_uuid, subscribe: subscribe)
|
DamusPurpleView.IAPProductStateView(products: products, purchased: purchased, account_uuid: account_uuid, subscribe: subscribe)
|
||||||
if let iap_error {
|
if let iap_error {
|
||||||
Text("There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: \(iap_error)", comment: "In-app purchase error message for the user")
|
Text(String(format: NSLocalizedString("There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@", comment: "In-app purchase error message for the user"), iap_error))
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
@@ -158,7 +158,7 @@ struct DamusPurpleView: View, DamusPurpleStoreKitManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ManageOnWebsiteNote: some View {
|
var ManageOnWebsiteNote: some View {
|
||||||
Text("Visit the Damus website on a web browser to manage billing", comment: "Instruction on how to manage billing externally")
|
Text(NSLocalizedString("Visit the Damus website on a web browser to manage billing", comment: "Instruction on how to manage billing externally"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.white.opacity(0.6))
|
.foregroundColor(.white.opacity(0.6))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct DamusPurpleWelcomeView: View {
|
|||||||
.opacity(start ? 1.0 : 0.0)
|
.opacity(start ? 1.0 : 0.0)
|
||||||
.animation(.content(), value: start)
|
.animation(.content(), value: start)
|
||||||
|
|
||||||
Text("Welcome to Purple", comment: "Greeting to subscription service")
|
Text(NSLocalizedString("Welcome to Purple", comment: "Greeting to subscription service"))
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
@@ -70,7 +70,7 @@ struct DamusPurpleWelcomeView: View {
|
|||||||
.opacity(start ? 1.0 : 0.0)
|
.opacity(start ? 1.0 : 0.0)
|
||||||
.animation(Animation.snappy(duration: 2).delay(0), value: start)
|
.animation(Animation.snappy(duration: 2).delay(0), value: start)
|
||||||
|
|
||||||
Text("Thank you very much for signing up for Damus\u{00A0}Purple. Your contribution helps us continue our fight for a more Open and Free\u{00A0}internet.\n\nYou will also get access to premium features, and a star badge on your profile.\n\nEnjoy!", comment: "Appreciation to user for purchasing subscription service")
|
Text(NSLocalizedString("Thank you very much for signing up for Damus\u{00A0}Purple. Your contribution helps us continue our fight for a more Open and Free\u{00A0}internet.\n\nYou will also get access to premium features, and a star badge on your profile.\n\nEnjoy!", comment: "Appreciation to user for purchasing subscription service"))
|
||||||
.lineSpacing(5)
|
.lineSpacing(5)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundStyle(.white.opacity(0.8))
|
.foregroundStyle(.white.opacity(0.8))
|
||||||
@@ -85,7 +85,7 @@ struct DamusPurpleWelcomeView: View {
|
|||||||
}, label: {
|
}, label: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Continue", comment: "Prompt to user to continue")
|
Text(NSLocalizedString("Continue", comment: "Prompt to user to continue"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ extension DamusPurpleView {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
if subscription_purchase_loading {
|
if subscription_purchase_loading {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
Text("Purchasing", comment: "Loading label indicating the purchase action is in progress")
|
Text(NSLocalizedString("Purchasing", comment: "Loading label indicating the purchase action is in progress"))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
@@ -66,7 +66,7 @@ extension DamusPurpleView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func PurchasedUnmanageableView(_ purchased: PurchasedProduct) -> some View {
|
func PurchasedUnmanageableView(_ purchased: PurchasedProduct) -> some View {
|
||||||
Text("This device's in-app purchase is registered to a different Nostr account. Unable to manage this Purple account. If you believe this was a mistake, please contact us via support@damus.io.", comment: "Notice label that user cannot manage their In-App purchases")
|
Text(NSLocalizedString("This device's in-app purchase is registered to a different Nostr account. Unable to manage this Purple account. If you believe this was a mistake, please contact us via support@damus.io.", comment: "Notice label that user cannot manage their In-App purchases"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.white.opacity(0.6))
|
.foregroundColor(.white.opacity(0.6))
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -76,21 +76,21 @@ extension DamusPurpleView {
|
|||||||
func PurchasedManageView(_ purchased: PurchasedProduct) -> some View {
|
func PurchasedManageView(_ purchased: PurchasedProduct) -> some View {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
if SHOW_IAP_DEBUG_INFO == true {
|
if SHOW_IAP_DEBUG_INFO == true {
|
||||||
Text("Purchased!", comment: "User purchased a subscription")
|
Text(NSLocalizedString("Purchased!", comment: "User purchased a subscription"))
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
price_description(product: purchased.product)
|
price_description(product: purchased.product)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.opacity(0.65)
|
.opacity(0.65)
|
||||||
.frame(width: 200)
|
.frame(width: 200)
|
||||||
Text("Purchased on", comment: "Indicating when the user purchased the subscription")
|
Text(NSLocalizedString("Purchased on", comment: "Indicating when the user purchased the subscription"))
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
Text(format_date(date: purchased.tx.purchaseDate))
|
Text(format_date(date: purchased.tx.purchaseDate))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.opacity(0.65)
|
.opacity(0.65)
|
||||||
if let expiry = purchased.tx.expirationDate {
|
if let expiry = purchased.tx.expirationDate {
|
||||||
Text("Renews on", comment: "Indicating when the subscription will renew")
|
Text(NSLocalizedString("Renews on", comment: "Indicating when the subscription will renew"))
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
Text(format_date(date: expiry))
|
Text(format_date(date: expiry))
|
||||||
@@ -101,7 +101,7 @@ extension DamusPurpleView {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
show_manage_subscriptions = true
|
show_manage_subscriptions = true
|
||||||
}, label: {
|
}, label: {
|
||||||
Text("Manage", comment: "Manage the damus subscription")
|
Text(NSLocalizedString("Manage", comment: "Manage the damus subscription"))
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
})
|
})
|
||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
@@ -112,7 +112,7 @@ extension DamusPurpleView {
|
|||||||
|
|
||||||
func ProductsView(_ products: [Product]) -> some View {
|
func ProductsView(_ products: [Product]) -> some View {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
Text("Save 20% off on an annual subscription", comment: "Savings for purchasing an annual subscription")
|
Text(NSLocalizedString("Save 20% off on an annual subscription", comment: "Savings for purchasing an annual subscription"))
|
||||||
.font(.callout.bold())
|
.font(.callout.bold())
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
ForEach(products) { product in
|
ForEach(products) { product in
|
||||||
@@ -132,7 +132,7 @@ extension DamusPurpleView {
|
|||||||
.buttonStyle(GradientButtonStyle())
|
.buttonStyle(GradientButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("By subscribing to Damus Purple, you are accepting our [privacy policy](https://damus.io/privacy-policy.txt) and Apple's Standard [EULA](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)", comment: "Text explaining the terms and conditions of subscribing to Damus Purple. EULA stands for End User License Agreement.")
|
Text("By subscribing to Damus Purple you are accepting our [privacy policy](https://damus.io/privacy-policy.txt) and Apple's Standard [EULA](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)")
|
||||||
.foregroundColor(.white.opacity(0.6))
|
.foregroundColor(.white.opacity(0.6))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.padding()
|
.padding()
|
||||||
@@ -148,11 +148,11 @@ extension DamusPurpleView {
|
|||||||
Text(purple_type?.label() ?? product.displayName)
|
Text(purple_type?.label() ?? product.displayName)
|
||||||
Spacer()
|
Spacer()
|
||||||
if let non_discounted_price = purple_type?.non_discounted_price(product: product) {
|
if let non_discounted_price = purple_type?.non_discounted_price(product: product) {
|
||||||
Text(non_discounted_price)
|
Text(verbatim: non_discounted_price)
|
||||||
.strikethrough()
|
.strikethrough()
|
||||||
.foregroundColor(DamusColors.white.opacity(0.5))
|
.foregroundColor(DamusColors.white.opacity(0.5))
|
||||||
}
|
}
|
||||||
Text(product.displayPrice)
|
Text(verbatim: product.displayPrice)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ extension DamusPurpleView {
|
|||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Purple", comment: "Subscription service name")
|
Text(NSLocalizedString("Purple", comment: "Subscription service name"))
|
||||||
.font(.system(size: 60.0).weight(.bold))
|
.font(.system(size: 60.0).weight(.bold))
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ extension DamusPurpleView {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 15, height: 15)
|
.frame(width: 15, height: 15)
|
||||||
|
|
||||||
Text("Coming soon", comment: "Feature is still in development and will be available soon")
|
Text(NSLocalizedString("Coming soon", comment: "Feature is still in development and will be available soon"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.bold()
|
.bold()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ struct PurpleBackdrop<T: View>: View {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
PurpleBackdrop {
|
PurpleBackdrop {
|
||||||
Text(verbatim: "Hello, World")
|
Text("Hello, World")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,14 +67,14 @@ struct PurpleViewPrimitives {
|
|||||||
|
|
||||||
struct ProductLoadErrorView: View {
|
struct ProductLoadErrorView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Subscription Error", comment: "Ah dang there was an error loading subscription information from the AppStore. Please try again later :(")
|
Text(NSLocalizedString("Subscription Error", comment: "Ah dang there was an error loading subscription information from the AppStore. Please try again later :("))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SaveTextView: View {
|
struct SaveTextView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text("Save 14%", comment: "Percentage of purchase price the user will save")
|
Text(NSLocalizedString("Save 14%", comment: "Percentage of purchase price the user will save"))
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.italic()
|
.italic()
|
||||||
.foregroundColor(DamusColors.green)
|
.foregroundColor(DamusColors.green)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct RelayAdminDetail: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 15) {
|
HStack(spacing: 15) {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
Text("ADMIN", comment: "Text label indicating the profile picture underneath it is the admin of the Nostr relay.")
|
Text("ADMIN")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
@@ -36,18 +36,18 @@ struct RelayAdminDetail: View {
|
|||||||
Divider().frame(width: 1)
|
Divider().frame(width: 1)
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text("CONTACT", comment: "Text label indicating that the information below is the contact information of the admin of the Nostr relay.")
|
Text("CONTACT")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
Image("messages")
|
Image("messages")
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
if let contact = nip11?.contact, !contact.isEmpty {
|
if nip11?.contact == "" {
|
||||||
Text(contact)
|
Text("N/A")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
} else {
|
} else {
|
||||||
Text("N/A", comment: "Text label indicating that there is no NIP-11 relay admin contact information found. In English, N/A stands for not applicable.")
|
Text(nip11?.contact ?? "N/A")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct RelayAuthenticationDetail: View {
|
|||||||
case .none:
|
case .none:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
case .pending:
|
case .pending:
|
||||||
Text("Pending", comment: "Label to display that authentication to a server is pending.")
|
Text(NSLocalizedString("Pending", comment: "Label to display that authentication to a server is pending."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
@@ -27,7 +27,7 @@ struct RelayAuthenticationDetail: View {
|
|||||||
.stroke(DamusColors.warningBorder, lineWidth: 1)
|
.stroke(DamusColors.warningBorder, lineWidth: 1)
|
||||||
)
|
)
|
||||||
case .verified:
|
case .verified:
|
||||||
Text("Authenticated", comment: "Label to display that authentication to a server has succeeded.")
|
Text(NSLocalizedString("Authenticated", comment: "Label to display that authentication to a server has succeeded."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
@@ -39,7 +39,7 @@ struct RelayAuthenticationDetail: View {
|
|||||||
.stroke(DamusColors.successBorder, lineWidth: 1)
|
.stroke(DamusColors.successBorder, lineWidth: 1)
|
||||||
)
|
)
|
||||||
case .error:
|
case .error:
|
||||||
Text("Error", comment: "Label to display that authentication to a server has failed.")
|
Text(NSLocalizedString("Error", comment: "Label to display that authentication to a server has failed."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ struct RelayNipList: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
Text("Supported NIPs", comment: "Label to display relay's supported NIPs.")
|
Text(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs."))
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
|
|||||||
@@ -19,21 +19,18 @@ struct RelayPaidDetail: View {
|
|||||||
let formattedString = formatter.string(from: TimeInterval(time)) ?? ""
|
let formattedString = formatter.string(from: TimeInterval(time)) ?? ""
|
||||||
return formattedString
|
return formattedString
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayAmount(unit: String, amount: Int64) -> String {
|
|
||||||
if unit == "msats" {
|
|
||||||
format_msats(amount)
|
|
||||||
} else {
|
|
||||||
"\(amount) \(unit)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Amount(unit: String, amount: Int64) -> some View {
|
func Amount(unit: String, amount: Int64) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
let displayString = displayAmount(unit: unit, amount: amount)
|
if unit == "msats" {
|
||||||
Text(displayString)
|
Text("\(format_msats(amount))")
|
||||||
.font(.system(size: 13, weight: .heavy))
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
|
} else {
|
||||||
|
Text("\(amount) \(unit)")
|
||||||
|
.font(.system(size: 13, weight: .heavy))
|
||||||
|
.foregroundColor(DamusColors.white)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,24 +48,26 @@ struct RelayPaidDetail: View {
|
|||||||
if !admission.isEmpty {
|
if !admission.isEmpty {
|
||||||
Amount(unit: admission[0].unit, amount: admission[0].amount)
|
Amount(unit: admission[0].unit, amount: admission[0].amount)
|
||||||
} else {
|
} else {
|
||||||
Text("Paid Relay", comment: "Text indicating that this is a paid relay.")
|
Text(verbatim: "Paid Relay")
|
||||||
.font(.system(size: 13, weight: .heavy))
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
}
|
}
|
||||||
} else if let subscription = fees?.subscription {
|
} else if let subscription = fees?.subscription {
|
||||||
if !subscription.isEmpty {
|
if !subscription.isEmpty {
|
||||||
Text("\(displayAmount(unit: subscription[0].unit, amount: subscription[0].amount)) / \(timeString(time: subscription[0].period))", comment: "Amount of money required to subscribe to the Nostr relay. In English, this would look something like '4,000 sats / 30 days', meaning it costs 4000 sats to subscribe to the Nostr relay for 30 days.")
|
Amount(unit: subscription[0].unit, amount: subscription[0].amount)
|
||||||
|
Text("/ \(timeString(time: subscription[0].period))")
|
||||||
.font(.system(size: 13, weight: .heavy))
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
}
|
}
|
||||||
} else if let publication = fees?.publication {
|
} else if let publication = fees?.publication {
|
||||||
if !publication.isEmpty {
|
if !publication.isEmpty {
|
||||||
Text("\(displayAmount(unit: publication[0].unit, amount: publication[0].amount)) / event", comment: "Amount of money required to publish to the Nostr relay. In English, this would look something like '10 sats / event', meaning it costs 10 sats to publish one event.")
|
Amount(unit: publication[0].unit, amount: publication[0].amount)
|
||||||
|
Text("/ event")
|
||||||
.font(.system(size: 13, weight: .heavy))
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("Paid Relay", comment: "Text indicating that this is a paid relay.")
|
Text(verbatim: "Paid Relay")
|
||||||
.font(.system(size: 13, weight: .heavy))
|
.font(.system(size: 13, weight: .heavy))
|
||||||
.foregroundColor(DamusColors.white)
|
.foregroundColor(DamusColors.white)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct RelaySoftwareDetail: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 15) {
|
HStack(spacing: 15) {
|
||||||
VStack {
|
VStack {
|
||||||
Text("SOFTWARE", comment: "Text label indicating which relay software is used to run this Nostr relay.")
|
Text("SOFTWARE")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
@@ -24,37 +24,26 @@ struct RelaySoftwareDetail: View {
|
|||||||
|
|
||||||
let software = nip11?.software
|
let software = nip11?.software
|
||||||
let softwareSeparated = software?.components(separatedBy: "/")
|
let softwareSeparated = software?.components(separatedBy: "/")
|
||||||
if let softwareShortened = softwareSeparated?.last {
|
let softwareShortened = softwareSeparated?.last
|
||||||
Text(softwareShortened)
|
Text(softwareShortened ?? "N/A")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
} else {
|
|
||||||
Text("N/A", comment: "Text label indicating that there is no NIP-11 relay software information found. In English, N/A stands for not applicable.")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider().frame(width: 1)
|
Divider().frame(width: 1)
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text("VERSION", comment: "Text label indicating which version of the relay software is being run for this Nostr relay.")
|
Text("VERSION")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
|
|
||||||
Image("branches")
|
Image("branches")
|
||||||
.foregroundColor(.gray)
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
if let version = nip11?.version, !version.isEmpty {
|
Text(nip11?.version ?? "N/A")
|
||||||
Text(version)
|
.font(.subheadline)
|
||||||
.font(.subheadline)
|
.foregroundColor(.gray)
|
||||||
.foregroundColor(.gray)
|
|
||||||
} else {
|
|
||||||
Text("N/A", comment: "Text label indicating that there is no NIP-11 relay software version information found. In English, N/A stands for not applicable.")
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ struct RelayConfigView: View {
|
|||||||
func RelayList(title: String, relayList: [RelayDescriptor], recommended: Bool) -> some View {
|
func RelayList(title: String, relayList: [RelayDescriptor], recommended: Bool) -> some View {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView(showsIndicators: false) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(title)
|
Text(NSLocalizedString(title, comment: "Section title for type of relay server list"))
|
||||||
.font(.system(size: 32, weight: .bold))
|
.font(.system(size: 32, weight: .bold))
|
||||||
|
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ struct RelayConfigView: View {
|
|||||||
show_add_relay.toggle()
|
show_add_relay.toggle()
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Add relay", comment: "Button text to add a relay")
|
Text(verbatim: "Add relay")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,18 +144,13 @@ struct RelayDetailView: View {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Text("Description", comment: "Description of the specific Nostr relay server.")
|
Text("Description")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
|
|
||||||
if let description = nip11?.description, !description.isEmpty {
|
Text(nip11?.description ?? "N/A")
|
||||||
Text(description)
|
.font(.subheadline)
|
||||||
.font(.subheadline)
|
|
||||||
} else {
|
|
||||||
Text("N/A", comment: "Text label indicating that there is no NIP-11 relay description information found. In English, N/A stands for not applicable.")
|
|
||||||
.font(.subheadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
RelayInfo
|
RelayInfo
|
||||||
@@ -180,7 +175,7 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state.settings.developer_mode {
|
if state.settings.developer_mode {
|
||||||
Text("Relay Logs", comment: "Text label indicating that the text below it are developer mode logs.")
|
Text("Relay Logs")
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
Divider()
|
Divider()
|
||||||
Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
|
Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ struct RelayView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
remove_action(privkey: keypair.privkey)
|
remove_action(privkey: keypair.privkey)
|
||||||
}) {
|
}) {
|
||||||
Text("Added", comment: "Button to show relay server is already added to list.")
|
Text(NSLocalizedString("Added", comment: "Button to show relay server is already added to list."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
.buttonStyle(NeutralButtonShape.capsule.style)
|
.buttonStyle(NeutralButtonShape.capsule.style)
|
||||||
@@ -147,7 +147,7 @@ struct RelayView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
add_action(keypair: keypair)
|
add_action(keypair: keypair)
|
||||||
}) {
|
}) {
|
||||||
Text("Add", comment: "Button to add relay server to list.")
|
Text(NSLocalizedString("Add", comment: "Button to add relay server to list."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
.buttonStyle(NeutralButtonShape.capsule.style)
|
.buttonStyle(NeutralButtonShape.capsule.style)
|
||||||
@@ -166,7 +166,7 @@ struct RelayView: View {
|
|||||||
remove_action(privkey: privkey)
|
remove_action(privkey: privkey)
|
||||||
}) {
|
}) {
|
||||||
if showText {
|
if showText {
|
||||||
Text("Disconnect", comment: "Button to disconnect from a relay server.")
|
Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server."))
|
||||||
}
|
}
|
||||||
|
|
||||||
Image("minus-circle")
|
Image("minus-circle")
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ struct SaveKeysView: View {
|
|||||||
@FocusState var pubkey_focused: Bool
|
@FocusState var pubkey_focused: Bool
|
||||||
@FocusState var privkey_focused: Bool
|
@FocusState var privkey_focused: Bool
|
||||||
|
|
||||||
let first_contact_event: NdbNote?
|
|
||||||
|
|
||||||
init(account: CreateAccountModel) {
|
|
||||||
self.account = account
|
|
||||||
self.first_contact_event = make_first_contact_event(keypair: account.keypair)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
@@ -109,13 +102,6 @@ struct SaveKeysView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func complete_account_creation(_ account: CreateAccountModel) {
|
func complete_account_creation(_ account: CreateAccountModel) {
|
||||||
guard let first_contact_event else {
|
|
||||||
error = NSLocalizedString("Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.", comment: "Error message to the user indicating that the initial contact list failed to be created.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Save contact list to storage right away so that we don't need to depend on the network to complete this important step
|
|
||||||
self.save_to_storage(first_contact_event: first_contact_event, for: account)
|
|
||||||
|
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: account.pubkey)
|
let bootstrap_relays = load_bootstrap_relays(pubkey: account.pubkey)
|
||||||
for relay in bootstrap_relays {
|
for relay in bootstrap_relays {
|
||||||
add_rw_relay(self.pool, relay)
|
add_rw_relay(self.pool, relay)
|
||||||
@@ -129,15 +115,6 @@ struct SaveKeysView: View {
|
|||||||
|
|
||||||
self.pool.connect()
|
self.pool.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func save_to_storage(first_contact_event: NdbNote, for account: CreateAccountModel) {
|
|
||||||
// Send to NostrDB so that we have a local copy in storage
|
|
||||||
self.pool.send_raw_to_local_ndb(.typical(.event(first_contact_event)))
|
|
||||||
|
|
||||||
// Save the ID to user settings so that we can easily find it later.
|
|
||||||
let settings = UserSettingsStore.globally_load_for(pubkey: account.pubkey)
|
|
||||||
settings.latest_contact_event_id_hex = first_contact_event.id.hex()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(relay: RelayURL, ev: NostrConnectionEvent) {
|
func handle_event(relay: RelayURL, ev: NostrConnectionEvent) {
|
||||||
switch ev {
|
switch ev {
|
||||||
@@ -145,14 +122,15 @@ struct SaveKeysView: View {
|
|||||||
switch wsev {
|
switch wsev {
|
||||||
case .connected:
|
case .connected:
|
||||||
let metadata = create_account_to_metadata(account)
|
let metadata = create_account_to_metadata(account)
|
||||||
|
let contacts_ev = make_first_contact_event(keypair: account.keypair)
|
||||||
|
|
||||||
if let keypair = account.keypair.to_full(),
|
if let keypair = account.keypair.to_full(),
|
||||||
let metadata_ev = make_metadata_event(keypair: keypair, metadata: metadata) {
|
let metadata_ev = make_metadata_event(keypair: keypair, metadata: metadata) {
|
||||||
self.pool.send(.event(metadata_ev))
|
self.pool.send(.event(metadata_ev))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let first_contact_event {
|
if let contacts_ev {
|
||||||
self.pool.send(.event(first_contact_event))
|
self.pool.send(.event(contacts_ev))
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ struct PullDownSearchView: View {
|
|||||||
if results.count > 0 {
|
if results.count > 0 {
|
||||||
HStack {
|
HStack {
|
||||||
Image("search")
|
Image("search")
|
||||||
Text("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results")
|
Text(NSLocalizedString("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
@@ -101,7 +101,7 @@ struct PullDownSearchView: View {
|
|||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Image("notes.fill")
|
Image("notes.fill")
|
||||||
Text("Notes", comment: "A label indicating that the notes being displayed below it are from a timeline, not search results")
|
Text(NSLocalizedString("Notes", comment: "A label indicating that the notes being displayed below it are from a timeline, not search results"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@@ -109,7 +109,7 @@ struct PullDownSearchView: View {
|
|||||||
} else if results.count == 0 && !search_text.isEmpty {
|
} else if results.count == 0 && !search_text.isEmpty {
|
||||||
HStack {
|
HStack {
|
||||||
Image("search")
|
Image("search")
|
||||||
Text("No results", comment: "A label indicating that note search resulted in no results")
|
Text(NSLocalizedString("No results", comment: "A label indicating that note search resulted in no results"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// SearchingProfileView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-03-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SearchingProfileView: View {
|
||||||
|
var body: some View {
|
||||||
|
Text(verbatim: /*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchingProfileView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
SearchingProfileView()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,7 @@ struct SearchHomeView: View {
|
|||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Image("notes.fill")
|
Image("notes.fill")
|
||||||
Text("All recent notes", comment: "A label indicating that the notes being displayed below it are all recent notes")
|
Text(NSLocalizedString("All recent notes", comment: "A label indicating that the notes being displayed below it are all recent notes"))
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|||||||
@@ -113,11 +113,11 @@ struct SearchResultsView: View {
|
|||||||
.frame(maxHeight: .infinity)
|
.frame(maxHeight: .infinity)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
||||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
|
self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn)
|
||||||
}
|
}
|
||||||
.onChange(of: search) { new in
|
.onChange(of: search) { new in
|
||||||
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
||||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
|
self.result = search_for_string(profiles: damus_state.profiles, search: search, txn: txn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ struct SearchResultsView_Previews: PreviewProvider {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
func search_for_string<Y>(profiles: Profiles, contacts: Contacts, search new: String, txn: NdbTxn<Y>) -> Search? {
|
func search_for_string<Y>(profiles: Profiles, search new: String, txn: NdbTxn<Y>) -> Search? {
|
||||||
guard new.count != 0 else {
|
guard new.count != 0 else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ func search_for_string<Y>(profiles: Profiles, contacts: Contacts, search new: St
|
|||||||
return .naddr(naddr)
|
return .naddr(naddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
let multisearch = MultiSearch(hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, contacts: contacts, search: new, txn: txn))
|
let multisearch = MultiSearch(hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, search: new, txn: txn))
|
||||||
return .multi(multisearch)
|
return .multi(multisearch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ func make_hashtagable(_ str: String) -> String {
|
|||||||
return String(new.filter{$0 != " "})
|
return String(new.filter{$0 != " "})
|
||||||
}
|
}
|
||||||
|
|
||||||
func search_profiles<Y>(profiles: Profiles, contacts: Contacts, search: String, txn: NdbTxn<Y>) -> [Pubkey] {
|
func search_profiles<Y>(profiles: Profiles, search: String, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||||
// Search by hex pubkey.
|
// Search by hex pubkey.
|
||||||
if let pubkey = hex_decode_pubkey(search),
|
if let pubkey = hex_decode_pubkey(search),
|
||||||
profiles.lookup_key_by_pubkey(pubkey) != nil
|
profiles.lookup_key_by_pubkey(pubkey) != nil
|
||||||
@@ -208,16 +208,8 @@ func search_profiles<Y>(profiles: Profiles, contacts: Contacts, search: String,
|
|||||||
return [pk]
|
return [pk]
|
||||||
}
|
}
|
||||||
|
|
||||||
return profiles.search(search, limit: 10, txn: txn).sorted { a, b in
|
let new = search.lowercased()
|
||||||
let aFriendTypePriority = get_friend_type(contacts: contacts, pubkey: a)?.priority ?? 0
|
|
||||||
let bFriendTypePriority = get_friend_type(contacts: contacts, pubkey: b)?.priority ?? 0
|
|
||||||
|
|
||||||
if aFriendTypePriority > bFriendTypePriority {
|
return profiles.search(search, limit: 10, txn: txn)
|
||||||
// `a` should be sorted before `b`
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ struct AppearanceSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Text Truncation
|
// MARK: - Text Truncation
|
||||||
Section(header: Text("Text Truncation", comment: "Section header for damus text truncation user configuration")) {
|
Section(header: Text(NSLocalizedString("Text Truncation", comment: "Section header for damus text truncation user configuration"))) {
|
||||||
Toggle(NSLocalizedString("Truncate timeline text", comment: "Setting to truncate text in timeline"), isOn: $settings.truncate_timeline_text)
|
Toggle(NSLocalizedString("Truncate timeline text", comment: "Setting to truncate text in timeline"), isOn: $settings.truncate_timeline_text)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
Toggle(NSLocalizedString("Truncate notification mention text", comment: "Setting to truncate text in mention notifications"), isOn: $settings.truncate_mention_text)
|
Toggle(NSLocalizedString("Truncate notification mention text", comment: "Setting to truncate text in mention notifications"), isOn: $settings.truncate_mention_text)
|
||||||
@@ -70,7 +70,7 @@ struct AppearanceSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Accessibility
|
// MARK: - Accessibility
|
||||||
Section(header: Text("Accessibility", comment: "Section header for accessibility settings")) {
|
Section(header: Text(NSLocalizedString("Accessibility", comment: "Section header for accessibility settings"))) {
|
||||||
Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $settings.left_handed)
|
Toggle(NSLocalizedString("Left Handed", comment: "Moves the post button to the left side of the screen"), isOn: $settings.left_handed)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
@@ -97,8 +97,8 @@ struct AppearanceSettingsView: View {
|
|||||||
|
|
||||||
// MARK: - Content filters and moderation
|
// MARK: - Content filters and moderation
|
||||||
Section(
|
Section(
|
||||||
header: Text("Content filters", comment: "Section title for content filtering/moderation configuration."),
|
header: Text(NSLocalizedString("Content filters", comment: "Section title for content filtering/moderation configuration.")),
|
||||||
footer: Text("Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content", comment: "Section footer clarifying what #nsfw (not safe for work) tags mean")
|
footer: Text(NSLocalizedString("Notes with the #nsfw tag usually contains adult content or other \"Not safe for work\" content", comment: "Section footer clarifying what #nsfw (not safe for work) tags mean"))
|
||||||
) {
|
) {
|
||||||
Toggle(NSLocalizedString("Hide notes with #nsfw tags", comment: "Setting to hide notes with the #nsfw (not safe for work) tags"), isOn: $settings.hide_nsfw_tagged_content)
|
Toggle(NSLocalizedString("Hide notes with #nsfw tags", comment: "Setting to hide notes with the #nsfw (not safe for work) tags"), isOn: $settings.hide_nsfw_tagged_content)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
@@ -106,8 +106,8 @@ struct AppearanceSettingsView: View {
|
|||||||
|
|
||||||
// MARK: - Profiles
|
// MARK: - Profiles
|
||||||
Section(
|
Section(
|
||||||
header: Text("Profiles", comment: "Section title for profile view configuration."),
|
header: Text(NSLocalizedString("Profiles", comment: "Section title for profile view configuration.")),
|
||||||
footer: Text("Profile action sheets allow you to follow, zap, or DM profiles more quickly without having to view their full profile", comment: "Section footer clarifying what the profile action sheet feature does")
|
footer: Text(NSLocalizedString("Profile action sheets allow you to follow, zap, or DM profiles more quickly without having to view their full profile", comment: "Section footer clarifying what the profile action sheet feature does"))
|
||||||
) {
|
) {
|
||||||
Toggle(NSLocalizedString("Show profile action sheets", comment: "Setting to show profile action sheets when clicking on a user's profile picture"), isOn: $settings.show_profile_action_sheet_on_pfp_click)
|
Toggle(NSLocalizedString("Show profile action sheets", comment: "Setting to show profile action sheets when clicking on a user's profile picture"), isOn: $settings.show_profile_action_sheet_on_pfp_click)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
@@ -157,9 +157,9 @@ struct AppearanceSettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alert(isPresented: $showing_enable_animation_alert) {
|
.alert(isPresented: $showing_enable_animation_alert) {
|
||||||
Alert(title: Text("Confirmation", comment: "Confirmation dialog title"),
|
Alert(title: Text(NSLocalizedString("Confirmation", comment: "Confirmation dialog title")),
|
||||||
message: Text("Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?", comment: "Message explaining consequences of changing the 'enable animation' setting"),
|
message: Text(NSLocalizedString("Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?", comment: "Message explaining consequences of changing the 'enable animation' setting")),
|
||||||
primaryButton: .default(Text("OK", comment: "Button label indicating user wants to proceed.")) {
|
primaryButton: .default(Text(NSLocalizedString("OK", comment: "Button label indicating user wants to proceed."))) {
|
||||||
self.clear_cache_button_action()
|
self.clear_cache_button_action()
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel() {
|
secondaryButton: .cancel() {
|
||||||
@@ -176,22 +176,22 @@ struct AppearanceSettingsView: View {
|
|||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
switch cache_clearing_state {
|
switch cache_clearing_state {
|
||||||
case .not_cleared:
|
case .not_cleared:
|
||||||
Text("Clear Cache", comment: "Button to clear image cache.")
|
Text(NSLocalizedString("Clear Cache", comment: "Button to clear image cache."))
|
||||||
case .clearing:
|
case .clearing:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
Text("Clearing Cache", comment: "Loading message indicating that the cache is being cleared.")
|
Text(NSLocalizedString("Clearing Cache", comment: "Loading message indicating that the cache is being cleared."))
|
||||||
case .cleared:
|
case .cleared:
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
Text("Cache has been cleared", comment: "Message indicating that the cache was successfully cleared.")
|
Text(NSLocalizedString("Cache has been cleared", comment: "Message indicating that the cache was successfully cleared."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.disabled(self.cache_clearing_state != .not_cleared)
|
.disabled(self.cache_clearing_state != .not_cleared)
|
||||||
.alert(isPresented: $showing_cache_clear_alert) {
|
.alert(isPresented: $showing_cache_clear_alert) {
|
||||||
Alert(title: Text("Confirmation", comment: "Confirmation dialog title"),
|
Alert(title: Text(NSLocalizedString("Confirmation", comment: "Confirmation dialog title")),
|
||||||
message: Text("Are you sure you want to clear the cache? This will free space, but images may take longer to load again.", comment: "Message explaining what it means to clear the cache, asking if user wants to proceed."),
|
message: Text(NSLocalizedString("Are you sure you want to clear the cache? This will free space, but images may take longer to load again.", comment: "Message explaining what it means to clear the cache, asking if user wants to proceed.")),
|
||||||
primaryButton: .default(Text("OK", comment: "Button label indicating user wants to proceed.")) {
|
primaryButton: .default(Text(NSLocalizedString("OK", comment: "Button label indicating user wants to proceed."))) {
|
||||||
self.clear_cache_button_action()
|
self.clear_cache_button_action()
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel())
|
secondaryButton: .cancel())
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ struct DeveloperSettingsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section(footer: Text("Developer Mode enables features and options that may help developers diagnose issues and improve this app. Most users will not need Developer Mode.", comment: "Section header for Developer Settings view")) {
|
Section(footer: Text(NSLocalizedString("Developer Mode enables features and options that may help developers diagnose issues and improve this app. Most users will not need Developer Mode.", comment: "Section header for Developer Settings view"))) {
|
||||||
Toggle(NSLocalizedString("Developer Mode", comment: "Setting to enable developer mode"), isOn: $settings.developer_mode)
|
Toggle(NSLocalizedString("Developer Mode", comment: "Setting to enable developer mode"), isOn: $settings.developer_mode)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
if settings.developer_mode {
|
if settings.developer_mode {
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
//
|
|
||||||
// FirstAidSettingsView.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by Daniel D’Aquino on 2024-04-19.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct FirstAidSettingsView: View {
|
|
||||||
let damus_state: DamusState
|
|
||||||
@ObservedObject var settings: UserSettingsStore
|
|
||||||
@State var reset_contact_list_state: ContactListResetState = .not_started
|
|
||||||
|
|
||||||
enum ContactListResetState: Equatable {
|
|
||||||
case not_started
|
|
||||||
case confirming_with_user
|
|
||||||
case error(String)
|
|
||||||
case in_progress
|
|
||||||
case completed
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Form {
|
|
||||||
if damus_state.contacts.event == nil {
|
|
||||||
Section(
|
|
||||||
header: Text(NSLocalizedString("Contact list (Follows + Relay list)", comment: "Section title for Contact list first aid tools")),
|
|
||||||
footer: Text(NSLocalizedString("No contact list was found. You might experience issues using the app. If you suspect you have permanently lost your contact list (or if you never had one), you can fix this by resetting it", comment: "Section footer for Contact list first aid tools"))
|
|
||||||
) {
|
|
||||||
Button(action: {
|
|
||||||
reset_contact_list_state = .confirming_with_user
|
|
||||||
}, label: {
|
|
||||||
HStack(spacing: 6) {
|
|
||||||
switch reset_contact_list_state {
|
|
||||||
case .not_started, .error:
|
|
||||||
Label(NSLocalizedString("Reset contact list", comment: "Button to reset contact list."), image: "broom")
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.foregroundColor(.red)
|
|
||||||
case .confirming_with_user, .in_progress:
|
|
||||||
ProgressView()
|
|
||||||
Text(NSLocalizedString("In progress…", comment: "Loading message indicating that a contact list reset operation is in progress."))
|
|
||||||
case .completed:
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text(NSLocalizedString("Contact list has been reset", comment: "Message indicating that the contact list was successfully reset."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.disabled(reset_contact_list_state == .in_progress || reset_contact_list_state == .completed)
|
|
||||||
|
|
||||||
if case let .error(error_message) = reset_contact_list_state {
|
|
||||||
Text(error_message)
|
|
||||||
.foregroundStyle(.red)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.alert(NSLocalizedString("WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.", comment: "Alert for resetting the user's contact list."),
|
|
||||||
isPresented: Binding(get: { reset_contact_list_state == .confirming_with_user }, set: { _ in return })
|
|
||||||
) {
|
|
||||||
Button(NSLocalizedString("Cancel", comment: "Cancel resetting the contact list."), role: .cancel) {
|
|
||||||
reset_contact_list_state = .not_started
|
|
||||||
}
|
|
||||||
Button(NSLocalizedString("Continue", comment: "Continue with resetting the contact list.")) {
|
|
||||||
guard let new_contact_list_event = make_first_contact_event(keypair: damus_state.keypair) else {
|
|
||||||
reset_contact_list_state = .error(NSLocalizedString("An unexpected error happened while trying to create the new contact list. Please contact support.", comment: "Error message for a failed contact list reset operation"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
damus_state.pool.send(.event(new_contact_list_event))
|
|
||||||
reset_contact_list_state = .completed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if damus_state.contacts.event != nil {
|
|
||||||
Text("We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support: [support@damus.io](mailto:support@damus.io)", comment: "Message indicating that no First Aid actions are available.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle(NSLocalizedString("First Aid", comment: "Navigation title for first aid settings and tools"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
FirstAidSettingsView(damus_state: test_damus_state, settings: test_damus_state.settings)
|
|
||||||
}
|
|
||||||
@@ -8,9 +8,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationSettingsView: View {
|
struct NotificationSettingsView: View {
|
||||||
let damus_state: DamusState
|
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
@State var notification_mode_setting_error: String? = nil
|
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@@ -26,61 +24,9 @@ struct NotificationSettingsView: View {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func try_to_set_notifications_mode(new_value: UserSettingsStore.NotificationsMode) {
|
|
||||||
notification_mode_setting_error = nil
|
|
||||||
if new_value == .push {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
try await damus_state.push_notification_client.send_token()
|
|
||||||
settings.notifications_mode = new_value
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
notification_mode_setting_error = String(format: NSLocalizedString("Error configuring push notifications with the server: %@", comment: "Error label shown when user tries to enable push notifications but something fails"), error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
try await damus_state.push_notification_client.revoke_token()
|
|
||||||
settings.notifications_mode = new_value
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
notification_mode_setting_error = String(format: NSLocalizedString("Error disabling push notifications with the server: %@", comment: "Error label shown when user tries to disable push notifications but something fails"), error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
if settings.enable_experimental_push_notifications {
|
Section(header: Text(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"))) {
|
||||||
Section(
|
|
||||||
header: Text("General", comment: "Section header for general damus notifications user configuration"),
|
|
||||||
footer: VStack {
|
|
||||||
if let notification_mode_setting_error {
|
|
||||||
Text(notification_mode_setting_error)
|
|
||||||
.foregroundStyle(.damusDangerPrimary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Picker(NSLocalizedString("Notifications mode", comment: "Prompt selection of the notification mode (Feature to switch between local notifications (generated from user's own phone) or push notifications (generated by Damus server)."),
|
|
||||||
selection: Binding(
|
|
||||||
get: { settings.notifications_mode },
|
|
||||||
set: { newValue in
|
|
||||||
self.try_to_set_notifications_mode(new_value: newValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
ForEach(UserSettingsStore.NotificationsMode.allCases, id: \.self) { notification_mode in
|
|
||||||
Text(notification_mode.text_description())
|
|
||||||
.tag(notification_mode.rawValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Text("Local Notifications", comment: "Section header for damus local notifications user configuration")) {
|
|
||||||
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: $settings.zap_notification)
|
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: $settings.zap_notification)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: $settings.mention_notification)
|
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: $settings.mention_notification)
|
||||||
@@ -93,12 +39,12 @@ struct NotificationSettingsView: View {
|
|||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Notification Preference", comment: "Section header for Notification Preferences")) {
|
Section(header: Text(NSLocalizedString("Notification Preference", comment: "Section header for Notification Preferences"))) {
|
||||||
Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: $settings.notification_only_from_following)
|
Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: $settings.notification_only_from_following)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Notification Dots", comment: "Section header for notification indicator dot settings")) {
|
Section(header: Text(NSLocalizedString("Notification Dots", comment: "Section header for notification indicator dot settings"))) {
|
||||||
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps))
|
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps))
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))
|
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))
|
||||||
@@ -119,6 +65,6 @@ struct NotificationSettingsView: View {
|
|||||||
|
|
||||||
struct NotificationSettings_Previews: PreviewProvider {
|
struct NotificationSettings_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NotificationSettingsView(damus_state: test_damus_state, settings: UserSettingsStore())
|
NotificationSettingsView(settings: UserSettingsStore())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct ReactionsSettingsView: View {
|
|||||||
isReactionsVisible = true
|
isReactionsVisible = true
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Select default emoji", comment: "Prompt selection of user's default emoji reaction")
|
Text(NSLocalizedString("Select default emoji", comment: "Prompt selection of user's default emoji reaction"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
|
.navigationTitle(NSLocalizedString("Reactions", comment: "Title of emoji reactions view"))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ struct SearchSettingsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section(header: Text("Spam", comment: "Section header for Universe/Search spam")) {
|
Section(header: Text(NSLocalizedString("Spam", comment: "Section header for Universe/Search spam"))) {
|
||||||
Toggle(NSLocalizedString("View multiple events per user", comment: "Setting to only see 1 event per user (npub) in the search/universe"), isOn: $settings.multiple_events_per_pubkey)
|
Toggle(NSLocalizedString("View multiple events per user", comment: "Setting to only see 1 event per user (npub) in the search/universe"), isOn: $settings.multiple_events_per_pubkey)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user