Compare commits
108 Commits
tyiu/no-tr
...
tyiu/libre
| Author | SHA1 | Date | |
|---|---|---|---|
|
94504a116d
|
|||
|
|
00aa897f05 | ||
|
|
71a1a6f0a3 | ||
|
|
9d22f40a53 | ||
|
|
0bd40c0018 | ||
|
|
ec75769a0f | ||
|
|
47e349558c | ||
|
|
aa559b2916 | ||
|
|
9bf8349db6 | ||
|
|
4c44de9276 | ||
|
|
aa5f8d19f7 | ||
|
|
040e452132 | ||
|
|
ff41bb1b35 | ||
|
|
efe231c122 | ||
|
|
0305712f65 | ||
|
|
ba844aec97 | ||
|
|
62eac0032f | ||
|
|
b673e9b43b | ||
|
|
1dd274e07f | ||
|
|
e8aa52efea | ||
|
|
c7f6db84dd | ||
|
|
4a28ddefab | ||
|
|
3f4e37f1f8 | ||
|
|
9dc304aa04 | ||
|
|
d3dd4faa42 | ||
|
|
b40ee38895 | ||
|
|
fb9d1db6e6 | ||
|
|
591cdd478e | ||
|
|
b97204400f | ||
|
|
ebfe00362c | ||
|
|
accafb4cb2 | ||
|
|
20b124aa59 | ||
|
|
057bb2add5 | ||
|
|
c1b31a9938 | ||
|
|
1e532f9e63 | ||
|
|
16a7d5dedf | ||
|
|
8898bffbed | ||
|
|
7583346b06 | ||
|
|
8fbc71a2c2 | ||
|
|
e100f8c313 | ||
|
|
676c6f2afb | ||
|
|
e7c66156d3 | ||
|
|
99816695ae | ||
|
|
8e984ffa98 | ||
|
|
61e4359164 | ||
|
|
71940aaca0 | ||
|
|
042237ace7 | ||
|
|
b9c10d1eb1 | ||
|
|
638b98624e | ||
|
|
c8224f841d | ||
|
|
18439bbdf9 | ||
|
|
1fd7b759b1 | ||
|
|
0642abe064 | ||
|
|
080efda25e | ||
|
|
bbfe5380e0 | ||
|
|
8a20b7e4a7 | ||
|
|
191950a5aa | ||
|
|
ac82f1bc09 | ||
|
|
209f3e8759 | ||
|
|
897621b5ed | ||
| 66641fc9ae | |||
| 681e0f0be9 | |||
|
|
2ff12823f2 | ||
|
|
d6d996e84b | ||
|
|
d1fce5054d | ||
|
|
300cd87fc2 | ||
| bff3c0dd52 | |||
|
|
ddd027141a | ||
|
|
9b3e25dd6d | ||
|
|
1ae6a3d871 | ||
|
|
4821ba61a5 | ||
|
|
15849e290e | ||
|
|
6ed562ed24 | ||
| 93580e5296 | |||
|
|
a5c33e4431 | ||
| ad7a79c2bb | |||
|
|
eaec3ae011 | ||
|
|
5a1b966191 | ||
|
|
a320fae2bc | ||
|
|
8e0136a13a | ||
|
|
8f767b03ae | ||
|
|
209b23674d | ||
|
|
86917dbd69 | ||
|
|
59daf555cd | ||
|
|
916c7885f8 | ||
|
|
252763ea77 | ||
|
|
aa610c18b2 | ||
|
|
1de96a9dc5 | ||
|
|
746a4093de | ||
|
|
5cc9288759 | ||
|
|
3bedc83764 | ||
|
|
eb17e07478 | ||
|
|
b96ae84068 | ||
|
|
af7d4d2c53 | ||
|
d93a0600f3
|
|||
|
|
e9e5756c94
|
||
|
|
0b3cc2092f
|
||
|
|
285ab11324
|
||
|
|
3610a76c55
|
||
|
|
4644c57bf3
|
||
|
|
33887982b0
|
||
|
|
c3d3db352e
|
||
|
|
8f08e5c4c8
|
||
|
|
e00e89c16b
|
||
|
|
9151ef02a0
|
||
|
|
709a707942
|
||
|
|
343b7a2bcc
|
||
|
|
6534ba3bde
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,8 +1,8 @@
|
||||
## [1.4.3-1] - 2023-04-15
|
||||
## [1.4.3-2] - 2023-04-17
|
||||
|
||||
### Added
|
||||
|
||||
- Add deep links for local notifications (Swift + Will)
|
||||
- Add deep links for local notifications (Swift)
|
||||
- Add thread muting (Terry Yiu)
|
||||
- Preview media uploads when posting (Swift)
|
||||
- Add QR Code in profiles (ericholguin)
|
||||
@@ -10,18 +10,24 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Always check signatures of profile events (William Casarin)
|
||||
- Ask permission before uploading media (Swift)
|
||||
- Show DM message in local notification (William Casarin)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed repost turning green too early and not reposting sometimes (Swift)
|
||||
- Fix shuffling when choosing users to reply to (Joshua Jiang)
|
||||
- Do not translate own notes if logged in with private key (Terry Yiu)
|
||||
- Load missing profiles from boosts on home view (Gísli Kristjánsson)
|
||||
- Load missing profiles from boosts on profile view (Gísli Kristjánsson)
|
||||
- Fix tap area when mentioning users (OlegAba)
|
||||
- Fix invalid DM author notifications (William Casarin)
|
||||
- Fix relay signal indicator, properly show how many relays you are connected to (William Casarin)
|
||||
|
||||
|
||||
[1.4.3-1]: https://github.com/damus-io/damus/releases/tag/v1.4.3-1
|
||||
[1.4.3-2]: https://github.com/damus-io/damus/releases/tag/v1.4.3-2
|
||||
|
||||
## [1.4.2-2] - 2023-04-12
|
||||
|
||||
|
||||
@@ -142,8 +142,9 @@
|
||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */; };
|
||||
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
|
||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
|
||||
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
||||
@@ -154,6 +155,7 @@
|
||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
|
||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
|
||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; };
|
||||
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
||||
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
||||
@@ -182,7 +184,6 @@
|
||||
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
|
||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
|
||||
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEF297F11C700430951 /* SelectedEventView.swift */; };
|
||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */; };
|
||||
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF3297F18B400430951 /* ReplyDescription.swift */; };
|
||||
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
|
||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
||||
@@ -278,7 +279,7 @@
|
||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
||||
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
|
||||
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
|
||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
|
||||
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParticipantsView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -550,8 +551,9 @@
|
||||
4C8D00D129E397AD0036AF10 /* block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = block.h; sourceTree = "<group>"; };
|
||||
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
|
||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
|
||||
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendIcon.swift; sourceTree = "<group>"; };
|
||||
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsButton.swift; sourceTree = "<group>"; };
|
||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
|
||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
||||
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||
@@ -562,6 +564,7 @@
|
||||
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
|
||||
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; };
|
||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
||||
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = "<group>"; };
|
||||
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
|
||||
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
|
||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||
@@ -590,7 +593,6 @@
|
||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
|
||||
4CC7AAEC297F0B9E00430951 /* Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Highlight.swift; sourceTree = "<group>"; };
|
||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedEventView.swift; sourceTree = "<group>"; };
|
||||
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedEventView.swift; sourceTree = "<group>"; };
|
||||
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyDescription.swift; sourceTree = "<group>"; };
|
||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBody.swift; sourceTree = "<group>"; };
|
||||
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
||||
@@ -688,7 +690,7 @@
|
||||
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
|
||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePictureControl.swift; sourceTree = "<group>"; };
|
||||
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
|
||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
|
||||
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -883,6 +885,7 @@
|
||||
4C75EFA227FA576C0006080F /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
|
||||
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
||||
4CFF8F6129CC9A80008DB934 /* Images */,
|
||||
4CCEB7AC29B53D180078AA28 /* Search */,
|
||||
@@ -908,7 +911,6 @@
|
||||
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
|
||||
4C216F31286E388800040376 /* DMChatView.swift */,
|
||||
4C216F33286F5ACD00040376 /* DMView.swift */,
|
||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
||||
3169CAE4294E699400EE4006 /* Empty Views */,
|
||||
4C75EFB82804A2740006080F /* EventView.swift */,
|
||||
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
|
||||
@@ -926,7 +928,7 @@
|
||||
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
||||
4C363A8B28236B92006E126D /* PubkeyView.swift */,
|
||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
|
||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */,
|
||||
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */,
|
||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
||||
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
||||
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
||||
@@ -963,7 +965,6 @@
|
||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
||||
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
||||
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */,
|
||||
);
|
||||
path = Nostr;
|
||||
sourceTree = "<group>";
|
||||
@@ -1009,10 +1010,19 @@
|
||||
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
||||
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
||||
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
|
||||
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C8D1A6D29F31E4100ACDF75 /* Buttons */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
|
||||
);
|
||||
path = Buttons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CAAD8AE29888A9B00060CEA /* Relays */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1053,12 +1063,14 @@
|
||||
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
|
||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
||||
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
|
||||
4C8682862814DE470026224F /* ProfileView.swift */,
|
||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
|
||||
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
|
||||
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
|
||||
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */,
|
||||
);
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
@@ -1067,7 +1079,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */,
|
||||
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */,
|
||||
@@ -1484,6 +1495,7 @@
|
||||
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
||||
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */,
|
||||
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
|
||||
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */,
|
||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
|
||||
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
|
||||
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
||||
@@ -1515,6 +1527,7 @@
|
||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
|
||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
||||
@@ -1529,7 +1542,7 @@
|
||||
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
|
||||
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
||||
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
|
||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||
@@ -1537,6 +1550,7 @@
|
||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
||||
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||
@@ -1582,7 +1596,6 @@
|
||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||
@@ -1597,7 +1610,6 @@
|
||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
|
||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
|
||||
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */,
|
||||
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
|
||||
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
|
||||
@@ -2012,7 +2024,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -2059,7 +2071,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
||||
@@ -66,7 +66,7 @@ struct ImageCarousel: View {
|
||||
}
|
||||
|
||||
var height: CGFloat {
|
||||
image_fill?.height ?? 0
|
||||
image_fill?.height ?? 100
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -118,10 +118,7 @@ extension KFOptionSetter {
|
||||
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
|
||||
let img_size = image.size
|
||||
let geo_size = size
|
||||
let fill = ImageFill.calculate_image_fill(geo_size: geo_size,
|
||||
img_size: img_size,
|
||||
maxHeight: max,
|
||||
fillHeight: fill)
|
||||
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: img_size, maxHeight: max, fillHeight: fill)
|
||||
DispatchQueue.main.async { [block, fill] in
|
||||
try? block(fill)
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ struct NIP05Badge: View {
|
||||
.mask(Image(systemName: "checkmark.seal.fill")
|
||||
.resizable()
|
||||
).frame(width: 14, height: 14)
|
||||
} else {
|
||||
} else if show_domain {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
.nip05_colorized(gradient: nip05_color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,14 @@ struct TranslateView: View {
|
||||
} else {
|
||||
self.currentLanguage = Locale.current.languageCode ?? "en"
|
||||
}
|
||||
|
||||
if let cached = damus_state.events.lookup_translated_artifacts(evid: event.id) {
|
||||
|
||||
if damus_state.pubkey == event.pubkey && damus_state.is_privkey_user {
|
||||
// Do not translate self-authored notes if logged in with a private key
|
||||
// as we can assume the user can understand their own notes.
|
||||
// The detected language prediction could be incorrect and not in the list of preferred languages.
|
||||
// Offering a translation in this case is definitely incorrect so let's avoid it altogether.
|
||||
self._translated = State(initialValue: .not_needed)
|
||||
} else if let cached = damus_state.events.lookup_translated_artifacts(evid: event.id) {
|
||||
self._translated = State(initialValue: cached)
|
||||
} else {
|
||||
let initval: TranslateStatus = self.damus_state.settings.auto_translate ? .trying : .havent_tried
|
||||
|
||||
@@ -173,7 +173,7 @@ func send_zap(damus_state: DamusState, event: NostrEvent, lnurl: String, is_cust
|
||||
damus_state.lnurls.endpoints[target.pubkey] = payreq
|
||||
}
|
||||
|
||||
let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
|
||||
let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey)
|
||||
|
||||
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -15,17 +15,15 @@ struct TimestampedProfile {
|
||||
}
|
||||
|
||||
enum Sheets: Identifiable {
|
||||
case post
|
||||
case post(PostAction)
|
||||
case report(ReportTarget)
|
||||
case reply(NostrEvent)
|
||||
case event(NostrEvent)
|
||||
case filter
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .report: return "report"
|
||||
case .post: return "post"
|
||||
case .reply(let ev): return "reply-" + ev.id
|
||||
case .post(let action): return "post-" + (action.ev?.id ?? "")
|
||||
case .event(let ev): return "event-" + ev.id
|
||||
case .filter: return "filter"
|
||||
}
|
||||
@@ -83,6 +81,7 @@ struct ContentView: View {
|
||||
@State var filter_state : FilterState = .posts_and_replies
|
||||
@State private var isSideBarOpened = false
|
||||
@StateObject var home: HomeModel = HomeModel()
|
||||
@State var shouldShowBoostAlert = false
|
||||
|
||||
// connect retry timer
|
||||
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
||||
@@ -114,7 +113,7 @@ struct ContentView: View {
|
||||
|
||||
if privkey != nil {
|
||||
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
|
||||
self.active_sheet = .post
|
||||
self.active_sheet = .post(.posting)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +182,7 @@ struct ContentView: View {
|
||||
NotificationsView(state: damus, notifications: home.notifications)
|
||||
|
||||
case .dms:
|
||||
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms)
|
||||
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
|
||||
|
||||
case .none:
|
||||
EmptyView()
|
||||
@@ -310,10 +309,8 @@ struct ContentView: View {
|
||||
switch item {
|
||||
case .report(let target):
|
||||
MaybeReportView(target: target)
|
||||
case .post:
|
||||
PostView(replying_to: nil, damus_state: damus_state!)
|
||||
case .reply(let event):
|
||||
PostView(replying_to: event, damus_state: damus_state!)
|
||||
case .post(let action):
|
||||
PostView(action: action, damus_state: damus_state!)
|
||||
case .event:
|
||||
EventDetailView()
|
||||
case .filter:
|
||||
@@ -353,11 +350,16 @@ struct ContentView: View {
|
||||
|
||||
}
|
||||
.onReceive(handle_notify(.boost)) { notif in
|
||||
current_boost = (notif.object as? NostrEvent)
|
||||
guard let ev = notif.object as? NostrEvent else {
|
||||
return
|
||||
}
|
||||
|
||||
current_boost = ev
|
||||
shouldShowBoostAlert = true
|
||||
}
|
||||
.onReceive(handle_notify(.reply)) { notif in
|
||||
let ev = notif.object as! NostrEvent
|
||||
self.active_sheet = .reply(ev)
|
||||
self.active_sheet = .post(.replying_to(ev))
|
||||
}
|
||||
.onReceive(handle_notify(.like)) { like in
|
||||
}
|
||||
@@ -465,18 +467,18 @@ struct ContentView: View {
|
||||
self.damus_state?.pool.connect_to_disconnected()
|
||||
}
|
||||
.onReceive(handle_notify(.new_mutes)) { notif in
|
||||
home.filter_muted()
|
||||
home.filter_events()
|
||||
}
|
||||
.onReceive(handle_notify(.mute_thread)) { notif in
|
||||
home.filter_muted()
|
||||
home.filter_events()
|
||||
}
|
||||
.onReceive(handle_notify(.unmute_thread)) { notif in
|
||||
home.filter_muted()
|
||||
home.filter_events()
|
||||
}
|
||||
.onReceive(handle_notify(.local_notification)) { notif in
|
||||
let local = notif.object as! LossyLocalNotification
|
||||
|
||||
guard let damus_state else {
|
||||
guard let local = notif.object as? LossyLocalNotification,
|
||||
let damus_state else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -496,6 +498,26 @@ struct ContentView: View {
|
||||
open_event(ev: target)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.onlyzaps_mode)) { notif in
|
||||
let hide = notif.object as! Bool
|
||||
home.filter_events()
|
||||
|
||||
guard let damus_state else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
profile.reactions = !hide
|
||||
|
||||
guard let profile_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.postbox.send(profile_ev)
|
||||
}
|
||||
.alert(NSLocalizedString("Deleted Account", comment: "Alert message to indicate this is a deleted account"), isPresented: $is_deleted_account) {
|
||||
Button(NSLocalizedString("Logout", comment: "Button to close the alert that informs that the current account has been deleted.")) {
|
||||
is_deleted_account = false
|
||||
@@ -583,17 +605,35 @@ struct ContentView: View {
|
||||
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
|
||||
}
|
||||
})
|
||||
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $current_boost.mappedToBool()) {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
|
||||
current_boost = nil
|
||||
}
|
||||
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
|
||||
if let current_boost {
|
||||
self.damus_state?.postbox.send(current_boost)
|
||||
.confirmationDialog("Repost", isPresented: $shouldShowBoostAlert) {
|
||||
Button(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post.")) {
|
||||
guard let current_boost else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let privkey = self.damus_state?.keypair.privkey else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let damus_state else {
|
||||
return
|
||||
}
|
||||
|
||||
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: current_boost)
|
||||
damus_state.postbox.send(boost)
|
||||
}
|
||||
|
||||
Button(NSLocalizedString("Quote", comment: "Title of alert for confirming to make a quoted post.")) {
|
||||
guard let current_boost else {
|
||||
return
|
||||
}
|
||||
self.active_sheet = .post(.quoting(current_boost))
|
||||
}
|
||||
}
|
||||
.onChange(of: shouldShowBoostAlert) { v in
|
||||
if v == false {
|
||||
self.current_boost = nil
|
||||
}
|
||||
} message: {
|
||||
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,7 +678,12 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||
|
||||
|
||||
// dumb stuff needed for property wrappers
|
||||
UserSettingsStore.pubkey = pubkey
|
||||
let settings = UserSettingsStore()
|
||||
UserSettingsStore.shared = settings
|
||||
|
||||
self.damus_state = DamusState(pool: pool,
|
||||
keypair: keypair,
|
||||
likes: EventCounter(our_pubkey: pubkey),
|
||||
@@ -650,7 +695,7 @@ struct ContentView: View {
|
||||
previews: PreviewCache(),
|
||||
zaps: Zaps(our_pubkey: pubkey),
|
||||
lnurls: LNUrls(),
|
||||
settings: UserSettingsStore(),
|
||||
settings: settings,
|
||||
relay_filters: relay_filters,
|
||||
relay_metadata: metadatas,
|
||||
drafts: Drafts(),
|
||||
@@ -821,7 +866,7 @@ func find_event(state: DamusState, evid: String, search_type: SearchType, find_f
|
||||
var filter = search_type == .event ? NostrFilter.filter_ids([ evid ]) : NostrFilter.filter_authors([ evid ])
|
||||
|
||||
if search_type == .profile {
|
||||
filter.kinds = [0]
|
||||
filter.kinds = [NostrKind.metadata.rawValue]
|
||||
}
|
||||
|
||||
filter.limit = 1
|
||||
|
||||
@@ -39,6 +39,8 @@ struct DamusState {
|
||||
keypair.privkey != nil
|
||||
}
|
||||
|
||||
static var settings_pubkey: String? = nil
|
||||
|
||||
static var empty: DamusState {
|
||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil))) }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,19 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum DeepLPlan: String, CaseIterable, Identifiable {
|
||||
enum DeepLPlan: String, CaseIterable, Identifiable, StringCodable {
|
||||
init?(from string: String) {
|
||||
guard let dl = DeepLPlan(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = dl
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
var id: String { self.rawValue }
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
|
||||
@@ -7,8 +7,23 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class Drafts: ObservableObject {
|
||||
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
|
||||
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
|
||||
@Published var medias: [UploadedMedia] = []
|
||||
class DraftArtifacts {
|
||||
var content: NSMutableAttributedString
|
||||
var media: [UploadedMedia]
|
||||
|
||||
init() {
|
||||
self.content = NSMutableAttributedString(string: "")
|
||||
self.media = []
|
||||
}
|
||||
|
||||
init(content: NSMutableAttributedString, media: [UploadedMedia]) {
|
||||
self.content = content
|
||||
self.media = media
|
||||
}
|
||||
}
|
||||
|
||||
class Drafts: ObservableObject {
|
||||
@Published var post: DraftArtifacts? = nil
|
||||
@Published var replies: [NostrEvent: DraftArtifacts] = [:]
|
||||
@Published var quotes: [NostrEvent: DraftArtifacts] = [:]
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class FollowingModel {
|
||||
}
|
||||
|
||||
func get_filter() -> NostrFilter {
|
||||
var f = NostrFilter.filter_kinds([0])
|
||||
var f = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
|
||||
f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in
|
||||
// don't fetch profiles we already have
|
||||
if damus_state.profiles.lookup(id: pk) != nil {
|
||||
|
||||
@@ -52,7 +52,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
init() {
|
||||
self.damus_state = DamusState.empty
|
||||
filter_muted()
|
||||
filter_events()
|
||||
self.setup_debouncer()
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ class HomeModel: ObservableObject {
|
||||
func handle_channel_meta(_ ev: NostrEvent) {
|
||||
}
|
||||
|
||||
func filter_muted() {
|
||||
func filter_events() {
|
||||
events.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey)
|
||||
}
|
||||
@@ -202,8 +202,11 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
notifications.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey) &&
|
||||
!damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey)
|
||||
if damus_state.settings.onlyzaps_mode && ev.known_kind == NostrKind.like {
|
||||
return false
|
||||
}
|
||||
|
||||
return !damus_state.contacts.is_muted(ev.pubkey) && !damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +261,10 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if damus_state.settings.onlyzaps_mode {
|
||||
return
|
||||
}
|
||||
|
||||
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||
case .already_counted:
|
||||
break
|
||||
@@ -322,6 +329,8 @@ class HomeModel: ObservableObject {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
||||
} else if sub_id == notifications_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
|
||||
} else if sub_id == home_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus_state)
|
||||
}
|
||||
|
||||
self.loading = false
|
||||
@@ -351,13 +360,13 @@ class HomeModel: ObservableObject {
|
||||
var friends = damus_state.contacts.get_friend_list()
|
||||
friends.append(damus_state.pubkey)
|
||||
|
||||
var contacts_filter = NostrFilter.filter_kinds([0])
|
||||
var contacts_filter = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
|
||||
contacts_filter.authors = friends
|
||||
|
||||
var our_contacts_filter = NostrFilter.filter_kinds([3, 0])
|
||||
var our_contacts_filter = NostrFilter.filter_kinds([NostrKind.contacts.rawValue, NostrKind.metadata.rawValue])
|
||||
our_contacts_filter.authors = [damus_state.pubkey]
|
||||
|
||||
var our_blocklist_filter = NostrFilter.filter_kinds([30000])
|
||||
var our_blocklist_filter = NostrFilter.filter_kinds([NostrKind.list.rawValue])
|
||||
our_blocklist_filter.parameter = ["mute"]
|
||||
our_blocklist_filter.authors = [damus_state.pubkey]
|
||||
|
||||
@@ -376,21 +385,27 @@ class HomeModel: ObservableObject {
|
||||
our_dms_filter.authors = [ damus_state.pubkey ]
|
||||
|
||||
// TODO: separate likes?
|
||||
var home_filter = NostrFilter.filter_kinds([
|
||||
var home_filter_kinds = [
|
||||
NostrKind.text.rawValue,
|
||||
NostrKind.like.rawValue,
|
||||
NostrKind.boost.rawValue,
|
||||
])
|
||||
NostrKind.boost.rawValue
|
||||
]
|
||||
if !damus_state.settings.onlyzaps_mode {
|
||||
home_filter_kinds.append(NostrKind.like.rawValue)
|
||||
}
|
||||
var home_filter = NostrFilter.filter_kinds(home_filter_kinds)
|
||||
// include our pubkey as well even if we're not technically a friend
|
||||
home_filter.authors = friends
|
||||
home_filter.limit = 500
|
||||
|
||||
var notifications_filter = NostrFilter.filter_kinds([
|
||||
var notifications_filter_kinds = [
|
||||
NostrKind.text.rawValue,
|
||||
NostrKind.like.rawValue,
|
||||
NostrKind.boost.rawValue,
|
||||
NostrKind.zap.rawValue,
|
||||
])
|
||||
]
|
||||
if !damus_state.settings.onlyzaps_mode {
|
||||
notifications_filter_kinds.append(NostrKind.like.rawValue)
|
||||
}
|
||||
var notifications_filter = NostrFilter.filter_kinds(notifications_filter_kinds)
|
||||
notifications_filter.pubkeys = [damus_state.pubkey]
|
||||
notifications_filter.limit = 500
|
||||
|
||||
@@ -658,9 +673,7 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
||||
|
||||
func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) {
|
||||
if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
|
||||
DispatchQueue.main.async {
|
||||
notify(.deleted_account, ())
|
||||
}
|
||||
notify(.deleted_account, ())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -694,20 +707,15 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
|
||||
// load pfps asap
|
||||
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
|
||||
if URL(string: picture) != nil {
|
||||
DispatchQueue.main.async {
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
|
||||
let banner = tprof.profile.banner ?? ""
|
||||
if URL(string: banner) != nil {
|
||||
DispatchQueue.main.async {
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
|
||||
}
|
||||
|
||||
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum LibreTranslateServer: String, CaseIterable, Identifiable {
|
||||
enum LibreTranslateServer: String, CaseIterable, Identifiable, StringCodable {
|
||||
var id: String { self.rawValue }
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
@@ -17,9 +17,19 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable {
|
||||
var url: String?
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
init?(from string: String) {
|
||||
guard let libreTranslateServer = LibreTranslateServer(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
self = libreTranslateServer
|
||||
}
|
||||
|
||||
case argosopentech
|
||||
case terraprint
|
||||
case vern
|
||||
case custom
|
||||
|
||||
var model: Model {
|
||||
@@ -28,8 +38,6 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable {
|
||||
return .init(tag: self.rawValue, displayName: "translate.argosopentech.com", url: "https://translate.argosopentech.com")
|
||||
case .terraprint:
|
||||
return .init(tag: self.rawValue, displayName: "translate.terraprint.co", url: "https://translate.terraprint.co")
|
||||
case .vern:
|
||||
return .init(tag: self.rawValue, displayName: "lt.vern.cc", url: "https://lt.vern.cc")
|
||||
case .custom:
|
||||
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
|
||||
}
|
||||
|
||||
@@ -29,4 +29,22 @@ class EventGroup {
|
||||
func insert(_ ev: NostrEvent) -> Bool {
|
||||
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
|
||||
}
|
||||
|
||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||
for ev in events {
|
||||
if !isIncluded(ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> EventGroup? {
|
||||
let new_evs = events.filter(isIncluded)
|
||||
guard new_evs.count > 0 else {
|
||||
return nil
|
||||
}
|
||||
return EventGroup(events: new_evs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,26 @@ class ZapGroup {
|
||||
}
|
||||
}
|
||||
|
||||
init(zaps: [Zap]) {
|
||||
self.zaps = zaps
|
||||
self.msat_total = 0
|
||||
self.zappers = Set()
|
||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||
for zap in zaps {
|
||||
if !isIncluded(zap.request_ev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
|
||||
let new_zaps = zaps.filter { isIncluded($0.request_ev) }
|
||||
guard new_zaps.count > 0 else {
|
||||
return nil
|
||||
}
|
||||
let grp = ZapGroup()
|
||||
for zap in new_zaps {
|
||||
grp.insert(zap)
|
||||
}
|
||||
return grp
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -42,6 +58,7 @@ class ZapGroup {
|
||||
self.zappers = Set()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert(_ zap: Zap) -> Bool {
|
||||
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
||||
return false
|
||||
|
||||
@@ -65,6 +65,37 @@ enum NotificationItem {
|
||||
return reply.created_at
|
||||
}
|
||||
}
|
||||
|
||||
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||
switch self {
|
||||
case .repost(_, let evgrp):
|
||||
return evgrp.would_filter(isIncluded)
|
||||
case .reaction(_, let evgrp):
|
||||
return evgrp.would_filter(isIncluded)
|
||||
case .profile_zap(let zapgrp):
|
||||
return zapgrp.would_filter(isIncluded)
|
||||
case .event_zap(_, let zapgrp):
|
||||
return zapgrp.would_filter(isIncluded)
|
||||
case .reply(let ev):
|
||||
return !isIncluded(ev)
|
||||
}
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) -> NotificationItem? {
|
||||
switch self {
|
||||
case .repost(let evid, let evgrp):
|
||||
return evgrp.filter(isIncluded).map { .repost(evid, $0) }
|
||||
case .reaction(let evid, let evgrp):
|
||||
return evgrp.filter(isIncluded).map { .reaction(evid, $0) }
|
||||
case .profile_zap(let zapgrp):
|
||||
return zapgrp.filter(isIncluded).map { .profile_zap($0) }
|
||||
case .event_zap(let evid, let zapgrp):
|
||||
return zapgrp.filter(isIncluded).map { .event_zap(evid, $0) }
|
||||
case .reply(let ev):
|
||||
if isIncluded(ev) { return .reply(ev) }
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
|
||||
@@ -140,6 +140,9 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
case .notice(let notice):
|
||||
notify(.notice, notice)
|
||||
case .eose:
|
||||
if resp.subid == sub_id {
|
||||
load_profiles(profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus)
|
||||
}
|
||||
progress += 1
|
||||
break
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class SearchHomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
func get_base_filter() -> NostrFilter {
|
||||
var filter = NostrFilter.filter_kinds([1, 42])
|
||||
var filter = NostrFilter.filter_kinds([NostrKind.text.rawValue, NostrKind.chat.rawValue])
|
||||
filter.limit = self.limit
|
||||
filter.until = Int64(Date.now.timeIntervalSince1970)
|
||||
return filter
|
||||
@@ -128,11 +128,14 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
|
||||
var pubkeys = Set<String>()
|
||||
|
||||
for ev in events {
|
||||
if profiles.lookup(id: ev.pubkey) != nil {
|
||||
continue
|
||||
// lookup profiles from boosted events
|
||||
if ev.known_kind == .boost, let bev = ev.inner_event, profiles.lookup(id: bev.pubkey) == nil {
|
||||
pubkeys.insert(bev.pubkey)
|
||||
}
|
||||
|
||||
pubkeys.insert(ev.pubkey)
|
||||
if profiles.lookup(id: ev.pubkey) == nil {
|
||||
pubkeys.insert(ev.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
return Array(pubkeys)
|
||||
|
||||
@@ -33,7 +33,7 @@ class SearchModel: ObservableObject {
|
||||
func subscribe() {
|
||||
// since 1 month
|
||||
search.limit = self.limit
|
||||
search.kinds = [1,5,7]
|
||||
search.kinds = [NostrKind.text.rawValue, NostrKind.like.rawValue]
|
||||
|
||||
//likes_filter.ids = ref_events.referenced_ids!
|
||||
|
||||
|
||||
@@ -77,18 +77,23 @@ class ThreadModel: ObservableObject {
|
||||
var meta_events = NostrFilter()
|
||||
var event_filter = NostrFilter()
|
||||
var ref_events = NostrFilter()
|
||||
//var likes_filter = NostrFilter.filter_kinds(7])
|
||||
|
||||
let thread_id = event.thread_id(privkey: nil)
|
||||
|
||||
ref_events.referenced_ids = [thread_id, event.id]
|
||||
ref_events.kinds = [1]
|
||||
ref_events.kinds = [NostrKind.text.rawValue]
|
||||
ref_events.limit = 1000
|
||||
|
||||
event_filter.ids = [thread_id, event.id]
|
||||
|
||||
meta_events.referenced_ids = [event.id]
|
||||
meta_events.kinds = [9735, 1, 6, 7]
|
||||
|
||||
var kinds = [NostrKind.zap.rawValue, NostrKind.text.rawValue, NostrKind.boost.rawValue]
|
||||
if !damus_state.settings.onlyzaps_mode {
|
||||
kinds.append(NostrKind.like.rawValue)
|
||||
}
|
||||
meta_events.kinds = kinds
|
||||
|
||||
meta_events.limit = 1000
|
||||
|
||||
/*
|
||||
|
||||
@@ -7,7 +7,19 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum TranslationService: String, CaseIterable, Identifiable {
|
||||
enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
|
||||
init?(from string: String) {
|
||||
guard let ts = TranslationService(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = ts
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
var id: String { self.rawValue }
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
|
||||
@@ -9,210 +9,141 @@ import Foundation
|
||||
import Vault
|
||||
import UIKit
|
||||
|
||||
func should_show_wallet_selector(_ pubkey: String) -> Bool {
|
||||
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
||||
}
|
||||
|
||||
func pk_setting_key(_ pubkey: String, key: String) -> String {
|
||||
return "\(pubkey)_\(key)"
|
||||
}
|
||||
|
||||
func default_zap_setting_key(pubkey: String) -> String {
|
||||
return pk_setting_key(pubkey, key: "default_zap_amount")
|
||||
}
|
||||
|
||||
func set_default_zap_amount(pubkey: String, amount: Int) {
|
||||
let key = default_zap_setting_key(pubkey: pubkey)
|
||||
UserDefaults.standard.setValue(amount, forKey: key)
|
||||
}
|
||||
|
||||
func get_default_zap_amount(pubkey: String) -> Int? {
|
||||
let key = default_zap_setting_key(pubkey: pubkey)
|
||||
let amt = UserDefaults.standard.integer(forKey: key)
|
||||
if amt == 0 {
|
||||
return nil
|
||||
}
|
||||
return amt
|
||||
}
|
||||
|
||||
func should_disable_image_animation() -> Bool {
|
||||
return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool)
|
||||
?? UIAccessibility.isReduceMotionEnabled
|
||||
}
|
||||
|
||||
func get_default_wallet(_ pubkey: String) -> Wallet {
|
||||
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
|
||||
let default_wallet = Wallet(rawValue: defaultWalletName)
|
||||
{
|
||||
return default_wallet
|
||||
} else {
|
||||
return .system_default_wallet
|
||||
}
|
||||
}
|
||||
|
||||
func get_media_uploader(_ pubkey: String) -> MediaUploader {
|
||||
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
|
||||
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
|
||||
return defaultMediaUploader
|
||||
} else {
|
||||
return .nostrBuild
|
||||
}
|
||||
}
|
||||
|
||||
private func get_translation_service(_ pubkey: String) -> TranslationService? {
|
||||
guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return TranslationService(rawValue: translation_service)
|
||||
}
|
||||
|
||||
private func get_deepl_plan(_ pubkey: String) -> DeepLPlan? {
|
||||
guard let server_name = UserDefaults.standard.string(forKey: "deepl_plan") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DeepLPlan(rawValue: server_name)
|
||||
}
|
||||
|
||||
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
|
||||
guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else {
|
||||
return nil
|
||||
@propertyWrapper struct Setting<T: Equatable> {
|
||||
private let key: String
|
||||
private var value: T
|
||||
|
||||
init(key: String, default_value: T) {
|
||||
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
|
||||
if let loaded = UserDefaults.standard.object(forKey: self.key) as? T {
|
||||
self.value = loaded
|
||||
} else if let loaded = UserDefaults.standard.object(forKey: key) as? T {
|
||||
// try to load from deprecated non-pubkey-keyed setting
|
||||
self.value = loaded
|
||||
} else {
|
||||
self.value = default_value
|
||||
}
|
||||
}
|
||||
|
||||
return LibreTranslateServer(rawValue: server_name)
|
||||
var wrappedValue: T {
|
||||
get { return value }
|
||||
set {
|
||||
guard self.value != newValue else {
|
||||
return
|
||||
}
|
||||
self.value = newValue
|
||||
UserDefaults.standard.set(newValue, forKey: key)
|
||||
UserSettingsStore.shared!.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
|
||||
if let url = server.model.url {
|
||||
return url
|
||||
@propertyWrapper class StringSetting<T: StringCodable & Equatable> {
|
||||
private let key: String
|
||||
private var value: T
|
||||
|
||||
init(key: String, default_value: T) {
|
||||
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
|
||||
if let loaded = UserDefaults.standard.string(forKey: self.key), let val = T.init(from: loaded) {
|
||||
self.value = val
|
||||
} else if let loaded = UserDefaults.standard.string(forKey: key), let val = T.init(from: loaded) {
|
||||
// try to load from deprecated non-pubkey-keyed setting
|
||||
self.value = val
|
||||
} else {
|
||||
self.value = default_value
|
||||
}
|
||||
}
|
||||
|
||||
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
|
||||
var wrappedValue: T {
|
||||
get { return value }
|
||||
set {
|
||||
guard self.value != newValue else {
|
||||
return
|
||||
}
|
||||
self.value = newValue
|
||||
UserDefaults.standard.set(newValue.to_string(), forKey: key)
|
||||
UserSettingsStore.shared!.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UserSettingsStore: ObservableObject {
|
||||
@Published var default_wallet: Wallet {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_wallet.rawValue, forKey: "default_wallet")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var default_media_uploader: MediaUploader {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader")
|
||||
}
|
||||
}
|
||||
static var pubkey: String? = nil
|
||||
static var shared: UserSettingsStore? = nil
|
||||
|
||||
@Published var show_wallet_selector: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var left_handed: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(left_handed, forKey: "left_handed")
|
||||
}
|
||||
}
|
||||
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
|
||||
var default_wallet: Wallet
|
||||
|
||||
@Published var always_show_images: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(always_show_images, forKey: "always_show_images")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var zap_vibration: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(zap_vibration, forKey: "zap_vibration")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var zap_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(zap_notification, forKey: "zap_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var mention_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(mention_notification, forKey: "mention_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var repost_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(repost_notification, forKey: "repost_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var dm_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(dm_notification, forKey: "dm_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var like_notification: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(like_notification, forKey: "like_notification")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var notification_only_from_following: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(notification_only_from_following, forKey: "notification_only_from_following")
|
||||
}
|
||||
}
|
||||
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
|
||||
var default_media_uploader: MediaUploader
|
||||
|
||||
@Published var translate_dms: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(translate_dms, forKey: "translate_dms")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var truncate_timeline_text: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text")
|
||||
}
|
||||
}
|
||||
@Setting(key: "show_wallet_selector", default_value: true)
|
||||
var show_wallet_selector: Bool
|
||||
|
||||
@Published var notification_indicators: Int {
|
||||
didSet {
|
||||
UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators")
|
||||
}
|
||||
}
|
||||
@Setting(key: "left_handed", default_value: false)
|
||||
var left_handed: Bool
|
||||
|
||||
@Published var truncate_mention_text: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
|
||||
}
|
||||
}
|
||||
@Setting(key: "always_show_images", default_value: false)
|
||||
var always_show_images: Bool
|
||||
|
||||
@Published var auto_translate: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
|
||||
}
|
||||
}
|
||||
@Setting(key: "zap_vibration", default_value: true)
|
||||
var zap_vibration: Bool
|
||||
|
||||
@Setting(key: "zap_notification", default_value: true)
|
||||
var zap_notification: Bool
|
||||
|
||||
@Setting(key: "mention_notification", default_value: true)
|
||||
var mention_notification: Bool
|
||||
|
||||
@Published var show_only_preferred_languages: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
||||
}
|
||||
}
|
||||
@Setting(key: "repost_notification", default_value: true)
|
||||
var repost_notification: Bool
|
||||
|
||||
@Setting(key: "dm_notification", default_value: true)
|
||||
var dm_notification: Bool
|
||||
|
||||
@Setting(key: "like_notification", default_value: true)
|
||||
var like_notification: Bool
|
||||
|
||||
@Setting(key: "notification_only_from_following", default_value: false)
|
||||
var notification_only_from_following: Bool
|
||||
|
||||
@Setting(key: "translate_dms", default_value: false)
|
||||
var translate_dms: Bool
|
||||
|
||||
@Setting(key: "truncate_timeline_text", default_value: false)
|
||||
var truncate_timeline_text: Bool
|
||||
|
||||
@Setting(key: "truncate_mention_text", default_value: true)
|
||||
var truncate_mention_text: Bool
|
||||
|
||||
@Setting(key: "notification_indicators", default_value: NewEventsBits.all.rawValue)
|
||||
var notification_indicators: Int
|
||||
|
||||
@Setting(key: "auto_translate", default_value: true)
|
||||
var auto_translate: Bool
|
||||
|
||||
@Published var translation_service: TranslationService {
|
||||
didSet {
|
||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
||||
}
|
||||
}
|
||||
@Setting(key: "show_only_preferred_languages", default_value: false)
|
||||
var show_only_preferred_languages: Bool
|
||||
|
||||
@Published var deepl_plan: DeepLPlan {
|
||||
didSet {
|
||||
UserDefaults.standard.set(deepl_plan.rawValue, forKey: "deepl_plan")
|
||||
}
|
||||
}
|
||||
@Setting(key: "onlyzaps_mode", default_value: false)
|
||||
var onlyzaps_mode: Bool
|
||||
|
||||
@Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled)
|
||||
var disable_animation: Bool
|
||||
|
||||
@StringSetting(key: "friend_filter", default_value: .all)
|
||||
var friend_filter: FriendFilter
|
||||
|
||||
@StringSetting(key: "notification_state", default_value: .all)
|
||||
var notification_state: NotificationFilterState
|
||||
|
||||
@Published var deepl_api_key: String {
|
||||
@StringSetting(key: "translation_service", default_value: .none)
|
||||
var translation_service: TranslationService
|
||||
|
||||
@StringSetting(key: "deepl_plan", default_value: .free)
|
||||
var deepl_plan: DeepLPlan
|
||||
|
||||
var deepl_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if deepl_api_key == "" {
|
||||
@@ -226,31 +157,14 @@ class UserSettingsStore: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_server: LibreTranslateServer {
|
||||
didSet {
|
||||
if oldValue == libretranslate_server {
|
||||
return
|
||||
}
|
||||
@StringSetting(key: "libretranslate_server", default_value: .terraprint)
|
||||
var libretranslate_server: LibreTranslateServer
|
||||
|
||||
@Setting(key: "libretranslate_url", default_value: "")
|
||||
var libretranslate_url: String
|
||||
|
||||
UserDefaults.standard.set(libretranslate_server.rawValue, forKey: "libretranslate_server")
|
||||
|
||||
libretranslate_api_key = ""
|
||||
|
||||
if libretranslate_server == .custom {
|
||||
libretranslate_url = ""
|
||||
} else {
|
||||
libretranslate_url = libretranslate_server.model.url!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_url: String {
|
||||
didSet {
|
||||
UserDefaults.standard.set(libretranslate_url, forKey: "libretranslate_url")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var libretranslate_api_key: String {
|
||||
@Setting(key: "libretranslate_api_key", default_value: "")
|
||||
var libretranslate_api_key: String {
|
||||
didSet {
|
||||
do {
|
||||
if libretranslate_api_key == "" {
|
||||
@@ -263,71 +177,8 @@ class UserSettingsStore: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var disable_animation: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(disable_animation, forKey: "disable_animation")
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
// TODO: pubkey-scoped settings
|
||||
let pubkey = ""
|
||||
self.default_wallet = get_default_wallet(pubkey)
|
||||
show_wallet_selector = should_show_wallet_selector(pubkey)
|
||||
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
|
||||
|
||||
default_media_uploader = get_media_uploader(pubkey)
|
||||
|
||||
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
||||
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
||||
zap_notification = UserDefaults.standard.object(forKey: "zap_notification") as? Bool ?? true
|
||||
mention_notification = UserDefaults.standard.object(forKey: "mention_notification") as? Bool ?? true
|
||||
repost_notification = UserDefaults.standard.object(forKey: "repost_notification") as? Bool ?? true
|
||||
like_notification = UserDefaults.standard.object(forKey: "like_notification") as? Bool ?? true
|
||||
dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true
|
||||
notification_indicators = UserDefaults.standard.object(forKey: "notification_indicators") as? Int ?? NewEventsBits.all.rawValue
|
||||
notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") as? Bool ?? false
|
||||
translate_dms = UserDefaults.standard.object(forKey: "translate_dms") as? Bool ?? false
|
||||
truncate_timeline_text = UserDefaults.standard.object(forKey: "truncate_timeline_text") as? Bool ?? false
|
||||
truncate_mention_text = UserDefaults.standard.object(forKey: "truncate_mention_text") as? Bool ?? false
|
||||
disable_animation = should_disable_image_animation()
|
||||
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
|
||||
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
|
||||
|
||||
// Note from @tyiu:
|
||||
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
||||
// Instead of throwing all Damus users onto feature immediately, allow for discovery of feature organically.
|
||||
// Also, we are connecting to servers listed as mirrors on the official LibreTranslate GitHub README that do not require API keys.
|
||||
// However, we have not asked them for permission to use, so we're trying to be good neighbors for now.
|
||||
// Opportunity: spin up dedicated trusted LibreTranslate server that requires an API key for any access (or higher rate limit access).
|
||||
if let translation_service = get_translation_service(pubkey) {
|
||||
self.translation_service = translation_service
|
||||
} else {
|
||||
self.translation_service = .none
|
||||
}
|
||||
|
||||
if let libretranslate_server = get_libretranslate_server(pubkey) {
|
||||
self.libretranslate_server = libretranslate_server
|
||||
self.libretranslate_url = get_libretranslate_url(pubkey, server: libretranslate_server) ?? ""
|
||||
} else {
|
||||
// Choose a random server to distribute load.
|
||||
libretranslate_server = .allCases.filter { $0 != .custom }.randomElement()!
|
||||
libretranslate_url = ""
|
||||
}
|
||||
|
||||
do {
|
||||
libretranslate_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
|
||||
} catch {
|
||||
libretranslate_api_key = ""
|
||||
}
|
||||
|
||||
if let deepl_plan = get_deepl_plan(pubkey) {
|
||||
self.deepl_plan = deepl_plan
|
||||
} else {
|
||||
self.deepl_plan = .free
|
||||
}
|
||||
|
||||
do {
|
||||
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||
} catch {
|
||||
@@ -374,3 +225,79 @@ struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
|
||||
var accessGroup: String? = nil
|
||||
var accountName = "deepl_apikey"
|
||||
}
|
||||
|
||||
func should_show_wallet_selector(_ pubkey: String) -> Bool {
|
||||
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
||||
}
|
||||
|
||||
func pk_setting_key(_ pubkey: String, key: String) -> String {
|
||||
return "\(pubkey)_\(key)"
|
||||
}
|
||||
|
||||
func default_zap_setting_key(pubkey: String) -> String {
|
||||
return pk_setting_key(pubkey, key: "default_zap_amount")
|
||||
}
|
||||
|
||||
func set_default_zap_amount(pubkey: String, amount: Int) {
|
||||
let key = default_zap_setting_key(pubkey: pubkey)
|
||||
UserDefaults.standard.setValue(amount, forKey: key)
|
||||
}
|
||||
|
||||
let fallback_zap_amount = 1000
|
||||
|
||||
func get_default_zap_amount(pubkey: String) -> Int {
|
||||
let key = default_zap_setting_key(pubkey: pubkey)
|
||||
let amt = UserDefaults.standard.integer(forKey: key)
|
||||
if amt == 0 {
|
||||
return fallback_zap_amount
|
||||
}
|
||||
return amt
|
||||
}
|
||||
|
||||
func should_disable_image_animation() -> Bool {
|
||||
return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool)
|
||||
?? UIAccessibility.isReduceMotionEnabled
|
||||
}
|
||||
|
||||
func get_default_wallet(_ pubkey: String) -> Wallet {
|
||||
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
|
||||
let default_wallet = Wallet(rawValue: defaultWalletName)
|
||||
{
|
||||
return default_wallet
|
||||
} else {
|
||||
return .system_default_wallet
|
||||
}
|
||||
}
|
||||
|
||||
func get_media_uploader(_ pubkey: String) -> MediaUploader {
|
||||
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
|
||||
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
|
||||
return defaultMediaUploader
|
||||
} else {
|
||||
return .nostrBuild
|
||||
}
|
||||
}
|
||||
|
||||
private func get_translation_service(_ pubkey: String) -> TranslationService? {
|
||||
guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return TranslationService(rawValue: translation_service)
|
||||
}
|
||||
|
||||
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
|
||||
if let url = server.model.url {
|
||||
return url
|
||||
}
|
||||
|
||||
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
|
||||
}
|
||||
|
||||
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
|
||||
guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return LibreTranslateServer(rawValue: server_name)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum Wallet: String, CaseIterable, Identifiable {
|
||||
enum Wallet: String, CaseIterable, Identifiable, StringCodable {
|
||||
var id: String { self.rawValue }
|
||||
|
||||
struct Model: Identifiable, Hashable {
|
||||
@@ -20,6 +20,17 @@ enum Wallet: String, CaseIterable, Identifiable {
|
||||
var image: String
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
init?(from string: String) {
|
||||
guard let w = Wallet(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
self = w
|
||||
}
|
||||
|
||||
// New url prefixes needed to be added to LSApplicationQueriesSchemes
|
||||
case system_default_wallet
|
||||
case strike
|
||||
|
||||
@@ -22,7 +22,7 @@ class ZapsModel: ObservableObject {
|
||||
}
|
||||
|
||||
func subscribe() {
|
||||
var filter = NostrFilter.filter_kinds([9735])
|
||||
var filter = NostrFilter.filter_kinds([NostrKind.zap.rawValue])
|
||||
switch target {
|
||||
case .profile(let profile_id):
|
||||
filter.pubkeys = [profile_id]
|
||||
|
||||
@@ -52,6 +52,11 @@ class Profile: Codable {
|
||||
set_val(key, val)
|
||||
}
|
||||
|
||||
var reactions: Bool? {
|
||||
get { return get_val("reactions"); }
|
||||
set(s) { set_val("reactions", s) }
|
||||
}
|
||||
|
||||
var deleted: Bool? {
|
||||
get { return get_val("deleted"); }
|
||||
set(s) { set_val("deleted", s) }
|
||||
|
||||
@@ -560,7 +560,7 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
||||
return ev
|
||||
}
|
||||
|
||||
func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEvent? {
|
||||
func make_metadata_event(keypair: Keypair, metadata: Profile) -> NostrEvent? {
|
||||
guard let privkey = keypair.privkey else {
|
||||
return nil
|
||||
}
|
||||
@@ -720,11 +720,34 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
|
||||
return ev
|
||||
}
|
||||
|
||||
func uniq<T: Hashable>(_ xs: [T]) -> [T] {
|
||||
var s = Set<T>()
|
||||
var ys: [T] = []
|
||||
|
||||
for x in xs {
|
||||
if s.contains(x) {
|
||||
continue
|
||||
}
|
||||
s.insert(x)
|
||||
ys.append(x)
|
||||
}
|
||||
|
||||
return ys
|
||||
}
|
||||
|
||||
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []
|
||||
|
||||
ids.append(ReferencedId(ref_id: from.id, relay_id: nil, key: "e"))
|
||||
ids.append(contentsOf: from.referenced_pubkeys.filter { $0.ref_id != our_pubkey })
|
||||
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey }))
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||
var ids: [ReferencedId] = []
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ struct NostrFilter: Codable, Equatable {
|
||||
}
|
||||
|
||||
public static var filter_text: NostrFilter {
|
||||
return filter_kinds([1])
|
||||
return filter_kinds([NostrKind.text.rawValue])
|
||||
}
|
||||
|
||||
public static func filter_ids(_ ids: [String]) -> NostrFilter {
|
||||
@@ -49,11 +49,11 @@ struct NostrFilter: Codable, Equatable {
|
||||
}
|
||||
|
||||
public static var filter_profiles: NostrFilter {
|
||||
return filter_kinds([0])
|
||||
return filter_kinds([NostrKind.metadata.rawValue])
|
||||
}
|
||||
|
||||
public static var filter_contacts: NostrFilter {
|
||||
return filter_kinds([3])
|
||||
return filter_kinds([NostrKind.contacts.rawValue])
|
||||
}
|
||||
|
||||
public static func filter_authors(_ authors: [String]) -> NostrFilter {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// NostrMetadata.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2022-05-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
struct NostrMetadata: Codable {
|
||||
let display_name: String?
|
||||
let name: String?
|
||||
let about: String?
|
||||
let website: String?
|
||||
let nip05: String?
|
||||
let picture: String?
|
||||
let banner: String?
|
||||
let lud06: String?
|
||||
let lud16: String?
|
||||
}
|
||||
|
||||
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
|
||||
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: model.profile_image, banner: nil, lud06: nil, lud16: nil)
|
||||
}
|
||||
@@ -35,7 +35,9 @@ let custom_hashtags: [String: CustomHashtag] = [
|
||||
"coffeechain": CustomHashtag.coffee,
|
||||
"plebchain": CustomHashtag.plebchain,
|
||||
"zap": CustomHashtag.zap,
|
||||
"zaps": CustomHashtag.zap,
|
||||
"zapathon": CustomHashtag.zap,
|
||||
"onlyzaps": CustomHashtag.zap,
|
||||
]
|
||||
|
||||
func hashtag_str(_ htag: String) -> CompatibleText {
|
||||
|
||||
@@ -113,6 +113,9 @@ extension Notification.Name {
|
||||
static var local_notification: Notification.Name {
|
||||
return Notification.Name("local_notification")
|
||||
}
|
||||
static var onlyzaps_mode: Notification.Name {
|
||||
return Notification.Name("hide_reactions")
|
||||
}
|
||||
}
|
||||
|
||||
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
||||
|
||||
13
damus/Util/StringCodable.swift
Normal file
13
damus/Util/StringCodable.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// StringCodable.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol StringCodable {
|
||||
init?(from string: String)
|
||||
func to_string() -> String
|
||||
}
|
||||
@@ -52,6 +52,10 @@ struct Zap {
|
||||
public let is_anon: Bool
|
||||
public let private_request: NostrEvent?
|
||||
|
||||
var request_ev: NostrEvent {
|
||||
return private_request ?? self.request.ev
|
||||
}
|
||||
|
||||
public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? {
|
||||
/// Make sure that we only create a zap event if it is authorized by the profile or event
|
||||
guard zapper == zap_ev.pubkey else {
|
||||
|
||||
@@ -30,6 +30,7 @@ struct EventActionBar: View {
|
||||
@State var show_share_action: Bool = false
|
||||
|
||||
@ObservedObject var bar: ActionBarModel
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@@ -38,12 +39,21 @@ struct EventActionBar: View {
|
||||
self.event = event
|
||||
self.test_lnurl = test_lnurl
|
||||
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
|
||||
_settings = ObservedObject(wrappedValue: damus_state.settings)
|
||||
}
|
||||
|
||||
var lnurl: String? {
|
||||
test_lnurl ?? damus_state.profiles.lookup(id: event.pubkey)?.lnurl
|
||||
}
|
||||
|
||||
var show_like: Bool {
|
||||
if settings.onlyzaps_mode {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if damus_state.keypair.privkey != nil {
|
||||
@@ -72,22 +82,25 @@ struct EventActionBar: View {
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
LikeButton(liked: bar.liked) {
|
||||
if bar.liked {
|
||||
notify(.delete, bar.our_like)
|
||||
} else {
|
||||
send_like()
|
||||
|
||||
if show_like {
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
LikeButton(liked: bar.liked) {
|
||||
if bar.liked {
|
||||
notify(.delete, bar.our_like)
|
||||
} else {
|
||||
send_like()
|
||||
}
|
||||
}
|
||||
|
||||
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.nip05_colorized(gradient: bar.liked)
|
||||
}
|
||||
|
||||
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.nip05_colorized(gradient: bar.liked)
|
||||
}
|
||||
|
||||
|
||||
if let lnurl = self.lnurl {
|
||||
Spacer()
|
||||
ZapButton(damus_state: damus_state, event: event, lnurl: lnurl, bar: bar)
|
||||
@@ -137,15 +150,7 @@ struct EventActionBar: View {
|
||||
}
|
||||
|
||||
func send_boost() {
|
||||
guard let privkey = self.damus_state.keypair.privkey else {
|
||||
return
|
||||
}
|
||||
|
||||
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
|
||||
|
||||
self.bar.our_boost = boost
|
||||
|
||||
notify(.boost, boost)
|
||||
notify(.boost, self.event)
|
||||
}
|
||||
|
||||
func send_like() {
|
||||
|
||||
@@ -32,7 +32,7 @@ struct EventDetailBar: View {
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
if bar.likes > 0 {
|
||||
if bar.likes > 0 && !state.settings.onlyzaps_mode {
|
||||
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
|
||||
let noun = Text(verbatim: "\(reactionsCountString(bar.likes))").foregroundColor(.gray)
|
||||
Text("\(Text("\(bar.likes)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
|
||||
|
||||
@@ -89,10 +89,22 @@ extension NSMutableData {
|
||||
}
|
||||
}
|
||||
|
||||
enum MediaUploader: String, CaseIterable, Identifiable {
|
||||
enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
|
||||
var id: String { self.rawValue }
|
||||
case nostrBuild
|
||||
case nostrImg
|
||||
|
||||
init?(from string: String) {
|
||||
guard let mu = MediaUploader(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = mu
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
var nameParam: String {
|
||||
switch self {
|
||||
|
||||
44
damus/Views/Buttons/FriendsButton.swift
Normal file
44
damus/Views/Buttons/FriendsButton.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// FriendsButton.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FriendsButton: View {
|
||||
@Binding var filter: FriendFilter
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
switch self.filter {
|
||||
case .all:
|
||||
self.filter = .friends
|
||||
case .friends:
|
||||
self.filter = .all
|
||||
}
|
||||
}) {
|
||||
if filter == .friends {
|
||||
LINEAR_GRADIENT
|
||||
.mask(Image(systemName: "person.2.fill")
|
||||
.resizable()
|
||||
).frame(width: 30, height: 20)
|
||||
} else {
|
||||
Image(systemName: "person.2.fill")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 20)
|
||||
.foregroundColor(DamusColors.adaptableGrey)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
struct FriendsButton_Previews: PreviewProvider {
|
||||
@State static var enabled: FriendFilter = .all
|
||||
|
||||
static var previews: some View {
|
||||
FriendsButton(filter: $enabled)
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ struct ConfigView: View {
|
||||
}
|
||||
|
||||
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
|
||||
IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
|
||||
IconLabel(NSLocalizedString("Notifications", comment: "Section header for Damus notifications"), img_name: "bell.fill", color: .blue)
|
||||
}
|
||||
|
||||
NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
|
||||
@@ -144,14 +144,15 @@ struct ConfigView_Previews: PreviewProvider {
|
||||
|
||||
|
||||
func handle_string_amount(new_value: String) -> Int? {
|
||||
let digits = Set("0123456789")
|
||||
let filtered = new_value.filter { digits.contains($0) }
|
||||
let filtered = new_value.filter {
|
||||
$0.isNumber
|
||||
}
|
||||
|
||||
if filtered == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let amt = Int(filtered) else {
|
||||
guard let amt = NumberFormatter().number(from: filtered) as? Int else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ struct DirectMessagesView: View {
|
||||
|
||||
@State var dm_type: DMType = .friend
|
||||
@ObservedObject var model: DirectMessagesModel
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
func MainContent(requests: Bool) -> some View {
|
||||
ScrollView {
|
||||
@@ -29,12 +30,9 @@ struct DirectMessagesView: View {
|
||||
EmptyTimelineView()
|
||||
} else {
|
||||
let dms = requests ? model.message_requests : model.friend_dms
|
||||
ForEach(dms, id: \.pubkey) { tup in
|
||||
MaybeEvent(tup)
|
||||
ForEach(dms, id: \.pubkey) { dm in
|
||||
MaybeEvent(dm)
|
||||
.padding(.top, 10)
|
||||
|
||||
Divider()
|
||||
.padding([.top], 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,11 +50,15 @@ struct DirectMessagesView: View {
|
||||
|
||||
func MaybeEvent(_ model: DirectMessageModel) -> some View {
|
||||
Group {
|
||||
if let ev = model.events.last {
|
||||
let ok = damus_state.settings.friend_filter.filter(contacts: damus_state.contacts, pubkey: model.pubkey)
|
||||
if ok, let ev = model.events.last {
|
||||
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
||||
.onTapGesture {
|
||||
self.model.open_dm_by_model(model)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding([.top], 10)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
@@ -84,10 +86,28 @@ struct DirectMessagesView: View {
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if would_filter_non_friends_from_dms(contacts: damus_state.contacts, dms: self.model.dms) {
|
||||
|
||||
FriendsButton(filter: $settings.friend_filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for view of DMs, where DM is an English abbreviation for Direct Message."))
|
||||
}
|
||||
}
|
||||
|
||||
func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool {
|
||||
for dm in dms {
|
||||
if !FriendFilter.friends.filter(contacts: contacts, pubkey: dm.pubkey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
struct DirectMessagesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ev = NostrEvent(content: "encrypted stuff",
|
||||
@@ -95,6 +115,6 @@ struct DirectMessagesView_Previews: PreviewProvider {
|
||||
kind: 4,
|
||||
tags: [])
|
||||
let ds = test_damus_state()
|
||||
DirectMessagesView(damus_state: ds, model: ds.dms)
|
||||
DirectMessagesView(damus_state: ds, model: ds.dms, settings: ds.settings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,10 +48,6 @@ struct BuilderEventView: View {
|
||||
return
|
||||
}
|
||||
|
||||
guard nostr_event.known_kind == .text else {
|
||||
return
|
||||
}
|
||||
|
||||
if event != nil {
|
||||
return
|
||||
}
|
||||
@@ -78,8 +74,8 @@ struct BuilderEventView: View {
|
||||
let thread = ThreadModel(event: ev, damus_state: damus)
|
||||
let dest = ThreadView(state: damus, thread: thread)
|
||||
NavigationLink(destination: dest) {
|
||||
EmbeddedEventView(damus_state: damus, event: event)
|
||||
.padding(8)
|
||||
EventView(damus: damus, event: event, options: .embedded)
|
||||
.padding([.top, .bottom], 8)
|
||||
}.buttonStyle(.plain)
|
||||
} else {
|
||||
ProgressView().padding()
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//
|
||||
// EmbeddedEventView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-01-23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct EmbeddedEventView: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
|
||||
var pubkey: String {
|
||||
event.pubkey
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
HStack {
|
||||
EventProfile(damus_state: damus_state, pubkey: pubkey, profile: profile, size: .small)
|
||||
|
||||
Spacer()
|
||||
|
||||
EventMenuContext(event: event, keypair: damus_state.keypair, target_pubkey: event.pubkey, bookmarks: damus_state.bookmarks, muted_threads: damus_state.muted_threads)
|
||||
.padding([.bottom], 4)
|
||||
|
||||
}
|
||||
.minimumScaleFactor(0.75)
|
||||
.lineLimit(1)
|
||||
|
||||
if event_is_reply(event, privkey: damus_state.keypair.privkey) {
|
||||
ReplyDescription(event: event, profiles: damus_state.profiles)
|
||||
}
|
||||
|
||||
EventBody(damus_state: damus_state, event: event, size: .small, options: [.truncate_content])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmbeddedEventView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EmbeddedEventView(damus_state: test_damus_state(), event: test_event)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,13 @@ import SwiftUI
|
||||
struct MutedEventView: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let scroller: ScrollViewProxy?
|
||||
|
||||
let selected: Bool
|
||||
@State var shown: Bool
|
||||
|
||||
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
|
||||
init(damus_state: DamusState, event: NostrEvent, selected: Bool) {
|
||||
self.damus_state = damus_state
|
||||
self.event = event
|
||||
self.scroller = scroller
|
||||
self.selected = selected
|
||||
self._shown = State(initialValue: should_show_event(contacts: damus_state.contacts, ev: event))
|
||||
}
|
||||
@@ -89,7 +87,7 @@ struct MutedEventView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
|
||||
MutedEventView(damus_state: test_damus_state(), event: test_event, scroller: nil, selected: false)
|
||||
MutedEventView(damus_state: test_damus_state(), event: test_event, selected: false)
|
||||
.frame(width: .infinity, height: 50)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EventViewOptions: OptionSet {
|
||||
let rawValue: UInt8
|
||||
let rawValue: UInt32
|
||||
|
||||
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
||||
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
||||
static let no_images = EventViewOptions(rawValue: 1 << 2)
|
||||
@@ -16,6 +17,10 @@ struct EventViewOptions: OptionSet {
|
||||
static let truncate_content = EventViewOptions(rawValue: 1 << 4)
|
||||
static let pad_content = EventViewOptions(rawValue: 1 << 5)
|
||||
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
||||
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
|
||||
static let nested = EventViewOptions(rawValue: 1 << 8)
|
||||
|
||||
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
|
||||
}
|
||||
|
||||
struct TextEvent: View {
|
||||
@@ -43,7 +48,7 @@ struct TextEvent: View {
|
||||
}
|
||||
|
||||
func Pfp(is_anon: Bool) -> some View {
|
||||
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
|
||||
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey, size: options.contains(.small_pfp) ? eventview_pfp_size(.small) : PFP_SIZE )
|
||||
}
|
||||
|
||||
func TopPart(is_anon: Bool) -> some View {
|
||||
@@ -82,7 +87,7 @@ struct TextEvent: View {
|
||||
|
||||
EvBody(options: self.options.union(.pad_content))
|
||||
|
||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
||||
if let mention = get_mention() {
|
||||
Mention(mention)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
@@ -135,6 +140,14 @@ struct TextEvent: View {
|
||||
return Rectangle().frame(height: 2).opacity(0)
|
||||
}
|
||||
|
||||
func get_mention() -> Mention? {
|
||||
if self.options.contains(.nested) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return first_eref_mention(ev: event, privkey: damus.keypair.privkey)
|
||||
}
|
||||
|
||||
var ThreadedStyle: some View {
|
||||
HStack(alignment: .top) {
|
||||
|
||||
@@ -151,7 +164,7 @@ struct TextEvent: View {
|
||||
ReplyPart
|
||||
EvBody(options: self.options)
|
||||
|
||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
||||
if let mention = get_mention() {
|
||||
Mention(mention)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,90 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum NotificationFilterState: String {
|
||||
enum FriendFilter: String, StringCodable {
|
||||
case all
|
||||
case friends
|
||||
|
||||
init?(from string: String) {
|
||||
guard let ff = FriendFilter(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = ff
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
self.rawValue
|
||||
}
|
||||
|
||||
func filter(contacts: Contacts, pubkey: String) -> Bool {
|
||||
switch self {
|
||||
case .all:
|
||||
return true
|
||||
case .friends:
|
||||
return contacts.is_in_friendosphere(pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationFilter: ObservableObject, Equatable {
|
||||
@Published var state: NotificationFilterState
|
||||
@Published var fine_filter: FriendFilter
|
||||
|
||||
static func == (lhs: NotificationFilter, rhs: NotificationFilter) -> Bool {
|
||||
return lhs.state == rhs.state && lhs.fine_filter == rhs.fine_filter
|
||||
}
|
||||
|
||||
init() {
|
||||
self.state = .all
|
||||
self.fine_filter = .all
|
||||
}
|
||||
|
||||
init(state: NotificationFilterState, fine_filter: FriendFilter) {
|
||||
self.state = state
|
||||
self.fine_filter = fine_filter
|
||||
}
|
||||
|
||||
func toggle_fine_filter() {
|
||||
switch self.fine_filter {
|
||||
case .all:
|
||||
self.fine_filter = .friends
|
||||
case .friends:
|
||||
self.fine_filter = .all
|
||||
}
|
||||
}
|
||||
|
||||
func filter(contacts: Contacts, items: [NotificationItem]) -> [NotificationItem] {
|
||||
|
||||
return items.reduce(into: []) { acc, item in
|
||||
if !self.state.filter(item) {
|
||||
return
|
||||
}
|
||||
|
||||
if let item = item.filter({ self.fine_filter.filter(contacts: contacts, pubkey: $0.pubkey) }) {
|
||||
acc.append(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NotificationFilterState: String, StringCodable {
|
||||
case all
|
||||
case zaps
|
||||
case replies
|
||||
|
||||
init?(from string: String) {
|
||||
guard let val = NotificationFilterState(rawValue: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self = val
|
||||
}
|
||||
|
||||
func to_string() -> String {
|
||||
self.rawValue
|
||||
}
|
||||
|
||||
func is_other( item: NotificationItem) -> Bool {
|
||||
return item.is_zap == nil && item.is_reply == nil
|
||||
}
|
||||
@@ -31,7 +110,7 @@ enum NotificationFilterState: String {
|
||||
struct NotificationsView: View {
|
||||
let state: DamusState
|
||||
@ObservedObject var notifications: NotificationsModel
|
||||
@State var filter_state: NotificationFilterState = .all
|
||||
@StateObject var filter_state: NotificationFilter = NotificationFilter()
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@@ -44,28 +123,54 @@ struct NotificationsView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $filter_state) {
|
||||
TabView(selection: $filter_state.state) {
|
||||
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
||||
mystery
|
||||
|
||||
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
||||
NotificationTab(NotificationFilterState.all)
|
||||
.tag(NotificationFilterState.all)
|
||||
NotificationTab(
|
||||
NotificationFilter(
|
||||
state: .all,
|
||||
fine_filter: filter_state.fine_filter
|
||||
)
|
||||
)
|
||||
.tag(NotificationFilterState.all)
|
||||
|
||||
NotificationTab(NotificationFilterState.zaps)
|
||||
.tag(NotificationFilterState.zaps)
|
||||
NotificationTab(
|
||||
NotificationFilter(
|
||||
state: .zaps,
|
||||
fine_filter: filter_state.fine_filter
|
||||
)
|
||||
)
|
||||
.tag(NotificationFilterState.zaps)
|
||||
|
||||
NotificationTab(NotificationFilterState.replies)
|
||||
.tag(NotificationFilterState.replies)
|
||||
NotificationTab(
|
||||
NotificationFilter(
|
||||
state: .replies,
|
||||
fine_filter: filter_state.fine_filter
|
||||
)
|
||||
)
|
||||
.tag(NotificationFilterState.replies)
|
||||
}
|
||||
.onChange(of: filter_state) { val in
|
||||
save_notification_filter_state(pubkey: state.pubkey, state: val)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
if would_filter_non_friends_from_notifications(contacts: state.contacts, state: self.filter_state.state, items: self.notifications.notifications) {
|
||||
FriendsButton(filter: $filter_state.fine_filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: filter_state.fine_filter) { val in
|
||||
state.settings.friend_filter = val
|
||||
}
|
||||
.onChange(of: filter_state.state) { val in
|
||||
state.settings.notification_state = val
|
||||
}
|
||||
.onAppear {
|
||||
self.filter_state = load_notification_filter_state(pubkey: state.pubkey)
|
||||
self.filter_state.fine_filter = state.settings.friend_filter
|
||||
self.filter_state.state = state.settings.notification_state
|
||||
}
|
||||
.safeAreaInset(edge: .top, spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
CustomPicker(selection: $filter_state, content: {
|
||||
CustomPicker(selection: $filter_state.state, content: {
|
||||
Text("All", comment: "Label for filter for all notifications.")
|
||||
.tag(NotificationFilterState.all)
|
||||
|
||||
@@ -83,14 +188,14 @@ struct NotificationsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationTab(_ filter: NotificationFilterState) -> some View {
|
||||
func NotificationTab(_ filter: NotificationFilter) -> some View {
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading) {
|
||||
Color.white.opacity(0)
|
||||
.id("startblock")
|
||||
.frame(height: 5)
|
||||
ForEach(notifications.notifications.filter(filter.filter), id: \.id) { item in
|
||||
ForEach(filter.filter(contacts: state.contacts, items: notifications.notifications), id: \.id) { item in
|
||||
NotificationItemView(state: state, item: item)
|
||||
}
|
||||
}
|
||||
@@ -116,30 +221,22 @@ struct NotificationsView: View {
|
||||
|
||||
struct NotificationsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationsView(state: test_damus_state(), notifications: NotificationsModel(), filter_state: NotificationFilterState.all)
|
||||
NotificationsView(state: test_damus_state(), notifications: NotificationsModel(), filter_state: NotificationFilter())
|
||||
}
|
||||
}
|
||||
|
||||
func notification_filter_state_key(pubkey: String) -> String {
|
||||
return pk_setting_key(pubkey, key: "notification_filter_state")
|
||||
}
|
||||
|
||||
func load_notification_filter_state(pubkey: String) -> NotificationFilterState {
|
||||
let key = notification_filter_state_key(pubkey: pubkey)
|
||||
|
||||
guard let state_str = UserDefaults.standard.string(forKey: key) else {
|
||||
return .all
|
||||
func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
|
||||
for item in items {
|
||||
// this is only valid depending on which tab we're looking at
|
||||
if !state.filter(item) {
|
||||
continue
|
||||
}
|
||||
|
||||
if item.would_filter({ ev in FriendFilter.friends.filter(contacts: contacts, pubkey: ev.pubkey) }) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
guard let state = NotificationFilterState(rawValue: state_str) else {
|
||||
return .all
|
||||
}
|
||||
|
||||
return state
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func save_notification_filter_state(pubkey: String, state: NotificationFilterState) {
|
||||
let key = notification_filter_state_key(pubkey: pubkey)
|
||||
UserDefaults.standard.set(state.rawValue, forKey: key)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ParicipantsView.swift
|
||||
// ParticipantsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Joel Klabo on 1/18/23.
|
||||
@@ -15,6 +15,23 @@ enum NostrPostResult {
|
||||
|
||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||
|
||||
enum PostAction {
|
||||
case replying_to(NostrEvent)
|
||||
case quoting(NostrEvent)
|
||||
case posting
|
||||
|
||||
var ev: NostrEvent? {
|
||||
switch self {
|
||||
case .replying_to(let ev):
|
||||
return ev
|
||||
case .quoting(let ev):
|
||||
return ev
|
||||
case .posting:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PostView: View {
|
||||
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
||||
@FocusState var focus: Bool
|
||||
@@ -31,7 +48,7 @@ struct PostView: View {
|
||||
|
||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||
|
||||
let replying_to: NostrEvent?
|
||||
let action: PostAction
|
||||
let damus_state: DamusState
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@@ -51,7 +68,8 @@ struct PostView: View {
|
||||
|
||||
func send_post() {
|
||||
var kind: NostrKind = .text
|
||||
if replying_to?.known_kind == .chat {
|
||||
|
||||
if case .replying_to(let ev) = action, ev.known_kind == .chat {
|
||||
kind = .chat
|
||||
}
|
||||
|
||||
@@ -61,25 +79,21 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
|
||||
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
|
||||
|
||||
content.append(" " + imagesString + " ")
|
||||
|
||||
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) {
|
||||
content.append(" nostr:" + id)
|
||||
}
|
||||
|
||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||
|
||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||
|
||||
if let replying_to {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||
uploadedMedias = []
|
||||
damus_state.drafts.medias = []
|
||||
}
|
||||
|
||||
clear_draft()
|
||||
|
||||
dismiss()
|
||||
}
|
||||
@@ -131,17 +145,67 @@ struct PostView: View {
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
var isEmpty: Bool {
|
||||
self.uploadedMedias.count == 0 &&
|
||||
self.post.mutableString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
}
|
||||
|
||||
func clear_draft() {
|
||||
switch action {
|
||||
case .replying_to(let replying_to):
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
case .quoting(let quoting):
|
||||
damus_state.drafts.quotes.removeValue(forKey: quoting)
|
||||
case .posting:
|
||||
damus_state.drafts.post = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func load_draft() {
|
||||
guard let draft = load_draft_for_post(drafts: self.damus_state.drafts, action: self.action) else {
|
||||
self.post = NSMutableAttributedString("")
|
||||
self.uploadedMedias = []
|
||||
return
|
||||
}
|
||||
|
||||
self.uploadedMedias = draft.media
|
||||
self.post = draft.content
|
||||
}
|
||||
|
||||
func post_changed(post: NSMutableAttributedString, media: [UploadedMedia]) {
|
||||
switch action {
|
||||
case .replying_to(let ev):
|
||||
if let draft = damus_state.drafts.replies[ev] {
|
||||
draft.content = post
|
||||
draft.media = media
|
||||
} else {
|
||||
damus_state.drafts.replies[ev] = DraftArtifacts(content: post, media: media)
|
||||
}
|
||||
case .quoting(let ev):
|
||||
if let draft = damus_state.drafts.quotes[ev] {
|
||||
draft.content = post
|
||||
draft.media = media
|
||||
} else {
|
||||
damus_state.drafts.quotes[ev] = DraftArtifacts(content: post, media: media)
|
||||
}
|
||||
case .posting:
|
||||
if let draft = damus_state.drafts.post {
|
||||
draft.content = post
|
||||
draft.media = media
|
||||
} else {
|
||||
damus_state.drafts.post = DraftArtifacts(content: post, media: media)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var TextEntry: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
TextViewWrapper(attributedText: $post)
|
||||
.focused($focus)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
.onChange(of: post) { _ in
|
||||
if let replying_to {
|
||||
damus_state.drafts.replies[replying_to] = post
|
||||
} else {
|
||||
damus_state.drafts.post = post
|
||||
}
|
||||
.onChange(of: post) { p in
|
||||
post_changed(post: p, media: uploadedMedias)
|
||||
}
|
||||
|
||||
if post.string.isEmpty {
|
||||
@@ -207,6 +271,35 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var has_artifacts: Bool {
|
||||
if case .quoting = action {
|
||||
return true
|
||||
}
|
||||
return !uploadedMedias.isEmpty
|
||||
}
|
||||
|
||||
func Editor(deviceSize: GeometryProxy) -> some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
TextEntry
|
||||
}
|
||||
.frame(height: has_artifacts ? deviceSize.size.height*0.4 : deviceSize.size.height)
|
||||
.id("post")
|
||||
|
||||
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
||||
.onChange(of: uploadedMedias) { media in
|
||||
post_changed(post: post, media: media)
|
||||
}
|
||||
|
||||
if case .quoting(let ev) = action {
|
||||
BuilderEventView(damus: damus_state, event: ev)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { (deviceSize: GeometryProxy) in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
@@ -217,25 +310,11 @@ struct PostView: View {
|
||||
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
if let replying_to = replying_to {
|
||||
if case .replying_to(let replying_to) = self.action {
|
||||
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
TextEntry
|
||||
}
|
||||
.frame(height: uploadedMedias.isEmpty ? deviceSize.size.height*0.78 : deviceSize.size.height*0.2)
|
||||
.id("post")
|
||||
|
||||
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
||||
.onChange(of: uploadedMedias) { _ in
|
||||
damus_state.drafts.medias = uploadedMedias
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Editor(deviceSize: deviceSize)
|
||||
}
|
||||
.frame(maxHeight: searching == nil ? .infinity : 70)
|
||||
.onAppear {
|
||||
@@ -262,7 +341,7 @@ struct PostView: View {
|
||||
} onVideoPicked: { url in
|
||||
self.mediaToUpload = .video(url)
|
||||
}
|
||||
.alert("Are you sure you want to upload this image?", isPresented: $image_upload_confirm) {
|
||||
.alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
|
||||
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
|
||||
if let mediaToUpload {
|
||||
self.handle_upload(media: mediaToUpload)
|
||||
@@ -281,18 +360,17 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
if let replying_to {
|
||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||
load_draft()
|
||||
|
||||
switch action {
|
||||
case .replying_to(let replying_to):
|
||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||
originalReferences = references
|
||||
if damus_state.drafts.replies[replying_to] == nil {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||
}
|
||||
if let p = damus_state.drafts.replies[replying_to] {
|
||||
post = p
|
||||
}
|
||||
} else {
|
||||
post = damus_state.drafts.post
|
||||
uploadedMedias = damus_state.drafts.medias
|
||||
case .quoting(let quoting):
|
||||
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||
originalReferences = references
|
||||
case .posting:
|
||||
break
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
@@ -300,11 +378,8 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
||||
damus_state.drafts.medias = uploadedMedias
|
||||
if isEmpty {
|
||||
clear_draft()
|
||||
}
|
||||
}
|
||||
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
||||
@@ -342,7 +417,7 @@ func get_searching_string(_ post: String) -> String? {
|
||||
|
||||
struct PostView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PostView(replying_to: nil, damus_state: test_damus_state())
|
||||
PostView(action: .posting, damus_state: test_damus_state())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,9 +437,19 @@ struct PVImageCarouselView: View {
|
||||
.frame(width: media.count == 1 ? deviceWidth*0.8 : 250, height: media.count == 1 ? 400 : 250)
|
||||
.cornerRadius(10)
|
||||
.padding()
|
||||
.contextMenu {
|
||||
if let uploadedURL = media.first(where: { $0.representingImage == image })?.uploadedURL {
|
||||
Button(action: {
|
||||
UIPasteboard.general.string = uploadedURL.absoluteString
|
||||
}) {
|
||||
Label(NSLocalizedString("Copy URL", comment: "Label for button in context menu to copy URL of the selected uploaded media asset."), systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.padding(20)
|
||||
.shadow(radius: 5)
|
||||
.onTapGesture {
|
||||
if let index = media.map({$0.representingImage}).firstIndex(of: image) {
|
||||
media.remove(at: index)
|
||||
@@ -378,7 +463,6 @@ struct PVImageCarouselView: View {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate func getImage(media: MediaUpload) -> UIImage {
|
||||
var uiimage: UIImage = UIImage()
|
||||
if media.is_image {
|
||||
@@ -418,3 +502,15 @@ struct UploadedMedia: Equatable {
|
||||
let uploadedURL: URL
|
||||
let representingImage: UIImage
|
||||
}
|
||||
|
||||
|
||||
func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts? {
|
||||
switch action {
|
||||
case .replying_to(let ev):
|
||||
return drafts.replies[ev]
|
||||
case .quoting(let ev):
|
||||
return drafts.quotes[ev]
|
||||
case .posting:
|
||||
return drafts.post
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ struct EditMetadataView: View {
|
||||
@State var name: String
|
||||
@State var ln: String
|
||||
@State var website: String
|
||||
let profile: Profile?
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@@ -73,6 +74,7 @@ struct EditMetadataView: View {
|
||||
init (damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||
self.profile = data
|
||||
|
||||
_name = State(initialValue: data?.name ?? "")
|
||||
_display_name = State(initialValue: data?.display_name ?? "")
|
||||
@@ -85,27 +87,31 @@ struct EditMetadataView: View {
|
||||
}
|
||||
|
||||
func imageBorderColor() -> Color {
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
|
||||
func to_profile() -> Profile {
|
||||
let profile = self.profile ?? Profile()
|
||||
|
||||
profile.name = name
|
||||
profile.display_name = display_name
|
||||
profile.about = about
|
||||
profile.website = website
|
||||
profile.nip05 = nip05.isEmpty ? nil : nip05
|
||||
profile.picture = picture.isEmpty ? nil : picture
|
||||
profile.banner = banner.isEmpty ? nil : banner
|
||||
profile.lud06 = ln.contains("@") ? nil : ln
|
||||
profile.lud16 = ln.contains("@") ? ln : nil
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
func save() {
|
||||
let metadata = NostrMetadata(
|
||||
display_name: display_name,
|
||||
name: name,
|
||||
about: about,
|
||||
website: website,
|
||||
nip05: nip05.isEmpty ? nil : nip05,
|
||||
picture: picture.isEmpty ? nil : picture,
|
||||
banner: banner.isEmpty ? nil : banner,
|
||||
lud06: ln.contains("@") ? nil : ln,
|
||||
lud16: ln.contains("@") ? ln : nil
|
||||
);
|
||||
|
||||
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
|
||||
|
||||
if let metadata_ev = m_metadata_ev {
|
||||
damus_state.postbox.send(metadata_ev)
|
||||
let profile = to_profile()
|
||||
guard let metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
|
||||
return
|
||||
}
|
||||
damus_state.postbox.send(metadata_ev)
|
||||
}
|
||||
|
||||
func is_ln_valid(ln: String) -> Bool {
|
||||
@@ -39,8 +39,8 @@ struct EventProfileName: View {
|
||||
self.size = size
|
||||
}
|
||||
|
||||
var friend_icon: String? {
|
||||
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
|
||||
var friend_type: FriendType? {
|
||||
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
||||
}
|
||||
|
||||
var current_nip05: NIP05? {
|
||||
@@ -50,7 +50,15 @@ struct EventProfileName: View {
|
||||
var current_display_name: DisplayName {
|
||||
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
|
||||
}
|
||||
|
||||
|
||||
var onlyzapper: Bool {
|
||||
guard let profile else {
|
||||
return false
|
||||
}
|
||||
|
||||
return profile.reactions == false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 2) {
|
||||
switch current_display_name {
|
||||
@@ -71,10 +79,13 @@ struct EventProfileName: View {
|
||||
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: false, clickable: false)
|
||||
}
|
||||
|
||||
if let frend = friend_icon, current_nip05 == nil {
|
||||
Label("", systemImage: frend)
|
||||
.foregroundColor(.gray)
|
||||
.font(.footnote)
|
||||
if current_nip05 == nil, let frend = friend_type {
|
||||
FriendIcon(friend: frend)
|
||||
}
|
||||
|
||||
if onlyzapper {
|
||||
Image("zap-hashtag")
|
||||
.frame(width: 14, height: 14)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||
|
||||
39
damus/Views/Profile/FriendIcon.swift
Normal file
39
damus/Views/Profile/FriendIcon.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// FriendIcon.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-04-20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FriendIcon: View {
|
||||
let friend: FriendType
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch friend {
|
||||
case .friend:
|
||||
LINEAR_GRADIENT
|
||||
.mask(Image(systemName: "person.fill.checkmark")
|
||||
.resizable()
|
||||
).frame(width: 20, height: 14)
|
||||
case .fof:
|
||||
Image(systemName: "person.fill.and.arrow.left.and.arrow.right")
|
||||
.resizable()
|
||||
.frame(width: 21, height: 14)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FriendIcon_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
FriendIcon(friend: .friend)
|
||||
|
||||
FriendIcon(friend: .fof)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,20 @@ struct MaybeAnonPfpView: View {
|
||||
let state: DamusState
|
||||
let is_anon: Bool
|
||||
let pubkey: String
|
||||
let size: CGFloat
|
||||
|
||||
init(state: DamusState, event: NostrEvent, pubkey: String) {
|
||||
init(state: DamusState, event: NostrEvent, pubkey: String, size: CGFloat) {
|
||||
self.state = state
|
||||
self.is_anon = event_is_anonymous(ev: event)
|
||||
self.pubkey = pubkey
|
||||
self.size = size
|
||||
}
|
||||
|
||||
init(state: DamusState, is_anon: Bool, pubkey: String) {
|
||||
init(state: DamusState, is_anon: Bool, pubkey: String, size: CGFloat) {
|
||||
self.state = state
|
||||
self.is_anon = is_anon
|
||||
self.pubkey = pubkey
|
||||
self.size = size
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -29,10 +32,10 @@ struct MaybeAnonPfpView: View {
|
||||
if is_anon {
|
||||
Image(systemName: "person.fill.questionmark")
|
||||
.font(.largeTitle)
|
||||
.frame(width: PFP_SIZE, height: PFP_SIZE)
|
||||
.frame(width: size, height: size)
|
||||
} else {
|
||||
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: state.profiles)
|
||||
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +44,6 @@ struct MaybeAnonPfpView: View {
|
||||
|
||||
struct MaybeAnonPfpView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon")
|
||||
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon", size: PFP_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,18 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
func get_friend_icon(contacts: Contacts, pubkey: String, show_confirmed: Bool) -> String? {
|
||||
if !show_confirmed {
|
||||
return nil
|
||||
}
|
||||
|
||||
enum FriendType {
|
||||
case friend
|
||||
case fof
|
||||
}
|
||||
|
||||
func get_friend_type(contacts: Contacts, pubkey: String) -> FriendType? {
|
||||
if contacts.is_friend_or_self(pubkey) {
|
||||
return "person.fill.checkmark"
|
||||
return .friend
|
||||
}
|
||||
|
||||
if contacts.is_friend_of_friend(pubkey) {
|
||||
return "person.fill.and.arrow.left.and.arrow.right"
|
||||
return .fof
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -53,8 +54,8 @@ struct ProfileName: View {
|
||||
self.show_nip5_domain = show_nip5_domain
|
||||
}
|
||||
|
||||
var friend_icon: String? {
|
||||
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
|
||||
var friend_type: FriendType? {
|
||||
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
||||
}
|
||||
|
||||
var current_nip05: NIP05? {
|
||||
@@ -69,6 +70,14 @@ struct ProfileName: View {
|
||||
return prefix == "@" ? current_display_name.username : current_display_name.display_name
|
||||
}
|
||||
|
||||
var onlyzapper: Bool {
|
||||
guard let profile else {
|
||||
return false
|
||||
}
|
||||
|
||||
return profile.reactions == false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 2) {
|
||||
Text(verbatim: "\(prefix)\(name_choice)")
|
||||
@@ -77,9 +86,12 @@ struct ProfileName: View {
|
||||
if let nip05 = current_nip05 {
|
||||
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, clickable: true)
|
||||
}
|
||||
if let friend = friend_icon, current_nip05 == nil {
|
||||
Image(systemName: friend)
|
||||
.foregroundColor(.gray)
|
||||
if let friend = friend_type, current_nip05 == nil {
|
||||
FriendIcon(friend: friend)
|
||||
}
|
||||
if onlyzapper {
|
||||
Image("zap-hashtag")
|
||||
.frame(width: 14, height: 14)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||
|
||||
@@ -245,20 +245,33 @@ struct ProfileView: View {
|
||||
}
|
||||
|
||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||
Button(action: {
|
||||
let button_img = profile.reactions == false ? "bolt.brakesignal" : "bolt.circle"
|
||||
return Button(action: {
|
||||
if damus_state.settings.show_wallet_selector {
|
||||
showing_select_wallet = true
|
||||
} else {
|
||||
open_with_wallet(wallet: damus_state.settings.default_wallet.model, invoice: lnurl)
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "bolt.circle")
|
||||
Image(systemName: button_img)
|
||||
.profile_button_style(scheme: colorScheme)
|
||||
.contextMenu {
|
||||
Button {
|
||||
UIPasteboard.general.string = profile.lnurl ?? ""
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
|
||||
if profile.reactions == false {
|
||||
Text("OnlyZaps Enabled")
|
||||
}
|
||||
|
||||
if let addr = profile.lud16 {
|
||||
Button {
|
||||
UIPasteboard.general.string = addr
|
||||
} label: {
|
||||
Label(addr, systemImage: "doc.on.doc")
|
||||
}
|
||||
} else if let lnurl = profile.lnurl {
|
||||
Button {
|
||||
UIPasteboard.general.string = lnurl
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,3 +217,7 @@ struct SaveKeysView_Previews: PreviewProvider {
|
||||
SaveKeysView(account: model)
|
||||
}
|
||||
}
|
||||
|
||||
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
|
||||
return Profile(name: model.nick_name, display_name: model.real_name, about: model.about, picture: model.profile_image, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ struct AppearanceSettingsView: View {
|
||||
|
||||
|
||||
}
|
||||
.navigationTitle("Appearance")
|
||||
.navigationTitle(NSLocalizedString("Appearance", comment: "Navigation title for text and appearance settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ struct KeySettingsView: View {
|
||||
}
|
||||
|
||||
}
|
||||
.navigationTitle("Keys")
|
||||
.navigationTitle(NSLocalizedString("Keys", comment: "Navigation title for managing keys."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ struct TranslationSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Translation")
|
||||
.navigationTitle(NSLocalizedString("Translation", comment: "Navigation title for translation settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -14,17 +14,29 @@ struct ZapSettingsView: View {
|
||||
|
||||
@State var default_zap_amount: String
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
|
||||
init(pubkey: String, settings: UserSettingsStore) {
|
||||
self.pubkey = pubkey
|
||||
let zap_amt = get_default_zap_amount(pubkey: pubkey).map({ "\($0)" }) ?? "1000"
|
||||
let zap_amt = get_default_zap_amount(pubkey: pubkey).formatted()
|
||||
_default_zap_amount = State(initialValue: zap_amt)
|
||||
self._settings = ObservedObject(initialValue: settings)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Wallet") {
|
||||
Section(
|
||||
header: Text(NSLocalizedString("OnlyZaps", comment: "Section header for enabling OnlyZaps mode (hide reactions)")),
|
||||
footer: Text(NSLocalizedString("Hide all 🤙's", comment: "Section footer describing onlyzaps mode"))
|
||||
|
||||
) {
|
||||
Toggle(NSLocalizedString("Enable OnlyZaps mode", comment: "Setting toggle to hide reactions."), isOn: $settings.onlyzaps_mode)
|
||||
.toggleStyle(.switch)
|
||||
.onChange(of: settings.onlyzaps_mode) { newVal in
|
||||
notify(.onlyzaps_mode, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Wallet", comment: "Title for section in zap settings that controls the Lightning wallet selection.")) {
|
||||
|
||||
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
|
||||
Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
|
||||
@@ -36,23 +48,26 @@ struct ZapSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Zaps") {
|
||||
Section(NSLocalizedString("Zaps", comment: "Title for section in zap settings that controls general zap preferences.")) {
|
||||
Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
|
||||
Section("Default Zap Amount in sats") {
|
||||
TextField(String("1000"), text: $default_zap_amount)
|
||||
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Title for section in zap settings that controls the default zap amount in sats.")) {
|
||||
TextField(fallback_zap_amount.formatted(), text: $default_zap_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(default_zap_amount)) { newValue in
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.default_zap_amount = String(parsed)
|
||||
self.default_zap_amount = parsed.formatted()
|
||||
set_default_zap_amount(pubkey: self.pubkey, amount: parsed)
|
||||
} else {
|
||||
self.default_zap_amount = ""
|
||||
set_default_zap_amount(pubkey: self.pubkey, amount: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Zaps")
|
||||
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ struct ThreadView: View {
|
||||
ForEach(parent_events, id: \.id) { parent_event in
|
||||
MutedEventView(damus_state: state,
|
||||
event: parent_event,
|
||||
scroller: reader,
|
||||
selected: false)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
@@ -56,7 +55,6 @@ struct ThreadView: View {
|
||||
MutedEventView(
|
||||
damus_state: state,
|
||||
event: self.thread.event,
|
||||
scroller: reader,
|
||||
selected: true
|
||||
)
|
||||
.id(self.thread.event.id)
|
||||
@@ -65,7 +63,6 @@ struct ThreadView: View {
|
||||
MutedEventView(
|
||||
damus_state: state,
|
||||
event: child_event,
|
||||
scroller: nil,
|
||||
selected: false
|
||||
)
|
||||
.padding(.horizontal)
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ZapAmountItem: Identifiable, Hashable {
|
||||
}
|
||||
|
||||
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
|
||||
let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
|
||||
let def = get_default_zap_amount(pubkey: pubkey)
|
||||
return ZapAmountItem(amount: def, icon: "🤙")
|
||||
}
|
||||
|
||||
@@ -181,13 +181,17 @@ struct CustomizeZapView: View {
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
TextField(String("100000"), text: $custom_amount)
|
||||
// Use the selected sats amount as the placeholder text so that the UI is less confusing.
|
||||
// User can type in their custom amount, which hides the placeholder.
|
||||
TextField(selected_amount.amount.formatted(), text: $custom_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(custom_amount)) { newValue in
|
||||
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.custom_amount = String(parsed)
|
||||
self.custom_amount = parsed.formatted()
|
||||
self.custom_amount_sats = parsed
|
||||
} else {
|
||||
self.custom_amount = ""
|
||||
self.custom_amount_sats = nil
|
||||
}
|
||||
}
|
||||
}, header: {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -61,9 +61,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ und %1$d andere*r reagierten auf einen Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere*r reagierten auf einen Beitrag in dem du markiert warst</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ und %1$d andere reagierten auf einen Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere reagierten auf einen Beitrag in dem du markiert warst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
@@ -157,9 +157,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ und %1$d andere*r teilten einen Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere*r teilten einen Beitrag in dem du markiert warst</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ und %1$d andere teilten ein Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere teilten ein Beitrag in dem du markiert warst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
@@ -269,9 +269,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ und %1$d andere:r zappten einen Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere:r zappten einen Beitrag in dem du markiert warst</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ und %1$d andere zappten einen Beitrag in dem Du markiert warst</string>
|
||||
<string>%2$@ und %1$d andere zappten einen Beitrag in dem du markiert warst</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
|
||||
Binary file not shown.
@@ -47,7 +47,7 @@
|
||||
<key>one</key>
|
||||
<string>Ακόλουθος</string>
|
||||
<key>other</key>
|
||||
<string>Ακολουθείτε</string>
|
||||
<string>Ακολουθεί</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
@@ -127,7 +127,7 @@
|
||||
<key>one</key>
|
||||
<string>Διακομιστής Relay</string>
|
||||
<key>other</key>
|
||||
<string>Διακομιστές Relays</string>
|
||||
<string>Relays</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
@@ -141,9 +141,9 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Απάντηση προς %2$@, %3$@ & %1$d άλλον</string>
|
||||
<string>Απάντηση προς %2$@, %3$@ & %1$d ακόμα</string>
|
||||
<key>other</key>
|
||||
<string>Απάντηση προς %2$@, %3$@ & %1$d άλλους</string>
|
||||
<string>Απάντηση προς %2$@, %3$@ & %1$d ακόμα</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_tagged_in_3</key>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>DM by heading in local notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ %@" xml:space="preserve">
|
||||
<source>%@ %@</source>
|
||||
@@ -188,17 +188,18 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Appearance</target>
|
||||
<note>Section header for text and appearance settings</note>
|
||||
<note>Navigation title for text and appearance settings.
|
||||
Section header for text and appearance settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you lost?" xml:space="preserve">
|
||||
<source>Are you lost?</source>
|
||||
<target>Are you lost?</target>
|
||||
<note>Text asking the user if they are lost in the app.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you sure you want to repost this?" xml:space="preserve">
|
||||
<source>Are you sure you want to repost this?</source>
|
||||
<target>Are you sure you want to repost this?</target>
|
||||
<note>Alert message to ask if user wants to repost a post.</note>
|
||||
<trans-unit id="Are you sure you want to upload this image?" xml:space="preserve">
|
||||
<source>Are you sure you want to upload this image?</source>
|
||||
<target>Are you sure you want to upload this image?</target>
|
||||
<note>Alert message asking if the user wants to upload an image.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Automatically translate notes" xml:space="preserve">
|
||||
<source>Automatically translate notes</source>
|
||||
@@ -249,8 +250,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
Button to cancel a repost.
|
||||
Button to cancel out of alert that creates a new mutelist.
|
||||
Button to cancel out of posting a note.
|
||||
Button to cancel out of reposting a post.
|
||||
Button to cancel out of view adding user inputted relay.
|
||||
Button to cancel the upload.
|
||||
Cancel deleting the user.
|
||||
Cancel out of logging out the user.</note>
|
||||
</trans-unit>
|
||||
@@ -354,6 +355,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Copy Text</target>
|
||||
<note>Context menu option for copying the text from an note.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy URL" xml:space="preserve">
|
||||
<source>Copy URL</source>
|
||||
<target>Copy URL</target>
|
||||
<note>Label for button in context menu to copy URL of the selected uploaded media asset.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Copy User Pubkey" xml:space="preserve">
|
||||
<source>Copy User Pubkey</source>
|
||||
<target>Copy User Pubkey</target>
|
||||
@@ -404,11 +410,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Custom Zap Amount</target>
|
||||
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DM by %@" xml:space="preserve">
|
||||
<source>DM by %@</source>
|
||||
<target>DM by %@</target>
|
||||
<note>DM by heading in local notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DMs" xml:space="preserve">
|
||||
<source>DMs</source>
|
||||
<target>DMs</target>
|
||||
@@ -435,7 +436,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Default Zap Amount in sats" xml:space="preserve">
|
||||
<source>Default Zap Amount in sats</source>
|
||||
<target>Default Zap Amount in sats</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>Title for section in zap settings that controls the default zap amount in sats.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete" xml:space="preserve">
|
||||
<source>Delete</source>
|
||||
@@ -627,7 +628,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Keys" xml:space="preserve">
|
||||
<source>Keys</source>
|
||||
<target>Keys</target>
|
||||
<note>Settings section for managing keys</note>
|
||||
<note>Navigation title for managing keys.
|
||||
Settings section for managing keys</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Left Handed" xml:space="preserve">
|
||||
<source>Left Handed</source>
|
||||
@@ -759,6 +761,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>NIP-05 Verification</target>
|
||||
<note>Label for NIP-05 Verification section of user profile form.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New encrypted direct message" xml:space="preserve">
|
||||
<source>New encrypted direct message</source>
|
||||
<target>New encrypted direct message</target>
|
||||
<note>Notification that the user has received a new direct message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No" xml:space="preserve">
|
||||
<source>No</source>
|
||||
<target>No</target>
|
||||
@@ -802,7 +809,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Notifications</target>
|
||||
<note>Toolbar label for Notifications view.</note>
|
||||
<note>Section header for Damus notifications
|
||||
Toolbar label for Notifications view.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nudity or explicit content" xml:space="preserve">
|
||||
<source>Nudity or explicit content</source>
|
||||
@@ -933,6 +941,11 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>QR Code</target>
|
||||
<note>Button to view profile's qr code.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Quote" xml:space="preserve">
|
||||
<source>Quote</source>
|
||||
<target>Quote</target>
|
||||
<note>Title of alert for confirming to make a quoted post.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reactions" xml:space="preserve">
|
||||
<source>Reactions</source>
|
||||
<target>Reactions</target>
|
||||
@@ -1021,8 +1034,7 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<trans-unit id="Repost" xml:space="preserve">
|
||||
<source>Repost</source>
|
||||
<target>Repost</target>
|
||||
<note>Button to confirm reposting a post.
|
||||
Title of alert for confirming to repost a post.</note>
|
||||
<note>Title of alert for confirming to repost a post.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Reposted" xml:space="preserve">
|
||||
<source>Reposted</source>
|
||||
@@ -1273,7 +1285,8 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<trans-unit id="Translation" xml:space="preserve">
|
||||
<source>Translation</source>
|
||||
<target>Translation</target>
|
||||
<note>Section header for text and appearance settings</note>
|
||||
<note>Navigation title for translation settings.
|
||||
Section header for text and appearance settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translations" xml:space="preserve">
|
||||
<source>Translations</source>
|
||||
@@ -1325,6 +1338,11 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>Unmute conversation</target>
|
||||
<note>Context menu option for unmuting a conversation.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Upload" xml:space="preserve">
|
||||
<source>Upload</source>
|
||||
<target>Upload</target>
|
||||
<note>Button to proceed with uploading.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="User has been muted" xml:space="preserve">
|
||||
<source>User has been muted</source>
|
||||
<target>User has been muted</target>
|
||||
@@ -1372,7 +1390,8 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<trans-unit id="Wallet" xml:space="preserve">
|
||||
<source>Wallet</source>
|
||||
<target>Wallet</target>
|
||||
<note>Sidebar menu label for Wallet view.</note>
|
||||
<note>Sidebar menu label for Wallet view.
|
||||
Title for section in zap settings that controls the Lightning wallet selection.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Website" xml:space="preserve">
|
||||
<source>Website</source>
|
||||
@@ -1460,8 +1479,10 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<source>Zaps</source>
|
||||
<target>Zaps</target>
|
||||
<note>Navigation bar title for the Zaps view.
|
||||
Navigation title for zap settings.
|
||||
Section header for zap settings
|
||||
Setting to enable Zap Local Notification</note>
|
||||
Setting to enable Zap Local Notification
|
||||
Title for section in zap settings that controls general zap preferences.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
|
||||
<source>https://example.com/pic.jpg</source>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,7 +17,7 @@
|
||||
<key>many</key>
|
||||
<string>... %d Outras observações ... </string>
|
||||
<key>other</key>
|
||||
<string>... %d outras observações ... </string>
|
||||
<string>... %d outras notas ... </string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -71,7 +71,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros reagiram a uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros reagiram a uma publicação na qual você está marcado</string>
|
||||
<string>%2$@ e %1$d outros reagiram a um post que você está marcado</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
@@ -89,7 +89,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros reagiram à sua publicação</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros reagiram à sua publicação</string>
|
||||
<string>%2$@ e %1$d outros reagiram ao seu post</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -143,7 +143,7 @@
|
||||
<key>many</key>
|
||||
<string>Transmissões</string>
|
||||
<key>other</key>
|
||||
<string>Transmissões</string>
|
||||
<string>Relays</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
@@ -179,7 +179,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram uma publicação na qual você está marcado</string>
|
||||
<string>%2$@ e %1$d outros repostaram um post que você está marcado</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
@@ -197,7 +197,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram seu perfil</string>
|
||||
<string>%2$@ e %1$d outros repostaram seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -215,7 +215,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram seu perfil</string>
|
||||
<string>%2$@ e %1$d outros repostaram seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposts_count</key>
|
||||
@@ -233,7 +233,7 @@
|
||||
<key>many</key>
|
||||
<string>Republicações</string>
|
||||
<key>other</key>
|
||||
<string>Republicações</string>
|
||||
<string>Reposts</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats_count</key>
|
||||
@@ -305,7 +305,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram uma publicação na qual você está marcado</string>
|
||||
<string>%2$@ e %1$d outros zapped um post que você está marcado</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
@@ -323,7 +323,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram sua publicação</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram sua publicação</string>
|
||||
<string>%2$@ e %1$d outros zapped seu post</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -341,7 +341,7 @@
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram seu perfil</string>
|
||||
<string>%2$@ e %1$d outros zapped seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
@@ -359,7 +359,7 @@
|
||||
<key>many</key>
|
||||
<string>Muitos iluminaram</string>
|
||||
<key>other</key>
|
||||
<string>Iluminaram</string>
|
||||
<string>Zaps</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>... %d 条更多便条...</string>
|
||||
<string>... %d 条更多笔记...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -55,7 +55,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了提到你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了提到你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
@@ -69,7 +69,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -139,7 +139,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了提到你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了提到你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
@@ -153,7 +153,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -237,7 +237,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户电击了提到你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户打闪了提到你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
@@ -251,7 +251,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户电击了你的便条</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户打闪了你的帖子</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -265,7 +265,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户电击了你的档案</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户打闪了你的档案</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
@@ -279,7 +279,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>电击</string>
|
||||
<string>打闪</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>...還有%d 条便條...</string>
|
||||
<string>...還有%d 条筆記...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -237,7 +237,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了提到你的便條</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了提到你的便條</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
@@ -251,7 +251,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了你的便條</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了你的便條</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -265,7 +265,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了你的檔案</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了你的檔案</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
@@ -279,7 +279,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>電擊</string>
|
||||
<string>打閃</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>...還有%d 条便條...</string>
|
||||
<string>...還有%d 条筆記...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -237,7 +237,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了提到你的便條</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了提到你的便條</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
@@ -251,7 +251,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了你的便條</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了你的便條</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_profile_3</key>
|
||||
@@ -265,7 +265,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 個其他用戶電擊了你的檔案</string>
|
||||
<string>%2$@ 和 %1$d 個其他用戶打閃了你的檔案</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
@@ -279,7 +279,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>電擊</string>
|
||||
<string>打閃</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@@ -30,7 +30,7 @@ final class RequestTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMakeSubscriptionRequest() {
|
||||
let filter = NostrFilter(kinds: [3], limit: 1, authors: ["d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62"])
|
||||
let filter = NostrFilter(kinds: [NostrKind.contacts.rawValue], limit: 1, authors: ["d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62"])
|
||||
let subscribe = NostrSubscribe(filters: [filter], sub_id: "31C737B7-C8F9-41DD-8707-325974F279A4")
|
||||
let result = make_nostr_req(.subscribe(subscribe))
|
||||
let expectedResult = "[\"REQ\",\"31C737B7-C8F9-41DD-8707-325974F279A4\",{\"kinds\":[3],\"authors\":[\"d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62\"],\"limit\":1}]"
|
||||
|
||||
@@ -97,9 +97,9 @@ class damusTests: XCTestCase {
|
||||
|
||||
func testSaveDefaultZapAmount() {
|
||||
let pubkey = "test_pubkey"
|
||||
let amt = 1000
|
||||
let amt = 1234
|
||||
set_default_zap_amount(pubkey: pubkey, amount: amt)
|
||||
let loaded = get_default_zap_amount(pubkey: pubkey)!
|
||||
let loaded = get_default_zap_amount(pubkey: pubkey)
|
||||
XCTAssertEqual(loaded, amt)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user