Compare commits
182 Commits
| 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
|
|||
| 02fc065005 | |||
| 668b0a94df | |||
| ebba9d3004 | |||
| 8733a34933 | |||
| 0de6cfe344 | |||
| a4d40dbfa6 | |||
| 05d332eac3 | |||
| b5a3697d78 | |||
| 247270f3d3 | |||
| 9327068264 | |||
| c277c14bcd | |||
| e688a691fc | |||
| b470af8f1d | |||
| efd1168217 | |||
| f7a3f9ab76 | |||
| 95041600dc | |||
| 8a8d2ebbc3 | |||
| fad0a6b783 | |||
| f5d7465368 | |||
| 8a785559c6 | |||
| d4c8c15cc3 | |||
| 41a462871c | |||
| 76a669acc2 | |||
| 39236dc094 | |||
| 5860125802 | |||
| ae96c3b707 | |||
| 136f6f37e8 | |||
| 21f84f722b | |||
| 47747379ee | |||
| a1b95d40e6 | |||
| 0fc69d862a | |||
|
fb0330476d
|
|||
|
4b978594fa
|
|||
| f0bbba7a33 | |||
| b5faae9d1c | |||
| a4d4954abd | |||
| 735376b00f | |||
| 042e02d2e4 | |||
| 40468b1603 | |||
| 8c19ec1532 | |||
| 1ac9620242 | |||
| d5ecc9bce4 | |||
| d82b69aac5 | |||
| bad6ba3643 | |||
| 5c131e62d7 | |||
| 29ab48287f | |||
| 5c854519db | |||
| 2cc04e24a3 | |||
| d24bea366d | |||
| 85ce8cb93c | |||
|
32bb8c365d
|
|||
|
d9285ab3ca
|
|||
|
3cba771655
|
|||
|
f6f2517fda
|
|||
|
047325e6b2
|
|||
|
ba2108d659
|
|||
|
863c7baa8b
|
|||
|
8a5e95e47a
|
|||
|
de0997216d
|
|||
|
cc64c82ec4
|
|||
|
e2ca02399b
|
|||
|
5418f55cee
|
|||
|
eb65d473cd
|
|||
|
dd337c4805
|
|||
|
2f6ed72f6d
|
|||
|
d71bb33408
|
|||
|
72cfb2b071
|
|||
|
a67cb2df90
|
|||
| 23e9ce1455 | |||
| 5f1132cbc8 | |||
| 806c6257df | |||
| 18aafb086e | |||
| fc534ea42d | |||
| 54c8958250 |
@@ -1,3 +1,54 @@
|
|||||||
|
## [1.4.3-2] - 2023-04-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add deep links for local notifications (Swift)
|
||||||
|
- Add thread muting (Terry Yiu)
|
||||||
|
- Preview media uploads when posting (Swift)
|
||||||
|
- Add QR Code in profiles (ericholguin)
|
||||||
|
|
||||||
|
|
||||||
|
### 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-2]: https://github.com/damus-io/damus/releases/tag/v1.4.3-2
|
||||||
|
|
||||||
|
## [1.4.2-2] - 2023-04-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Include #btc in custom #bitcoin hashtag (William Casarin)
|
||||||
|
- Make notification dots configurable (William Casarin)
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Display follows in most recent to oldest (Luis Cabrera)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix hitches caused by syncronous loading of cached images (William Casarin)
|
||||||
|
- Fix tabs sometimes not switching (William Casarin)
|
||||||
|
|
||||||
|
|
||||||
|
[1.4.2-2]: https://github.com/damus-io/damus/releases/tag/v1.4.2-2
|
||||||
|
|
||||||
## [1.4.1-8] - 2023-04-10
|
## [1.4.1-8] - 2023-04-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -987,3 +1038,4 @@
|
|||||||
|
|
||||||
|
|
||||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "bech32.h"
|
|
||||||
|
|
||||||
typedef unsigned char u8;
|
typedef unsigned char u8;
|
||||||
|
|
||||||
@@ -32,8 +31,8 @@ static inline int is_invalid_url_ending(char c) {
|
|||||||
return c == '!' || c == '?' || c == ')' || c == '.' || c == ',' || c == ';';
|
return c == '!' || c == '?' || c == ')' || c == '.' || c == ',' || c == ';';
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int is_bech32_character(char c) {
|
static inline int is_alphanumeric(char c) {
|
||||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || bech32_charset_rev[c] != -1;
|
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void make_cursor(struct cursor *c, const u8 *content, size_t len)
|
static inline void make_cursor(struct cursor *c, const u8 *content, size_t len)
|
||||||
@@ -75,14 +74,14 @@ static inline int consume_until_whitespace(struct cursor *cur, int or_end) {
|
|||||||
return or_end;
|
return or_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int consume_until_non_bech32_character(struct cursor *cur, int or_end) {
|
static inline int consume_until_non_alphanumeric(struct cursor *cur, int or_end) {
|
||||||
char c;
|
char c;
|
||||||
int consumedAtLeastOne = 0;
|
int consumedAtLeastOne = 0;
|
||||||
|
|
||||||
while (cur->p < cur->end) {
|
while (cur->p < cur->end) {
|
||||||
c = *cur->p;
|
c = *cur->p;
|
||||||
|
|
||||||
if (!is_bech32_character(c))
|
if (!is_alphanumeric(c))
|
||||||
return consumedAtLeastOne;
|
return consumedAtLeastOne;
|
||||||
|
|
||||||
cur->p++;
|
cur->p++;
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
|||||||
|
|
||||||
start = cur->p;
|
start = cur->p;
|
||||||
|
|
||||||
if (!consume_until_non_bech32_character(cur, 1)) {
|
if (!consume_until_non_alphanumeric(cur, 1)) {
|
||||||
cur->p = start;
|
cur->p = start;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,8 +142,9 @@
|
|||||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */; };
|
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */; };
|
||||||
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
|
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
|
||||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
|
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
|
||||||
|
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
|
||||||
|
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
|
||||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
||||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
|
||||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||||
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.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 */; };
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
|
||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.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 */; };
|
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
|
||||||
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.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 */; };
|
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
|
||||||
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEF297F11C700430951 /* SelectedEventView.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 */; };
|
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF3297F18B400430951 /* ReplyDescription.swift */; };
|
||||||
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
|
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
|
||||||
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
|
||||||
@@ -192,6 +193,8 @@
|
|||||||
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
|
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
|
||||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
|
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
|
||||||
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
|
||||||
|
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; };
|
||||||
|
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128B29EB19C40006FA5A /* LocalNotification.swift */; };
|
||||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
||||||
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
||||||
@@ -276,7 +279,7 @@
|
|||||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
||||||
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
|
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
|
||||||
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -330,9 +333,6 @@
|
|||||||
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
||||||
3A4F3320297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
|
||||||
3A4F3321297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
|
||||||
3A4F3322297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-FR"; path = "fr-FR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
|
||||||
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
@@ -340,6 +340,9 @@
|
|||||||
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
|
3A821C3E29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
3A821C3F29E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
|
3A821C4029E819D500B4BCA7 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3A827A18299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3A827A18299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3A827A19299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3A827A19299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
3A827A1A299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3A827A1A299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
@@ -386,9 +389,6 @@
|
|||||||
3AD14EB829C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "sv-SE"; path = "sv-SE.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
3AD14EB829C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "sv-SE"; path = "sv-SE.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
3AD14EB929C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
3AD14EB929C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
3AD14EBA29C40F3F009D2D9C /* sv-SE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sv-SE"; path = "sv-SE.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
3AD14EBB29C40F47009D2D9C /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
|
||||||
3AD14EBC29C40F47009D2D9C /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
|
||||||
3AD14EBD29C40F47009D2D9C /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
|
||||||
3AD5662B29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3AD5662B29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
3AD5662C29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fa; path = fa.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
3AD5662C29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fa; path = fa.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
3AD5662D29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = "<group>"; };
|
3AD5662D29BD2F5300BF77C5 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -551,8 +551,9 @@
|
|||||||
4C8D00D129E397AD0036AF10 /* block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = block.h; sourceTree = "<group>"; };
|
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>"; };
|
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
|
||||||
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
|
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
|
||||||
|
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendIcon.swift; sourceTree = "<group>"; };
|
||||||
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||||
@@ -563,6 +564,7 @@
|
|||||||
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||||
@@ -591,7 +593,6 @@
|
|||||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
|
||||||
@@ -601,6 +602,8 @@
|
|||||||
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
|
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
|
||||||
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
|
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
|
||||||
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
|
||||||
|
4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; };
|
||||||
|
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotification.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
||||||
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
|
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
|
||||||
@@ -687,7 +690,7 @@
|
|||||||
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -882,6 +885,7 @@
|
|||||||
4C75EFA227FA576C0006080F /* Views */ = {
|
4C75EFA227FA576C0006080F /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
|
||||||
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
4C1A9A1B29DDCF8B00516EAC /* Settings */,
|
||||||
4CFF8F6129CC9A80008DB934 /* Images */,
|
4CFF8F6129CC9A80008DB934 /* Images */,
|
||||||
4CCEB7AC29B53D180078AA28 /* Search */,
|
4CCEB7AC29B53D180078AA28 /* Search */,
|
||||||
@@ -907,7 +911,6 @@
|
|||||||
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
|
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
|
||||||
4C216F31286E388800040376 /* DMChatView.swift */,
|
4C216F31286E388800040376 /* DMChatView.swift */,
|
||||||
4C216F33286F5ACD00040376 /* DMView.swift */,
|
4C216F33286F5ACD00040376 /* DMView.swift */,
|
||||||
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
|
||||||
3169CAE4294E699400EE4006 /* Empty Views */,
|
3169CAE4294E699400EE4006 /* Empty Views */,
|
||||||
4C75EFB82804A2740006080F /* EventView.swift */,
|
4C75EFB82804A2740006080F /* EventView.swift */,
|
||||||
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
|
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
|
||||||
@@ -925,7 +928,7 @@
|
|||||||
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
||||||
4C363A8B28236B92006E126D /* PubkeyView.swift */,
|
4C363A8B28236B92006E126D /* PubkeyView.swift */,
|
||||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
|
||||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */,
|
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */,
|
||||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
||||||
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
||||||
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
||||||
@@ -962,7 +965,6 @@
|
|||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
||||||
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
||||||
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
||||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */,
|
|
||||||
);
|
);
|
||||||
path = Nostr;
|
path = Nostr;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1007,10 +1009,20 @@
|
|||||||
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */,
|
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */,
|
||||||
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
||||||
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
||||||
|
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
|
||||||
|
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4C8D1A6D29F31E4100ACDF75 /* Buttons */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
|
||||||
|
);
|
||||||
|
path = Buttons;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4CAAD8AE29888A9B00060CEA /* Relays */ = {
|
4CAAD8AE29888A9B00060CEA /* Relays */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1022,6 +1034,7 @@
|
|||||||
4CE8794D2996B16A00F758CC /* RelayToggle.swift */,
|
4CE8794D2996B16A00F758CC /* RelayToggle.swift */,
|
||||||
4CE8794F2996B2BD00F758CC /* RelayStatus.swift */,
|
4CE8794F2996B2BD00F758CC /* RelayStatus.swift */,
|
||||||
4CE879512996B68900F758CC /* RelayType.swift */,
|
4CE879512996B68900F758CC /* RelayType.swift */,
|
||||||
|
4CDA128929E9D10C0006FA5A /* SignalView.swift */,
|
||||||
);
|
);
|
||||||
path = Relays;
|
path = Relays;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1050,12 +1063,14 @@
|
|||||||
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
|
||||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
||||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
|
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
|
||||||
|
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
|
||||||
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
|
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
|
||||||
4C8682862814DE470026224F /* ProfileView.swift */,
|
4C8682862814DE470026224F /* ProfileView.swift */,
|
||||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
|
||||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
|
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
|
||||||
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
|
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
|
||||||
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
|
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
|
||||||
|
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */,
|
||||||
);
|
);
|
||||||
path = Profile;
|
path = Profile;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1064,7 +1079,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
|
||||||
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */,
|
|
||||||
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
|
||||||
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
|
||||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */,
|
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */,
|
||||||
@@ -1406,8 +1420,7 @@
|
|||||||
"es-419",
|
"es-419",
|
||||||
"es-ES",
|
"es-ES",
|
||||||
fa,
|
fa,
|
||||||
"fr-CA",
|
fr,
|
||||||
"fr-FR",
|
|
||||||
"hu-HU",
|
"hu-HU",
|
||||||
id,
|
id,
|
||||||
"it-IT",
|
"it-IT",
|
||||||
@@ -1482,6 +1495,7 @@
|
|||||||
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
||||||
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */,
|
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */,
|
||||||
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
|
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
|
||||||
|
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */,
|
||||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
|
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
|
||||||
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
|
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
|
||||||
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
|
||||||
@@ -1495,6 +1509,7 @@
|
|||||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */,
|
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */,
|
||||||
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
|
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
|
||||||
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
||||||
|
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */,
|
||||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
||||||
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
@@ -1512,6 +1527,7 @@
|
|||||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
|
||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
|
||||||
|
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
|
||||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
|
||||||
@@ -1526,7 +1542,7 @@
|
|||||||
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
|
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
|
||||||
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
|
||||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||||
@@ -1534,6 +1550,7 @@
|
|||||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
|
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
||||||
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||||
@@ -1579,7 +1596,6 @@
|
|||||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
|
||||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||||
@@ -1594,7 +1610,6 @@
|
|||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
||||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||||
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
|
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
|
||||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
|
|
||||||
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */,
|
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */,
|
||||||
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
|
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
|
||||||
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
|
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
|
||||||
@@ -1650,6 +1665,7 @@
|
|||||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
||||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||||
|
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
|
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
|
||||||
@@ -1779,7 +1795,6 @@
|
|||||||
3A5C4575296A879E0032D398 /* es-419 */,
|
3A5C4575296A879E0032D398 /* es-419 */,
|
||||||
3A2B8B0A296A8982009CC16D /* en-US */,
|
3A2B8B0A296A8982009CC16D /* en-US */,
|
||||||
3AEB8005297CCEA900713A25 /* tr-TR */,
|
3AEB8005297CCEA900713A25 /* tr-TR */,
|
||||||
3A4F3322297CCFEE004B5F72 /* fr-FR */,
|
|
||||||
3A185A06297F2C3800F4BDC0 /* lv-LV */,
|
3A185A06297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A929C22297F2CF80090925E /* it-IT */,
|
3A929C22297F2CF80090925E /* it-IT */,
|
||||||
3AB5B86C2986D8A3006599D2 /* de */,
|
3AB5B86C2986D8A3006599D2 /* de */,
|
||||||
@@ -1801,10 +1816,10 @@
|
|||||||
3AD5663229C0DA4B00BF77C5 /* ko */,
|
3AD5663229C0DA4B00BF77C5 /* ko */,
|
||||||
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBC29C40F47009D2D9C /* fr-CA */,
|
|
||||||
3A325AC629C9E0B8002BE7ED /* vi */,
|
3A325AC629C9E0B8002BE7ED /* vi */,
|
||||||
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
||||||
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C4029E819D500B4BCA7 /* fr */,
|
||||||
);
|
);
|
||||||
name = Localizable.stringsdict;
|
name = Localizable.stringsdict;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1814,7 +1829,6 @@
|
|||||||
children = (
|
children = (
|
||||||
3ACB685B297633BC00C46468 /* es-419 */,
|
3ACB685B297633BC00C46468 /* es-419 */,
|
||||||
3AEB8003297CCEA800713A25 /* tr-TR */,
|
3AEB8003297CCEA800713A25 /* tr-TR */,
|
||||||
3A4F3320297CCFEE004B5F72 /* fr-FR */,
|
|
||||||
3A185A04297F2C3800F4BDC0 /* lv-LV */,
|
3A185A04297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A929C20297F2CF80090925E /* it-IT */,
|
3A929C20297F2CF80090925E /* it-IT */,
|
||||||
3AB5B86A2986D8A3006599D2 /* de */,
|
3AB5B86A2986D8A3006599D2 /* de */,
|
||||||
@@ -1836,10 +1850,10 @@
|
|||||||
3AD5663329C0DA4B00BF77C5 /* ko */,
|
3AD5663329C0DA4B00BF77C5 /* ko */,
|
||||||
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBB29C40F47009D2D9C /* fr-CA */,
|
|
||||||
3A325AC529C9E0B8002BE7ED /* vi */,
|
3A325AC529C9E0B8002BE7ED /* vi */,
|
||||||
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
||||||
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C3F29E819D500B4BCA7 /* fr */,
|
||||||
);
|
);
|
||||||
name = InfoPlist.strings;
|
name = InfoPlist.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1849,7 +1863,6 @@
|
|||||||
children = (
|
children = (
|
||||||
3ACB685E297633BC00C46468 /* es-419 */,
|
3ACB685E297633BC00C46468 /* es-419 */,
|
||||||
3AEB8004297CCEA800713A25 /* tr-TR */,
|
3AEB8004297CCEA800713A25 /* tr-TR */,
|
||||||
3A4F3321297CCFEE004B5F72 /* fr-FR */,
|
|
||||||
3A185A05297F2C3800F4BDC0 /* lv-LV */,
|
3A185A05297F2C3800F4BDC0 /* lv-LV */,
|
||||||
3A929C21297F2CF80090925E /* it-IT */,
|
3A929C21297F2CF80090925E /* it-IT */,
|
||||||
3AB5B86B2986D8A3006599D2 /* de */,
|
3AB5B86B2986D8A3006599D2 /* de */,
|
||||||
@@ -1872,10 +1885,10 @@
|
|||||||
3AD5663129C0DA4B00BF77C5 /* ko */,
|
3AD5663129C0DA4B00BF77C5 /* ko */,
|
||||||
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
||||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
||||||
3AD14EBD29C40F47009D2D9C /* fr-CA */,
|
|
||||||
3A325AC429C9E0B8002BE7ED /* vi */,
|
3A325AC429C9E0B8002BE7ED /* vi */,
|
||||||
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
||||||
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
||||||
|
3A821C3E29E819D500B4BCA7 /* fr */,
|
||||||
);
|
);
|
||||||
name = Localizable.strings;
|
name = Localizable.strings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2011,7 +2024,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2036,9 +2049,12 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -2055,7 +2071,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 8;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -2080,9 +2096,12 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)",
|
"$(PROJECT_DIR)",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.4.2;
|
MARKETING_VERSION = 1.4.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
|||||||
text
|
text
|
||||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 10, trailing: 0))
|
.padding(EdgeInsets(top: 15, leading: 0, bottom: 10, trailing: 0))
|
||||||
.font(.system(size: 14, weight: .heavy))
|
.font(.system(size: 14, weight: .heavy))
|
||||||
.contentShape(Rectangle())
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
.background(
|
.background(
|
||||||
Group {
|
Group {
|
||||||
@@ -50,6 +48,7 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
|||||||
},
|
},
|
||||||
alignment: .bottom
|
alignment: .bottom
|
||||||
)
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
.accentColor(tag == selection ? textColor() : .gray)
|
.accentColor(tag == selection ? textColor() : .gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ struct ImageCarousel: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var height: CGFloat {
|
var height: CGFloat {
|
||||||
image_fill?.height ?? 0
|
image_fill?.height ?? 100
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -118,10 +118,7 @@ extension KFOptionSetter {
|
|||||||
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
|
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
|
||||||
let img_size = image.size
|
let img_size = image.size
|
||||||
let geo_size = size
|
let geo_size = size
|
||||||
let fill = ImageFill.calculate_image_fill(geo_size: geo_size,
|
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: img_size, maxHeight: max, fillHeight: fill)
|
||||||
img_size: img_size,
|
|
||||||
maxHeight: max,
|
|
||||||
fillHeight: fill)
|
|
||||||
DispatchQueue.main.async { [block, fill] in
|
DispatchQueue.main.async { [block, fill] in
|
||||||
try? block(fill)
|
try? block(fill)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ struct NIP05Badge: View {
|
|||||||
.mask(Image(systemName: "checkmark.seal.fill")
|
.mask(Image(systemName: "checkmark.seal.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
).frame(width: 14, height: 14)
|
).frame(width: 14, height: 14)
|
||||||
} else {
|
} else if show_domain {
|
||||||
Image(systemName: "checkmark.seal.fill")
|
Image(systemName: "checkmark.seal.fill")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.gray)
|
.nip05_colorized(gradient: nip05_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,14 @@ struct TranslateView: View {
|
|||||||
} else {
|
} else {
|
||||||
self.currentLanguage = Locale.current.languageCode ?? "en"
|
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)
|
self._translated = State(initialValue: cached)
|
||||||
} else {
|
} else {
|
||||||
let initval: TranslateStatus = self.damus_state.settings.auto_translate ? .trying : .havent_tried
|
let initval: TranslateStatus = self.damus_state.settings.auto_translate ? .trying : .havent_tried
|
||||||
|
|||||||
@@ -7,11 +7,34 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserViewRow: View {
|
||||||
|
let damus_state: DamusState
|
||||||
|
let pubkey: String
|
||||||
|
|
||||||
|
@State var navigating: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let dest = ProfileView(damus_state: damus_state, pubkey: pubkey)
|
||||||
|
|
||||||
|
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.background(
|
||||||
|
NavigationLink(destination: dest, isActive: $navigating) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
navigating = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct UserView: View {
|
struct UserView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||||
@@ -28,7 +51,6 @@ struct UserView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func send_zap(damus_state: DamusState, event: NostrEvent, lnurl: String, is_cust
|
|||||||
damus_state.lnurls.endpoints[target.pubkey] = payreq
|
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 {
|
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
+111
-47
@@ -15,17 +15,15 @@ struct TimestampedProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Sheets: Identifiable {
|
enum Sheets: Identifiable {
|
||||||
case post
|
case post(PostAction)
|
||||||
case report(ReportTarget)
|
case report(ReportTarget)
|
||||||
case reply(NostrEvent)
|
|
||||||
case event(NostrEvent)
|
case event(NostrEvent)
|
||||||
case filter
|
case filter
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .report: return "report"
|
case .report: return "report"
|
||||||
case .post: return "post"
|
case .post(let action): return "post-" + (action.ev?.id ?? "")
|
||||||
case .reply(let ev): return "reply-" + ev.id
|
|
||||||
case .event(let ev): return "event-" + ev.id
|
case .event(let ev): return "event-" + ev.id
|
||||||
case .filter: return "filter"
|
case .filter: return "filter"
|
||||||
}
|
}
|
||||||
@@ -66,7 +64,6 @@ struct ContentView: View {
|
|||||||
@State var active_sheet: Sheets? = nil
|
@State var active_sheet: Sheets? = nil
|
||||||
@State var damus_state: DamusState? = nil
|
@State var damus_state: DamusState? = nil
|
||||||
@State var selected_timeline: Timeline? = .home
|
@State var selected_timeline: Timeline? = .home
|
||||||
@State var is_thread_open: Bool = false
|
|
||||||
@State var is_deleted_account: Bool = false
|
@State var is_deleted_account: Bool = false
|
||||||
@State var is_profile_open: Bool = false
|
@State var is_profile_open: Bool = false
|
||||||
@State var event: NostrEvent? = nil
|
@State var event: NostrEvent? = nil
|
||||||
@@ -84,6 +81,7 @@ struct ContentView: View {
|
|||||||
@State var filter_state : FilterState = .posts_and_replies
|
@State var filter_state : FilterState = .posts_and_replies
|
||||||
@State private var isSideBarOpened = false
|
@State private var isSideBarOpened = false
|
||||||
@StateObject var home: HomeModel = HomeModel()
|
@StateObject var home: HomeModel = HomeModel()
|
||||||
|
@State var shouldShowBoostAlert = false
|
||||||
|
|
||||||
// connect retry timer
|
// connect retry timer
|
||||||
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
||||||
@@ -91,14 +89,19 @@ struct ContentView: View {
|
|||||||
let sub_id = UUID().description
|
let sub_id = UUID().description
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
|
var mystery: some View {
|
||||||
|
Text("Are you lost?", comment: "Text asking the user if they are lost in the app.")
|
||||||
|
.id("what")
|
||||||
|
}
|
||||||
|
|
||||||
var PostingTimelineView: some View {
|
var PostingTimelineView: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
TabView(selection: $filter_state) {
|
TabView(selection: $filter_state) {
|
||||||
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
||||||
Text("")
|
mystery
|
||||||
.id("what")
|
|
||||||
contentTimelineView(filter: FilterState.posts.filter)
|
contentTimelineView(filter: FilterState.posts.filter)
|
||||||
.tag(FilterState.posts)
|
.tag(FilterState.posts)
|
||||||
.id(FilterState.posts)
|
.id(FilterState.posts)
|
||||||
@@ -110,7 +113,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
if privkey != nil {
|
if privkey != nil {
|
||||||
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
|
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
|
||||||
self.active_sheet = .post
|
self.active_sheet = .post(.posting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,8 +182,7 @@ struct ContentView: View {
|
|||||||
NotificationsView(state: damus, notifications: home.notifications)
|
NotificationsView(state: damus, notifications: home.notifications)
|
||||||
|
|
||||||
case .dms:
|
case .dms:
|
||||||
DirectMessagesView(damus_state: damus_state!)
|
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
|
||||||
.environmentObject(home.dms)
|
|
||||||
|
|
||||||
case .none:
|
case .none:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
@@ -243,6 +245,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func open_event(ev: NostrEvent) {
|
||||||
|
self.active_event = ev
|
||||||
|
self.thread_open = true
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
if let damus = self.damus_state {
|
if let damus = self.damus_state {
|
||||||
@@ -263,13 +270,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
if home.signal.signal != home.signal.max_signal {
|
SignalView(state: damus_state!, signal: home.signal)
|
||||||
NavigationLink(destination: RelayConfigView(state: damus_state!)) {
|
|
||||||
Text("\(home.signal.signal)/\(home.signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe expand this to other timelines in the future
|
// maybe expand this to other timelines in the future
|
||||||
if selected_timeline == .search {
|
if selected_timeline == .search {
|
||||||
@@ -308,10 +309,8 @@ struct ContentView: View {
|
|||||||
switch item {
|
switch item {
|
||||||
case .report(let target):
|
case .report(let target):
|
||||||
MaybeReportView(target: target)
|
MaybeReportView(target: target)
|
||||||
case .post:
|
case .post(let action):
|
||||||
PostView(replying_to: nil, damus_state: damus_state!)
|
PostView(action: action, damus_state: damus_state!)
|
||||||
case .reply(let event):
|
|
||||||
PostView(replying_to: event, damus_state: damus_state!)
|
|
||||||
case .event:
|
case .event:
|
||||||
EventDetailView()
|
EventDetailView()
|
||||||
case .filter:
|
case .filter:
|
||||||
@@ -338,10 +337,9 @@ struct ContentView: View {
|
|||||||
} else if ref.key == "e" {
|
} else if ref.key == "e" {
|
||||||
find_event(state: damus_state!, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
|
find_event(state: damus_state!, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
|
||||||
if let ev {
|
if let ev {
|
||||||
active_event = ev
|
open_event(ev: ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thread_open = true
|
|
||||||
}
|
}
|
||||||
case .filter(let filt):
|
case .filter(let filt):
|
||||||
active_search = filt
|
active_search = filt
|
||||||
@@ -352,16 +350,16 @@ struct ContentView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.boost)) { notif in
|
.onReceive(handle_notify(.boost)) { notif in
|
||||||
current_boost = (notif.object as? NostrEvent)
|
guard let ev = notif.object as? NostrEvent else {
|
||||||
}
|
return
|
||||||
.onReceive(handle_notify(.open_thread)) { obj in
|
}
|
||||||
//let ev = obj.object as! NostrEvent
|
|
||||||
//thread.set_active_event(ev)
|
current_boost = ev
|
||||||
//is_thread_open = true
|
shouldShowBoostAlert = true
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.reply)) { notif in
|
.onReceive(handle_notify(.reply)) { notif in
|
||||||
let ev = notif.object as! NostrEvent
|
let ev = notif.object as! NostrEvent
|
||||||
self.active_sheet = .reply(ev)
|
self.active_sheet = .post(.replying_to(ev))
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.like)) { like in
|
.onReceive(handle_notify(.like)) { like in
|
||||||
}
|
}
|
||||||
@@ -469,13 +467,56 @@ struct ContentView: View {
|
|||||||
self.damus_state?.pool.connect_to_disconnected()
|
self.damus_state?.pool.connect_to_disconnected()
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.new_mutes)) { notif in
|
.onReceive(handle_notify(.new_mutes)) { notif in
|
||||||
home.filter_muted()
|
home.filter_events()
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.mute_thread)) { notif in
|
.onReceive(handle_notify(.mute_thread)) { notif in
|
||||||
home.filter_muted()
|
home.filter_events()
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unmute_thread)) { notif in
|
.onReceive(handle_notify(.unmute_thread)) { notif in
|
||||||
home.filter_muted()
|
home.filter_events()
|
||||||
|
}
|
||||||
|
.onReceive(handle_notify(.local_notification)) { notif in
|
||||||
|
|
||||||
|
guard let local = notif.object as? LossyLocalNotification,
|
||||||
|
let damus_state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let target = damus_state.events.lookup(local.event_id) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch local.type {
|
||||||
|
case .dm:
|
||||||
|
selected_timeline = .dms
|
||||||
|
damus_state.dms.open_dm_by_pk(target.pubkey)
|
||||||
|
|
||||||
|
case .like: fallthrough
|
||||||
|
case .zap: fallthrough
|
||||||
|
case .mention: fallthrough
|
||||||
|
case .repost:
|
||||||
|
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) {
|
.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.")) {
|
Button(NSLocalizedString("Logout", comment: "Button to close the alert that informs that the current account has been deleted.")) {
|
||||||
@@ -564,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.")
|
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()) {
|
.confirmationDialog("Repost", isPresented: $shouldShowBoostAlert) {
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
|
Button(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post.")) {
|
||||||
current_boost = nil
|
guard let current_boost else {
|
||||||
}
|
return
|
||||||
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
|
|
||||||
if let current_boost {
|
|
||||||
self.damus_state?.pool.send(.event(current_boost))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,7 +678,12 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
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,
|
self.damus_state = DamusState(pool: pool,
|
||||||
keypair: keypair,
|
keypair: keypair,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
@@ -631,7 +695,7 @@ struct ContentView: View {
|
|||||||
previews: PreviewCache(),
|
previews: PreviewCache(),
|
||||||
zaps: Zaps(our_pubkey: pubkey),
|
zaps: Zaps(our_pubkey: pubkey),
|
||||||
lnurls: LNUrls(),
|
lnurls: LNUrls(),
|
||||||
settings: UserSettingsStore(),
|
settings: settings,
|
||||||
relay_filters: relay_filters,
|
relay_filters: relay_filters,
|
||||||
relay_metadata: metadatas,
|
relay_metadata: metadatas,
|
||||||
drafts: Drafts(),
|
drafts: Drafts(),
|
||||||
@@ -640,7 +704,7 @@ struct ContentView: View {
|
|||||||
postbox: PostBox(pool: pool),
|
postbox: PostBox(pool: pool),
|
||||||
bootstrap_relays: bootstrap_relays,
|
bootstrap_relays: bootstrap_relays,
|
||||||
replies: ReplyCounter(our_pubkey: pubkey),
|
replies: ReplyCounter(our_pubkey: pubkey),
|
||||||
muted_threads: MutedThreadsManager(pubkey: pubkey)
|
muted_threads: MutedThreadsManager(keypair: keypair)
|
||||||
)
|
)
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
@@ -802,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 ])
|
var filter = search_type == .event ? NostrFilter.filter_ids([ evid ]) : NostrFilter.filter_authors([ evid ])
|
||||||
|
|
||||||
if search_type == .profile {
|
if search_type == .profile {
|
||||||
filter.kinds = [0]
|
filter.kinds = [NostrKind.metadata.rawValue]
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.limit = 1
|
filter.limit = 1
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ struct DamusState {
|
|||||||
keypair.privkey != nil
|
keypair.privkey != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var settings_pubkey: String? = nil
|
||||||
|
|
||||||
static var empty: DamusState {
|
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(pubkey: ""))
|
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
|
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 }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class DirectMessageModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var draft: String
|
@Published var draft: String
|
||||||
|
|
||||||
|
let pubkey: String
|
||||||
|
|
||||||
var is_request: Bool
|
var is_request: Bool
|
||||||
var our_pubkey: String
|
var our_pubkey: String
|
||||||
|
|
||||||
@@ -29,17 +31,19 @@ class DirectMessageModel: ObservableObject {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(events: [NostrEvent], our_pubkey: String) {
|
init(events: [NostrEvent], our_pubkey: String, pubkey: String) {
|
||||||
self.events = events
|
self.events = events
|
||||||
self.is_request = false
|
self.is_request = false
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
self.draft = ""
|
self.draft = ""
|
||||||
|
self.pubkey = pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
init(our_pubkey: String) {
|
init(our_pubkey: String, pubkey: String) {
|
||||||
self.events = []
|
self.events = []
|
||||||
self.is_request = false
|
self.is_request = false
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
self.draft = ""
|
self.draft = ""
|
||||||
|
self.pubkey = pubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,43 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class DirectMessagesModel: ObservableObject {
|
class DirectMessagesModel: ObservableObject {
|
||||||
@Published var dms: [(String, DirectMessageModel)] = []
|
@Published var dms: [DirectMessageModel] = []
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
@Published var open_dm: Bool = false
|
||||||
|
@Published private(set) var active_model: DirectMessageModel = DirectMessageModel(our_pubkey: "", pubkey: "")
|
||||||
let our_pubkey: String
|
let our_pubkey: String
|
||||||
|
|
||||||
init(our_pubkey: String) {
|
init(our_pubkey: String) {
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
var message_requests: [(String, DirectMessageModel)] {
|
var message_requests: [DirectMessageModel] {
|
||||||
return dms.filter { dm in dm.1.is_request }
|
return dms.filter { dm in dm.is_request }
|
||||||
}
|
}
|
||||||
|
|
||||||
var friend_dms: [(String, DirectMessageModel)] {
|
var friend_dms: [DirectMessageModel] {
|
||||||
return dms.filter { dm in !dm.1.is_request }
|
return dms.filter { dm in !dm.is_request }
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_active_dm_model(_ model: DirectMessageModel) {
|
||||||
|
self.active_model = model
|
||||||
|
}
|
||||||
|
|
||||||
|
func open_dm_by_pk(_ pubkey: String) {
|
||||||
|
self.set_active_dm(pubkey)
|
||||||
|
self.open_dm = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func open_dm_by_model(_ model: DirectMessageModel) {
|
||||||
|
self.set_active_dm_model(model)
|
||||||
|
self.open_dm = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_active_dm(_ pubkey: String) {
|
||||||
|
for model in self.dms where model.pubkey == pubkey {
|
||||||
|
self.set_active_dm_model(model)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup_or_create(_ pubkey: String) -> DirectMessageModel {
|
func lookup_or_create(_ pubkey: String) -> DirectMessageModel {
|
||||||
@@ -29,15 +52,15 @@ class DirectMessagesModel: ObservableObject {
|
|||||||
return dm
|
return dm
|
||||||
}
|
}
|
||||||
|
|
||||||
let new = DirectMessageModel(our_pubkey: our_pubkey)
|
let new = DirectMessageModel(our_pubkey: our_pubkey, pubkey: pubkey)
|
||||||
dms.append((pubkey, new))
|
dms.append(new)
|
||||||
return new
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(_ pubkey: String) -> DirectMessageModel? {
|
func lookup(_ pubkey: String) -> DirectMessageModel? {
|
||||||
for dm in dms {
|
for dm in dms {
|
||||||
if pubkey == dm.0 {
|
if pubkey == dm.pubkey {
|
||||||
return dm.1
|
return dm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,23 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Drafts: ObservableObject {
|
class DraftArtifacts {
|
||||||
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
|
var content: NSMutableAttributedString
|
||||||
@Published var replies: [NostrEvent: 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] = [:]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class FollowersModel: ObservableObject {
|
|||||||
if ev.known_kind == .contacts {
|
if ev.known_kind == .contacts {
|
||||||
handle_contact_event(ev)
|
handle_contact_event(ev)
|
||||||
} else if ev.known_kind == .metadata {
|
} else if ev.known_kind == .metadata {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class FollowingModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_filter() -> NostrFilter {
|
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
|
f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in
|
||||||
// don't fetch profiles we already have
|
// don't fetch profiles we already have
|
||||||
if damus_state.profiles.lookup(id: pk) != nil {
|
if damus_state.profiles.lookup(id: pk) != nil {
|
||||||
@@ -62,7 +62,7 @@ class FollowingModel {
|
|||||||
break
|
break
|
||||||
case .event(_, let ev):
|
case .event(_, let ev):
|
||||||
if ev.kind == 0 {
|
if ev.kind == 0 {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
case .notice(let msg):
|
case .notice(let msg):
|
||||||
print("followingmodel notice: \(msg)")
|
print("followingmodel notice: \(msg)")
|
||||||
|
|||||||
+155
-103
@@ -41,36 +41,27 @@ class HomeModel: ObservableObject {
|
|||||||
let dms_subid = UUID().description
|
let dms_subid = UUID().description
|
||||||
let init_subid = UUID().description
|
let init_subid = UUID().description
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
|
|
||||||
|
var loading: Bool = false
|
||||||
|
|
||||||
|
var signal = SignalModel()
|
||||||
|
|
||||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||||
@Published var notifications = NotificationsModel()
|
@Published var notifications = NotificationsModel()
|
||||||
@Published var dms: DirectMessagesModel
|
|
||||||
@Published var events = EventHolder()
|
@Published var events = EventHolder()
|
||||||
@Published var loading: Bool = false
|
|
||||||
@Published var signal: SignalModel = SignalModel()
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.damus_state = DamusState.empty
|
self.damus_state = DamusState.empty
|
||||||
self.dms = DirectMessagesModel(our_pubkey: "")
|
filter_events()
|
||||||
filter_muted()
|
self.setup_debouncer()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
|
||||||
self.setup_debouncer()
|
|
||||||
filter_muted()
|
|
||||||
}
|
|
||||||
|
|
||||||
var pool: RelayPool {
|
var pool: RelayPool {
|
||||||
return damus_state.pool
|
return damus_state.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup_debouncer() {
|
var dms: DirectMessagesModel {
|
||||||
// turn off debouncer after initial load
|
return damus_state.dms
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
|
||||||
self.should_debounce_dms = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func has_sub_id_event(sub_id: String, ev_id: String) -> Bool {
|
func has_sub_id_event(sub_id: String, ev_id: String) -> Bool {
|
||||||
@@ -81,6 +72,13 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
return has_event[sub_id]!.contains(ev_id)
|
return has_event[sub_id]!.contains(ev_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setup_debouncer() {
|
||||||
|
// turn off debouncer after initial load
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||||
|
self.should_debounce_dms = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||||
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
||||||
@@ -147,7 +145,7 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
if damus_state.settings.zap_notification {
|
if damus_state.settings.zap_notification {
|
||||||
// Create in-app local notification for zap received.
|
// Create in-app local notification for zap received.
|
||||||
create_in_app_zap_notification(profiles: profiles, zap: zap)
|
create_in_app_zap_notification(profiles: profiles, zap: zap, evId: ev.referenced_ids.first?.id ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,27 +186,31 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handle_channel_create(_ ev: NostrEvent) {
|
func handle_channel_create(_ ev: NostrEvent) {
|
||||||
guard ev.is_valid else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.channels[ev.id] = ev
|
self.channels[ev.id] = ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_channel_meta(_ ev: NostrEvent) {
|
func handle_channel_meta(_ ev: NostrEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter_muted() {
|
func filter_events() {
|
||||||
events.filter { !damus_state.contacts.is_muted($0.pubkey) && !damus_state.muted_threads.isMutedThread($0) }
|
events.filter { ev in
|
||||||
self.dms.dms = dms.dms.filter { !damus_state.contacts.is_muted($0.0) }
|
!damus_state.contacts.is_muted(ev.pubkey)
|
||||||
notifications.filter_and_build_notifications(damus_state)
|
}
|
||||||
|
|
||||||
|
self.dms.dms = dms.dms.filter { ev in
|
||||||
|
!damus_state.contacts.is_muted(ev.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.filter { ev in
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_delete_event(_ ev: NostrEvent) {
|
func handle_delete_event(_ ev: NostrEvent) {
|
||||||
guard ev.is_valid else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.deleted_events.insert(ev.id)
|
self.deleted_events.insert(ev.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +232,7 @@ class HomeModel: ObservableObject {
|
|||||||
if let inner_ev = ev.inner_event {
|
if let inner_ev = ev.inner_event {
|
||||||
boost_ev_id = inner_ev.id
|
boost_ev_id = inner_ev.id
|
||||||
|
|
||||||
guard inner_ev.is_valid else {
|
guard validate_event(ev: inner_ev) == .ok else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +261,10 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if damus_state.settings.onlyzaps_mode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||||
case .already_counted:
|
case .already_counted:
|
||||||
break
|
break
|
||||||
@@ -299,10 +305,7 @@ class HomeModel: ObservableObject {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
update_signal_from_pool(signal: signal, pool: damus_state.pool)
|
update_signal_from_pool(signal: self.signal, pool: damus_state.pool)
|
||||||
|
|
||||||
print("ws_event \(ev)")
|
|
||||||
|
|
||||||
case .nostr_event(let ev):
|
case .nostr_event(let ev):
|
||||||
switch ev {
|
switch ev {
|
||||||
case .event(let sub_id, let ev):
|
case .event(let sub_id, let ev):
|
||||||
@@ -321,11 +324,13 @@ class HomeModel: ObservableObject {
|
|||||||
case .eose(let sub_id):
|
case .eose(let sub_id):
|
||||||
|
|
||||||
if sub_id == dms_subid {
|
if sub_id == dms_subid {
|
||||||
var dms = dms.dms.flatMap { $0.1.events }
|
var dms = dms.dms.flatMap { $0.events }
|
||||||
dms.append(contentsOf: incoming_dms)
|
dms.append(contentsOf: incoming_dms)
|
||||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
||||||
} else if sub_id == notifications_subid {
|
} 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)
|
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
|
self.loading = false
|
||||||
@@ -355,13 +360,13 @@ class HomeModel: ObservableObject {
|
|||||||
var friends = damus_state.contacts.get_friend_list()
|
var friends = damus_state.contacts.get_friend_list()
|
||||||
friends.append(damus_state.pubkey)
|
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
|
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]
|
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.parameter = ["mute"]
|
||||||
our_blocklist_filter.authors = [damus_state.pubkey]
|
our_blocklist_filter.authors = [damus_state.pubkey]
|
||||||
|
|
||||||
@@ -380,21 +385,27 @@ class HomeModel: ObservableObject {
|
|||||||
our_dms_filter.authors = [ damus_state.pubkey ]
|
our_dms_filter.authors = [ damus_state.pubkey ]
|
||||||
|
|
||||||
// TODO: separate likes?
|
// TODO: separate likes?
|
||||||
var home_filter = NostrFilter.filter_kinds([
|
var home_filter_kinds = [
|
||||||
NostrKind.text.rawValue,
|
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
|
// include our pubkey as well even if we're not technically a friend
|
||||||
home_filter.authors = friends
|
home_filter.authors = friends
|
||||||
home_filter.limit = 500
|
home_filter.limit = 500
|
||||||
|
|
||||||
var notifications_filter = NostrFilter.filter_kinds([
|
var notifications_filter_kinds = [
|
||||||
NostrKind.text.rawValue,
|
NostrKind.text.rawValue,
|
||||||
NostrKind.like.rawValue,
|
|
||||||
NostrKind.boost.rawValue,
|
NostrKind.boost.rawValue,
|
||||||
NostrKind.zap.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.pubkeys = [damus_state.pubkey]
|
||||||
notifications_filter.limit = 500
|
notifications_filter.limit = 500
|
||||||
|
|
||||||
@@ -449,7 +460,7 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handle_metadata_event(_ ev: NostrEvent) {
|
func handle_metadata_event(_ ev: NostrEvent) {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? {
|
func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? {
|
||||||
@@ -471,11 +482,12 @@ class HomeModel: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
guard should_show_event(contacts: damus_state.contacts, ev: ev) && !damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
damus_state.events.insert(ev)
|
damus_state.events.insert(ev)
|
||||||
|
|
||||||
if let inner_ev = ev.inner_event {
|
if let inner_ev = ev.inner_event {
|
||||||
damus_state.events.insert(inner_ev)
|
damus_state.events.insert(inner_ev)
|
||||||
}
|
}
|
||||||
@@ -522,15 +534,26 @@ class HomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
||||||
|
self.new_events = notifs
|
||||||
|
if damus_state.settings.dm_notification {
|
||||||
|
let convo = ev.decrypted(privkey: self.damus_state.keypair.privkey) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message")
|
||||||
|
let notify = LocalNotification(type: .dm, event: ev, target: ev, content: convo)
|
||||||
|
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handle_dm(_ ev: NostrEvent) {
|
func handle_dm(_ ev: NostrEvent) {
|
||||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
damus_state.events.insert(ev)
|
||||||
|
|
||||||
if !should_debounce_dms {
|
if !should_debounce_dms {
|
||||||
self.incoming_dms.append(ev)
|
self.incoming_dms.append(ev)
|
||||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||||
self.new_events = notifs
|
got_new_dm(notifs: notifs, ev: ev)
|
||||||
}
|
}
|
||||||
self.incoming_dms = []
|
self.incoming_dms = []
|
||||||
return
|
return
|
||||||
@@ -540,11 +563,7 @@ class HomeModel: ObservableObject {
|
|||||||
|
|
||||||
dm_debouncer.debounce { [self] in
|
dm_debouncer.debounce { [self] in
|
||||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||||
self.new_events = notifs
|
got_new_dm(notifs: notifs, ev: ev)
|
||||||
if damus_state.settings.dm_notification,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: self.incoming_dms.last!.pubkey)?.display_name {
|
|
||||||
create_local_notification(displayName: displayName, conversation: "You have received a direct message", type: .dm)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.incoming_dms = []
|
self.incoming_dms = []
|
||||||
}
|
}
|
||||||
@@ -557,8 +576,8 @@ func update_signal_from_pool(signal: SignalModel, pool: RelayPool) {
|
|||||||
signal.max_signal = pool.relays.count
|
signal.max_signal = pool.relays.count
|
||||||
}
|
}
|
||||||
|
|
||||||
if signal.signal != pool.num_connecting {
|
if signal.signal != pool.num_connected {
|
||||||
signal.signal = signal.max_signal - pool.num_connecting
|
signal.signal = pool.num_connected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,15 +671,9 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
|||||||
print("-----")
|
print("-----")
|
||||||
}
|
}
|
||||||
|
|
||||||
func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEvent) {
|
func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) {
|
||||||
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
|
if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
|
||||||
DispatchQueue.main.async {
|
notify(.deleted_account, ())
|
||||||
notify(.deleted_account, ())
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,21 +707,57 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
|||||||
// load pfps asap
|
// load pfps asap
|
||||||
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
|
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
|
||||||
if URL(string: picture) != nil {
|
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 ?? ""
|
let banner = tprof.profile.banner ?? ""
|
||||||
if URL(string: banner) != nil {
|
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))
|
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
|
||||||
|
let validated = events.is_event_valid(ev.id)
|
||||||
|
|
||||||
|
switch validated {
|
||||||
|
case .unknown:
|
||||||
|
Task {
|
||||||
|
let result = validate_event(ev: ev)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
events.validation[ev.id] = result
|
||||||
|
guard result == .ok else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .ok:
|
||||||
|
callback()
|
||||||
|
|
||||||
|
case .bad_id: fallthrough
|
||||||
|
case .bad_sig:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent) {
|
||||||
|
guard_valid_event(events: events, ev: ev) {
|
||||||
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func robohash(_ pk: String) -> String {
|
func robohash(_ pk: String) -> String {
|
||||||
return "https://robohash.org/" + pk
|
return "https://robohash.org/" + pk
|
||||||
}
|
}
|
||||||
@@ -847,10 +896,10 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (pk, _) in dms.dms {
|
for model in dms.dms {
|
||||||
if pk == the_pk {
|
if model.pubkey == the_pk {
|
||||||
found = true
|
found = true
|
||||||
inserted = insert_uniq_sorted_event(events: &(dms.dms[i].1.events), new_ev: ev) {
|
inserted = insert_uniq_sorted_event(events: &(dms.dms[i].events), new_ev: ev) {
|
||||||
$0.created_at < $1.created_at
|
$0.created_at < $1.created_at
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,8 +909,8 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey)
|
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey, pubkey: the_pk)
|
||||||
dms.dms.append((the_pk, model))
|
dms.dms.append(model)
|
||||||
inserted = true
|
inserted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,8 +937,8 @@ func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
if inserted {
|
if inserted {
|
||||||
dms.dms = dms.dms.filter({ $0.1.events.count > 0 }).sorted { a, b in
|
dms.dms = dms.dms.filter({ $0.events.count > 0 }).sorted { a, b in
|
||||||
return a.1.events.last!.created_at > b.1.events.last!.created_at
|
return a.events.last!.created_at > b.events.last!.created_at
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,12 +1058,13 @@ func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_in_app_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) {
|
func create_in_app_zap_notification(profiles: Profiles, zap: Zap, locale: Locale = Locale.current, evId: String) {
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
content.title = zap_notification_title(zap)
|
content.title = zap_notification_title(zap)
|
||||||
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
||||||
content.sound = UNNotificationSound.default
|
content.sound = UNNotificationSound.default
|
||||||
|
content.userInfo = LossyLocalNotification(type: .zap, event_id: evId).to_user_info()
|
||||||
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||||
|
|
||||||
@@ -1042,57 +1092,59 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't show notifications from muted threads.
|
// Don't show notifications from muted threads.
|
||||||
if damus_state.muted_threads.isMutedThread(ev) {
|
if damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if type == .text && damus_state.settings.mention_notification {
|
if type == .text && damus_state.settings.mention_notification {
|
||||||
for block in ev.blocks(damus_state.keypair.privkey) {
|
let blocks = ev.blocks(damus_state.keypair.privkey)
|
||||||
if case .mention(let mention) = block, mention.ref.ref_id == damus_state.keypair.pubkey,
|
for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey {
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
let content = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string
|
||||||
let justContent = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string
|
|
||||||
create_local_notification(displayName: displayName, conversation: justContent, type: type)
|
let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content)
|
||||||
}
|
create_local_notification(profiles: damus_state.profiles, notify: notify )
|
||||||
}
|
|
||||||
} else if type == .boost && damus_state.settings.repost_notification,
|
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
|
||||||
|
|
||||||
if let inner_ev = ev.inner_event {
|
|
||||||
create_local_notification(displayName: displayName, conversation: inner_ev.content, type: type)
|
|
||||||
}
|
}
|
||||||
|
} else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.inner_event {
|
||||||
|
let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: inner_ev.content)
|
||||||
|
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
||||||
} else if type == .like && damus_state.settings.like_notification,
|
} else if type == .like && damus_state.settings.like_notification,
|
||||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name,
|
let evid = ev.referenced_ids.first?.ref_id,
|
||||||
let e_ref = ev.referenced_ids.first?.ref_id,
|
let liked_event = damus_state.events.lookup(evid)
|
||||||
let content = damus_state.events.lookup(e_ref)?.content {
|
{
|
||||||
|
let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: liked_event.content)
|
||||||
create_local_notification(displayName: displayName, conversation: content, type: type)
|
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func create_local_notification(displayName: String, conversation: String, type: NostrKind) {
|
func create_local_notification(profiles: Profiles, notify: LocalNotification) {
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
var title = ""
|
var title = ""
|
||||||
var identifier = ""
|
var identifier = ""
|
||||||
switch type {
|
|
||||||
case .text:
|
let displayName = event_author_name(profiles: profiles, pubkey: notify.event.pubkey)
|
||||||
|
|
||||||
|
switch notify.type {
|
||||||
|
case .mention:
|
||||||
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
title = String(format: NSLocalizedString("Mentioned by %@", comment: "Mentioned by heading in local notification"), displayName)
|
||||||
identifier = "myMentionNotification"
|
identifier = "myMentionNotification"
|
||||||
case .boost:
|
case .repost:
|
||||||
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
||||||
identifier = "myBoostNotification"
|
identifier = "myBoostNotification"
|
||||||
case .like:
|
case .like:
|
||||||
title = String(format: NSLocalizedString("Liked by %@", comment: "Liked by heading in local notification"), displayName)
|
title = String(format: NSLocalizedString("Liked by %@", comment: "Liked by heading in local notification"), displayName)
|
||||||
identifier = "myLikeNotification"
|
identifier = "myLikeNotification"
|
||||||
case .dm:
|
case .dm:
|
||||||
title = String(format: NSLocalizedString("DM by %@", comment: "DM by heading in local notification"), displayName)
|
title = String(format: NSLocalizedString("%@", comment: "DM by heading in local notification"), displayName)
|
||||||
identifier = "myDMNotification"
|
identifier = "myDMNotification"
|
||||||
default:
|
case .zap:
|
||||||
|
// not handled here
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
content.title = title
|
content.title = title
|
||||||
content.body = conversation
|
content.body = notify.content
|
||||||
content.sound = UNNotificationSound.default
|
content.sound = UNNotificationSound.default
|
||||||
|
content.userInfo = notify.to_lossy().to_user_info()
|
||||||
|
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ enum MediaUpload {
|
|||||||
return url.pathExtension
|
return url.pathExtension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localURL: URL {
|
||||||
|
switch self {
|
||||||
|
case .image(let url):
|
||||||
|
return url
|
||||||
|
case .video(let url):
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var is_image: Bool {
|
var is_image: Bool {
|
||||||
if case .image = self {
|
if case .image = self {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum LibreTranslateServer: String, CaseIterable, Identifiable {
|
enum LibreTranslateServer: String, CaseIterable, Identifiable, StringCodable {
|
||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
@@ -17,9 +17,19 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable {
|
|||||||
var url: String?
|
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 argosopentech
|
||||||
case terraprint
|
case terraprint
|
||||||
case vern
|
|
||||||
case custom
|
case custom
|
||||||
|
|
||||||
var model: Model {
|
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")
|
return .init(tag: self.rawValue, displayName: "translate.argosopentech.com", url: "https://translate.argosopentech.com")
|
||||||
case .terraprint:
|
case .terraprint:
|
||||||
return .init(tag: self.rawValue, displayName: "translate.terraprint.co", url: "https://translate.terraprint.co")
|
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:
|
case .custom:
|
||||||
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
|
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func saveMutedThreads(pubkey: String, currentValue: [String], value: [String]) -
|
|||||||
class MutedThreadsManager: ObservableObject {
|
class MutedThreadsManager: ObservableObject {
|
||||||
|
|
||||||
private let userDefaults = UserDefaults.standard
|
private let userDefaults = UserDefaults.standard
|
||||||
private let pubkey: String
|
private let keypair: Keypair
|
||||||
|
|
||||||
private var _mutedThreadsSet: Set<String>
|
private var _mutedThreadsSet: Set<String>
|
||||||
private var _mutedThreads: [String]
|
private var _mutedThreads: [String]
|
||||||
@@ -39,26 +39,26 @@ class MutedThreadsManager: ObservableObject {
|
|||||||
return _mutedThreads
|
return _mutedThreads
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if saveMutedThreads(pubkey: pubkey, currentValue: _mutedThreads, value: newValue) {
|
if saveMutedThreads(pubkey: keypair.pubkey, currentValue: _mutedThreads, value: newValue) {
|
||||||
self._mutedThreads = newValue
|
self._mutedThreads = newValue
|
||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(pubkey: String) {
|
init(keypair: Keypair) {
|
||||||
self._mutedThreads = loadMutedThreads(pubkey: pubkey)
|
self._mutedThreads = loadMutedThreads(pubkey: keypair.pubkey)
|
||||||
self._mutedThreadsSet = Set(_mutedThreads)
|
self._mutedThreadsSet = Set(_mutedThreads)
|
||||||
self.pubkey = pubkey
|
self.keypair = keypair
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMutedThread(_ ev: NostrEvent) -> Bool {
|
func isMutedThread(_ ev: NostrEvent, privkey: String?) -> Bool {
|
||||||
return _mutedThreadsSet.contains(ev.thread_id(privkey: nil))
|
return _mutedThreadsSet.contains(ev.thread_id(privkey: privkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMutedThread(_ ev: NostrEvent) {
|
func updateMutedThread(_ ev: NostrEvent) {
|
||||||
let threadId = ev.thread_id(privkey: nil)
|
let threadId = ev.thread_id(privkey: nil)
|
||||||
if isMutedThread(ev) {
|
if isMutedThread(ev, privkey: keypair.privkey) {
|
||||||
mutedThreads = mutedThreads.filter { $0 != threadId }
|
mutedThreads = mutedThreads.filter { $0 != threadId }
|
||||||
_mutedThreadsSet.remove(threadId)
|
_mutedThreadsSet.remove(threadId)
|
||||||
notify(.unmute_thread, ev)
|
notify(.unmute_thread, ev)
|
||||||
|
|||||||
@@ -29,4 +29,22 @@ class EventGroup {
|
|||||||
func insert(_ ev: NostrEvent) -> Bool {
|
func insert(_ ev: NostrEvent) -> Bool {
|
||||||
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
|
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]) {
|
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
|
||||||
self.zaps = zaps
|
for zap in zaps {
|
||||||
self.msat_total = 0
|
if !isIncluded(zap.request_ev) {
|
||||||
self.zappers = Set()
|
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() {
|
init() {
|
||||||
@@ -42,6 +58,7 @@ class ZapGroup {
|
|||||||
self.zappers = Set()
|
self.zappers = Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
func insert(_ zap: Zap) -> Bool {
|
func insert(_ zap: Zap) -> Bool {
|
||||||
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -65,6 +65,37 @@ enum NotificationItem {
|
|||||||
return reply.created_at
|
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 {
|
class NotificationsModel: ObservableObject, ScrollQueue {
|
||||||
@@ -239,7 +270,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if insert_event_immediate(ev) {
|
if insert_event_immediate(ev) {
|
||||||
filter_and_build_notifications(damus_state)
|
self.notifications = build_notifications()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,47 +283,47 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if insert_zap_immediate(zap) {
|
if insert_zap_immediate(zap) {
|
||||||
filter_and_build_notifications(damus_state)
|
self.notifications = build_notifications()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func filter_and_build_notifications(_ damus_state: DamusState) {
|
func filter(_ isIncluded: (NostrEvent) -> Bool) {
|
||||||
var changed = false
|
var changed = false
|
||||||
var count = 0
|
var count = 0
|
||||||
|
|
||||||
count = incoming_events.count
|
count = incoming_events.count
|
||||||
incoming_events = incoming_events.filter { include_event($0, damus_state: damus_state) }
|
incoming_events = incoming_events.filter(isIncluded)
|
||||||
changed = changed || incoming_events.count != count
|
changed = changed || incoming_events.count != count
|
||||||
|
|
||||||
count = profile_zaps.zaps.count
|
count = profile_zaps.zaps.count
|
||||||
profile_zaps.zaps = profile_zaps.zaps.filter { zap in include_event(zap.request.ev, damus_state: damus_state) }
|
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
|
||||||
changed = changed || profile_zaps.zaps.count != count
|
changed = changed || profile_zaps.zaps.count != count
|
||||||
|
|
||||||
for el in reactions {
|
for el in reactions {
|
||||||
count = el.value.events.count
|
count = el.value.events.count
|
||||||
el.value.events = el.value.events.filter { include_event($0, damus_state: damus_state) }
|
el.value.events = el.value.events.filter(isIncluded)
|
||||||
changed = changed || el.value.events.count != count
|
changed = changed || el.value.events.count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
for el in reposts {
|
for el in reposts {
|
||||||
count = el.value.events.count
|
count = el.value.events.count
|
||||||
el.value.events = el.value.events.filter { include_event($0, damus_state: damus_state) }
|
el.value.events = el.value.events.filter(isIncluded)
|
||||||
changed = changed || el.value.events.count != count
|
changed = changed || el.value.events.count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
for el in zaps {
|
for el in zaps {
|
||||||
count = el.value.zaps.count
|
count = el.value.zaps.count
|
||||||
el.value.zaps = el.value.zaps.filter {
|
el.value.zaps = el.value.zaps.filter {
|
||||||
include_event($0.request.ev, damus_state: damus_state)
|
isIncluded($0.request.ev)
|
||||||
}
|
}
|
||||||
changed = changed || el.value.zaps.count != count
|
changed = changed || el.value.zaps.count != count
|
||||||
}
|
}
|
||||||
|
|
||||||
count = replies.count
|
count = replies.count
|
||||||
replies = replies.filter { include_event($0, damus_state: damus_state) }
|
replies = replies.filter(isIncluded)
|
||||||
changed = changed || replies.count != count
|
changed = changed || replies.count != count
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
@@ -312,13 +343,9 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if inserted {
|
if inserted {
|
||||||
filter_and_build_notifications(damus_state)
|
self.notifications = build_notifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
return inserted
|
return inserted
|
||||||
}
|
}
|
||||||
|
|
||||||
func include_event(_ event: NostrEvent, damus_state: DamusState) -> Bool {
|
|
||||||
return !damus_state.contacts.is_muted(event.pubkey) && !damus_state.muted_threads.isMutedThread(event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
} else if ev.known_kind == .contacts {
|
} else if ev.known_kind == .contacts {
|
||||||
handle_profile_contact_event(ev)
|
handle_profile_contact_event(ev)
|
||||||
} else if ev.known_kind == .metadata {
|
} else if ev.known_kind == .metadata {
|
||||||
process_metadata_event(our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
|
process_metadata_event(events: damus.events, our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
seen_event.insert(ev.id)
|
seen_event.insert(ev.id)
|
||||||
}
|
}
|
||||||
@@ -140,6 +140,9 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
case .notice(let notice):
|
case .notice(let notice):
|
||||||
notify(.notice, notice)
|
notify(.notice, notice)
|
||||||
case .eose:
|
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
|
progress += 1
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_base_filter() -> NostrFilter {
|
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.limit = self.limit
|
||||||
filter.until = Int64(Date.now.timeIntervalSince1970)
|
filter.until = Int64(Date.now.timeIntervalSince1970)
|
||||||
return filter
|
return filter
|
||||||
@@ -128,11 +128,14 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
|
|||||||
var pubkeys = Set<String>()
|
var pubkeys = Set<String>()
|
||||||
|
|
||||||
for ev in events {
|
for ev in events {
|
||||||
if profiles.lookup(id: ev.pubkey) != nil {
|
// lookup profiles from boosted events
|
||||||
continue
|
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)
|
return Array(pubkeys)
|
||||||
@@ -161,7 +164,7 @@ func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ev.known_kind == .metadata {
|
if ev.known_kind == .metadata {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SearchModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
// since 1 month
|
// since 1 month
|
||||||
search.limit = self.limit
|
search.limit = self.limit
|
||||||
search.kinds = [1,5,7]
|
search.kinds = [NostrKind.text.rawValue, NostrKind.like.rawValue]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
|
||||||
|
|||||||
@@ -77,18 +77,23 @@ class ThreadModel: ObservableObject {
|
|||||||
var meta_events = NostrFilter()
|
var meta_events = NostrFilter()
|
||||||
var event_filter = NostrFilter()
|
var event_filter = NostrFilter()
|
||||||
var ref_events = NostrFilter()
|
var ref_events = NostrFilter()
|
||||||
//var likes_filter = NostrFilter.filter_kinds(7])
|
|
||||||
|
|
||||||
let thread_id = event.thread_id(privkey: nil)
|
let thread_id = event.thread_id(privkey: nil)
|
||||||
|
|
||||||
ref_events.referenced_ids = [thread_id, event.id]
|
ref_events.referenced_ids = [thread_id, event.id]
|
||||||
ref_events.kinds = [1]
|
ref_events.kinds = [NostrKind.text.rawValue]
|
||||||
ref_events.limit = 1000
|
ref_events.limit = 1000
|
||||||
|
|
||||||
event_filter.ids = [thread_id, event.id]
|
event_filter.ids = [thread_id, event.id]
|
||||||
|
|
||||||
meta_events.referenced_ids = [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
|
meta_events.limit = 1000
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -129,7 +134,7 @@ class ThreadModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ev.known_kind == .metadata {
|
if ev.known_kind == .metadata {
|
||||||
process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
|
||||||
} else if ev.is_textlike {
|
} else if ev.is_textlike {
|
||||||
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,19 @@
|
|||||||
|
|
||||||
import Foundation
|
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 }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
|
|||||||
@@ -9,210 +9,141 @@ import Foundation
|
|||||||
import Vault
|
import Vault
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
func should_show_wallet_selector(_ pubkey: String) -> Bool {
|
@propertyWrapper struct Setting<T: Equatable> {
|
||||||
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
|
private let key: String
|
||||||
}
|
private var value: T
|
||||||
|
|
||||||
func pk_setting_key(_ pubkey: String, key: String) -> String {
|
init(key: String, default_value: T) {
|
||||||
return "\(pubkey)_\(key)"
|
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
|
||||||
}
|
if let loaded = UserDefaults.standard.object(forKey: self.key) as? T {
|
||||||
|
self.value = loaded
|
||||||
func default_zap_setting_key(pubkey: String) -> String {
|
} else if let loaded = UserDefaults.standard.object(forKey: key) as? T {
|
||||||
return pk_setting_key(pubkey, key: "default_zap_amount")
|
// try to load from deprecated non-pubkey-keyed setting
|
||||||
}
|
self.value = loaded
|
||||||
|
} else {
|
||||||
func set_default_zap_amount(pubkey: String, amount: Int) {
|
self.value = default_value
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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? {
|
@propertyWrapper class StringSetting<T: StringCodable & Equatable> {
|
||||||
if let url = server.model.url {
|
private let key: String
|
||||||
return url
|
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 {
|
class UserSettingsStore: ObservableObject {
|
||||||
@Published var default_wallet: Wallet {
|
static var pubkey: String? = nil
|
||||||
didSet {
|
static var shared: UserSettingsStore? = nil
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var show_wallet_selector: Bool {
|
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
|
||||||
didSet {
|
var default_wallet: Wallet
|
||||||
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var left_handed: Bool {
|
|
||||||
didSet {
|
|
||||||
UserDefaults.standard.set(left_handed, forKey: "left_handed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var always_show_images: Bool {
|
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
|
||||||
didSet {
|
var default_media_uploader: MediaUploader
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var translate_dms: Bool {
|
@Setting(key: "show_wallet_selector", default_value: true)
|
||||||
didSet {
|
var show_wallet_selector: Bool
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var notification_indicators: Int {
|
@Setting(key: "left_handed", default_value: false)
|
||||||
didSet {
|
var left_handed: Bool
|
||||||
UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var truncate_mention_text: Bool {
|
@Setting(key: "always_show_images", default_value: false)
|
||||||
didSet {
|
var always_show_images: Bool
|
||||||
UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var auto_translate: Bool {
|
@Setting(key: "zap_vibration", default_value: true)
|
||||||
didSet {
|
var zap_vibration: Bool
|
||||||
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
|
|
||||||
}
|
@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 {
|
@Setting(key: "repost_notification", default_value: true)
|
||||||
didSet {
|
var repost_notification: Bool
|
||||||
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
|
||||||
}
|
@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 {
|
@Setting(key: "show_only_preferred_languages", default_value: false)
|
||||||
didSet {
|
var show_only_preferred_languages: Bool
|
||||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var deepl_plan: DeepLPlan {
|
@Setting(key: "onlyzaps_mode", default_value: false)
|
||||||
didSet {
|
var onlyzaps_mode: Bool
|
||||||
UserDefaults.standard.set(deepl_plan.rawValue, forKey: "deepl_plan")
|
|
||||||
}
|
@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 {
|
didSet {
|
||||||
do {
|
do {
|
||||||
if deepl_api_key == "" {
|
if deepl_api_key == "" {
|
||||||
@@ -226,31 +157,14 @@ class UserSettingsStore: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var libretranslate_server: LibreTranslateServer {
|
@StringSetting(key: "libretranslate_server", default_value: .terraprint)
|
||||||
didSet {
|
var libretranslate_server: LibreTranslateServer
|
||||||
if oldValue == libretranslate_server {
|
|
||||||
return
|
@Setting(key: "libretranslate_url", default_value: "")
|
||||||
}
|
var libretranslate_url: String
|
||||||
|
|
||||||
UserDefaults.standard.set(libretranslate_server.rawValue, forKey: "libretranslate_server")
|
@Setting(key: "libretranslate_api_key", default_value: "")
|
||||||
|
var libretranslate_api_key: String {
|
||||||
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 {
|
|
||||||
didSet {
|
didSet {
|
||||||
do {
|
do {
|
||||||
if libretranslate_api_key == "" {
|
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() {
|
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 {
|
do {
|
||||||
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
|
||||||
} catch {
|
} catch {
|
||||||
@@ -374,3 +225,79 @@ struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
|
|||||||
var accessGroup: String? = nil
|
var accessGroup: String? = nil
|
||||||
var accountName = "deepl_apikey"
|
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
|
import Foundation
|
||||||
|
|
||||||
enum Wallet: String, CaseIterable, Identifiable {
|
enum Wallet: String, CaseIterable, Identifiable, StringCodable {
|
||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
struct Model: Identifiable, Hashable {
|
struct Model: Identifiable, Hashable {
|
||||||
@@ -20,6 +20,17 @@ enum Wallet: String, CaseIterable, Identifiable {
|
|||||||
var image: String
|
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
|
// New url prefixes needed to be added to LSApplicationQueriesSchemes
|
||||||
case system_default_wallet
|
case system_default_wallet
|
||||||
case strike
|
case strike
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ZapsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
var filter = NostrFilter.filter_kinds([9735])
|
var filter = NostrFilter.filter_kinds([NostrKind.zap.rawValue])
|
||||||
switch target {
|
switch target {
|
||||||
case .profile(let profile_id):
|
case .profile(let profile_id):
|
||||||
filter.pubkeys = [profile_id]
|
filter.pubkeys = [profile_id]
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class Profile: Codable {
|
|||||||
set_val(key, val)
|
set_val(key, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reactions: Bool? {
|
||||||
|
get { return get_val("reactions"); }
|
||||||
|
set(s) { set_val("reactions", s) }
|
||||||
|
}
|
||||||
|
|
||||||
var deleted: Bool? {
|
var deleted: Bool? {
|
||||||
get { return get_val("deleted"); }
|
get { return get_val("deleted"); }
|
||||||
set(s) { set_val("deleted", s) }
|
set(s) { set_val("deleted", s) }
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ import CryptoKit
|
|||||||
import NaturalLanguage
|
import NaturalLanguage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum ValidationResult: Decodable {
|
enum ValidationResult: Decodable {
|
||||||
|
case unknown
|
||||||
case ok
|
case ok
|
||||||
case bad_id
|
case bad_id
|
||||||
case bad_sig
|
case bad_sig
|
||||||
|
|
||||||
|
var is_bad: Bool {
|
||||||
|
return self == .bad_id || self == .bad_sig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OtherEvent {
|
struct OtherEvent {
|
||||||
@@ -82,7 +86,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
var too_big: Bool {
|
var too_big: Bool {
|
||||||
return self.content.count > 16000
|
return self.content.utf8.count > 16000
|
||||||
}
|
}
|
||||||
|
|
||||||
var should_show_event: Bool {
|
var should_show_event: Bool {
|
||||||
@@ -93,14 +97,6 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
|||||||
return calculate_event_id(ev: self) == self.id
|
return calculate_event_id(ev: self) == self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_valid: Bool {
|
|
||||||
return validity == .ok
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var validity: ValidationResult = {
|
|
||||||
return .ok //validate_event(ev: self)
|
|
||||||
}()
|
|
||||||
|
|
||||||
private var _blocks: [Block]? = nil
|
private var _blocks: [Block]? = nil
|
||||||
func blocks(_ privkey: String?) -> [Block] {
|
func blocks(_ privkey: String?) -> [Block] {
|
||||||
if let bs = _blocks {
|
if let bs = _blocks {
|
||||||
@@ -564,7 +560,7 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
|||||||
return ev
|
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 {
|
guard let privkey = keypair.privkey else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -724,11 +720,34 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
|
|||||||
return ev
|
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] {
|
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||||
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []
|
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(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 {
|
if from.pubkey != our_pubkey {
|
||||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
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 {
|
public static var filter_text: NostrFilter {
|
||||||
return filter_kinds([1])
|
return filter_kinds([NostrKind.text.rawValue])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func filter_ids(_ ids: [String]) -> NostrFilter {
|
public static func filter_ids(_ ids: [String]) -> NostrFilter {
|
||||||
@@ -49,11 +49,11 @@ struct NostrFilter: Codable, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static var filter_profiles: NostrFilter {
|
public static var filter_profiles: NostrFilter {
|
||||||
return filter_kinds([0])
|
return filter_kinds([NostrKind.metadata.rawValue])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var filter_contacts: NostrFilter {
|
public static var filter_contacts: NostrFilter {
|
||||||
return filter_kinds([3])
|
return filter_kinds([NostrKind.contacts.rawValue])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func filter_authors(_ authors: [String]) -> NostrFilter {
|
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)
|
|
||||||
}
|
|
||||||
@@ -89,7 +89,7 @@ final class RelayConnection: WebSocketDelegate {
|
|||||||
self.isConnected = false
|
self.isConnected = false
|
||||||
|
|
||||||
case .text(let txt):
|
case .text(let txt):
|
||||||
if txt.count > 2000 {
|
if txt.utf8.count > 2000 {
|
||||||
DispatchQueue.global(qos: .default).async {
|
DispatchQueue.global(qos: .default).async {
|
||||||
if let ev = decode_nostr_event(txt: txt) {
|
if let ev = decode_nostr_event(txt: txt) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -105,8 +105,6 @@ final class RelayConnection: WebSocketDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("decode failed for \(txt)")
|
|
||||||
// TODO: trigger event error
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ class RelayPool {
|
|||||||
var num_connecting: Int {
|
var num_connecting: Int {
|
||||||
return relays.reduce(0) { n, r in n + (r.connection.isConnecting ? 1 : 0) }
|
return relays.reduce(0) { n, r in n + (r.connection.isConnecting ? 1 : 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var num_connected: Int {
|
||||||
|
return relays.reduce(0) { n, r in n + (r.connection.isConnected ? 1 : 0) }
|
||||||
|
}
|
||||||
|
|
||||||
func remove_handler(sub_id: String) {
|
func remove_handler(sub_id: String) {
|
||||||
self.handlers = handlers.filter { $0.sub_id != sub_id }
|
self.handlers = handlers.filter { $0.sub_id != sub_id }
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class EventCache {
|
|||||||
private var cancellable: AnyCancellable?
|
private var cancellable: AnyCancellable?
|
||||||
private var translations: [String: TranslateStatus] = [:]
|
private var translations: [String: TranslateStatus] = [:]
|
||||||
private var artifacts: [String: NoteArtifacts] = [:]
|
private var artifacts: [String: NoteArtifacts] = [:]
|
||||||
|
var validation: [String: ValidationResult] = [:]
|
||||||
|
|
||||||
//private var thread_latest: [String: Int64]
|
//private var thread_latest: [String: Int64]
|
||||||
|
|
||||||
@@ -26,6 +27,14 @@ class EventCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func is_event_valid(_ evid: String) -> ValidationResult {
|
||||||
|
guard let result = validation[evid] else {
|
||||||
|
return .unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func store_translation_artifacts(evid: String, translated: TranslateStatus) {
|
func store_translation_artifacts(evid: String, translated: TranslateStatus) {
|
||||||
self.translations[evid] = translated
|
self.translations[evid] = translated
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ let custom_hashtags: [String: CustomHashtag] = [
|
|||||||
"coffeechain": CustomHashtag.coffee,
|
"coffeechain": CustomHashtag.coffee,
|
||||||
"plebchain": CustomHashtag.plebchain,
|
"plebchain": CustomHashtag.plebchain,
|
||||||
"zap": CustomHashtag.zap,
|
"zap": CustomHashtag.zap,
|
||||||
|
"zaps": CustomHashtag.zap,
|
||||||
"zapathon": CustomHashtag.zap,
|
"zapathon": CustomHashtag.zap,
|
||||||
|
"onlyzaps": CustomHashtag.zap,
|
||||||
]
|
]
|
||||||
|
|
||||||
func hashtag_str(_ htag: String) -> CompatibleText {
|
func hashtag_str(_ htag: String) -> CompatibleText {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// LocalNotification.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-04-15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct LossyLocalNotification {
|
||||||
|
let type: LocalNotificationType
|
||||||
|
let event_id: String
|
||||||
|
|
||||||
|
func to_user_info() -> [AnyHashable: Any] {
|
||||||
|
return [
|
||||||
|
"type": self.type.rawValue,
|
||||||
|
"evid": self.event_id
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification {
|
||||||
|
let target_id = user_info["evid"] as! String
|
||||||
|
let typestr = user_info["type"] as! String
|
||||||
|
let type = LocalNotificationType(rawValue: typestr)!
|
||||||
|
|
||||||
|
return LossyLocalNotification(type: type, event_id: target_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LocalNotification {
|
||||||
|
let type: LocalNotificationType
|
||||||
|
let event: NostrEvent
|
||||||
|
let target: NostrEvent
|
||||||
|
let content: String
|
||||||
|
|
||||||
|
func to_lossy() -> LossyLocalNotification {
|
||||||
|
return LossyLocalNotification(type: self.type, event_id: self.target.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LocalNotificationType: String {
|
||||||
|
case dm
|
||||||
|
case like
|
||||||
|
case mention
|
||||||
|
case repost
|
||||||
|
case zap
|
||||||
|
}
|
||||||
@@ -110,6 +110,12 @@ extension Notification.Name {
|
|||||||
static var unmute_thread: Notification.Name {
|
static var unmute_thread: Notification.Name {
|
||||||
return Notification.Name("unmute_thread")
|
return Notification.Name("unmute_thread")
|
||||||
}
|
}
|
||||||
|
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 {
|
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
||||||
|
|||||||
@@ -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 is_anon: Bool
|
||||||
public let private_request: NostrEvent?
|
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? {
|
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
|
/// Make sure that we only create a zap event if it is authorized by the profile or event
|
||||||
guard zapper == zap_ev.pubkey else {
|
guard zapper == zap_ev.pubkey else {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ struct EventActionBar: View {
|
|||||||
@State var show_share_action: Bool = false
|
@State var show_share_action: Bool = false
|
||||||
|
|
||||||
@ObservedObject var bar: ActionBarModel
|
@ObservedObject var bar: ActionBarModel
|
||||||
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
@@ -38,12 +39,21 @@ struct EventActionBar: View {
|
|||||||
self.event = event
|
self.event = event
|
||||||
self.test_lnurl = test_lnurl
|
self.test_lnurl = test_lnurl
|
||||||
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
|
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
|
||||||
|
_settings = ObservedObject(wrappedValue: damus_state.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
test_lnurl ?? damus_state.profiles.lookup(id: event.pubkey)?.lnurl
|
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 {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if damus_state.keypair.privkey != nil {
|
if damus_state.keypair.privkey != nil {
|
||||||
@@ -72,22 +82,25 @@ struct EventActionBar: View {
|
|||||||
.font(.footnote.weight(.medium))
|
.font(.footnote.weight(.medium))
|
||||||
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
||||||
}
|
}
|
||||||
Spacer()
|
|
||||||
|
if show_like {
|
||||||
HStack(spacing: 4) {
|
Spacer()
|
||||||
LikeButton(liked: bar.liked) {
|
|
||||||
if bar.liked {
|
HStack(spacing: 4) {
|
||||||
notify(.delete, bar.our_like)
|
LikeButton(liked: bar.liked) {
|
||||||
} else {
|
if bar.liked {
|
||||||
send_like()
|
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 {
|
if let lnurl = self.lnurl {
|
||||||
Spacer()
|
Spacer()
|
||||||
ZapButton(damus_state: damus_state, event: event, lnurl: lnurl, bar: bar)
|
ZapButton(damus_state: damus_state, event: event, lnurl: lnurl, bar: bar)
|
||||||
@@ -137,15 +150,7 @@ struct EventActionBar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func send_boost() {
|
func send_boost() {
|
||||||
guard let privkey = self.damus_state.keypair.privkey else {
|
notify(.boost, self.event)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
|
|
||||||
|
|
||||||
self.bar.our_boost = boost
|
|
||||||
|
|
||||||
notify(.boost, boost)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func send_like() {
|
func send_like() {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ struct EventDetailBar: View {
|
|||||||
.buttonStyle(PlainButtonStyle())
|
.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))) {
|
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
|
||||||
let noun = Text(verbatim: "\(reactionsCountString(bar.likes))").foregroundColor(.gray)
|
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'.")
|
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 }
|
var id: String { self.rawValue }
|
||||||
case nostrBuild
|
case nostrBuild
|
||||||
case nostrImg
|
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 {
|
var nameParam: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
|||||||
@@ -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)) {
|
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)) {
|
NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
|
||||||
@@ -144,14 +144,15 @@ struct ConfigView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
|
|
||||||
func handle_string_amount(new_value: String) -> Int? {
|
func handle_string_amount(new_value: String) -> Int? {
|
||||||
let digits = Set("0123456789")
|
let filtered = new_value.filter {
|
||||||
let filtered = new_value.filter { digits.contains($0) }
|
$0.isNumber
|
||||||
|
}
|
||||||
|
|
||||||
if filtered == "" {
|
if filtered == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let amt = Int(filtered) else {
|
guard let amt = NumberFormatter().number(from: filtered) as? Int else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import SwiftUI
|
|||||||
|
|
||||||
struct DMChatView: View {
|
struct DMChatView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: String
|
@ObservedObject var dms: DirectMessageModel
|
||||||
@EnvironmentObject var dms: DirectMessageModel
|
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
|
|
||||||
|
var pubkey: String {
|
||||||
|
dms.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
var Messages: some View {
|
var Messages: some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -177,10 +180,9 @@ struct DMChatView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ev = NostrEvent(content: "hi", pubkey: "pubkey", kind: 1, tags: [])
|
let ev = NostrEvent(content: "hi", pubkey: "pubkey", kind: 1, tags: [])
|
||||||
|
|
||||||
let model = DirectMessageModel(events: [ev], our_pubkey: "pubkey")
|
let model = DirectMessageModel(events: [ev], our_pubkey: "pubkey", pubkey: "the_pk")
|
||||||
|
|
||||||
DMChatView(damus_state: test_damus_state(), pubkey: "pubkey")
|
DMChatView(damus_state: test_damus_state(), dms: model)
|
||||||
.environmentObject(model)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,13 @@ struct DirectMessagesView: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
@State var dm_type: DMType = .friend
|
@State var dm_type: DMType = .friend
|
||||||
@State var open_dm: Bool = false
|
@ObservedObject var model: DirectMessagesModel
|
||||||
@State var pubkey: String = ""
|
@ObservedObject var settings: UserSettingsStore
|
||||||
@EnvironmentObject var model: DirectMessagesModel
|
|
||||||
@State var active_model: DirectMessageModel
|
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
|
||||||
self.damus_state = damus_state
|
|
||||||
self._active_model = State(initialValue: DirectMessageModel(our_pubkey: damus_state.pubkey))
|
|
||||||
}
|
|
||||||
|
|
||||||
func MainContent(requests: Bool) -> some View {
|
func MainContent(requests: Bool) -> some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
|
let chat = DMChatView(damus_state: damus_state, dms: model.active_model)
|
||||||
.environmentObject(active_model)
|
NavigationLink(destination: chat, isActive: $model.open_dm) {
|
||||||
NavigationLink(destination: chat, isActive: $open_dm) {
|
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
LazyVStack(spacing: 0) {
|
LazyVStack(spacing: 0) {
|
||||||
@@ -38,12 +30,9 @@ struct DirectMessagesView: View {
|
|||||||
EmptyTimelineView()
|
EmptyTimelineView()
|
||||||
} else {
|
} else {
|
||||||
let dms = requests ? model.message_requests : model.friend_dms
|
let dms = requests ? model.message_requests : model.friend_dms
|
||||||
ForEach(dms, id: \.0) { tup in
|
ForEach(dms, id: \.pubkey) { dm in
|
||||||
MaybeEvent(tup)
|
MaybeEvent(dm)
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
|
||||||
Divider()
|
|
||||||
.padding([.top], 10)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,15 +48,17 @@ struct DirectMessagesView: View {
|
|||||||
return [.truncate_content, .no_action_bar, .no_translate]
|
return [.truncate_content, .no_action_bar, .no_translate]
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaybeEvent(_ tup: (String, DirectMessageModel)) -> some View {
|
func MaybeEvent(_ model: DirectMessageModel) -> some View {
|
||||||
Group {
|
Group {
|
||||||
if let ev = tup.1.events.last {
|
let ok = damus_state.settings.friend_filter.filter(contacts: damus_state.contacts, pubkey: model.pubkey)
|
||||||
EventView(damus: damus_state, event: ev, pubkey: tup.0, options: options)
|
if ok, let ev = model.events.last {
|
||||||
|
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
pubkey = tup.0
|
self.model.open_dm_by_model(model)
|
||||||
active_model = tup.1
|
|
||||||
open_dm = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.padding([.top], 10)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -95,10 +86,28 @@ struct DirectMessagesView: View {
|
|||||||
}
|
}
|
||||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
.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."))
|
.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 {
|
struct DirectMessagesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let ev = NostrEvent(content: "encrypted stuff",
|
let ev = NostrEvent(content: "encrypted stuff",
|
||||||
@@ -106,8 +115,6 @@ struct DirectMessagesView_Previews: PreviewProvider {
|
|||||||
kind: 4,
|
kind: 4,
|
||||||
tags: [])
|
tags: [])
|
||||||
let ds = test_damus_state()
|
let ds = test_damus_state()
|
||||||
let model = DirectMessageModel(events: [ev], our_pubkey: ds.pubkey)
|
DirectMessagesView(damus_state: ds, model: ds.dms, settings: ds.settings)
|
||||||
DirectMessagesView(damus_state: ds)
|
|
||||||
.environmentObject(model)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,19 +69,6 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func event_validity_color(_ validation: ValidationResult) -> some View {
|
|
||||||
Group {
|
|
||||||
switch validation {
|
|
||||||
case .ok:
|
|
||||||
EmptyView()
|
|
||||||
case .bad_id:
|
|
||||||
Color.orange.opacity(0.4)
|
|
||||||
case .bad_sig:
|
|
||||||
Color.red.opacity(0.4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func pubkey_context_menu(bech32_pubkey: String) -> some View {
|
func pubkey_context_menu(bech32_pubkey: String) -> some View {
|
||||||
return self.contextMenu {
|
return self.contextMenu {
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ struct BuilderEventView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard nostr_event.known_kind == .text else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if event != nil {
|
if event != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -78,8 +74,8 @@ struct BuilderEventView: View {
|
|||||||
let thread = ThreadModel(event: ev, damus_state: damus)
|
let thread = ThreadModel(event: ev, damus_state: damus)
|
||||||
let dest = ThreadView(state: damus, thread: thread)
|
let dest = ThreadView(state: damus, thread: thread)
|
||||||
NavigationLink(destination: dest) {
|
NavigationLink(destination: dest) {
|
||||||
EmbeddedEventView(damus_state: damus, event: event)
|
EventView(damus: damus, event: event, options: .embedded)
|
||||||
.padding(8)
|
.padding([.top, .bottom], 8)
|
||||||
}.buttonStyle(.plain)
|
}.buttonStyle(.plain)
|
||||||
} else {
|
} else {
|
||||||
ProgressView().padding()
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,7 @@ struct MenuItems: View {
|
|||||||
let bookmarked = bookmarks.isBookmarked(event)
|
let bookmarked = bookmarks.isBookmarked(event)
|
||||||
self._isBookmarked = State(initialValue: bookmarked)
|
self._isBookmarked = State(initialValue: bookmarked)
|
||||||
|
|
||||||
let muted_thread = muted_threads.isMutedThread(event)
|
let muted_thread = muted_threads.isMutedThread(event, privkey: keypair.privkey)
|
||||||
self._isMutedThread = State(initialValue: muted_thread)
|
self._isMutedThread = State(initialValue: muted_thread)
|
||||||
|
|
||||||
self.bookmarks = bookmarks
|
self.bookmarks = bookmarks
|
||||||
@@ -96,7 +96,7 @@ struct MenuItems: View {
|
|||||||
if event.known_kind != .dm {
|
if event.known_kind != .dm {
|
||||||
Button {
|
Button {
|
||||||
self.muted_threads.updateMutedThread(event)
|
self.muted_threads.updateMutedThread(event)
|
||||||
let muted = self.muted_threads.isMutedThread(event)
|
let muted = self.muted_threads.isMutedThread(event, privkey: self.keypair.privkey)
|
||||||
isMutedThread = muted
|
isMutedThread = muted
|
||||||
} label: {
|
} label: {
|
||||||
let imageName = isMutedThread ? "speaker" : "speaker.slash"
|
let imageName = isMutedThread ? "speaker" : "speaker.slash"
|
||||||
|
|||||||
@@ -10,15 +10,13 @@ import SwiftUI
|
|||||||
struct MutedEventView: View {
|
struct MutedEventView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let scroller: ScrollViewProxy?
|
|
||||||
|
|
||||||
let selected: Bool
|
let selected: Bool
|
||||||
@State var shown: 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.damus_state = damus_state
|
||||||
self.event = event
|
self.event = event
|
||||||
self.scroller = scroller
|
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
self._shown = State(initialValue: should_show_event(contacts: damus_state.contacts, ev: event))
|
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 {
|
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)
|
.frame(width: .infinity, height: 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EventViewOptions: OptionSet {
|
struct EventViewOptions: OptionSet {
|
||||||
let rawValue: UInt8
|
let rawValue: UInt32
|
||||||
|
|
||||||
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
|
||||||
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
|
||||||
static let no_images = EventViewOptions(rawValue: 1 << 2)
|
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 truncate_content = EventViewOptions(rawValue: 1 << 4)
|
||||||
static let pad_content = EventViewOptions(rawValue: 1 << 5)
|
static let pad_content = EventViewOptions(rawValue: 1 << 5)
|
||||||
static let no_translate = EventViewOptions(rawValue: 1 << 6)
|
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 {
|
struct TextEvent: View {
|
||||||
@@ -37,14 +42,13 @@ struct TextEvent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.background(event_validity_color(event.validity))
|
|
||||||
.id(event.id)
|
.id(event.id)
|
||||||
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
||||||
.padding([.bottom], 2)
|
.padding([.bottom], 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pfp(is_anon: Bool) -> some 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 {
|
func TopPart(is_anon: Bool) -> some View {
|
||||||
@@ -83,7 +87,7 @@ struct TextEvent: View {
|
|||||||
|
|
||||||
EvBody(options: self.options.union(.pad_content))
|
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)
|
Mention(mention)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
@@ -136,6 +140,14 @@ struct TextEvent: View {
|
|||||||
return Rectangle().frame(height: 2).opacity(0)
|
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 {
|
var ThreadedStyle: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
|
|
||||||
@@ -152,7 +164,7 @@ struct TextEvent: View {
|
|||||||
ReplyPart
|
ReplyPart
|
||||||
EvBody(options: self.options)
|
EvBody(options: self.options)
|
||||||
|
|
||||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
if let mention = get_mention() {
|
||||||
Mention(mention)
|
Mention(mention)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,11 @@ struct FollowUserView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
UserView(damus_state: damus_state, pubkey: target.pubkey)
|
UserViewRow(damus_state: damus_state, pubkey: target.pubkey)
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture {
|
|
||||||
navigating = true
|
|
||||||
}
|
|
||||||
|
|
||||||
FollowButtonView(target: target, follows_you: false, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
FollowButtonView(target: target, follows_you: false, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct ImagePicker: UIViewControllerRepresentable {
|
|||||||
|
|
||||||
let sourceType: UIImagePickerController.SourceType
|
let sourceType: UIImagePickerController.SourceType
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
|
@Binding var image_upload_confirm: Bool
|
||||||
var imagesOnly: Bool = false
|
var imagesOnly: Bool = false
|
||||||
let onImagePicked: (URL) -> Void
|
let onImagePicked: (URL) -> Void
|
||||||
let onVideoPicked: (URL) -> Void
|
let onVideoPicked: (URL) -> Void
|
||||||
@@ -24,15 +25,18 @@ struct ImagePicker: UIViewControllerRepresentable {
|
|||||||
private let sourceType: UIImagePickerController.SourceType
|
private let sourceType: UIImagePickerController.SourceType
|
||||||
private let onImagePicked: (URL) -> Void
|
private let onImagePicked: (URL) -> Void
|
||||||
private let onVideoPicked: (URL) -> Void
|
private let onVideoPicked: (URL) -> Void
|
||||||
|
@Binding var image_upload_confirm: Bool
|
||||||
|
|
||||||
init(presentationMode: Binding<PresentationMode>,
|
init(presentationMode: Binding<PresentationMode>,
|
||||||
sourceType: UIImagePickerController.SourceType,
|
sourceType: UIImagePickerController.SourceType,
|
||||||
onImagePicked: @escaping (URL) -> Void,
|
onImagePicked: @escaping (URL) -> Void,
|
||||||
onVideoPicked: @escaping (URL) -> Void) {
|
onVideoPicked: @escaping (URL) -> Void,
|
||||||
|
image_upload_confirm: Binding<Bool>) {
|
||||||
_presentationMode = presentationMode
|
_presentationMode = presentationMode
|
||||||
self.sourceType = sourceType
|
self.sourceType = sourceType
|
||||||
self.onImagePicked = onImagePicked
|
self.onImagePicked = onImagePicked
|
||||||
self.onVideoPicked = onVideoPicked
|
self.onVideoPicked = onVideoPicked
|
||||||
|
self._image_upload_confirm = image_upload_confirm
|
||||||
}
|
}
|
||||||
|
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||||
@@ -51,9 +55,9 @@ struct ImagePicker: UIViewControllerRepresentable {
|
|||||||
onImagePicked(editedImageURL)
|
onImagePicked(editedImageURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
presentationMode.dismiss()
|
image_upload_confirm = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||||
presentationMode.dismiss()
|
presentationMode.dismiss()
|
||||||
}
|
}
|
||||||
@@ -98,7 +102,7 @@ struct ImagePicker: UIViewControllerRepresentable {
|
|||||||
onVideoPicked: { videoURL in
|
onVideoPicked: { videoURL in
|
||||||
// Handle the selected video URL
|
// Handle the selected video URL
|
||||||
onVideoPicked(videoURL)
|
onVideoPicked(videoURL)
|
||||||
})
|
}, image_upload_confirm: $image_upload_confirm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct MutelistView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(users, id: \.self) { pubkey in
|
List(users, id: \.self) { pubkey in
|
||||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
UserViewRow(damus_state: damus_state, pubkey: pubkey)
|
||||||
.id(pubkey)
|
.id(pubkey)
|
||||||
.swipeActions {
|
.swipeActions {
|
||||||
RemoveAction(pubkey: pubkey)
|
RemoveAction(pubkey: pubkey)
|
||||||
|
|||||||
@@ -7,11 +7,90 @@
|
|||||||
|
|
||||||
import SwiftUI
|
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 all
|
||||||
case zaps
|
case zaps
|
||||||
case replies
|
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 {
|
func is_other( item: NotificationItem) -> Bool {
|
||||||
return item.is_zap == nil && item.is_reply == nil
|
return item.is_zap == nil && item.is_reply == nil
|
||||||
}
|
}
|
||||||
@@ -31,34 +110,67 @@ enum NotificationFilterState: String {
|
|||||||
struct NotificationsView: View {
|
struct NotificationsView: View {
|
||||||
let state: DamusState
|
let state: DamusState
|
||||||
@ObservedObject var notifications: NotificationsModel
|
@ObservedObject var notifications: NotificationsModel
|
||||||
@State var filter_state: NotificationFilterState = .all
|
@StateObject var filter_state: NotificationFilter = NotificationFilter()
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
var body: some View {
|
var mystery: some View {
|
||||||
TabView(selection: $filter_state) {
|
VStack(spacing: 20) {
|
||||||
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
Text("Wake up, \(Profile.displayName(profile: state.profiles.lookup(id: state.pubkey), pubkey: state.pubkey).display_name)", comment: "Text telling the user to wake up, where the argument is their display name.")
|
||||||
Text("")
|
Text("You are dreaming...", comment: "Text telling the user that they are dreaming.")
|
||||||
.id("what")
|
|
||||||
|
|
||||||
NotificationTab(NotificationFilterState.all)
|
|
||||||
.tag(NotificationFilterState.all)
|
|
||||||
|
|
||||||
NotificationTab(NotificationFilterState.zaps)
|
|
||||||
.tag(NotificationFilterState.zaps)
|
|
||||||
|
|
||||||
NotificationTab(NotificationFilterState.replies)
|
|
||||||
.tag(NotificationFilterState.replies)
|
|
||||||
}
|
}
|
||||||
.onChange(of: filter_state) { val in
|
.id("what")
|
||||||
save_notification_filter_state(pubkey: state.pubkey, state: val)
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
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
|
||||||
|
|
||||||
|
NotificationTab(
|
||||||
|
NotificationFilter(
|
||||||
|
state: .all,
|
||||||
|
fine_filter: filter_state.fine_filter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.tag(NotificationFilterState.all)
|
||||||
|
|
||||||
|
NotificationTab(
|
||||||
|
NotificationFilter(
|
||||||
|
state: .zaps,
|
||||||
|
fine_filter: filter_state.fine_filter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.tag(NotificationFilterState.zaps)
|
||||||
|
|
||||||
|
NotificationTab(
|
||||||
|
NotificationFilter(
|
||||||
|
state: .replies,
|
||||||
|
fine_filter: filter_state.fine_filter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.tag(NotificationFilterState.replies)
|
||||||
|
}
|
||||||
|
.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 {
|
.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) {
|
.safeAreaInset(edge: .top, spacing: 0) {
|
||||||
VStack(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.")
|
Text("All", comment: "Label for filter for all notifications.")
|
||||||
.tag(NotificationFilterState.all)
|
.tag(NotificationFilterState.all)
|
||||||
|
|
||||||
@@ -76,14 +188,14 @@ struct NotificationsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotificationTab(_ filter: NotificationFilterState) -> some View {
|
func NotificationTab(_ filter: NotificationFilter) -> some View {
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
Color.white.opacity(0)
|
Color.white.opacity(0)
|
||||||
.id("startblock")
|
.id("startblock")
|
||||||
.frame(height: 5)
|
.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)
|
NotificationItemView(state: state, item: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,30 +221,22 @@ struct NotificationsView: View {
|
|||||||
|
|
||||||
struct NotificationsView_Previews: PreviewProvider {
|
struct NotificationsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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 {
|
func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
|
||||||
return pk_setting_key(pubkey, key: "notification_filter_state")
|
for item in items {
|
||||||
}
|
// this is only valid depending on which tab we're looking at
|
||||||
|
if !state.filter(item) {
|
||||||
func load_notification_filter_state(pubkey: String) -> NotificationFilterState {
|
continue
|
||||||
let key = notification_filter_state_key(pubkey: pubkey)
|
}
|
||||||
|
|
||||||
guard let state_str = UserDefaults.standard.string(forKey: key) else {
|
if item.would_filter({ ev in FriendFilter.friends.filter(contacts: contacts, pubkey: ev.pubkey) }) {
|
||||||
return .all
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let state = NotificationFilterState(rawValue: state_str) else {
|
return false
|
||||||
return .all
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
// damus
|
||||||
//
|
//
|
||||||
// Created by Joel Klabo on 1/18/23.
|
// Created by Joel Klabo on 1/18/23.
|
||||||
+255
-64
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
enum NostrPostResult {
|
enum NostrPostResult {
|
||||||
case post(NostrPost)
|
case post(NostrPost)
|
||||||
@@ -14,6 +15,23 @@ enum NostrPostResult {
|
|||||||
|
|
||||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
let POST_PLACEHOLDER = NSLocalizedString("Type your 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 {
|
struct PostView: View {
|
||||||
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
@State var post: NSMutableAttributedString = NSMutableAttributedString()
|
||||||
@FocusState var focus: Bool
|
@FocusState var focus: Bool
|
||||||
@@ -21,13 +39,16 @@ struct PostView: View {
|
|||||||
@State var attach_media: Bool = false
|
@State var attach_media: Bool = false
|
||||||
@State var attach_camera: Bool = false
|
@State var attach_camera: Bool = false
|
||||||
@State var error: String? = nil
|
@State var error: String? = nil
|
||||||
|
@State var uploadedMedias: [UploadedMedia] = []
|
||||||
|
@State var image_upload_confirm: Bool = false
|
||||||
@State var originalReferences: [ReferencedId] = []
|
@State var originalReferences: [ReferencedId] = []
|
||||||
@State var references: [ReferencedId] = []
|
@State var references: [ReferencedId] = []
|
||||||
|
|
||||||
|
@State var mediaToUpload: MediaUpload? = nil
|
||||||
|
|
||||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||||
|
|
||||||
let replying_to: NostrEvent?
|
let action: PostAction
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@@ -47,7 +68,8 @@ struct PostView: View {
|
|||||||
|
|
||||||
func send_post() {
|
func send_post() {
|
||||||
var kind: NostrKind = .text
|
var kind: NostrKind = .text
|
||||||
if replying_to?.known_kind == .chat {
|
|
||||||
|
if case .replying_to(let ev) = action, ev.known_kind == .chat {
|
||||||
kind = .chat
|
kind = .chat
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,22 +79,27 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
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)
|
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||||
|
|
||||||
if let replying_to {
|
clear_draft()
|
||||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
|
||||||
} else {
|
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_post_empty: Bool {
|
var is_post_empty: Bool {
|
||||||
return post.string.allSatisfy { $0.isWhitespace }
|
return post.string.allSatisfy { $0.isWhitespace } && uploadedMedias.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
var ImageButton: some View {
|
var ImageButton: some View {
|
||||||
@@ -118,17 +145,67 @@ struct PostView: View {
|
|||||||
.clipShape(Capsule())
|
.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 {
|
var TextEntry: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
TextViewWrapper(attributedText: $post)
|
TextViewWrapper(attributedText: $post)
|
||||||
.focused($focus)
|
.focused($focus)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
.onChange(of: post) { _ in
|
.onChange(of: post) { p in
|
||||||
if let replying_to {
|
post_changed(post: p, media: uploadedMedias)
|
||||||
damus_state.drafts.replies[replying_to] = post
|
|
||||||
} else {
|
|
||||||
damus_state.drafts.post = post
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if post.string.isEmpty {
|
if post.string.isEmpty {
|
||||||
@@ -168,29 +245,20 @@ struct PostView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
func append_url(_ url: String) {
|
|
||||||
let uploadedImageURL = NSMutableAttributedString(string: url)
|
|
||||||
let combinedAttributedString = NSMutableAttributedString()
|
|
||||||
combinedAttributedString.append(post)
|
|
||||||
if !post.string.hasSuffix(" ") {
|
|
||||||
combinedAttributedString.append(NSAttributedString(string: " "))
|
|
||||||
}
|
|
||||||
combinedAttributedString.append(uploadedImageURL)
|
|
||||||
|
|
||||||
// make sure we have a space at the end
|
|
||||||
combinedAttributedString.append(NSAttributedString(string: " "))
|
|
||||||
post = combinedAttributedString
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_upload(media: MediaUpload) {
|
func handle_upload(media: MediaUpload) {
|
||||||
let uploader = get_media_uploader(damus_state.pubkey)
|
let uploader = get_media_uploader(damus_state.pubkey)
|
||||||
|
|
||||||
Task.init {
|
Task.init {
|
||||||
|
let img = getImage(media: media)
|
||||||
let res = await image_upload.start(media: media, uploader: uploader)
|
let res = await image_upload.start(media: media, uploader: uploader)
|
||||||
|
|
||||||
switch res {
|
switch res {
|
||||||
case .success(let url):
|
case .success(let url):
|
||||||
append_url(url)
|
guard let url = URL(string: url) else {
|
||||||
|
self.error = "Error uploading image :("
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let uploadedMedia = UploadedMedia(localURL: media.localURL, uploadedURL: url, representingImage: img)
|
||||||
|
uploadedMedias.append(uploadedMedia)
|
||||||
|
|
||||||
case .failed(let error):
|
case .failed(let error):
|
||||||
if let error {
|
if let error {
|
||||||
@@ -203,29 +271,50 @@ 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 {
|
var body: some View {
|
||||||
GeometryReader { (deviceSize: GeometryProxy) in
|
GeometryReader { (deviceSize: GeometryProxy) in
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
|
||||||
let searching = get_searching_string(post.string)
|
let searching = get_searching_string(post.string)
|
||||||
|
|
||||||
TopBar
|
TopBar
|
||||||
|
|
||||||
ScrollViewReader { scroller in
|
ScrollViewReader { scroller in
|
||||||
ScrollView {
|
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)
|
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
|
||||||
HStack(alignment: .top) {
|
Editor(deviceSize: deviceSize)
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
|
||||||
|
|
||||||
TextEntry
|
|
||||||
}
|
|
||||||
.frame(height: deviceSize.size.height*0.78)
|
|
||||||
.id("post")
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
}
|
||||||
.frame(maxHeight: searching == nil ? .infinity : 70)
|
.frame(maxHeight: searching == nil ? .infinity : 70)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
@@ -247,31 +336,41 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $attach_media) {
|
.sheet(isPresented: $attach_media) {
|
||||||
ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey) { img in
|
ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
||||||
handle_upload(media: .image(img))
|
self.mediaToUpload = .image(img)
|
||||||
} onVideoPicked: { url in
|
} onVideoPicked: { url in
|
||||||
handle_upload(media: .video(url))
|
self.mediaToUpload = .video(url)
|
||||||
|
}
|
||||||
|
.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)
|
||||||
|
self.attach_media = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $attach_camera) {
|
.sheet(isPresented: $attach_camera) {
|
||||||
ImagePicker(sourceType: .camera, pubkey: damus_state.pubkey) { img in
|
// image_upload_confirm isn't handled here, I don't know we need to display it here too tbh
|
||||||
|
ImagePicker(sourceType: .camera, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
||||||
handle_upload(media: .image(img))
|
handle_upload(media: .image(img))
|
||||||
} onVideoPicked: { url in
|
} onVideoPicked: { url in
|
||||||
handle_upload(media: .video(url))
|
handle_upload(media: .video(url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
if let replying_to {
|
load_draft()
|
||||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
|
||||||
|
switch action {
|
||||||
|
case .replying_to(let replying_to):
|
||||||
|
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||||
originalReferences = references
|
originalReferences = references
|
||||||
if damus_state.drafts.replies[replying_to] == nil {
|
case .quoting(let quoting):
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||||
}
|
originalReferences = references
|
||||||
if let p = damus_state.drafts.replies[replying_to] {
|
case .posting:
|
||||||
post = p
|
break
|
||||||
}
|
|
||||||
} else {
|
|
||||||
post = damus_state.drafts.post
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
@@ -279,10 +378,8 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
if isEmpty {
|
||||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
clear_draft()
|
||||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
||||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.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: {
|
.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: {
|
||||||
@@ -320,6 +417,100 @@ func get_searching_string(_ post: String) -> String? {
|
|||||||
|
|
||||||
struct PostView_Previews: PreviewProvider {
|
struct PostView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PostView(replying_to: nil, damus_state: test_damus_state())
|
PostView(action: .posting, damus_state: test_damus_state())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PVImageCarouselView: View {
|
||||||
|
@Binding var media: [UploadedMedia]
|
||||||
|
|
||||||
|
let deviceWidth: CGFloat
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack {
|
||||||
|
ForEach(media.map({$0.representingImage}), id: \.self) { image in
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
Image(uiImage: image)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func getImage(media: MediaUpload) -> UIImage {
|
||||||
|
var uiimage: UIImage = UIImage()
|
||||||
|
if media.is_image {
|
||||||
|
// fetch the image data
|
||||||
|
if let data = try? Data(contentsOf: media.localURL) {
|
||||||
|
uiimage = UIImage(data: data) ?? UIImage()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let asset = AVURLAsset(url: media.localURL)
|
||||||
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
|
generator.appliesPreferredTrackTransform = true
|
||||||
|
let time = CMTimeMake(value: 1, timescale: 60) // get the thumbnail image at the 1st second
|
||||||
|
do {
|
||||||
|
let cgImage = try generator.copyCGImage(at: time, actualTime: nil)
|
||||||
|
uiimage = UIImage(cgImage: cgImage)
|
||||||
|
} catch {
|
||||||
|
print("No thumbnail: \(error)")
|
||||||
|
}
|
||||||
|
// create a play icon on the top to differentiate if media upload is image or a video, gif is an image
|
||||||
|
let playIcon = UIImage(systemName: "play.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal)
|
||||||
|
let size = uiimage.size
|
||||||
|
let scale = UIScreen.main.scale
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||||
|
uiimage.draw(at: .zero)
|
||||||
|
let playIconSize = CGSize(width: 60, height: 60)
|
||||||
|
let playIconOrigin = CGPoint(x: (size.width - playIconSize.width) / 2, y: (size.height - playIconSize.height) / 2)
|
||||||
|
playIcon?.draw(in: CGRect(origin: playIconOrigin, size: playIconSize))
|
||||||
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
uiimage = newImage ?? UIImage()
|
||||||
|
}
|
||||||
|
return uiimage
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UploadedMedia: Equatable {
|
||||||
|
let localURL: URL
|
||||||
|
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 name: String
|
||||||
@State var ln: String
|
@State var ln: String
|
||||||
@State var website: String
|
@State var website: String
|
||||||
|
let profile: Profile?
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@@ -73,6 +74,7 @@ struct EditMetadataView: View {
|
|||||||
init (damus_state: DamusState) {
|
init (damus_state: DamusState) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
|
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||||
|
self.profile = data
|
||||||
|
|
||||||
_name = State(initialValue: data?.name ?? "")
|
_name = State(initialValue: data?.name ?? "")
|
||||||
_display_name = State(initialValue: data?.display_name ?? "")
|
_display_name = State(initialValue: data?.display_name ?? "")
|
||||||
@@ -85,27 +87,31 @@ struct EditMetadataView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func imageBorderColor() -> Color {
|
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() {
|
func save() {
|
||||||
let metadata = NostrMetadata(
|
let profile = to_profile()
|
||||||
display_name: display_name,
|
guard let metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
|
||||||
name: name,
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
damus_state.postbox.send(metadata_ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_ln_valid(ln: String) -> Bool {
|
func is_ln_valid(ln: String) -> Bool {
|
||||||
@@ -18,7 +18,8 @@ struct EditProfilePictureControl: View {
|
|||||||
|
|
||||||
@State private var show_camera = false
|
@State private var show_camera = false
|
||||||
@State private var show_library = false
|
@State private var show_library = false
|
||||||
|
@State var image_upload_confirm: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Menu {
|
Menu {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
@@ -44,14 +45,16 @@ struct EditProfilePictureControl: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_camera) {
|
.sheet(isPresented: $show_camera) {
|
||||||
ImagePicker(sourceType: .camera, pubkey: pubkey, imagesOnly: true) { img in
|
// The alert may not be required for the profile pic upload case. Not showing the confirm check alert for this scenario
|
||||||
|
ImagePicker(sourceType: .camera, pubkey: pubkey, image_upload_confirm: $image_upload_confirm, imagesOnly: true) { img in
|
||||||
handle_upload(media: .image(img))
|
handle_upload(media: .image(img))
|
||||||
} onVideoPicked: { url in
|
} onVideoPicked: { url in
|
||||||
print("Cannot upload videos as profile image")
|
print("Cannot upload videos as profile image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $show_library) {
|
.sheet(isPresented: $show_library) {
|
||||||
ImagePicker(sourceType: .photoLibrary, pubkey: pubkey, imagesOnly: true) { img in
|
// The alert may not be required for the profile pic upload case. Not showing the confirm check alert for this scenario
|
||||||
|
ImagePicker(sourceType: .photoLibrary, pubkey: pubkey, image_upload_confirm: $image_upload_confirm, imagesOnly: true) { img in
|
||||||
handle_upload(media: .image(img))
|
handle_upload(media: .image(img))
|
||||||
} onVideoPicked: { url in
|
} onVideoPicked: { url in
|
||||||
print("Cannot upload videos as profile image")
|
print("Cannot upload videos as profile image")
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ struct EventProfileName: View {
|
|||||||
self.size = size
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
var friend_icon: String? {
|
var friend_type: FriendType? {
|
||||||
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
|
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
var current_nip05: NIP05? {
|
var current_nip05: NIP05? {
|
||||||
@@ -50,7 +50,15 @@ struct EventProfileName: View {
|
|||||||
var current_display_name: DisplayName {
|
var current_display_name: DisplayName {
|
||||||
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
|
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 {
|
var body: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
switch current_display_name {
|
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)
|
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: false, clickable: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let frend = friend_icon, current_nip05 == nil {
|
if current_nip05 == nil, let frend = friend_type {
|
||||||
Label("", systemImage: frend)
|
FriendIcon(friend: frend)
|
||||||
.foregroundColor(.gray)
|
}
|
||||||
.font(.footnote)
|
|
||||||
|
if onlyzapper {
|
||||||
|
Image("zap-hashtag")
|
||||||
|
.frame(width: 14, height: 14)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||||
|
|||||||
@@ -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 state: DamusState
|
||||||
let is_anon: Bool
|
let is_anon: Bool
|
||||||
let pubkey: String
|
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.state = state
|
||||||
self.is_anon = event_is_anonymous(ev: event)
|
self.is_anon = event_is_anonymous(ev: event)
|
||||||
self.pubkey = pubkey
|
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.state = state
|
||||||
self.is_anon = is_anon
|
self.is_anon = is_anon
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -29,10 +32,10 @@ struct MaybeAnonPfpView: View {
|
|||||||
if is_anon {
|
if is_anon {
|
||||||
Image(systemName: "person.fill.questionmark")
|
Image(systemName: "person.fill.questionmark")
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.frame(width: PFP_SIZE, height: PFP_SIZE)
|
.frame(width: size, height: size)
|
||||||
} else {
|
} else {
|
||||||
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
|
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 {
|
struct MaybeAnonPfpView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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
|
import SwiftUI
|
||||||
|
|
||||||
func get_friend_icon(contacts: Contacts, pubkey: String, show_confirmed: Bool) -> String? {
|
enum FriendType {
|
||||||
if !show_confirmed {
|
case friend
|
||||||
return nil
|
case fof
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func get_friend_type(contacts: Contacts, pubkey: String) -> FriendType? {
|
||||||
if contacts.is_friend_or_self(pubkey) {
|
if contacts.is_friend_or_self(pubkey) {
|
||||||
return "person.fill.checkmark"
|
return .friend
|
||||||
}
|
}
|
||||||
|
|
||||||
if contacts.is_friend_of_friend(pubkey) {
|
if contacts.is_friend_of_friend(pubkey) {
|
||||||
return "person.fill.and.arrow.left.and.arrow.right"
|
return .fof
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -53,8 +54,8 @@ struct ProfileName: View {
|
|||||||
self.show_nip5_domain = show_nip5_domain
|
self.show_nip5_domain = show_nip5_domain
|
||||||
}
|
}
|
||||||
|
|
||||||
var friend_icon: String? {
|
var friend_type: FriendType? {
|
||||||
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
|
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
var current_nip05: NIP05? {
|
var current_nip05: NIP05? {
|
||||||
@@ -69,6 +70,14 @@ struct ProfileName: View {
|
|||||||
return prefix == "@" ? current_display_name.username : current_display_name.display_name
|
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 {
|
var body: some View {
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Text(verbatim: "\(prefix)\(name_choice)")
|
Text(verbatim: "\(prefix)\(name_choice)")
|
||||||
@@ -77,9 +86,12 @@ struct ProfileName: View {
|
|||||||
if let nip05 = current_nip05 {
|
if let nip05 = current_nip05 {
|
||||||
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, clickable: true)
|
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, clickable: true)
|
||||||
}
|
}
|
||||||
if let friend = friend_icon, current_nip05 == nil {
|
if let friend = friend_type, current_nip05 == nil {
|
||||||
Image(systemName: friend)
|
FriendIcon(friend: friend)
|
||||||
.foregroundColor(.gray)
|
}
|
||||||
|
if onlyzapper {
|
||||||
|
Image("zap-hashtag")
|
||||||
|
.frame(width: 14, height: 14)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.profile_updated)) { notif in
|
.onReceive(handle_notify(.profile_updated)) { notif in
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ struct ProfileView: View {
|
|||||||
@State var showing_select_wallet: Bool = false
|
@State var showing_select_wallet: Bool = false
|
||||||
@State var is_zoomed: Bool = false
|
@State var is_zoomed: Bool = false
|
||||||
@State var show_share_sheet: Bool = false
|
@State var show_share_sheet: Bool = false
|
||||||
|
@State var show_qr_code: Bool = false
|
||||||
@State var action_sheet_presented: Bool = false
|
@State var action_sheet_presented: Bool = false
|
||||||
@State var filter_state : FilterState = .posts
|
@State var filter_state : FilterState = .posts
|
||||||
@State var yOffset: CGFloat = 0
|
@State var yOffset: CGFloat = 0
|
||||||
@@ -213,6 +214,10 @@ struct ProfileView: View {
|
|||||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||||
show_share_sheet = true
|
show_share_sheet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button(NSLocalizedString("QR Code", comment: "Button to view profile's qr code.")) {
|
||||||
|
show_qr_code = true
|
||||||
|
}
|
||||||
|
|
||||||
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
||||||
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
||||||
@@ -240,20 +245,33 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lnButton(lnurl: String, profile: Profile) -> some 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 {
|
if damus_state.settings.show_wallet_selector {
|
||||||
showing_select_wallet = true
|
showing_select_wallet = true
|
||||||
} else {
|
} else {
|
||||||
open_with_wallet(wallet: damus_state.settings.default_wallet.model, invoice: lnurl)
|
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)
|
.profile_button_style(scheme: colorScheme)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
if profile.reactions == false {
|
||||||
UIPasteboard.general.string = profile.lnurl ?? ""
|
Text("OnlyZaps Enabled")
|
||||||
} label: {
|
}
|
||||||
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,8 +284,7 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
var dmButton: some View {
|
var dmButton: some View {
|
||||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||||
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
let dmview = DMChatView(damus_state: damus_state, dms: dm_model)
|
||||||
.environmentObject(dm_model)
|
|
||||||
return NavigationLink(destination: dmview) {
|
return NavigationLink(destination: dmview) {
|
||||||
Image(systemName: "bubble.left.circle")
|
Image(systemName: "bubble.left.circle")
|
||||||
.profile_button_style(scheme: colorScheme)
|
.profile_button_style(scheme: colorScheme)
|
||||||
@@ -465,6 +482,9 @@ struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.fullScreenCover(isPresented: $show_qr_code) {
|
||||||
|
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import CoreImage.CIFilterBuiltins
|
|||||||
|
|
||||||
struct QRCodeView: View {
|
struct QRCodeView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
@State var pubkey: String
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
var maybe_key: String? {
|
var maybe_key: String? {
|
||||||
guard let key = bech32_pubkey(damus_state.pubkey) else {
|
guard let key = bech32_pubkey(pubkey) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +40,11 @@ struct QRCodeView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
|
||||||
|
|
||||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
|
||||||
|
if (damus_state.profiles.lookup(id: pubkey)?.picture) != nil {
|
||||||
|
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
||||||
.padding(.top, 50)
|
.padding(.top, 50)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "person.fill")
|
Image(systemName: "person.fill")
|
||||||
@@ -119,6 +121,6 @@ struct QRCodeView: View {
|
|||||||
|
|
||||||
struct QRCodeView_Previews: PreviewProvider {
|
struct QRCodeView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
QRCodeView(damus_state: test_damus_state())
|
QRCodeView(damus_state: test_damus_state(), pubkey: test_event.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ struct RelayDetailView: View {
|
|||||||
|
|
||||||
if let pubkey = nip11.pubkey {
|
if let pubkey = nip11.pubkey {
|
||||||
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
||||||
UserView(damus_state: state, pubkey: pubkey)
|
UserViewRow(damus_state: state, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// SignalView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2023-04-14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SignalView: View {
|
||||||
|
let state: DamusState
|
||||||
|
@ObservedObject var signal: SignalModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if signal.signal != signal.max_signal {
|
||||||
|
NavigationLink(destination: RelayConfigView(state: state)) {
|
||||||
|
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SignalView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
SignalView(state: test_damus_state(), signal: SignalModel(signal: 5, max_signal: 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -217,3 +217,7 @@ struct SaveKeysView_Previews: PreviewProvider {
|
|||||||
SaveKeysView(account: model)
|
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 SearchHomeView: View {
|
|||||||
damus: damus_state,
|
damus: damus_state,
|
||||||
show_friend_icon: true,
|
show_friend_icon: true,
|
||||||
filter: {
|
filter: {
|
||||||
if damus_state.muted_threads.isMutedThread($0) {
|
if damus_state.muted_threads.isMutedThread($0, privkey: self.damus_state.keypair.privkey) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
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
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
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
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,29 @@ struct ZapSettingsView: View {
|
|||||||
|
|
||||||
@State var default_zap_amount: String
|
@State var default_zap_amount: String
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
init(pubkey: String, settings: UserSettingsStore) {
|
init(pubkey: String, settings: UserSettingsStore) {
|
||||||
self.pubkey = pubkey
|
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)
|
_default_zap_amount = State(initialValue: zap_amt)
|
||||||
self._settings = ObservedObject(initialValue: settings)
|
self._settings = ObservedObject(initialValue: settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
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)
|
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"),
|
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)
|
Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Default Zap Amount in sats") {
|
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Title for section in zap settings that controls the default zap amount in sats.")) {
|
||||||
TextField(String("1000"), text: $default_zap_amount)
|
TextField(fallback_zap_amount.formatted(), text: $default_zap_amount)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
.onReceive(Just(default_zap_amount)) { newValue in
|
.onReceive(Just(default_zap_amount)) { newValue in
|
||||||
if let parsed = handle_string_amount(new_value: newValue) {
|
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)
|
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
|
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ struct SideMenuView: View {
|
|||||||
.font(.title)
|
.font(.title)
|
||||||
.foregroundColor(textColor())
|
.foregroundColor(textColor())
|
||||||
}).fullScreenCover(isPresented: $showQRCode) {
|
}).fullScreenCover(isPresented: $showQRCode) {
|
||||||
QRCodeView(damus_state: damus_state)
|
QRCodeView(damus_state: damus_state, pubkey: damus_state.pubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, verticalSpacing)
|
.padding(.top, verticalSpacing)
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ struct ThreadView: View {
|
|||||||
ForEach(parent_events, id: \.id) { parent_event in
|
ForEach(parent_events, id: \.id) { parent_event in
|
||||||
MutedEventView(damus_state: state,
|
MutedEventView(damus_state: state,
|
||||||
event: parent_event,
|
event: parent_event,
|
||||||
scroller: reader,
|
|
||||||
selected: false)
|
selected: false)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
@@ -56,7 +55,6 @@ struct ThreadView: View {
|
|||||||
MutedEventView(
|
MutedEventView(
|
||||||
damus_state: state,
|
damus_state: state,
|
||||||
event: self.thread.event,
|
event: self.thread.event,
|
||||||
scroller: reader,
|
|
||||||
selected: true
|
selected: true
|
||||||
)
|
)
|
||||||
.id(self.thread.event.id)
|
.id(self.thread.event.id)
|
||||||
@@ -65,7 +63,6 @@ struct ThreadView: View {
|
|||||||
MutedEventView(
|
MutedEventView(
|
||||||
damus_state: state,
|
damus_state: state,
|
||||||
event: child_event,
|
event: child_event,
|
||||||
scroller: nil,
|
|
||||||
selected: false
|
selected: false
|
||||||
)
|
)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ struct ZapAmountItem: Identifiable, Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
|
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: "🤙")
|
return ZapAmountItem(amount: def, icon: "🤙")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,13 +181,17 @@ struct CustomizeZapView: View {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Section(content: {
|
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)
|
.keyboardType(.numberPad)
|
||||||
.onReceive(Just(custom_amount)) { newValue in
|
.onReceive(Just(custom_amount)) { newValue in
|
||||||
|
|
||||||
if let parsed = handle_string_amount(new_value: newValue) {
|
if let parsed = handle_string_amount(new_value: newValue) {
|
||||||
self.custom_amount = String(parsed)
|
self.custom_amount = parsed.formatted()
|
||||||
self.custom_amount_sats = parsed
|
self.custom_amount_sats = parsed
|
||||||
|
} else {
|
||||||
|
self.custom_amount = ""
|
||||||
|
self.custom_amount_sats = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, header: {
|
}, header: {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -39,7 +39,7 @@
|
|||||||
<key>many</key>
|
<key>many</key>
|
||||||
<string>Followers</string>
|
<string>Followers</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>Sledují</string>
|
<string>Sledující</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>following_count</key>
|
<key>following_count</key>
|
||||||
|
|||||||
@@ -9,6 +9,16 @@
|
|||||||
<string>applinks:damus.io</string>
|
<string>applinks:damus.io</string>
|
||||||
<string>webcredentials:damus.io</string>
|
<string>webcredentials:damus.io</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.personal-information.photos-library</key>
|
||||||
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(AppIdentifierPrefix)com.jb55.damus2</string>
|
<string>$(AppIdentifierPrefix)com.jb55.damus2</string>
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
// Display the notification in the foreground
|
// Display the notification in the foreground
|
||||||
completionHandler([.banner, .list, .sound, .badge])
|
completionHandler([.banner, .list, .sound, .badge])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
|
let userInfo = response.notification.request.content.userInfo
|
||||||
|
let notification = LossyLocalNotification.from_user_info(user_info: userInfo)
|
||||||
|
notify(.local_notification, notification)
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func needs_setup() -> Keypair? {
|
func needs_setup() -> Keypair? {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -61,9 +61,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<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>
|
<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>
|
||||||
</dict>
|
</dict>
|
||||||
<key>reacted_your_post_3</key>
|
<key>reacted_your_post_3</key>
|
||||||
@@ -157,9 +157,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<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>
|
<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>
|
||||||
</dict>
|
</dict>
|
||||||
<key>reposted_your_post_3</key>
|
<key>reposted_your_post_3</key>
|
||||||
@@ -269,9 +269,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<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>
|
<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>
|
||||||
</dict>
|
</dict>
|
||||||
<key>zapped_your_post_3</key>
|
<key>zapped_your_post_3</key>
|
||||||
|
|||||||
Binary file not shown.
@@ -47,7 +47,7 @@
|
|||||||
<key>one</key>
|
<key>one</key>
|
||||||
<string>Ακόλουθος</string>
|
<string>Ακόλουθος</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>Ακολουθείτε</string>
|
<string>Ακολουθεί</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>reacted_tagged_in_3</key>
|
<key>reacted_tagged_in_3</key>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<key>one</key>
|
<key>one</key>
|
||||||
<string>Διακομιστής Relay</string>
|
<string>Διακομιστής Relay</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>Διακομιστές Relays</string>
|
<string>Relays</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>replying_to_two_and_others</key>
|
<key>replying_to_two_and_others</key>
|
||||||
@@ -141,9 +141,9 @@
|
|||||||
<key>NSStringFormatValueTypeKey</key>
|
<key>NSStringFormatValueTypeKey</key>
|
||||||
<string>d</string>
|
<string>d</string>
|
||||||
<key>one</key>
|
<key>one</key>
|
||||||
<string>Απάντηση προς %2$@, %3$@ & %1$d άλλον</string>
|
<string>Απάντηση προς %2$@, %3$@ & %1$d ακόμα</string>
|
||||||
<key>other</key>
|
<key>other</key>
|
||||||
<string>Απάντηση προς %2$@, %3$@ & %1$d άλλους</string>
|
<string>Απάντηση προς %2$@, %3$@ & %1$d ακόμα</string>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>reposted_tagged_in_3</key>
|
<key>reposted_tagged_in_3</key>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
<trans-unit id="%@" xml:space="preserve">
|
<trans-unit id="%@" xml:space="preserve">
|
||||||
<source>%@</source>
|
<source>%@</source>
|
||||||
<target>%@</target>
|
<target>%@</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>DM by heading in local notification</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="%@ %@" xml:space="preserve">
|
<trans-unit id="%@ %@" xml:space="preserve">
|
||||||
<source>%@ %@</source>
|
<source>%@ %@</source>
|
||||||
@@ -188,12 +188,18 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<trans-unit id="Appearance" xml:space="preserve">
|
<trans-unit id="Appearance" xml:space="preserve">
|
||||||
<source>Appearance</source>
|
<source>Appearance</source>
|
||||||
<target>Appearance</target>
|
<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>
|
||||||
<trans-unit id="Are you sure you want to repost this?" xml:space="preserve">
|
<trans-unit id="Are you lost?" xml:space="preserve">
|
||||||
<source>Are you sure you want to repost this?</source>
|
<source>Are you lost?</source>
|
||||||
<target>Are you sure you want to repost this?</target>
|
<target>Are you lost?</target>
|
||||||
<note>Alert message to ask if user wants to repost a post.</note>
|
<note>Text asking the user if they are lost in the app.</note>
|
||||||
|
</trans-unit>
|
||||||
|
<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>
|
||||||
<trans-unit id="Automatically translate notes" xml:space="preserve">
|
<trans-unit id="Automatically translate notes" xml:space="preserve">
|
||||||
<source>Automatically translate notes</source>
|
<source>Automatically translate notes</source>
|
||||||
@@ -244,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 a repost.
|
||||||
Button to cancel out of alert that creates a new mutelist.
|
Button to cancel out of alert that creates a new mutelist.
|
||||||
Button to cancel out of posting a note.
|
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 out of view adding user inputted relay.
|
||||||
|
Button to cancel the upload.
|
||||||
Cancel deleting the user.
|
Cancel deleting the user.
|
||||||
Cancel out of logging out the user.</note>
|
Cancel out of logging out the user.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -349,6 +355,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<target>Copy Text</target>
|
<target>Copy Text</target>
|
||||||
<note>Context menu option for copying the text from an note.</note>
|
<note>Context menu option for copying the text from an note.</note>
|
||||||
</trans-unit>
|
</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">
|
<trans-unit id="Copy User Pubkey" xml:space="preserve">
|
||||||
<source>Copy User Pubkey</source>
|
<source>Copy User Pubkey</source>
|
||||||
<target>Copy User Pubkey</target>
|
<target>Copy User Pubkey</target>
|
||||||
@@ -399,11 +410,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<target>Custom Zap Amount</target>
|
<target>Custom Zap Amount</target>
|
||||||
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
|
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
|
||||||
</trans-unit>
|
</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">
|
<trans-unit id="DMs" xml:space="preserve">
|
||||||
<source>DMs</source>
|
<source>DMs</source>
|
||||||
<target>DMs</target>
|
<target>DMs</target>
|
||||||
@@ -430,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">
|
<trans-unit id="Default Zap Amount in sats" xml:space="preserve">
|
||||||
<source>Default Zap Amount in sats</source>
|
<source>Default Zap Amount in sats</source>
|
||||||
<target>Default Zap Amount in sats</target>
|
<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>
|
||||||
<trans-unit id="Delete" xml:space="preserve">
|
<trans-unit id="Delete" xml:space="preserve">
|
||||||
<source>Delete</source>
|
<source>Delete</source>
|
||||||
@@ -622,7 +628,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<trans-unit id="Keys" xml:space="preserve">
|
<trans-unit id="Keys" xml:space="preserve">
|
||||||
<source>Keys</source>
|
<source>Keys</source>
|
||||||
<target>Keys</target>
|
<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>
|
||||||
<trans-unit id="Left Handed" xml:space="preserve">
|
<trans-unit id="Left Handed" xml:space="preserve">
|
||||||
<source>Left Handed</source>
|
<source>Left Handed</source>
|
||||||
@@ -721,8 +728,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<source>Mute</source>
|
<source>Mute</source>
|
||||||
<target>Mute</target>
|
<target>Mute</target>
|
||||||
<note>Alert button to mute a user.
|
<note>Alert button to mute a user.
|
||||||
Button to mute a profile.
|
Button to mute a profile.</note>
|
||||||
Context menu option for muting users.</note>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="Mute %@?" xml:space="preserve">
|
<trans-unit id="Mute %@?" xml:space="preserve">
|
||||||
<source>Mute %@?</source>
|
<source>Mute %@?</source>
|
||||||
@@ -732,7 +738,13 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<trans-unit id="Mute User" xml:space="preserve">
|
<trans-unit id="Mute User" xml:space="preserve">
|
||||||
<source>Mute User</source>
|
<source>Mute User</source>
|
||||||
<target>Mute User</target>
|
<target>Mute User</target>
|
||||||
<note>Title of alert for muting a user.</note>
|
<note>Context menu option for muting users.
|
||||||
|
Title of alert for muting a user.</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="Mute conversation" xml:space="preserve">
|
||||||
|
<source>Mute conversation</source>
|
||||||
|
<target>Mute conversation</target>
|
||||||
|
<note>Context menu option for muting a conversation.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="Muted" xml:space="preserve">
|
<trans-unit id="Muted" xml:space="preserve">
|
||||||
<source>Muted</source>
|
<source>Muted</source>
|
||||||
@@ -749,6 +761,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<target>NIP-05 Verification</target>
|
<target>NIP-05 Verification</target>
|
||||||
<note>Label for NIP-05 Verification section of user profile form.</note>
|
<note>Label for NIP-05 Verification section of user profile form.</note>
|
||||||
</trans-unit>
|
</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">
|
<trans-unit id="No" xml:space="preserve">
|
||||||
<source>No</source>
|
<source>No</source>
|
||||||
<target>No</target>
|
<target>No</target>
|
||||||
@@ -779,6 +796,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<target>Nothing to see here. Check back later!</target>
|
<target>Nothing to see here. Check back later!</target>
|
||||||
<note>Indicates that there are no notes in the timeline to view.</note>
|
<note>Indicates that there are no notes in the timeline to view.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Notification Dots" xml:space="preserve">
|
||||||
|
<source>Notification Dots</source>
|
||||||
|
<target>Notification Dots</target>
|
||||||
|
<note>Section header for notification indicator dot settings</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Notification Preference" xml:space="preserve">
|
<trans-unit id="Notification Preference" xml:space="preserve">
|
||||||
<source>Notification Preference</source>
|
<source>Notification Preference</source>
|
||||||
<target>Notification Preference</target>
|
<target>Notification Preference</target>
|
||||||
@@ -787,7 +809,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
|||||||
<trans-unit id="Notifications" xml:space="preserve">
|
<trans-unit id="Notifications" xml:space="preserve">
|
||||||
<source>Notifications</source>
|
<source>Notifications</source>
|
||||||
<target>Notifications</target>
|
<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>
|
||||||
<trans-unit id="Nudity or explicit content" xml:space="preserve">
|
<trans-unit id="Nudity or explicit content" xml:space="preserve">
|
||||||
<source>Nudity or explicit content</source>
|
<source>Nudity or explicit content</source>
|
||||||
@@ -913,6 +936,16 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
|||||||
<target>Public key</target>
|
<target>Public key</target>
|
||||||
<note>Label indicating that the text is a user's public account key.</note>
|
<note>Label indicating that the text is a user's public account key.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="QR Code" xml:space="preserve">
|
||||||
|
<source>QR Code</source>
|
||||||
|
<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">
|
<trans-unit id="Reactions" xml:space="preserve">
|
||||||
<source>Reactions</source>
|
<source>Reactions</source>
|
||||||
<target>Reactions</target>
|
<target>Reactions</target>
|
||||||
@@ -1001,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">
|
<trans-unit id="Repost" xml:space="preserve">
|
||||||
<source>Repost</source>
|
<source>Repost</source>
|
||||||
<target>Repost</target>
|
<target>Repost</target>
|
||||||
<note>Button to confirm reposting a post.
|
<note>Title of alert for confirming to repost a post.</note>
|
||||||
Title of alert for confirming to repost a post.</note>
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="Reposted" xml:space="preserve">
|
<trans-unit id="Reposted" xml:space="preserve">
|
||||||
<source>Reposted</source>
|
<source>Reposted</source>
|
||||||
@@ -1253,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">
|
<trans-unit id="Translation" xml:space="preserve">
|
||||||
<source>Translation</source>
|
<source>Translation</source>
|
||||||
<target>Translation</target>
|
<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>
|
||||||
<trans-unit id="Translations" xml:space="preserve">
|
<trans-unit id="Translations" xml:space="preserve">
|
||||||
<source>Translations</source>
|
<source>Translations</source>
|
||||||
@@ -1300,6 +1333,16 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
|||||||
<target>Universe 🛸</target>
|
<target>Universe 🛸</target>
|
||||||
<note>Toolbar label for the universal view where posts from all connected relay servers appear.</note>
|
<note>Toolbar label for the universal view where posts from all connected relay servers appear.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Unmute conversation" xml:space="preserve">
|
||||||
|
<source>Unmute conversation</source>
|
||||||
|
<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">
|
<trans-unit id="User has been muted" xml:space="preserve">
|
||||||
<source>User has been muted</source>
|
<source>User has been muted</source>
|
||||||
<target>User has been muted</target>
|
<target>User has been muted</target>
|
||||||
@@ -1339,10 +1382,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
|||||||
ARE YOU SURE YOU WANT TO CONTINUE?</target>
|
ARE YOU SURE YOU WANT TO CONTINUE?</target>
|
||||||
<note>Alert for deleting the users account.</note>
|
<note>Alert for deleting the users account.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Wake up, %@" xml:space="preserve">
|
||||||
|
<source>Wake up, %@</source>
|
||||||
|
<target>Wake up, %@</target>
|
||||||
|
<note>Text telling the user to wake up, where the argument is their display name.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Wallet" xml:space="preserve">
|
<trans-unit id="Wallet" xml:space="preserve">
|
||||||
<source>Wallet</source>
|
<source>Wallet</source>
|
||||||
<target>Wallet</target>
|
<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>
|
||||||
<trans-unit id="Website" xml:space="preserve">
|
<trans-unit id="Website" xml:space="preserve">
|
||||||
<source>Website</source>
|
<source>Website</source>
|
||||||
@@ -1374,6 +1423,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
|||||||
<target>Yes, Post with Private Key</target>
|
<target>Yes, Post with Private Key</target>
|
||||||
<note>Button to proceed with posting a note even though it looks like they might be posting a private key.</note>
|
<note>Button to proceed with posting a note even though it looks like they might be posting a private key.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="You are dreaming..." xml:space="preserve">
|
||||||
|
<source>You are dreaming...</source>
|
||||||
|
<target>You are dreaming...</target>
|
||||||
|
<note>Text telling the user that they are dreaming.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="You have no bookmarks yet, add them in the context menu" xml:space="preserve">
|
<trans-unit id="You have no bookmarks yet, add them in the context menu" xml:space="preserve">
|
||||||
<source>You have no bookmarks yet, add them in the context menu</source>
|
<source>You have no bookmarks yet, add them in the context menu</source>
|
||||||
<target>You have no bookmarks yet, add them in the context menu</target>
|
<target>You have no bookmarks yet, add them in the context menu</target>
|
||||||
@@ -1425,8 +1479,10 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
|||||||
<source>Zaps</source>
|
<source>Zaps</source>
|
||||||
<target>Zaps</target>
|
<target>Zaps</target>
|
||||||
<note>Navigation bar title for the Zaps view.
|
<note>Navigation bar title for the Zaps view.
|
||||||
|
Navigation title for zap settings.
|
||||||
Section header 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>
|
||||||
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
|
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
|
||||||
<source>https://example.com/pic.jpg</source>
|
<source>https://example.com/pic.jpg</source>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,366 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>collapsed_event_view_other_notes</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@NOTES@</string>
|
|
||||||
<key>NOTES</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>... %d autre note ...</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>... %d autres notes ...</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>... %d autres notes ...</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>followers_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@FOLLOWERS@</string>
|
|
||||||
<key>FOLLOWERS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Abonné</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Abonnés</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Abonnés</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>following_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@FOLLOWING@</string>
|
|
||||||
<key>FOLLOWING</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Abonnement</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Abonnements</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Abonnements</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reacted_tagged_in_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REACTED@</string>
|
|
||||||
<key>REACTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont réagi à une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à une note dans laquelle vous apparaissez</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reacted_your_post_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REACTED@</string>
|
|
||||||
<key>REACTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont réagi à votre note</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à votre note</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à votre note</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reacted_your_profile_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REACTED@</string>
|
|
||||||
<key>REACTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont réagi à votre profil</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à votre profil</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont réagi à votre profil</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reactions_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REACTIONS@</string>
|
|
||||||
<key>REACTIONS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Réaction</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Réactions</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Réactions</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>relays_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@RELAYS@</string>
|
|
||||||
<key>RELAYS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Relai</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Relais</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Relais</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>replying_to_two_and_others</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@OTHERS@</string>
|
|
||||||
<key>OTHERS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Réponse à %2$@, %3$@ & %1$d autre</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Réponse à %2$@, %3$@ & %1$d autres</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Réponse à %2$@, %3$@ & %1$d autres</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reposted_tagged_in_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REPOSTED@</string>
|
|
||||||
<key>REPOSTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont cité une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont cité une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont republié une note dans laquelle vous apparaissez</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reposted_your_post_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REPOSTED@</string>
|
|
||||||
<key>REPOSTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont cité votre note</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont cité votre note</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont republié votre note</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reposted_your_profile_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REPOSTED@</string>
|
|
||||||
<key>REPOSTED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont republié votre profile</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont republié votre profile</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont republié votre profile</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>reposts_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@REPOSTS@</string>
|
|
||||||
<key>REPOSTS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Republication</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Republications</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Republications</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>sats_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%1$#@SATS@</string>
|
|
||||||
<key>SATS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>@</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ sat</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ sats</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ sats</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zap_notification_no_message</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%1$#@NOTIFICATION@</string>
|
|
||||||
<key>NOTIFICATION</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>@</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Vous avez reçu %2$@ sat de %3$@</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Vous avez reçu %2$@ sats de %3$@</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Vous avez reçu %2$@ sats de %3$@</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zap_notification_with_message</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%1$#@NOTIFICATION@</string>
|
|
||||||
<key>NOTIFICATION</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>@</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>You received %2$@ sat from %3$@: "%4$@"</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>You received %2$@ sats from %3$@: "%4$@"</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zapped_tagged_in_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@ZAPPED@</string>
|
|
||||||
<key>ZAPPED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont zappé une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé une note dans laquelle vous apparaissez</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé une note dans laquelle vous apparaissez</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zapped_your_post_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@ZAPPED@</string>
|
|
||||||
<key>ZAPPED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont zappé votre note</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé votre note</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé votre note</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zapped_your_profile_3</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@ZAPPED@</string>
|
|
||||||
<key>ZAPPED</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>%2$@ et %1$d autre ont zappé votre profile</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé votre profile</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>%2$@ et %1$d autres ont zappé votre profile</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>zaps_count</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringLocalizedFormatKey</key>
|
|
||||||
<string>%#@ZAPS@</string>
|
|
||||||
<key>ZAPS</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSStringFormatSpecTypeKey</key>
|
|
||||||
<string>NSStringPluralRuleType</string>
|
|
||||||
<key>NSStringFormatValueTypeKey</key>
|
|
||||||
<string>d</string>
|
|
||||||
<key>one</key>
|
|
||||||
<string>Zap</string>
|
|
||||||
<key>many</key>
|
|
||||||
<string>Zaps</string>
|
|
||||||
<key>other</key>
|
|
||||||
<string>Zaps</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user