Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
73ac677711
|
|||
| 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,55 @@
|
||||
## [1.4.3-2] - 2023-04-17
|
||||
|
||||
### Added
|
||||
|
||||
- Add deep links for local notifications (Swift)
|
||||
- Banner Image Upload (Joel Klabo)
|
||||
- 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
|
||||
|
||||
### Added
|
||||
@@ -987,3 +1039,4 @@
|
||||
|
||||
|
||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||
|
||||
|
||||
+4
-5
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include "bech32.h"
|
||||
|
||||
typedef unsigned char u8;
|
||||
|
||||
@@ -32,8 +31,8 @@ static inline int is_invalid_url_ending(char c) {
|
||||
return c == '!' || c == '?' || c == ')' || c == '.' || c == ',' || c == ';';
|
||||
}
|
||||
|
||||
static inline int is_bech32_character(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || bech32_charset_rev[c] != -1;
|
||||
static inline int is_alphanumeric(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
int consumedAtLeastOne = 0;
|
||||
|
||||
while (cur->p < cur->end) {
|
||||
c = *cur->p;
|
||||
|
||||
if (!is_bech32_character(c))
|
||||
if (!is_alphanumeric(c))
|
||||
return consumedAtLeastOne;
|
||||
|
||||
cur->p++;
|
||||
|
||||
@@ -222,7 +222,7 @@ int parse_nostr_bech32(struct cursor *cur, struct nostr_bech32 *obj) {
|
||||
|
||||
start = cur->p;
|
||||
|
||||
if (!consume_until_non_bech32_character(cur, 1)) {
|
||||
if (!consume_until_non_alphanumeric(cur, 1)) {
|
||||
cur->p = start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
|
||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
|
||||
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
|
||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
|
||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||
@@ -191,6 +192,8 @@
|
||||
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
|
||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.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 */; };
|
||||
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
||||
@@ -275,7 +278,7 @@
|
||||
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
|
||||
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
|
||||
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
|
||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
|
||||
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParticipantsView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -328,9 +331,7 @@
|
||||
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
|
||||
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
@@ -338,6 +339,9 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -384,9 +388,6 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -599,6 +600,8 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -685,7 +688,7 @@
|
||||
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
|
||||
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePictureControl.swift; sourceTree = "<group>"; };
|
||||
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
|
||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
|
||||
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -840,6 +843,7 @@
|
||||
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
||||
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
|
||||
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */,
|
||||
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -922,7 +926,7 @@
|
||||
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
|
||||
4C363A8B28236B92006E126D /* PubkeyView.swift */,
|
||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
|
||||
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */,
|
||||
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */,
|
||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
||||
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
||||
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
||||
@@ -1004,6 +1008,7 @@
|
||||
4C1A9A1929DCA17E00516EAC /* ReplyCounter.swift */,
|
||||
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
|
||||
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
|
||||
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
@@ -1019,6 +1024,7 @@
|
||||
4CE8794D2996B16A00F758CC /* RelayToggle.swift */,
|
||||
4CE8794F2996B2BD00F758CC /* RelayStatus.swift */,
|
||||
4CE879512996B68900F758CC /* RelayType.swift */,
|
||||
4CDA128929E9D10C0006FA5A /* SignalView.swift */,
|
||||
);
|
||||
path = Relays;
|
||||
sourceTree = "<group>";
|
||||
@@ -1403,8 +1409,7 @@
|
||||
"es-419",
|
||||
"es-ES",
|
||||
fa,
|
||||
"fr-CA",
|
||||
"fr-FR",
|
||||
fr,
|
||||
"hu-HU",
|
||||
id,
|
||||
"it-IT",
|
||||
@@ -1492,6 +1497,7 @@
|
||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */,
|
||||
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
|
||||
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
||||
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */,
|
||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
||||
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
|
||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||
@@ -1523,7 +1529,7 @@
|
||||
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
|
||||
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
|
||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
||||
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
|
||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||
@@ -1535,6 +1541,7 @@
|
||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
|
||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */,
|
||||
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */,
|
||||
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */,
|
||||
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */,
|
||||
4C8D00CA29DF80350036AF10 /* TruncatedText.swift in Sources */,
|
||||
@@ -1646,6 +1653,7 @@
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
|
||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */,
|
||||
@@ -1775,7 +1783,6 @@
|
||||
3A5C4575296A879E0032D398 /* es-419 */,
|
||||
3A2B8B0A296A8982009CC16D /* en-US */,
|
||||
3AEB8005297CCEA900713A25 /* tr-TR */,
|
||||
3A4F3322297CCFEE004B5F72 /* fr-FR */,
|
||||
3A185A06297F2C3800F4BDC0 /* lv-LV */,
|
||||
3A929C22297F2CF80090925E /* it-IT */,
|
||||
3AB5B86C2986D8A3006599D2 /* de */,
|
||||
@@ -1797,10 +1804,10 @@
|
||||
3AD5663229C0DA4B00BF77C5 /* ko */,
|
||||
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBC29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC629C9E0B8002BE7ED /* vi */,
|
||||
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
||||
3A821C4029E819D500B4BCA7 /* fr */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
@@ -1810,7 +1817,6 @@
|
||||
children = (
|
||||
3ACB685B297633BC00C46468 /* es-419 */,
|
||||
3AEB8003297CCEA800713A25 /* tr-TR */,
|
||||
3A4F3320297CCFEE004B5F72 /* fr-FR */,
|
||||
3A185A04297F2C3800F4BDC0 /* lv-LV */,
|
||||
3A929C20297F2CF80090925E /* it-IT */,
|
||||
3AB5B86A2986D8A3006599D2 /* de */,
|
||||
@@ -1832,10 +1838,10 @@
|
||||
3AD5663329C0DA4B00BF77C5 /* ko */,
|
||||
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBB29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC529C9E0B8002BE7ED /* vi */,
|
||||
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
||||
3A821C3F29E819D500B4BCA7 /* fr */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1845,7 +1851,6 @@
|
||||
children = (
|
||||
3ACB685E297633BC00C46468 /* es-419 */,
|
||||
3AEB8004297CCEA800713A25 /* tr-TR */,
|
||||
3A4F3321297CCFEE004B5F72 /* fr-FR */,
|
||||
3A185A05297F2C3800F4BDC0 /* lv-LV */,
|
||||
3A929C21297F2CF80090925E /* it-IT */,
|
||||
3AB5B86B2986D8A3006599D2 /* de */,
|
||||
@@ -1868,10 +1873,10 @@
|
||||
3AD5663129C0DA4B00BF77C5 /* ko */,
|
||||
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBD29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC429C9E0B8002BE7ED /* vi */,
|
||||
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
||||
3A821C3E29E819D500B4BCA7 /* fr */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -2007,7 +2012,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -2032,9 +2037,12 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.2;
|
||||
MARKETING_VERSION = 1.4.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -2051,7 +2059,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -2076,9 +2084,12 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.4.2;
|
||||
MARKETING_VERSION = 1.4.3;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "damus-c/damus-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -37,8 +37,6 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
||||
text
|
||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 10, trailing: 0))
|
||||
.font(.system(size: 14, weight: .heavy))
|
||||
.contentShape(Rectangle())
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.background(
|
||||
Group {
|
||||
@@ -50,6 +48,7 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
||||
},
|
||||
alignment: .bottom
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
.accentColor(tag == selection ? textColor() : .gray)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,14 @@ struct TranslateView: View {
|
||||
} else {
|
||||
self.currentLanguage = Locale.current.languageCode ?? "en"
|
||||
}
|
||||
|
||||
if let cached = damus_state.events.lookup_translated_artifacts(evid: event.id) {
|
||||
|
||||
if damus_state.pubkey == event.pubkey && damus_state.is_privkey_user {
|
||||
// Do not translate self-authored notes if logged in with a private key
|
||||
// as we can assume the user can understand their own notes.
|
||||
// The detected language prediction could be incorrect and not in the list of preferred languages.
|
||||
// Offering a translation in this case is definitely incorrect so let's avoid it altogether.
|
||||
self._translated = State(initialValue: .not_needed)
|
||||
} else if let cached = damus_state.events.lookup_translated_artifacts(evid: event.id) {
|
||||
self._translated = State(initialValue: cached)
|
||||
} else {
|
||||
let initval: TranslateStatus = self.damus_state.settings.auto_translate ? .trying : .havent_tried
|
||||
|
||||
@@ -7,11 +7,34 @@
|
||||
|
||||
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 {
|
||||
let damus_state: DamusState
|
||||
let pubkey: String
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
|
||||
@@ -28,7 +51,6 @@ struct UserView: View {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
|
||||
let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey)
|
||||
|
||||
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
+54
-24
@@ -66,7 +66,6 @@ struct ContentView: View {
|
||||
@State var active_sheet: Sheets? = nil
|
||||
@State var damus_state: DamusState? = nil
|
||||
@State var selected_timeline: Timeline? = .home
|
||||
@State var is_thread_open: Bool = false
|
||||
@State var is_deleted_account: Bool = false
|
||||
@State var is_profile_open: Bool = false
|
||||
@State var event: NostrEvent? = nil
|
||||
@@ -84,6 +83,7 @@ struct ContentView: View {
|
||||
@State var filter_state : FilterState = .posts_and_replies
|
||||
@State private var isSideBarOpened = false
|
||||
@StateObject var home: HomeModel = HomeModel()
|
||||
@State var shouldShowBoostAlert = false
|
||||
|
||||
// connect retry timer
|
||||
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
|
||||
@@ -91,14 +91,19 @@ struct ContentView: View {
|
||||
let sub_id = UUID().description
|
||||
|
||||
@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 {
|
||||
VStack {
|
||||
ZStack {
|
||||
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.
|
||||
Text("")
|
||||
.id("what")
|
||||
mystery
|
||||
|
||||
contentTimelineView(filter: FilterState.posts.filter)
|
||||
.tag(FilterState.posts)
|
||||
.id(FilterState.posts)
|
||||
@@ -179,8 +184,7 @@ struct ContentView: View {
|
||||
NotificationsView(state: damus, notifications: home.notifications)
|
||||
|
||||
case .dms:
|
||||
DirectMessagesView(damus_state: damus_state!)
|
||||
.environmentObject(home.dms)
|
||||
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms)
|
||||
|
||||
case .none:
|
||||
EmptyView()
|
||||
@@ -243,6 +247,11 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func open_event(ev: NostrEvent) {
|
||||
self.active_event = ev
|
||||
self.thread_open = true
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let damus = self.damus_state {
|
||||
@@ -263,13 +272,7 @@ struct ContentView: View {
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
HStack(alignment: .center) {
|
||||
if home.signal.signal != home.signal.max_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)
|
||||
}
|
||||
}
|
||||
SignalView(state: damus_state!, signal: home.signal)
|
||||
|
||||
// maybe expand this to other timelines in the future
|
||||
if selected_timeline == .search {
|
||||
@@ -338,10 +341,9 @@ struct ContentView: View {
|
||||
} else if ref.key == "e" {
|
||||
find_event(state: damus_state!, evid: ref.ref_id, search_type: .event, find_from: nil) { ev in
|
||||
if let ev {
|
||||
active_event = ev
|
||||
open_event(ev: ev)
|
||||
}
|
||||
}
|
||||
thread_open = true
|
||||
}
|
||||
case .filter(let filt):
|
||||
active_search = filt
|
||||
@@ -352,12 +354,10 @@ struct ContentView: View {
|
||||
|
||||
}
|
||||
.onReceive(handle_notify(.boost)) { notif in
|
||||
current_boost = (notif.object as? NostrEvent)
|
||||
}
|
||||
.onReceive(handle_notify(.open_thread)) { obj in
|
||||
//let ev = obj.object as! NostrEvent
|
||||
//thread.set_active_event(ev)
|
||||
//is_thread_open = true
|
||||
if let ev = (notif.object as? NostrEvent) {
|
||||
current_boost = ev
|
||||
shouldShowBoostAlert = true
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.reply)) { notif in
|
||||
let ev = notif.object as! NostrEvent
|
||||
@@ -471,6 +471,35 @@ struct ContentView: View {
|
||||
.onReceive(handle_notify(.new_mutes)) { notif in
|
||||
home.filter_muted()
|
||||
}
|
||||
.onReceive(handle_notify(.mute_thread)) { notif in
|
||||
home.filter_muted()
|
||||
}
|
||||
.onReceive(handle_notify(.unmute_thread)) { notif in
|
||||
home.filter_muted()
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
.alert(NSLocalizedString("Deleted Account", comment: "Alert message to indicate this is a deleted account"), isPresented: $is_deleted_account) {
|
||||
Button(NSLocalizedString("Logout", comment: "Button to close the alert that informs that the current account has been deleted.")) {
|
||||
is_deleted_account = false
|
||||
@@ -558,13 +587,13 @@ struct ContentView: View {
|
||||
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
|
||||
}
|
||||
})
|
||||
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $current_boost.mappedToBool()) {
|
||||
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $shouldShowBoostAlert) {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
|
||||
current_boost = nil
|
||||
}
|
||||
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
|
||||
if let current_boost {
|
||||
self.damus_state?.pool.send(.event(current_boost))
|
||||
self.damus_state?.postbox.send(current_boost)
|
||||
}
|
||||
}
|
||||
} message: {
|
||||
@@ -633,7 +662,8 @@ struct ContentView: View {
|
||||
bookmarks: BookmarksManager(pubkey: pubkey),
|
||||
postbox: PostBox(pool: pool),
|
||||
bootstrap_relays: bootstrap_relays,
|
||||
replies: ReplyCounter(our_pubkey: pubkey)
|
||||
replies: ReplyCounter(our_pubkey: pubkey),
|
||||
muted_threads: MutedThreadsManager(keypair: keypair)
|
||||
)
|
||||
home.damus_state = self.damus_state!
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ struct DamusState {
|
||||
let postbox: PostBox
|
||||
let bootstrap_relays: [String]
|
||||
let replies: ReplyCounter
|
||||
let muted_threads: MutedThreadsManager
|
||||
|
||||
var pubkey: String {
|
||||
return keypair.pubkey
|
||||
@@ -39,6 +40,5 @@ struct 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: ""))
|
||||
}
|
||||
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))) }
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ class DirectMessageModel: ObservableObject {
|
||||
|
||||
@Published var draft: String
|
||||
|
||||
let pubkey: String
|
||||
|
||||
var is_request: Bool
|
||||
var our_pubkey: String
|
||||
|
||||
@@ -29,17 +31,19 @@ class DirectMessageModel: ObservableObject {
|
||||
return true
|
||||
}
|
||||
|
||||
init(events: [NostrEvent], our_pubkey: String) {
|
||||
init(events: [NostrEvent], our_pubkey: String, pubkey: String) {
|
||||
self.events = events
|
||||
self.is_request = false
|
||||
self.our_pubkey = our_pubkey
|
||||
self.draft = ""
|
||||
self.pubkey = pubkey
|
||||
}
|
||||
|
||||
init(our_pubkey: String) {
|
||||
init(our_pubkey: String, pubkey: String) {
|
||||
self.events = []
|
||||
self.is_request = false
|
||||
self.our_pubkey = our_pubkey
|
||||
self.draft = ""
|
||||
self.pubkey = pubkey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,43 @@
|
||||
import Foundation
|
||||
|
||||
class DirectMessagesModel: ObservableObject {
|
||||
@Published var dms: [(String, DirectMessageModel)] = []
|
||||
@Published var dms: [DirectMessageModel] = []
|
||||
@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
|
||||
|
||||
init(our_pubkey: String) {
|
||||
self.our_pubkey = our_pubkey
|
||||
}
|
||||
|
||||
var message_requests: [(String, DirectMessageModel)] {
|
||||
return dms.filter { dm in dm.1.is_request }
|
||||
var message_requests: [DirectMessageModel] {
|
||||
return dms.filter { dm in dm.is_request }
|
||||
}
|
||||
|
||||
var friend_dms: [(String, DirectMessageModel)] {
|
||||
return dms.filter { dm in !dm.1.is_request }
|
||||
var friend_dms: [DirectMessageModel] {
|
||||
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 {
|
||||
@@ -29,15 +52,15 @@ class DirectMessagesModel: ObservableObject {
|
||||
return dm
|
||||
}
|
||||
|
||||
let new = DirectMessageModel(our_pubkey: our_pubkey)
|
||||
dms.append((pubkey, new))
|
||||
let new = DirectMessageModel(our_pubkey: our_pubkey, pubkey: pubkey)
|
||||
dms.append(new)
|
||||
return new
|
||||
}
|
||||
|
||||
func lookup(_ pubkey: String) -> DirectMessageModel? {
|
||||
for dm in dms {
|
||||
if pubkey == dm.0 {
|
||||
return dm.1
|
||||
if pubkey == dm.pubkey {
|
||||
return dm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,5 @@ import Foundation
|
||||
class Drafts: ObservableObject {
|
||||
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
|
||||
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
|
||||
@Published var medias: [UploadedMedia] = []
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ class FollowersModel: ObservableObject {
|
||||
if ev.known_kind == .contacts {
|
||||
handle_contact_event(ev)
|
||||
} 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):
|
||||
|
||||
@@ -62,7 +62,7 @@ class FollowingModel {
|
||||
break
|
||||
case .event(_, let ev):
|
||||
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):
|
||||
print("followingmodel notice: \(msg)")
|
||||
|
||||
+135
-82
@@ -41,34 +41,27 @@ class HomeModel: ObservableObject {
|
||||
let dms_subid = UUID().description
|
||||
let init_subid = UUID().description
|
||||
let profiles_subid = UUID().description
|
||||
|
||||
var loading: Bool = false
|
||||
|
||||
var signal = SignalModel()
|
||||
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
@Published var notifications = NotificationsModel()
|
||||
@Published var dms: DirectMessagesModel
|
||||
@Published var events = EventHolder()
|
||||
@Published var loading: Bool = false
|
||||
@Published var signal: SignalModel = SignalModel()
|
||||
|
||||
init() {
|
||||
self.damus_state = DamusState.empty
|
||||
self.dms = DirectMessagesModel(our_pubkey: "")
|
||||
}
|
||||
|
||||
init(damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||
filter_muted()
|
||||
self.setup_debouncer()
|
||||
}
|
||||
|
||||
|
||||
var pool: RelayPool {
|
||||
return damus_state.pool
|
||||
}
|
||||
|
||||
func setup_debouncer() {
|
||||
// turn off debouncer after initial load
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||
self.should_debounce_dms = false
|
||||
}
|
||||
var dms: DirectMessagesModel {
|
||||
return damus_state.dms
|
||||
}
|
||||
|
||||
func has_sub_id_event(sub_id: String, ev_id: String) -> Bool {
|
||||
@@ -79,6 +72,13 @@ class HomeModel: ObservableObject {
|
||||
|
||||
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) {
|
||||
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
|
||||
@@ -134,7 +134,7 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if !notifications.insert_zap(zap) {
|
||||
if !notifications.insert_zap(zap, damus_state: damus_state) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
if damus_state.settings.zap_notification {
|
||||
// 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 ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,10 +186,6 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
func handle_channel_create(_ ev: NostrEvent) {
|
||||
guard ev.is_valid else {
|
||||
return
|
||||
}
|
||||
|
||||
self.channels[ev.id] = ev
|
||||
}
|
||||
|
||||
@@ -197,16 +193,21 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
func filter_muted() {
|
||||
events.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||
self.dms.dms = dms.dms.filter { !damus_state.contacts.is_muted($0.0) }
|
||||
notifications.filter { !damus_state.contacts.is_muted($0.pubkey) }
|
||||
events.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey)
|
||||
}
|
||||
|
||||
self.dms.dms = dms.dms.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey)
|
||||
}
|
||||
|
||||
notifications.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey) &&
|
||||
!damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey)
|
||||
}
|
||||
}
|
||||
|
||||
func handle_delete_event(_ ev: NostrEvent) {
|
||||
guard ev.is_valid else {
|
||||
return
|
||||
}
|
||||
|
||||
self.deleted_events.insert(ev.id)
|
||||
}
|
||||
|
||||
@@ -228,7 +229,7 @@ class HomeModel: ObservableObject {
|
||||
if let inner_ev = ev.inner_event {
|
||||
boost_ev_id = inner_ev.id
|
||||
|
||||
guard inner_ev.is_valid else {
|
||||
guard validate_event(ev: inner_ev) == .ok else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -297,10 +298,7 @@ class HomeModel: ObservableObject {
|
||||
break
|
||||
}
|
||||
|
||||
update_signal_from_pool(signal: signal, pool: damus_state.pool)
|
||||
|
||||
print("ws_event \(ev)")
|
||||
|
||||
update_signal_from_pool(signal: self.signal, pool: damus_state.pool)
|
||||
case .nostr_event(let ev):
|
||||
switch ev {
|
||||
case .event(let sub_id, let ev):
|
||||
@@ -319,11 +317,13 @@ class HomeModel: ObservableObject {
|
||||
case .eose(let sub_id):
|
||||
|
||||
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)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
|
||||
} else if sub_id == notifications_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
|
||||
} else if sub_id == home_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus_state)
|
||||
}
|
||||
|
||||
self.loading = false
|
||||
@@ -447,7 +447,7 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
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? {
|
||||
@@ -469,16 +469,17 @@ class HomeModel: ObservableObject {
|
||||
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
|
||||
}
|
||||
|
||||
damus_state.events.insert(ev)
|
||||
|
||||
if let inner_ev = ev.inner_event {
|
||||
damus_state.events.insert(inner_ev)
|
||||
}
|
||||
|
||||
if !notifications.insert_event(ev) {
|
||||
if !notifications.insert_event(ev, damus_state: damus_state) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -520,15 +521,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) {
|
||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state.events.insert(ev)
|
||||
|
||||
if !should_debounce_dms {
|
||||
self.incoming_dms.append(ev)
|
||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
self.new_events = notifs
|
||||
got_new_dm(notifs: notifs, ev: ev)
|
||||
}
|
||||
self.incoming_dms = []
|
||||
return
|
||||
@@ -538,11 +550,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
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) {
|
||||
self.new_events = notifs
|
||||
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)
|
||||
}
|
||||
got_new_dm(notifs: notifs, ev: ev)
|
||||
}
|
||||
self.incoming_dms = []
|
||||
}
|
||||
@@ -555,8 +563,8 @@ func update_signal_from_pool(signal: SignalModel, pool: RelayPool) {
|
||||
signal.max_signal = pool.relays.count
|
||||
}
|
||||
|
||||
if signal.signal != pool.num_connecting {
|
||||
signal.signal = signal.max_signal - pool.num_connecting
|
||||
if signal.signal != pool.num_connected {
|
||||
signal.signal = pool.num_connected
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,11 +658,7 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
||||
print("-----")
|
||||
}
|
||||
|
||||
func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEvent) {
|
||||
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
||||
return
|
||||
}
|
||||
|
||||
func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) {
|
||||
if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
|
||||
DispatchQueue.main.async {
|
||||
notify(.deleted_account, ())
|
||||
@@ -705,6 +709,47 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -845,10 +890,10 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM
|
||||
}
|
||||
}
|
||||
|
||||
for (pk, _) in dms.dms {
|
||||
if pk == the_pk {
|
||||
for model in dms.dms {
|
||||
if model.pubkey == the_pk {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -858,8 +903,8 @@ func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesM
|
||||
}
|
||||
|
||||
if !found {
|
||||
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey)
|
||||
dms.dms.append((the_pk, model))
|
||||
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey, pubkey: the_pk)
|
||||
dms.dms.append(model)
|
||||
inserted = true
|
||||
}
|
||||
|
||||
@@ -886,8 +931,8 @@ func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, o
|
||||
}
|
||||
|
||||
if inserted {
|
||||
dms.dms = dms.dms.filter({ $0.1.events.count > 0 }).sorted { a, b in
|
||||
return a.1.events.last!.created_at > b.1.events.last!.created_at
|
||||
dms.dms = dms.dms.filter({ $0.events.count > 0 }).sorted { a, b in
|
||||
return a.events.last!.created_at > b.events.last!.created_at
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,12 +1052,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()
|
||||
|
||||
content.title = zap_notification_title(zap)
|
||||
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = LossyLocalNotification(type: .zap, event_id: evId).to_user_info()
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
|
||||
@@ -1039,53 +1085,60 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if type == .text && damus_state.settings.mention_notification {
|
||||
for block in ev.blocks(damus_state.keypair.privkey) {
|
||||
if case .mention(let mention) = block, mention.ref.ref_id == damus_state.keypair.pubkey,
|
||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else if type == .boost && damus_state.settings.repost_notification,
|
||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name {
|
||||
// Don't show notifications from muted threads.
|
||||
if damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey) {
|
||||
return
|
||||
}
|
||||
|
||||
if let inner_ev = ev.inner_event {
|
||||
create_local_notification(displayName: displayName, conversation: inner_ev.content, type: type)
|
||||
if type == .text && damus_state.settings.mention_notification {
|
||||
let blocks = ev.blocks(damus_state.keypair.privkey)
|
||||
for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey {
|
||||
let content = NSAttributedString(render_note_content(ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey).content.attributed).string
|
||||
|
||||
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 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,
|
||||
let displayName = damus_state.profiles.lookup(id: ev.pubkey)?.display_name,
|
||||
let e_ref = ev.referenced_ids.first?.ref_id,
|
||||
let content = damus_state.events.lookup(e_ref)?.content {
|
||||
|
||||
create_local_notification(displayName: displayName, conversation: content, type: type)
|
||||
let evid = ev.referenced_ids.first?.ref_id,
|
||||
let liked_event = damus_state.events.lookup(evid)
|
||||
{
|
||||
let notify = LocalNotification(type: .like, event: ev, target: liked_event, content: liked_event.content)
|
||||
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()
|
||||
var title = ""
|
||||
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)
|
||||
identifier = "myMentionNotification"
|
||||
case .boost:
|
||||
case .repost:
|
||||
title = String(format: NSLocalizedString("Reposted by %@", comment: "Reposted by heading in local notification"), displayName)
|
||||
identifier = "myBoostNotification"
|
||||
case .like:
|
||||
title = String(format: NSLocalizedString("Liked by %@", comment: "Liked by heading in local notification"), displayName)
|
||||
identifier = "myLikeNotification"
|
||||
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"
|
||||
default:
|
||||
case .zap:
|
||||
// not handled here
|
||||
break
|
||||
}
|
||||
content.title = title
|
||||
content.body = conversation
|
||||
content.body = notify.content
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = notify.to_lossy().to_user_info()
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
|
||||
|
||||
@@ -25,6 +25,15 @@ enum MediaUpload {
|
||||
return url.pathExtension
|
||||
}
|
||||
}
|
||||
|
||||
var localURL: URL {
|
||||
switch self {
|
||||
case .image(let url):
|
||||
return url
|
||||
case .video(let url):
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
var is_image: Bool {
|
||||
if case .image = self {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// MutedThreadsManager.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 4/6/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
fileprivate func getMutedThreadsKey(pubkey: String) -> String {
|
||||
pk_setting_key(pubkey, key: "muted_threads")
|
||||
}
|
||||
|
||||
func loadMutedThreads(pubkey: String) -> [String] {
|
||||
let key = getMutedThreadsKey(pubkey: pubkey)
|
||||
return UserDefaults.standard.stringArray(forKey: key) ?? []
|
||||
}
|
||||
|
||||
func saveMutedThreads(pubkey: String, currentValue: [String], value: [String]) -> Bool {
|
||||
let uniqueMutedThreads = Array(Set(value))
|
||||
|
||||
if uniqueMutedThreads != currentValue {
|
||||
UserDefaults.standard.set(uniqueMutedThreads, forKey: getMutedThreadsKey(pubkey: pubkey))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
class MutedThreadsManager: ObservableObject {
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let keypair: Keypair
|
||||
|
||||
private var _mutedThreadsSet: Set<String>
|
||||
private var _mutedThreads: [String]
|
||||
var mutedThreads: [String] {
|
||||
get {
|
||||
return _mutedThreads
|
||||
}
|
||||
set {
|
||||
if saveMutedThreads(pubkey: keypair.pubkey, currentValue: _mutedThreads, value: newValue) {
|
||||
self._mutedThreads = newValue
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(keypair: Keypair) {
|
||||
self._mutedThreads = loadMutedThreads(pubkey: keypair.pubkey)
|
||||
self._mutedThreadsSet = Set(_mutedThreads)
|
||||
self.keypair = keypair
|
||||
}
|
||||
|
||||
func isMutedThread(_ ev: NostrEvent, privkey: String?) -> Bool {
|
||||
return _mutedThreadsSet.contains(ev.thread_id(privkey: privkey))
|
||||
}
|
||||
|
||||
func updateMutedThread(_ ev: NostrEvent) {
|
||||
let threadId = ev.thread_id(privkey: nil)
|
||||
if isMutedThread(ev, privkey: keypair.privkey) {
|
||||
mutedThreads = mutedThreads.filter { $0 != threadId }
|
||||
_mutedThreadsSet.remove(threadId)
|
||||
notify(.unmute_thread, ev)
|
||||
} else {
|
||||
mutedThreads.append(threadId)
|
||||
_mutedThreadsSet.insert(threadId)
|
||||
notify(.mute_thread, ev)
|
||||
}
|
||||
}
|
||||
|
||||
func clearAll() {
|
||||
mutedThreads = []
|
||||
_mutedThreadsSet.removeAll()
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
for el in zaps {
|
||||
let evid = el.key
|
||||
let zapgrp = el.value
|
||||
|
||||
|
||||
let notif: NotificationItem = .event_zap(evid, zapgrp)
|
||||
notifs.append(notif)
|
||||
}
|
||||
@@ -233,7 +233,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
}
|
||||
}
|
||||
|
||||
func insert_event(_ ev: NostrEvent) -> Bool {
|
||||
func insert_event(_ ev: NostrEvent, damus_state: DamusState) -> Bool {
|
||||
if should_queue {
|
||||
return insert_uniq_sorted_event_created(events: &incoming_events, new_ev: ev)
|
||||
}
|
||||
@@ -246,7 +246,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
return false
|
||||
}
|
||||
|
||||
func insert_zap(_ zap: Zap) -> Bool {
|
||||
func insert_zap(_ zap: Zap, damus_state: DamusState) -> Bool {
|
||||
if should_queue {
|
||||
return insert_uniq_sorted_zap_by_created(zaps: &incoming_zaps, new_zap: zap)
|
||||
}
|
||||
@@ -300,7 +300,7 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
}
|
||||
}
|
||||
|
||||
func flush() -> Bool {
|
||||
func flush(_ damus_state: DamusState) -> Bool {
|
||||
var inserted = false
|
||||
|
||||
for zap in incoming_zaps {
|
||||
|
||||
@@ -119,7 +119,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
} else if ev.known_kind == .contacts {
|
||||
handle_profile_contact_event(ev)
|
||||
} 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)
|
||||
}
|
||||
@@ -140,6 +140,9 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
case .notice(let notice):
|
||||
notify(.notice, notice)
|
||||
case .eose:
|
||||
if resp.subid == sub_id {
|
||||
load_profiles(profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus)
|
||||
}
|
||||
progress += 1
|
||||
break
|
||||
}
|
||||
|
||||
@@ -128,11 +128,14 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
|
||||
var pubkeys = Set<String>()
|
||||
|
||||
for ev in events {
|
||||
if profiles.lookup(id: ev.pubkey) != nil {
|
||||
continue
|
||||
// lookup profiles from boosted events
|
||||
if ev.known_kind == .boost, let bev = ev.inner_event, profiles.lookup(id: bev.pubkey) == nil {
|
||||
pubkeys.insert(bev.pubkey)
|
||||
}
|
||||
|
||||
pubkeys.insert(ev.pubkey)
|
||||
if profiles.lookup(id: ev.pubkey) == nil {
|
||||
pubkeys.insert(ev.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
return Array(pubkeys)
|
||||
@@ -161,7 +164,7 @@ func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
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 {
|
||||
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
|
||||
}
|
||||
|
||||
@@ -26,11 +26,13 @@ func set_default_zap_amount(pubkey: String, amount: Int) {
|
||||
UserDefaults.standard.setValue(amount, forKey: key)
|
||||
}
|
||||
|
||||
func get_default_zap_amount(pubkey: String) -> Int? {
|
||||
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 nil
|
||||
return fallback_zap_amount
|
||||
}
|
||||
return amt
|
||||
}
|
||||
|
||||
@@ -13,11 +13,15 @@ import CryptoKit
|
||||
import NaturalLanguage
|
||||
|
||||
|
||||
|
||||
enum ValidationResult: Decodable {
|
||||
case unknown
|
||||
case ok
|
||||
case bad_id
|
||||
case bad_sig
|
||||
|
||||
var is_bad: Bool {
|
||||
return self == .bad_id || self == .bad_sig
|
||||
}
|
||||
}
|
||||
|
||||
struct OtherEvent {
|
||||
@@ -82,7 +86,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
}
|
||||
|
||||
var too_big: Bool {
|
||||
return self.content.count > 16000
|
||||
return self.content.utf8.count > 16000
|
||||
}
|
||||
|
||||
var should_show_event: Bool {
|
||||
@@ -93,14 +97,6 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
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
|
||||
func blocks(_ privkey: String?) -> [Block] {
|
||||
if let bs = _blocks {
|
||||
@@ -724,11 +720,26 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
|
||||
return ev
|
||||
}
|
||||
|
||||
func uniq<T: Hashable>(_ xs: [T]) -> [T] {
|
||||
var s = Set<T>()
|
||||
var ys: [T] = []
|
||||
|
||||
for x in xs {
|
||||
if s.contains(x) {
|
||||
continue
|
||||
}
|
||||
s.insert(x)
|
||||
ys.append(x)
|
||||
}
|
||||
|
||||
return ys
|
||||
}
|
||||
|
||||
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []
|
||||
|
||||
ids.append(ReferencedId(ref_id: from.id, relay_id: nil, key: "e"))
|
||||
ids.append(contentsOf: from.referenced_pubkeys.filter { $0.ref_id != our_pubkey })
|
||||
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey }))
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ final class RelayConnection: WebSocketDelegate {
|
||||
self.isConnected = false
|
||||
|
||||
case .text(let txt):
|
||||
if txt.count > 2000 {
|
||||
if txt.utf8.count > 2000 {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
if let ev = decode_nostr_event(txt: txt) {
|
||||
DispatchQueue.main.async {
|
||||
@@ -105,8 +105,6 @@ final class RelayConnection: WebSocketDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
print("decode failed for \(txt)")
|
||||
// TODO: trigger event error
|
||||
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -52,6 +52,10 @@ class RelayPool {
|
||||
var num_connecting: Int {
|
||||
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) {
|
||||
self.handlers = handlers.filter { $0.sub_id != sub_id }
|
||||
|
||||
@@ -15,6 +15,7 @@ class EventCache {
|
||||
private var cancellable: AnyCancellable?
|
||||
private var translations: [String: TranslateStatus] = [:]
|
||||
private var artifacts: [String: NoteArtifacts] = [:]
|
||||
var validation: [String: ValidationResult] = [:]
|
||||
|
||||
//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) {
|
||||
self.translations[evid] = translated
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -104,6 +104,15 @@ extension Notification.Name {
|
||||
static var zapping: Notification.Name {
|
||||
return Notification.Name("zapping")
|
||||
}
|
||||
static var mute_thread: Notification.Name {
|
||||
return Notification.Name("mute_thread")
|
||||
}
|
||||
static var unmute_thread: Notification.Name {
|
||||
return Notification.Name("unmute_thread")
|
||||
}
|
||||
static var local_notification: Notification.Name {
|
||||
return Notification.Name("local_notification")
|
||||
}
|
||||
}
|
||||
|
||||
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {
|
||||
|
||||
@@ -142,8 +142,10 @@ struct EventActionBar: View {
|
||||
}
|
||||
|
||||
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
|
||||
|
||||
self.bar.our_boost = boost
|
||||
|
||||
// As we will still have to wait for the confirmation from alert for repost, we do not turn it green yet.
|
||||
// However, turning green handled from EventActionBar spontaneously once reposted
|
||||
// self.bar.our_boost = boost
|
||||
|
||||
notify(.boost, boost)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ struct ConfigView: View {
|
||||
}
|
||||
|
||||
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
|
||||
IconLabel(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"), img_name: "bell.fill", color: .blue)
|
||||
IconLabel(NSLocalizedString("Notifications", comment: "Section header for Damus notifications"), img_name: "bell.fill", color: .blue)
|
||||
}
|
||||
|
||||
NavigationLink(destination: ZapSettingsView(pubkey: state.pubkey, settings: settings)) {
|
||||
@@ -144,14 +144,15 @@ struct ConfigView_Previews: PreviewProvider {
|
||||
|
||||
|
||||
func handle_string_amount(new_value: String) -> Int? {
|
||||
let digits = Set("0123456789")
|
||||
let filtered = new_value.filter { digits.contains($0) }
|
||||
let filtered = new_value.filter {
|
||||
$0.isNumber
|
||||
}
|
||||
|
||||
if filtered == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let amt = Int(filtered) else {
|
||||
guard let amt = NumberFormatter().number(from: filtered) as? Int else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,20 @@ import SwiftUI
|
||||
|
||||
struct DMChatView: View {
|
||||
let damus_state: DamusState
|
||||
let pubkey: String
|
||||
@EnvironmentObject var dms: DirectMessageModel
|
||||
@ObservedObject var dms: DirectMessageModel
|
||||
@State var showPrivateKeyWarning: Bool = false
|
||||
|
||||
|
||||
var pubkey: String {
|
||||
dms.pubkey
|
||||
}
|
||||
|
||||
var Messages: some View {
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
ForEach(Array(zip(dms.events, dms.events.indices)), id: \.0.id) { (ev, ind) in
|
||||
DMView(event: dms.events[ind], damus_state: damus_state)
|
||||
.contextMenu{MenuItems(event: ev, keypair: damus_state.keypair, target_pubkey: ev.pubkey, bookmarks: damus_state.bookmarks)}
|
||||
.contextMenu{MenuItems(event: ev, keypair: damus_state.keypair, target_pubkey: ev.pubkey, bookmarks: damus_state.bookmarks, muted_threads: damus_state.muted_threads)}
|
||||
}
|
||||
EndBlock(height: 80)
|
||||
}
|
||||
@@ -177,10 +180,9 @@ struct DMChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
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")
|
||||
.environmentObject(model)
|
||||
DMChatView(damus_state: test_damus_state(), dms: model)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,21 +16,12 @@ struct DirectMessagesView: View {
|
||||
let damus_state: DamusState
|
||||
|
||||
@State var dm_type: DMType = .friend
|
||||
@State var open_dm: Bool = false
|
||||
@State var pubkey: String = ""
|
||||
@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))
|
||||
}
|
||||
@ObservedObject var model: DirectMessagesModel
|
||||
|
||||
func MainContent(requests: Bool) -> some View {
|
||||
ScrollView {
|
||||
let chat = DMChatView(damus_state: damus_state, pubkey: pubkey)
|
||||
.environmentObject(active_model)
|
||||
NavigationLink(destination: chat, isActive: $open_dm) {
|
||||
let chat = DMChatView(damus_state: damus_state, dms: model.active_model)
|
||||
NavigationLink(destination: chat, isActive: $model.open_dm) {
|
||||
EmptyView()
|
||||
}
|
||||
LazyVStack(spacing: 0) {
|
||||
@@ -38,7 +29,7 @@ struct DirectMessagesView: View {
|
||||
EmptyTimelineView()
|
||||
} else {
|
||||
let dms = requests ? model.message_requests : model.friend_dms
|
||||
ForEach(dms, id: \.0) { tup in
|
||||
ForEach(dms, id: \.pubkey) { tup in
|
||||
MaybeEvent(tup)
|
||||
.padding(.top, 10)
|
||||
|
||||
@@ -59,14 +50,12 @@ struct DirectMessagesView: View {
|
||||
return [.truncate_content, .no_action_bar, .no_translate]
|
||||
}
|
||||
|
||||
func MaybeEvent(_ tup: (String, DirectMessageModel)) -> some View {
|
||||
func MaybeEvent(_ model: DirectMessageModel) -> some View {
|
||||
Group {
|
||||
if let ev = tup.1.events.last {
|
||||
EventView(damus: damus_state, event: ev, pubkey: tup.0, options: options)
|
||||
if let ev = model.events.last {
|
||||
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
|
||||
.onTapGesture {
|
||||
pubkey = tup.0
|
||||
active_model = tup.1
|
||||
open_dm = true
|
||||
self.model.open_dm_by_model(model)
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
@@ -106,8 +95,6 @@ struct DirectMessagesView_Previews: PreviewProvider {
|
||||
kind: 4,
|
||||
tags: [])
|
||||
let ds = test_damus_state()
|
||||
let model = DirectMessageModel(events: [ev], our_pubkey: ds.pubkey)
|
||||
DirectMessagesView(damus_state: ds)
|
||||
.environmentObject(model)
|
||||
DirectMessagesView(damus_state: ds, model: ds.dms)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,19 +69,6 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
|
||||
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 {
|
||||
func pubkey_context_menu(bech32_pubkey: String) -> some View {
|
||||
return self.contextMenu {
|
||||
@@ -93,9 +80,9 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
func event_context_menu(_ event: NostrEvent, keypair: Keypair, target_pubkey: String, bookmarks: BookmarksManager) -> some View {
|
||||
func event_context_menu(_ event: NostrEvent, keypair: Keypair, target_pubkey: String, bookmarks: BookmarksManager, muted_threads: MutedThreadsManager) -> some View {
|
||||
return self.contextMenu {
|
||||
EventMenuContext(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks)
|
||||
EventMenuContext(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct EmbeddedEventView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
EventMenuContext(event: event, keypair: damus_state.keypair, target_pubkey: event.pubkey, bookmarks: damus_state.bookmarks)
|
||||
EventMenuContext(event: event, keypair: damus_state.keypair, target_pubkey: event.pubkey, bookmarks: damus_state.bookmarks, muted_threads: damus_state.muted_threads)
|
||||
.padding([.bottom], 4)
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ struct EventMenuContext: View {
|
||||
let keypair: Keypair
|
||||
let target_pubkey: String
|
||||
let bookmarks: BookmarksManager
|
||||
let muted_threads: MutedThreadsManager
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@@ -19,7 +20,7 @@ struct EventMenuContext: View {
|
||||
HStack {
|
||||
Menu {
|
||||
|
||||
MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks)
|
||||
MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads)
|
||||
|
||||
} label: {
|
||||
Label("", systemImage: "ellipsis")
|
||||
@@ -36,14 +37,20 @@ struct MenuItems: View {
|
||||
let keypair: Keypair
|
||||
let target_pubkey: String
|
||||
let bookmarks: BookmarksManager
|
||||
let muted_threads: MutedThreadsManager
|
||||
|
||||
@State private var isBookmarked: Bool = false
|
||||
@State private var isMutedThread: Bool = false
|
||||
|
||||
init(event: NostrEvent, keypair: Keypair, target_pubkey: String, bookmarks: BookmarksManager) {
|
||||
init(event: NostrEvent, keypair: Keypair, target_pubkey: String, bookmarks: BookmarksManager, muted_threads: MutedThreadsManager) {
|
||||
let bookmarked = bookmarks.isBookmarked(event)
|
||||
self._isBookmarked = State(initialValue: bookmarked)
|
||||
|
||||
let muted_thread = muted_threads.isMutedThread(event, privkey: keypair.privkey)
|
||||
self._isMutedThread = State(initialValue: muted_thread)
|
||||
|
||||
self.bookmarks = bookmarks
|
||||
self.muted_threads = muted_threads
|
||||
self.event = event
|
||||
self.keypair = keypair
|
||||
self.target_pubkey = target_pubkey
|
||||
@@ -86,6 +93,19 @@ struct MenuItems: View {
|
||||
Label(isBookmarked ? removeBookmarkString : addBookmarkString, systemImage: imageName)
|
||||
}
|
||||
|
||||
if event.known_kind != .dm {
|
||||
Button {
|
||||
self.muted_threads.updateMutedThread(event)
|
||||
let muted = self.muted_threads.isMutedThread(event, privkey: self.keypair.privkey)
|
||||
isMutedThread = muted
|
||||
} label: {
|
||||
let imageName = isMutedThread ? "speaker" : "speaker.slash"
|
||||
let unmuteThreadString = NSLocalizedString("Unmute conversation", comment: "Context menu option for unmuting a conversation.")
|
||||
let muteThreadString = NSLocalizedString("Mute conversation", comment: "Context menu option for muting a conversation.")
|
||||
Label(isMutedThread ? unmuteThreadString : muteThreadString, systemImage: imageName)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
NotificationCenter.default.post(name: .broadcast_event, object: event)
|
||||
} label: {
|
||||
@@ -104,7 +124,7 @@ struct MenuItems: View {
|
||||
Button(role: .destructive) {
|
||||
notify(.mute, target_pubkey)
|
||||
} label: {
|
||||
Label(NSLocalizedString("Mute", comment: "Context menu option for muting users."), systemImage: "exclamationmark.octagon")
|
||||
Label(NSLocalizedString("Mute User", comment: "Context menu option for muting users."), systemImage: "exclamationmark.octagon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ struct SelectedEventView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks)
|
||||
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks, muted_threads: damus.muted_threads)
|
||||
.padding([.bottom], 4)
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ struct TextEvent: View {
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.background(event_validity_color(event.validity))
|
||||
.id(event.id)
|
||||
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
|
||||
.padding([.bottom], 2)
|
||||
@@ -109,7 +108,7 @@ struct TextEvent: View {
|
||||
}
|
||||
|
||||
var ContextButton: some View {
|
||||
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks)
|
||||
EventMenuContext(event: event, keypair: damus.keypair, target_pubkey: event.pubkey, bookmarks: damus.bookmarks, muted_threads: damus.muted_threads)
|
||||
.padding([.bottom], 4)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,11 @@ struct FollowUserView: View {
|
||||
}
|
||||
|
||||
HStack {
|
||||
UserView(damus_state: damus_state, pubkey: target.pubkey)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
navigating = true
|
||||
}
|
||||
UserViewRow(damus_state: damus_state, pubkey: 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 pubkey: String
|
||||
@Binding var image_upload_confirm: Bool
|
||||
var imagesOnly: Bool = false
|
||||
let onImagePicked: (URL) -> Void
|
||||
let onVideoPicked: (URL) -> Void
|
||||
@@ -24,15 +25,18 @@ struct ImagePicker: UIViewControllerRepresentable {
|
||||
private let sourceType: UIImagePickerController.SourceType
|
||||
private let onImagePicked: (URL) -> Void
|
||||
private let onVideoPicked: (URL) -> Void
|
||||
@Binding var image_upload_confirm: Bool
|
||||
|
||||
init(presentationMode: Binding<PresentationMode>,
|
||||
sourceType: UIImagePickerController.SourceType,
|
||||
onImagePicked: @escaping (URL) -> Void,
|
||||
onVideoPicked: @escaping (URL) -> Void) {
|
||||
onVideoPicked: @escaping (URL) -> Void,
|
||||
image_upload_confirm: Binding<Bool>) {
|
||||
_presentationMode = presentationMode
|
||||
self.sourceType = sourceType
|
||||
self.onImagePicked = onImagePicked
|
||||
self.onVideoPicked = onVideoPicked
|
||||
self._image_upload_confirm = image_upload_confirm
|
||||
}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
@@ -51,9 +55,9 @@ struct ImagePicker: UIViewControllerRepresentable {
|
||||
onImagePicked(editedImageURL)
|
||||
}
|
||||
}
|
||||
presentationMode.dismiss()
|
||||
image_upload_confirm = true
|
||||
}
|
||||
|
||||
|
||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||
presentationMode.dismiss()
|
||||
}
|
||||
@@ -98,7 +102,7 @@ struct ImagePicker: UIViewControllerRepresentable {
|
||||
onVideoPicked: { videoURL in
|
||||
// Handle the selected video URL
|
||||
onVideoPicked(videoURL)
|
||||
})
|
||||
}, image_upload_confirm: $image_upload_confirm)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
||||
|
||||
@@ -37,7 +37,7 @@ struct MutelistView: View {
|
||||
|
||||
var body: some View {
|
||||
List(users, id: \.self) { pubkey in
|
||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||
UserViewRow(damus_state: damus_state, pubkey: pubkey)
|
||||
.id(pubkey)
|
||||
.swipeActions {
|
||||
RemoveAction(pubkey: pubkey)
|
||||
|
||||
@@ -35,12 +35,19 @@ struct NotificationsView: View {
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var mystery: some View {
|
||||
VStack(spacing: 20) {
|
||||
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("You are dreaming...", comment: "Text telling the user that they are dreaming.")
|
||||
}
|
||||
.id("what")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $filter_state) {
|
||||
mystery
|
||||
|
||||
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
|
||||
Text("")
|
||||
.id("what")
|
||||
|
||||
NotificationTab(NotificationFilterState.all)
|
||||
.tag(NotificationFilterState.all)
|
||||
|
||||
@@ -96,13 +103,13 @@ struct NotificationsView: View {
|
||||
}
|
||||
.coordinateSpace(name: "scroll")
|
||||
.onReceive(handle_notify(.scroll_to_top)) { notif in
|
||||
let _ = notifications.flush()
|
||||
let _ = notifications.flush(state)
|
||||
self.notifications.should_queue = false
|
||||
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let _ = notifications.flush()
|
||||
let _ = notifications.flush(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ParicipantsView.swift
|
||||
// ParticipantsView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Joel Klabo on 1/18/23.
|
||||
+131
-25
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
enum NostrPostResult {
|
||||
case post(NostrPost)
|
||||
@@ -21,9 +22,12 @@ struct PostView: View {
|
||||
@State var attach_media: Bool = false
|
||||
@State var attach_camera: Bool = false
|
||||
@State var error: String? = nil
|
||||
|
||||
@State var uploadedMedias: [UploadedMedia] = []
|
||||
@State var image_upload_confirm: Bool = false
|
||||
@State var originalReferences: [ReferencedId] = []
|
||||
@State var references: [ReferencedId] = []
|
||||
|
||||
@State var mediaToUpload: MediaUpload? = nil
|
||||
|
||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||
|
||||
@@ -57,7 +61,14 @@ 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 + " ")
|
||||
|
||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||
|
||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||
@@ -66,13 +77,15 @@ struct PostView: View {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string: "")
|
||||
uploadedMedias = []
|
||||
damus_state.drafts.medias = []
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
var is_post_empty: Bool {
|
||||
return post.string.allSatisfy { $0.isWhitespace }
|
||||
return post.string.allSatisfy { $0.isWhitespace } && uploadedMedias.isEmpty
|
||||
}
|
||||
|
||||
var ImageButton: some View {
|
||||
@@ -168,29 +181,20 @@ struct PostView: View {
|
||||
.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) {
|
||||
let uploader = get_media_uploader(damus_state.pubkey)
|
||||
|
||||
Task.init {
|
||||
let img = getImage(media: media)
|
||||
let res = await image_upload.start(media: media, uploader: uploader)
|
||||
|
||||
switch res {
|
||||
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):
|
||||
if let error {
|
||||
@@ -206,7 +210,7 @@ struct PostView: View {
|
||||
var body: some View {
|
||||
GeometryReader { (deviceSize: GeometryProxy) in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
||||
|
||||
let searching = get_searching_string(post.string)
|
||||
|
||||
TopBar
|
||||
@@ -222,8 +226,16 @@ struct PostView: View {
|
||||
|
||||
TextEntry
|
||||
}
|
||||
.frame(height: deviceSize.size.height*0.78)
|
||||
.frame(height: uploadedMedias.isEmpty ? deviceSize.size.height*0.78 : deviceSize.size.height*0.2)
|
||||
.id("post")
|
||||
|
||||
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
||||
.onChange(of: uploadedMedias) { _ in
|
||||
if replying_to == nil {
|
||||
damus_state.drafts.medias = uploadedMedias
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
@@ -247,14 +259,24 @@ struct PostView: View {
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $attach_media) {
|
||||
ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey) { img in
|
||||
handle_upload(media: .image(img))
|
||||
ImagePicker(sourceType: .photoLibrary, pubkey: damus_state.pubkey, image_upload_confirm: $image_upload_confirm) { img in
|
||||
self.mediaToUpload = .image(img)
|
||||
} 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) {
|
||||
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))
|
||||
} onVideoPicked: { url in
|
||||
handle_upload(media: .video(url))
|
||||
@@ -272,6 +294,7 @@ struct PostView: View {
|
||||
}
|
||||
} else {
|
||||
post = damus_state.drafts.post
|
||||
uploadedMedias = damus_state.drafts.medias
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
@@ -283,6 +306,7 @@ struct PostView: View {
|
||||
damus_state.drafts.replies.removeValue(forKey: replying_to)
|
||||
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
damus_state.drafts.post = NSMutableAttributedString(string : "")
|
||||
damus_state.drafts.medias = uploadedMedias
|
||||
}
|
||||
}
|
||||
.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: {
|
||||
@@ -323,3 +347,85 @@ struct PostView_Previews: PreviewProvider {
|
||||
PostView(replying_to: nil, 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("Copy URL", 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
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ struct EditProfilePictureControl: View {
|
||||
|
||||
@State private var show_camera = false
|
||||
@State private var show_library = false
|
||||
|
||||
@State var image_upload_confirm: Bool = false
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Button(action: {
|
||||
@@ -44,14 +45,16 @@ struct EditProfilePictureControl: View {
|
||||
}
|
||||
}
|
||||
.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))
|
||||
} onVideoPicked: { url in
|
||||
print("Cannot upload videos as profile image")
|
||||
}
|
||||
}
|
||||
.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))
|
||||
} onVideoPicked: { url in
|
||||
print("Cannot upload videos as profile image")
|
||||
|
||||
@@ -119,6 +119,7 @@ struct ProfileView: View {
|
||||
@State var showing_select_wallet: Bool = false
|
||||
@State var is_zoomed: Bool = false
|
||||
@State var show_share_sheet: Bool = false
|
||||
@State var show_qr_code: Bool = false
|
||||
@State var action_sheet_presented: Bool = false
|
||||
@State var filter_state : FilterState = .posts
|
||||
@State var yOffset: CGFloat = 0
|
||||
@@ -213,6 +214,10 @@ struct ProfileView: View {
|
||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||
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.
|
||||
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
||||
@@ -266,8 +271,7 @@ struct ProfileView: View {
|
||||
|
||||
var dmButton: some View {
|
||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
.environmentObject(dm_model)
|
||||
let dmview = DMChatView(damus_state: damus_state, dms: dm_model)
|
||||
return NavigationLink(destination: dmview) {
|
||||
Image(systemName: "bubble.left.circle")
|
||||
.profile_button_style(scheme: colorScheme)
|
||||
@@ -465,6 +469,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 {
|
||||
let damus_state: DamusState
|
||||
@State var pubkey: String
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var maybe_key: String? {
|
||||
guard let key = bech32_pubkey(damus_state.pubkey) else {
|
||||
guard let key = bech32_pubkey(pubkey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,10 +40,11 @@ struct QRCodeView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .center) {
|
||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||
|
||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
|
||||
if (damus_state.profiles.lookup(id: pubkey)?.picture) != nil {
|
||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
||||
.padding(.top, 50)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
@@ -119,6 +121,6 @@ struct QRCodeView: View {
|
||||
|
||||
struct QRCodeView_Previews: PreviewProvider {
|
||||
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 {
|
||||
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.")) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ struct SearchHomeView: View {
|
||||
damus: damus_state,
|
||||
show_friend_icon: true,
|
||||
filter: {
|
||||
if damus_state.muted_threads.isMutedThread($0, privkey: self.damus_state.keypair.privkey) {
|
||||
return false
|
||||
}
|
||||
|
||||
if damus_state.settings.show_only_preferred_languages == false {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ struct AppearanceSettingsView: View {
|
||||
|
||||
|
||||
}
|
||||
.navigationTitle("Appearance")
|
||||
.navigationTitle(NSLocalizedString("Appearance", comment: "Navigation title for text and appearance settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ struct KeySettingsView: View {
|
||||
}
|
||||
|
||||
}
|
||||
.navigationTitle("Keys")
|
||||
.navigationTitle(NSLocalizedString("Keys", comment: "Navigation title for managing keys."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ struct TranslationSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Translation")
|
||||
.navigationTitle(NSLocalizedString("Translation", comment: "Navigation title for translation settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ struct ZapSettingsView: View {
|
||||
|
||||
@State var default_zap_amount: String
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
|
||||
init(pubkey: String, settings: UserSettingsStore) {
|
||||
self.pubkey = pubkey
|
||||
let zap_amt = get_default_zap_amount(pubkey: pubkey).map({ "\($0)" }) ?? "1000"
|
||||
let zap_amt = get_default_zap_amount(pubkey: pubkey).formatted()
|
||||
_default_zap_amount = State(initialValue: zap_amt)
|
||||
self._settings = ObservedObject(initialValue: settings)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Wallet") {
|
||||
Section(NSLocalizedString("Wallet", comment: "Title for section in zap settings that controls the Lightning wallet selection.")) {
|
||||
|
||||
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
|
||||
Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
|
||||
@@ -36,23 +36,26 @@ struct ZapSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Zaps") {
|
||||
Section(NSLocalizedString("Zaps", comment: "Title for section in zap settings that controls general zap preferences.")) {
|
||||
Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
|
||||
Section("Default Zap Amount in sats") {
|
||||
TextField(String("1000"), text: $default_zap_amount)
|
||||
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Title for section in zap settings that controls the default zap amount in sats.")) {
|
||||
TextField(fallback_zap_amount.formatted(), text: $default_zap_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(default_zap_amount)) { newValue in
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.default_zap_amount = String(parsed)
|
||||
self.default_zap_amount = parsed.formatted()
|
||||
set_default_zap_amount(pubkey: self.pubkey, amount: parsed)
|
||||
} else {
|
||||
self.default_zap_amount = ""
|
||||
set_default_zap_amount(pubkey: self.pubkey, amount: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Zaps")
|
||||
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ struct SideMenuView: View {
|
||||
.font(.title)
|
||||
.foregroundColor(textColor())
|
||||
}).fullScreenCover(isPresented: $showQRCode) {
|
||||
QRCodeView(damus_state: damus_state)
|
||||
QRCodeView(damus_state: damus_state, pubkey: damus_state.pubkey)
|
||||
}
|
||||
}
|
||||
.padding(.top, verticalSpacing)
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ZapAmountItem: Identifiable, Hashable {
|
||||
}
|
||||
|
||||
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
|
||||
let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
|
||||
let def = get_default_zap_amount(pubkey: pubkey)
|
||||
return ZapAmountItem(amount: def, icon: "🤙")
|
||||
}
|
||||
|
||||
@@ -181,13 +181,17 @@ struct CustomizeZapView: View {
|
||||
})
|
||||
|
||||
Section(content: {
|
||||
TextField(String("100000"), text: $custom_amount)
|
||||
// Use the selected sats amount as the placeholder text so that the UI is less confusing.
|
||||
// User can type in their custom amount, which hides the placeholder.
|
||||
TextField(selected_amount.amount.formatted(), text: $custom_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(custom_amount)) { newValue in
|
||||
|
||||
if let parsed = handle_string_amount(new_value: newValue) {
|
||||
self.custom_amount = String(parsed)
|
||||
self.custom_amount = parsed.formatted()
|
||||
self.custom_amount_sats = parsed
|
||||
} else {
|
||||
self.custom_amount = ""
|
||||
self.custom_amount_sats = nil
|
||||
}
|
||||
}
|
||||
}, header: {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -39,7 +39,7 @@
|
||||
<key>many</key>
|
||||
<string>Followers</string>
|
||||
<key>other</key>
|
||||
<string>Sledují</string>
|
||||
<string>Sledující</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
<string>applinks:damus.io</string>
|
||||
<string>webcredentials:damus.io</string>
|
||||
</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>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)com.jb55.damus2</string>
|
||||
|
||||
@@ -55,6 +55,13 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
||||
// Display the notification in the foreground
|
||||
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? {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -47,7 +47,7 @@
|
||||
<key>one</key>
|
||||
<string>Ακόλουθος</string>
|
||||
<key>other</key>
|
||||
<string>Ακολουθείτε</string>
|
||||
<string>Ακολουθεί</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
@@ -127,7 +127,7 @@
|
||||
<key>one</key>
|
||||
<string>Διακομιστής Relay</string>
|
||||
<key>other</key>
|
||||
<string>Διακομιστές Relays</string>
|
||||
<string>Relays</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<trans-unit id="%@" xml:space="preserve">
|
||||
<source>%@</source>
|
||||
<target>%@</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>DM by heading in local notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@ %@" xml:space="preserve">
|
||||
<source>%@ %@</source>
|
||||
@@ -188,13 +188,24 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Appearance" xml:space="preserve">
|
||||
<source>Appearance</source>
|
||||
<target>Appearance</target>
|
||||
<note>Section header for text and appearance settings</note>
|
||||
<note>Navigation title for text and appearance settings.
|
||||
Section header for text and appearance settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you lost?" xml:space="preserve">
|
||||
<source>Are you lost?</source>
|
||||
<target>Are you lost?</target>
|
||||
<note>Text asking the user if they are lost in the app.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you sure you want to repost this?" xml:space="preserve">
|
||||
<source>Are you sure you want to repost this?</source>
|
||||
<target>Are you sure you want to repost this?</target>
|
||||
<note>Alert message to ask if user wants to repost a post.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Are you sure you want to upload this image?" xml:space="preserve">
|
||||
<source>Are you sure you want to upload this image?</source>
|
||||
<target>Are you sure you want to upload this image?</target>
|
||||
<note>Alert message asking if the user wants to upload an image.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Automatically translate notes" xml:space="preserve">
|
||||
<source>Automatically translate notes</source>
|
||||
<target>Automatically translate notes</target>
|
||||
@@ -246,6 +257,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
Button to cancel out of posting a note.
|
||||
Button to cancel out of reposting a post.
|
||||
Button to cancel out of view adding user inputted relay.
|
||||
Button to cancel the upload.
|
||||
Cancel deleting the user.
|
||||
Cancel out of logging out the user.</note>
|
||||
</trans-unit>
|
||||
@@ -399,11 +411,6 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Custom Zap Amount</target>
|
||||
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DM by %@" xml:space="preserve">
|
||||
<source>DM by %@</source>
|
||||
<target>DM by %@</target>
|
||||
<note>DM by heading in local notification</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="DMs" xml:space="preserve">
|
||||
<source>DMs</source>
|
||||
<target>DMs</target>
|
||||
@@ -430,7 +437,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Default Zap Amount in sats" xml:space="preserve">
|
||||
<source>Default Zap Amount in sats</source>
|
||||
<target>Default Zap Amount in sats</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
<note>Title for section in zap settings that controls the default zap amount in sats.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete" xml:space="preserve">
|
||||
<source>Delete</source>
|
||||
@@ -622,7 +629,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Keys" xml:space="preserve">
|
||||
<source>Keys</source>
|
||||
<target>Keys</target>
|
||||
<note>Settings section for managing keys</note>
|
||||
<note>Navigation title for managing keys.
|
||||
Settings section for managing keys</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Left Handed" xml:space="preserve">
|
||||
<source>Left Handed</source>
|
||||
@@ -721,8 +729,7 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<source>Mute</source>
|
||||
<target>Mute</target>
|
||||
<note>Alert button to mute a user.
|
||||
Button to mute a profile.
|
||||
Context menu option for muting users.</note>
|
||||
Button to mute a profile.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mute %@?" xml:space="preserve">
|
||||
<source>Mute %@?</source>
|
||||
@@ -732,7 +739,13 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Mute User" xml:space="preserve">
|
||||
<source>Mute User</source>
|
||||
<target>Mute User</target>
|
||||
<note>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 id="Muted" xml:space="preserve">
|
||||
<source>Muted</source>
|
||||
@@ -749,6 +762,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>NIP-05 Verification</target>
|
||||
<note>Label for NIP-05 Verification section of user profile form.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="New encrypted direct message" xml:space="preserve">
|
||||
<source>New encrypted direct message</source>
|
||||
<target>New encrypted direct message</target>
|
||||
<note>Notification that the user has received a new direct message</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="No" xml:space="preserve">
|
||||
<source>No</source>
|
||||
<target>No</target>
|
||||
@@ -779,6 +797,11 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<target>Nothing to see here. Check back later!</target>
|
||||
<note>Indicates that there are no notes in the timeline to view.</note>
|
||||
</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">
|
||||
<source>Notification Preference</source>
|
||||
<target>Notification Preference</target>
|
||||
@@ -787,7 +810,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<trans-unit id="Notifications" xml:space="preserve">
|
||||
<source>Notifications</source>
|
||||
<target>Notifications</target>
|
||||
<note>Toolbar label for Notifications view.</note>
|
||||
<note>Section header for Damus notifications
|
||||
Toolbar label for Notifications view.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Nudity or explicit content" xml:space="preserve">
|
||||
<source>Nudity or explicit content</source>
|
||||
@@ -913,6 +937,11 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>Public key</target>
|
||||
<note>Label indicating that the text is a user's public account key.</note>
|
||||
</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="Reactions" xml:space="preserve">
|
||||
<source>Reactions</source>
|
||||
<target>Reactions</target>
|
||||
@@ -1253,7 +1282,8 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<trans-unit id="Translation" xml:space="preserve">
|
||||
<source>Translation</source>
|
||||
<target>Translation</target>
|
||||
<note>Section header for text and appearance settings</note>
|
||||
<note>Navigation title for translation settings.
|
||||
Section header for text and appearance settings</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translations" xml:space="preserve">
|
||||
<source>Translations</source>
|
||||
@@ -1300,6 +1330,16 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>Universe 🛸</target>
|
||||
<note>Toolbar label for the universal view where posts from all connected relay servers appear.</note>
|
||||
</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">
|
||||
<source>User has been muted</source>
|
||||
<target>User has been muted</target>
|
||||
@@ -1339,10 +1379,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
ARE YOU SURE YOU WANT TO CONTINUE?</target>
|
||||
<note>Alert for deleting the users account.</note>
|
||||
</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">
|
||||
<source>Wallet</source>
|
||||
<target>Wallet</target>
|
||||
<note>Sidebar menu label for Wallet view.</note>
|
||||
<note>Sidebar menu label for Wallet view.
|
||||
Title for section in zap settings that controls the Lightning wallet selection.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Website" xml:space="preserve">
|
||||
<source>Website</source>
|
||||
@@ -1374,6 +1420,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -1425,8 +1476,10 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
|
||||
<source>Zaps</source>
|
||||
<target>Zaps</target>
|
||||
<note>Navigation bar title for the Zaps view.
|
||||
Navigation title for zap settings.
|
||||
Section header for zap settings
|
||||
Setting to enable Zap Local Notification</note>
|
||||
Setting to enable Zap Local Notification
|
||||
Title for section in zap settings that controls general zap preferences.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
|
||||
<source>https://example.com/pic.jpg</source>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,7 +15,7 @@
|
||||
<key>one</key>
|
||||
<string>...%d andra inlägg...</string>
|
||||
<key>other</key>
|
||||
<string>...%d andra inlägg...</string>
|
||||
<string>...%d andra anteckningar...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -51,55 +51,55 @@ final class DMTests: XCTestCase {
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [alice_to_bob])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].0, bob.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let bob_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 1)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].0, bob.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let alice_to_bob_2 = create_dm("hi bob", to_pk: bob.pubkey, tags: [["p", bob.pubkey]], keypair: alice, created_at: now + 2)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [alice_to_bob_2])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].0, bob.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let fiatjaf_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: fiatjaf, created_at: now+5)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [fiatjaf_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 2)
|
||||
XCTAssertEqual(model.dms[0].0, fiatjaf.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, fiatjaf.pubkey)
|
||||
|
||||
let dave_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: dave, created_at: now + 10)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [dave_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 3)
|
||||
XCTAssertEqual(model.dms[0].0, dave.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, dave.pubkey)
|
||||
|
||||
let bob_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 15)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice_2])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 3)
|
||||
XCTAssertEqual(model.dms[0].0, bob.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let charlie_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: charlie, created_at: now + 20)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [charlie_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].0, charlie.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey)
|
||||
|
||||
let bob_to_alice_3 = create_dm("hi alice 3", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 25)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice_3])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].0, bob.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let charlie_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: charlie, created_at: now + 30)!
|
||||
handle_incoming_dms(prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [charlie_to_alice_2])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].0, charlie.pubkey)
|
||||
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,9 +97,9 @@ class damusTests: XCTestCase {
|
||||
|
||||
func testSaveDefaultZapAmount() {
|
||||
let pubkey = "test_pubkey"
|
||||
let amt = 1000
|
||||
let amt = 1234
|
||||
set_default_zap_amount(pubkey: pubkey, amount: amt)
|
||||
let loaded = get_default_zap_amount(pubkey: pubkey)!
|
||||
let loaded = get_default_zap_amount(pubkey: pubkey)
|
||||
XCTAssertEqual(loaded, amt)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user