Compare commits

...

108 Commits

Author SHA1 Message Date
94504a116d Fix crash with LibreTranslate server setting selection and remove delisted vern server
Broken after the settings refactor

Changelog-Fixed: Fix crash with LibreTranslate server setting selection and remove delisted vern server
2023-04-22 11:45:06 +02:00
William Casarin
00aa897f05 build 8 2023-04-21 17:48:34 -07:00
William Casarin
71a1a6f0a3 Add some default height to images 2023-04-21 17:45:58 -07:00
William Casarin
9d22f40a53 Remove some cruft 2023-04-21 17:15:23 -07:00
William Casarin
0bd40c0018 Add Friends filter to DMs
Changelog-Added: Add friends filter to DMs
2023-04-21 16:55:59 -07:00
William Casarin
ec75769a0f Refactor notification state filter saving and loading 2023-04-21 16:39:12 -07:00
William Casarin
47e349558c rename FineNotificationFilter to FriendFilter 2023-04-21 16:25:18 -07:00
William Casarin
aa559b2916 Refactor and Scope user settings to pubkey 2023-04-21 16:21:01 -07:00
William Casarin
9bf8349db6 Friends filter for notifications
Changelog-Added: Friends filter for notifications
2023-04-21 14:17:37 -07:00
William Casarin
4c44de9276 Merge remote-tracking branch 'github/translations' 2023-04-21 11:27:35 -07:00
William Casarin
aa5f8d19f7 build 7 2023-04-21 11:26:49 -07:00
William Casarin
040e452132 Fix having to set onlyzaps mode every time on restart
Changelog-Fixed: Fix having to set onlyzaps mode every time on restart
2023-04-21 10:01:32 -07:00
William Casarin
ff41bb1b35 Update OnlyZaps wording: others can still pointlessly send you likes 2023-04-21 09:54:29 -07:00
transifex-integration[bot]
efe231c122 Apply translations in nl [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'nl' language.
2023-04-21 16:52:37 +00:00
transifex-integration[bot]
0305712f65 Apply translations in es_419 [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'es_419' language.
2023-04-21 16:46:29 +00:00
William Casarin
ba844aec97 Enable like button on OnlyZaps profiles for people who don't have OnlyZaps mode on
It seems like people still want to like posts even if the receiver isn't
going to see it.

Changelog-Changed: Enable like button on OnlyZaps profiles for people who don't have OnlyZaps mode on
2023-04-21 09:43:18 -07:00
transifex-integration[bot]
62eac0032f Apply translations in ja [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ja' language.
2023-04-21 16:43:06 +00:00
transifex-integration[bot]
b673e9b43b Apply translations in cs [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'cs' language.
2023-04-21 16:43:03 +00:00
transifex-integration[bot]
1dd274e07f Apply translations in pt_PT [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'pt_PT' language.
2023-04-21 16:43:00 +00:00
transifex-integration[bot]
e8aa52efea Apply translations in sv_SE [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'sv_SE' language.
2023-04-21 16:42:59 +00:00
transifex-integration[bot]
c7f6db84dd Apply translations in pl_PL [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'pl_PL' language.
2023-04-21 16:42:57 +00:00
transifex-integration[bot]
4a28ddefab Apply translations in el_GR [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'el_GR' language.
2023-04-21 16:42:53 +00:00
transifex-integration[bot]
3f4e37f1f8 Apply translations in zh_HK [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'zh_HK' language.
2023-04-21 16:42:49 +00:00
transifex-integration[bot]
9dc304aa04 Apply translations in zh_CN [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'zh_CN' language.
2023-04-21 16:42:45 +00:00
transifex-integration[bot]
d3dd4faa42 Apply translations in vi [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'vi' language.
2023-04-21 16:42:42 +00:00
transifex-integration[bot]
b40ee38895 Apply translations in ru [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'ru' language.
2023-04-21 16:42:36 +00:00
transifex-integration[bot]
fb9d1db6e6 Apply translations in ja [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'ja' language.
2023-04-21 16:42:26 +00:00
transifex-integration[bot]
591cdd478e Apply translations in fr [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'fr' language.
2023-04-21 16:42:23 +00:00
transifex-integration[bot]
b97204400f Apply translations in cs [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'cs' language.
2023-04-21 16:42:19 +00:00
transifex-integration[bot]
ebfe00362c Apply translations in zh_TW [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'zh_TW' language.
2023-04-21 16:37:37 +00:00
transifex-integration[bot]
accafb4cb2 Apply translations in zh_CN [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'zh_CN' language.
2023-04-21 16:37:34 +00:00
transifex-integration[bot]
20b124aa59 Apply translations in pt_BR [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'pt_BR' language.
2023-04-21 16:37:30 +00:00
transifex-integration[bot]
057bb2add5 Apply translations in de [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'de' language.
2023-04-21 16:37:24 +00:00
transifex-integration[bot]
c1b31a9938 Apply translations in sv_SE [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'sv_SE' language.
2023-04-21 16:37:16 +00:00
transifex-integration[bot]
1e532f9e63 Apply translations in el_GR [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.
2023-04-21 16:37:12 +00:00
transifex-integration[bot]
16a7d5dedf Apply translations in pl_PL [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'pl_PL' language.
2023-04-21 16:37:08 +00:00
transifex-integration[bot]
8898bffbed Apply translations in hu_HU [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'hu_HU' language.
2023-04-21 16:37:04 +00:00
transifex-integration[bot]
7583346b06 Apply translations in zh_TW [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.
2023-04-21 16:37:00 +00:00
transifex-integration[bot]
8fbc71a2c2 Apply translations in zh_CN [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.
2023-04-21 16:36:56 +00:00
transifex-integration[bot]
e100f8c313 Apply translations in ru [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ru' language.
2023-04-21 16:36:53 +00:00
transifex-integration[bot]
676c6f2afb Apply translations in pt_BR [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'pt_BR' language.
2023-04-21 16:36:49 +00:00
transifex-integration[bot]
e7c66156d3 Apply translations in nl [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'nl' language.
2023-04-21 16:36:45 +00:00
transifex-integration[bot]
99816695ae Apply translations in de [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-21 16:36:41 +00:00
transifex-integration[bot]
8e984ffa98 Apply translations in ar [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ar' language.
2023-04-21 16:36:37 +00:00
transifex-integration[bot]
61e4359164 Apply translations in zh_TW [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'zh_TW' language.
2023-04-21 16:36:33 +00:00
transifex-integration[bot]
71940aaca0 Apply translations in hu_HU [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'hu_HU' language.
2023-04-21 16:36:22 +00:00
transifex-integration[bot]
042237ace7 Apply translations in es_ES [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'es_ES' language.
2023-04-21 16:36:17 +00:00
transifex-integration[bot]
b9c10d1eb1 Apply translations in pt_BR [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'pt_BR' language.
2023-04-21 16:35:59 +00:00
transifex-integration[bot]
638b98624e Apply translations in ko [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'ko' language.
2023-04-21 16:32:28 +00:00
transifex-integration[bot]
c8224f841d Apply translations in de [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'de' language.
2023-04-21 16:32:25 +00:00
transifex-integration[bot]
18439bbdf9 Apply translations in ar [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'ar' language.
2023-04-21 16:32:21 +00:00
transifex-integration[bot]
1fd7b759b1 Apply translations in zh_HK [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'zh_HK' language.
2023-04-21 16:32:12 +00:00
transifex-integration[bot]
0642abe064 Apply translations in zh_HK [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_HK' language.
2023-04-21 16:32:06 +00:00
transifex-integration[bot]
080efda25e Apply translations in vi [Manual Sync]
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'vi' language.
2023-04-21 16:32:04 +00:00
William Casarin
bbfe5380e0 Add onlyzap badges to usernames 2023-04-20 14:40:39 -07:00
William Casarin
8a20b7e4a7 Add #zaps and #onlyzaps to custom hashtags
Changelog-Added: Add #zaps and #onlyzaps to custom hashtags
2023-04-20 14:40:16 -07:00
William Casarin
191950a5aa Colorize friend icons
No reason why nip05 badges should have all the fun

Changelog-Added: Colorize friend icons
2023-04-20 14:30:27 -07:00
William Casarin
ac82f1bc09 Add OnlyZaps Mode
Changelog-Added: Add OnlyZaps mode: disable reactions, only zaps!
2023-04-20 13:41:05 -07:00
William Casarin
209f3e8759 onlyzaps: Clean up reaction filter logic a bit 2023-04-20 11:23:58 -07:00
William Casarin
897621b5ed Don't fetch delete events 2023-04-20 08:23:56 -07:00
66641fc9ae Add setting to hide reactions
Changelog-Added: Add setting to hide reactions
2023-04-20 08:20:50 -07:00
681e0f0be9 Translations (#976)
* Apply translations in pt_PT

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/InfoPlist.strings'
on the 'pt_PT' language.

* Apply translations in sv_SE

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'sv_SE' language.

* Apply translations in ja

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ja' language.

* Apply translations in ja

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ja' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in el_GR

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.

* Apply translations in hu_HU

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'hu_HU' language.

* Apply translations in es_419

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'es_419' language.

* Export strings for translation

* Apply translations in nl

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'nl' language.

* Apply translations in sv_SE

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'sv_SE' language.

* Apply translations in ja

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ja' language.

* Apply translations in hu_HU

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'hu_HU' language.

* Apply translations in pl_PL

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'pl_PL' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_CN

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.

* Apply translations in zh_HK

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_HK' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

* Apply translations in zh_TW

100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2023-04-20 08:16:23 -07:00
William Casarin
2ff12823f2 Don't nest embedded notes 2023-04-18 12:43:26 -07:00
William Casarin
d6d996e84b Don't create spiral hellthreads 2023-04-18 12:38:16 -07:00
William Casarin
d1fce5054d Implement quote reposting 2023-04-18 12:16:11 -07:00
William Casarin
300cd87fc2 Remove EmbeddedEventView
All embedded events are now just regular event views
2023-04-18 10:42:56 -07:00
bff3c0dd52 Improve sats numeric entry for zaps
Changelog-Changed: Add number formatting for sats entry and use selected zaps amount from picker as placeholder
Changelog-Fixed: Do not allow non-numeric characters for sats amount and fix numeric entry for other number systems for all locales
2023-04-17 15:20:14 -07:00
William Casarin
ddd027141a no banner uploads yet :( 2023-04-17 15:18:57 -07:00
William Casarin
9b3e25dd6d v1.4.3-2 changelog 2023-04-17 14:58:44 -07:00
William Casarin
1ae6a3d871 v1.4.3-2 2023-04-17 14:29:50 -07:00
William Casarin
4821ba61a5 reply_p_tags: Use uniq instead of Set to preserve order 2023-04-17 14:29:32 -07:00
Swift
15849e290e Fixed repost turning green too early and not reposting sometimes
Changelog-Fixed: Fixed repost turning green too early and not reposting sometimes
2023-04-17 12:55:52 -07:00
Joshua Jiang
6ed562ed24 Fix shuffling when choosing users to reply to
Changelog-Fixed: Fix shuffling when choosing users to reply to
Co-authored-by: William Casarin <jb55@jb55.com>
Closes: #937
2023-04-17 12:55:22 -07:00
93580e5296 Do not translate own notes if logged in with private key
If you're not the private key user, it's possible you're just lurking on
somebody's account and you might not speak their language, so you might
need translations.

Changelog-Fixed: Do not translate own notes if logged in with private key
Closes: #943
2023-04-17 11:06:50 -07:00
Swift
a5c33e4431 Fix reply image previews appearing on new posts
Closes: #954
2023-04-17 11:00:51 -07:00
ad7a79c2bb Fix typo on ParticipantsView.swift file name
Closes: #945
2023-04-17 10:27:47 -07:00
William Casarin
eaec3ae011 Merge remote-tracking branch 'github/translations' 2023-04-17 10:26:35 -07:00
William Casarin
5a1b966191 Only check inner_events on boosts 2023-04-17 10:19:47 -07:00
Gísli Kristjánsson
a320fae2bc Load missing profiles from boosts on home view
Changelog-Fixed: Load missing profiles from boosts on home view
Closes: #947
2023-04-17 10:19:29 -07:00
Gísli Kristjánsson
8e0136a13a Load profiles for profile model
Changelog-Fixed: Load missing profiles from boosts on profile view
2023-04-17 10:17:59 -07:00
Gísli Kristjánsson
8f767b03ae fix: find inner_event profiles from events 2023-04-17 10:10:59 -07:00
Swift
209b23674d Add ability to copy url from post media previews 2023-04-17 10:10:41 -07:00
transifex-integration[bot]
86917dbd69 Apply translations in zh_TW
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.
2023-04-17 08:56:51 +00:00
transifex-integration[bot]
59daf555cd Apply translations in zh_CN
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.
2023-04-17 08:56:06 +00:00
transifex-integration[bot]
916c7885f8 Apply translations in zh_CN
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.
2023-04-17 08:55:54 +00:00
transifex-integration[bot]
252763ea77 Apply translations in zh_HK
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_HK' language.
2023-04-17 08:55:31 +00:00
transifex-integration[bot]
aa610c18b2 Apply translations in zh_CN
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.
2023-04-17 08:34:09 +00:00
transifex-integration[bot]
1de96a9dc5 Apply translations in ja
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ja' language.
2023-04-17 05:07:03 +00:00
transifex-integration[bot]
746a4093de Apply translations in el_GR
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'el_GR' language.
2023-04-16 19:27:10 +00:00
transifex-integration[bot]
5cc9288759 Apply translations in el_GR
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'el_GR' language.
2023-04-16 19:12:53 +00:00
transifex-integration[bot]
3bedc83764 Apply translations in el_GR
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.stringsdict'
on the 'el_GR' language.
2023-04-16 19:12:32 +00:00
transifex-integration[bot]
eb17e07478 Apply translations in nl
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'nl' language.
2023-04-16 17:42:10 +00:00
transifex-integration[bot]
b96ae84068 Apply translations in ar
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'ar' language.
2023-04-16 17:35:54 +00:00
transifex-integration[bot]
af7d4d2c53 Apply translations in cs
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'cs' language.
2023-04-16 17:21:08 +00:00
d93a0600f3 Fix localization issues 2023-04-16 19:07:57 +02:00
transifex-integration[bot]
e9e5756c94 Apply translations in zh_TW
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_TW' language.
2023-04-16 17:01:24 +02:00
transifex-integration[bot]
0b3cc2092f Apply translations in zh_HK
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_HK' language.
2023-04-16 17:01:24 +02:00
transifex-integration[bot]
285ab11324 Apply translations in zh_CN
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'zh_CN' language.
2023-04-16 17:01:24 +02:00
transifex-integration[bot]
3610a76c55 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
4644c57bf3 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
33887982b0 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
c3d3db352e Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
8f08e5c4c8 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
e00e89c16b Apply translations in pl_PL
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'pl_PL' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
9151ef02a0 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
709a707942 Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
343b7a2bcc Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:23 +02:00
transifex-integration[bot]
6534ba3bde Apply translations in de
100% translated for the source file 'damus/en-US.xcloc/Source Contents/damus/en-US.lproj/Localizable.strings'
on the 'de' language.
2023-04-16 17:01:22 +02:00
86 changed files with 1248 additions and 725 deletions

View File

@@ -1,8 +1,8 @@
## [1.4.3-1] - 2023-04-15
## [1.4.3-2] - 2023-04-17
### Added
- Add deep links for local notifications (Swift + Will)
- Add deep links for local notifications (Swift)
- Add thread muting (Terry Yiu)
- Preview media uploads when posting (Swift)
- Add QR Code in profiles (ericholguin)
@@ -10,18 +10,24 @@
### Changed
- Always check signatures of profile events (William Casarin)
- Ask permission before uploading media (Swift)
- Show DM message in local notification (William Casarin)
### Fixed
- Fixed repost turning green too early and not reposting sometimes (Swift)
- Fix shuffling when choosing users to reply to (Joshua Jiang)
- Do not translate own notes if logged in with private key (Terry Yiu)
- Load missing profiles from boosts on home view (Gísli Kristjánsson)
- Load missing profiles from boosts on profile view (Gísli Kristjánsson)
- Fix tap area when mentioning users (OlegAba)
- Fix invalid DM author notifications (William Casarin)
- Fix relay signal indicator, properly show how many relays you are connected to (William Casarin)
[1.4.3-1]: https://github.com/damus-io/damus/releases/tag/v1.4.3-1
[1.4.3-2]: https://github.com/damus-io/damus/releases/tag/v1.4.3-2
## [1.4.2-2] - 2023-04-12

View File

@@ -142,8 +142,9 @@
4C8D00CC29DF92DF0036AF10 /* Hashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */; };
4C8D00CF29E38B950036AF10 /* nostr_bech32.c in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00CE29E38B950036AF10 /* nostr_bech32.c */; };
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */; };
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */; };
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */; };
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
@@ -154,6 +155,7 @@
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5588229F33F5B00DC6A45 /* StringCodable.swift */; };
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
@@ -182,7 +184,6 @@
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEC297F0B9E00430951 /* Highlight.swift */; };
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEF297F11C700430951 /* SelectedEventView.swift */; };
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */; };
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF3297F18B400430951 /* ReplyDescription.swift */; };
4CC7AAF6297F1A6A00430951 /* EventBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF5297F1A6A00430951 /* EventBody.swift */; };
4CC7AAF8297F1CEE00430951 /* EventProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF7297F1CEE00430951 /* EventProfile.swift */; };
@@ -278,7 +279,7 @@
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */; };
F79C7FAD29D5E9620000F946 /* EditProfilePictureControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */; };
F7F0BA25297892BD009531F3 /* SwipeToDismiss.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */; };
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParicipantsView.swift */; };
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F0BA262978E54D009531F3 /* ParticipantsView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -550,8 +551,9 @@
4C8D00D129E397AD0036AF10 /* block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = block.h; sourceTree = "<group>"; };
4C8D00D229E3C19F0036AF10 /* str_block.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = str_block.h; sourceTree = "<group>"; };
4C8D00D329E3C5D40036AF10 /* NIP19Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP19Tests.swift; sourceTree = "<group>"; };
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendIcon.swift; sourceTree = "<group>"; };
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsButton.swift; sourceTree = "<group>"; };
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
@@ -562,6 +564,7 @@
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; };
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringCodable.swift; sourceTree = "<group>"; };
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
@@ -590,7 +593,6 @@
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
4CC7AAEC297F0B9E00430951 /* Highlight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Highlight.swift; sourceTree = "<group>"; };
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedEventView.swift; sourceTree = "<group>"; };
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedEventView.swift; sourceTree = "<group>"; };
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyDescription.swift; sourceTree = "<group>"; };
4CC7AAF5297F1A6A00430951 /* EventBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBody.swift; sourceTree = "<group>"; };
4CC7AAF7297F1CEE00430951 /* EventProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfile.swift; sourceTree = "<group>"; };
@@ -688,7 +690,7 @@
F7908E96298B1FDF00AB113A /* NIPURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIPURLBuilder.swift; sourceTree = "<group>"; };
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePictureControl.swift; sourceTree = "<group>"; };
F7F0BA24297892BD009531F3 /* SwipeToDismiss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismiss.swift; sourceTree = "<group>"; };
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParicipantsView.swift; sourceTree = "<group>"; };
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantsView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -883,6 +885,7 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
4C1A9A1B29DDCF8B00516EAC /* Settings */,
4CFF8F6129CC9A80008DB934 /* Images */,
4CCEB7AC29B53D180078AA28 /* Search */,
@@ -908,7 +911,6 @@
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */,
4C216F31286E388800040376 /* DMChatView.swift */,
4C216F33286F5ACD00040376 /* DMView.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
3169CAE4294E699400EE4006 /* Empty Views */,
4C75EFB82804A2740006080F /* EventView.swift */,
4CEE2AF0280B216B00AB5EEF /* EventDetailView.swift */,
@@ -926,7 +928,7 @@
4C3AC7A42836987600E1F516 /* MainTabView.swift */,
4C363A8B28236B92006E126D /* PubkeyView.swift */,
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */,
F7F0BA262978E54D009531F3 /* ParicipantsView.swift */,
F7F0BA262978E54D009531F3 /* ParticipantsView.swift */,
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
@@ -963,7 +965,6 @@
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
4C363A8F28247A1D006E126D /* NostrLink.swift */,
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */,
);
path = Nostr;
sourceTree = "<group>";
@@ -1009,10 +1010,19 @@
4C8D00C729DF791C0036AF10 /* CompatibleAttribute.swift */,
4C8D00CB29DF92DF0036AF10 /* Hashtags.swift */,
4CDA128B29EB19C40006FA5A /* LocalNotification.swift */,
4CA5588229F33F5B00DC6A45 /* StringCodable.swift */,
);
path = Util;
sourceTree = "<group>";
};
4C8D1A6D29F31E4100ACDF75 /* Buttons */ = {
isa = PBXGroup;
children = (
4C8D1A6E29F31E5000ACDF75 /* FriendsButton.swift */,
);
path = Buttons;
sourceTree = "<group>";
};
4CAAD8AE29888A9B00060CEA /* Relays */ = {
isa = PBXGroup;
children = (
@@ -1053,12 +1063,14 @@
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
F79C7FAC29D5E9620000F946 /* EditProfilePictureControl.swift */,
E990020E2955F837003BBC5A /* EditMetadataView.swift */,
4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */,
4C8682862814DE470026224F /* ProfileView.swift */,
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */,
4C8D1A6B29F1DFC200ACDF75 /* FriendIcon.swift */,
);
path = Profile;
sourceTree = "<group>";
@@ -1067,7 +1079,6 @@
isa = PBXGroup;
children = (
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
4CC7AAF1297F129C00430951 /* EmbeddedEventView.swift */,
4CC7AAF3297F18B400430951 /* ReplyDescription.swift */,
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */,
@@ -1484,6 +1495,7 @@
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */,
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
4C8D1A6C29F1DFC200ACDF75 /* FriendIcon.swift in Sources */,
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
@@ -1515,6 +1527,7 @@
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */,
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
4CA5588329F33F5B00DC6A45 /* StringCodable.swift in Sources */,
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
4C8D00C829DF791C0036AF10 /* CompatibleAttribute.swift in Sources */,
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
@@ -1529,7 +1542,7 @@
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */,
4C3D52B6298DB4E6001C5831 /* ZapEvent.swift in Sources */,
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
F7F0BA272978E54D009531F3 /* ParticipantsView.swift in Sources */,
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
@@ -1537,6 +1550,7 @@
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
4CB883A62975F83C00DC99E7 /* LNUrlPayRequest.swift in Sources */,
@@ -1582,7 +1596,6 @@
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
@@ -1597,7 +1610,6 @@
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
4CE879522996B68900F758CC /* RelayType.swift in Sources */,
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */,
4CE8795B2996C47A00F758CC /* ZapsModel.swift in Sources */,
4C3A1D3729637E0500558C0F /* PreviewCache.swift in Sources */,
4C3EA67528FF7A5A00C48A62 /* take.c in Sources */,
@@ -2012,7 +2024,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@@ -2059,7 +2071,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;

View File

@@ -66,7 +66,7 @@ struct ImageCarousel: View {
}
var height: CGFloat {
image_fill?.height ?? 0
image_fill?.height ?? 100
}
var body: some View {
@@ -118,10 +118,7 @@ extension KFOptionSetter {
let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
let img_size = image.size
let geo_size = size
let fill = ImageFill.calculate_image_fill(geo_size: geo_size,
img_size: img_size,
maxHeight: max,
fillHeight: fill)
let fill = ImageFill.calculate_image_fill(geo_size: geo_size, img_size: img_size, maxHeight: max, fillHeight: fill)
DispatchQueue.main.async { [block, fill] in
try? block(fill)
}

View File

@@ -35,10 +35,10 @@ struct NIP05Badge: View {
.mask(Image(systemName: "checkmark.seal.fill")
.resizable()
).frame(width: 14, height: 14)
} else {
} else if show_domain {
Image(systemName: "checkmark.seal.fill")
.font(.footnote)
.foregroundColor(.gray)
.nip05_colorized(gradient: nip05_color)
}
}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -15,17 +15,15 @@ struct TimestampedProfile {
}
enum Sheets: Identifiable {
case post
case post(PostAction)
case report(ReportTarget)
case reply(NostrEvent)
case event(NostrEvent)
case filter
var id: String {
switch self {
case .report: return "report"
case .post: return "post"
case .reply(let ev): return "reply-" + ev.id
case .post(let action): return "post-" + (action.ev?.id ?? "")
case .event(let ev): return "event-" + ev.id
case .filter: return "filter"
}
@@ -83,6 +81,7 @@ struct ContentView: View {
@State var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false
@StateObject var home: HomeModel = HomeModel()
@State var shouldShowBoostAlert = false
// connect retry timer
let timer = Timer.publish(every: 4, on: .main, in: .common).autoconnect()
@@ -114,7 +113,7 @@ struct ContentView: View {
if privkey != nil {
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
self.active_sheet = .post
self.active_sheet = .post(.posting)
}
}
}
@@ -183,7 +182,7 @@ struct ContentView: View {
NotificationsView(state: damus, notifications: home.notifications)
case .dms:
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms)
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
case .none:
EmptyView()
@@ -310,10 +309,8 @@ struct ContentView: View {
switch item {
case .report(let target):
MaybeReportView(target: target)
case .post:
PostView(replying_to: nil, damus_state: damus_state!)
case .reply(let event):
PostView(replying_to: event, damus_state: damus_state!)
case .post(let action):
PostView(action: action, damus_state: damus_state!)
case .event:
EventDetailView()
case .filter:
@@ -353,11 +350,16 @@ struct ContentView: View {
}
.onReceive(handle_notify(.boost)) { notif in
current_boost = (notif.object as? NostrEvent)
guard let ev = notif.object as? NostrEvent else {
return
}
current_boost = ev
shouldShowBoostAlert = true
}
.onReceive(handle_notify(.reply)) { notif in
let ev = notif.object as! NostrEvent
self.active_sheet = .reply(ev)
self.active_sheet = .post(.replying_to(ev))
}
.onReceive(handle_notify(.like)) { like in
}
@@ -465,18 +467,18 @@ struct ContentView: View {
self.damus_state?.pool.connect_to_disconnected()
}
.onReceive(handle_notify(.new_mutes)) { notif in
home.filter_muted()
home.filter_events()
}
.onReceive(handle_notify(.mute_thread)) { notif in
home.filter_muted()
home.filter_events()
}
.onReceive(handle_notify(.unmute_thread)) { notif in
home.filter_muted()
home.filter_events()
}
.onReceive(handle_notify(.local_notification)) { notif in
let local = notif.object as! LossyLocalNotification
guard let damus_state else {
guard let local = notif.object as? LossyLocalNotification,
let damus_state else {
return
}
@@ -496,6 +498,26 @@ struct ContentView: View {
open_event(ev: target)
}
}
.onReceive(handle_notify(.onlyzaps_mode)) { notif in
let hide = notif.object as! Bool
home.filter_events()
guard let damus_state else {
return
}
guard let profile = damus_state.profiles.lookup(id: damus_state.pubkey) else {
return
}
profile.reactions = !hide
guard let profile_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
return
}
damus_state.postbox.send(profile_ev)
}
.alert(NSLocalizedString("Deleted Account", comment: "Alert message to indicate this is a deleted account"), isPresented: $is_deleted_account) {
Button(NSLocalizedString("Logout", comment: "Button to close the alert that informs that the current account has been deleted.")) {
is_deleted_account = false
@@ -583,17 +605,35 @@ struct ContentView: View {
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
}
})
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $current_boost.mappedToBool()) {
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
current_boost = nil
}
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
if let current_boost {
self.damus_state?.postbox.send(current_boost)
.confirmationDialog("Repost", isPresented: $shouldShowBoostAlert) {
Button(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post.")) {
guard let current_boost else {
return
}
guard let privkey = self.damus_state?.keypair.privkey else {
return
}
guard let damus_state else {
return
}
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: current_boost)
damus_state.postbox.send(boost)
}
Button(NSLocalizedString("Quote", comment: "Title of alert for confirming to make a quoted post.")) {
guard let current_boost else {
return
}
self.active_sheet = .post(.quoting(current_boost))
}
}
.onChange(of: shouldShowBoostAlert) { v in
if v == false {
self.current_boost = nil
}
} message: {
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
}
}
@@ -638,7 +678,12 @@ struct ContentView: View {
}
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
// dumb stuff needed for property wrappers
UserSettingsStore.pubkey = pubkey
let settings = UserSettingsStore()
UserSettingsStore.shared = settings
self.damus_state = DamusState(pool: pool,
keypair: keypair,
likes: EventCounter(our_pubkey: pubkey),
@@ -650,7 +695,7 @@ struct ContentView: View {
previews: PreviewCache(),
zaps: Zaps(our_pubkey: pubkey),
lnurls: LNUrls(),
settings: UserSettingsStore(),
settings: settings,
relay_filters: relay_filters,
relay_metadata: metadatas,
drafts: Drafts(),
@@ -821,7 +866,7 @@ func find_event(state: DamusState, evid: String, search_type: SearchType, find_f
var filter = search_type == .event ? NostrFilter.filter_ids([ evid ]) : NostrFilter.filter_authors([ evid ])
if search_type == .profile {
filter.kinds = [0]
filter.kinds = [NostrKind.metadata.rawValue]
}
filter.limit = 1

View File

@@ -39,6 +39,8 @@ struct DamusState {
keypair.privkey != nil
}
static var settings_pubkey: String? = nil
static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil))) }
}

View File

@@ -7,7 +7,19 @@
import Foundation
enum DeepLPlan: String, CaseIterable, Identifiable {
enum DeepLPlan: String, CaseIterable, Identifiable, StringCodable {
init?(from string: String) {
guard let dl = DeepLPlan(rawValue: string) else {
return nil
}
self = dl
}
func to_string() -> String {
return self.rawValue
}
var id: String { self.rawValue }
struct Model: Identifiable, Hashable {

View File

@@ -7,8 +7,23 @@
import Foundation
class Drafts: ObservableObject {
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
@Published var medias: [UploadedMedia] = []
class DraftArtifacts {
var content: NSMutableAttributedString
var media: [UploadedMedia]
init() {
self.content = NSMutableAttributedString(string: "")
self.media = []
}
init(content: NSMutableAttributedString, media: [UploadedMedia]) {
self.content = content
self.media = media
}
}
class Drafts: ObservableObject {
@Published var post: DraftArtifacts? = nil
@Published var replies: [NostrEvent: DraftArtifacts] = [:]
@Published var quotes: [NostrEvent: DraftArtifacts] = [:]
}

View File

@@ -22,7 +22,7 @@ class FollowingModel {
}
func get_filter() -> NostrFilter {
var f = NostrFilter.filter_kinds([0])
var f = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in
// don't fetch profiles we already have
if damus_state.profiles.lookup(id: pk) != nil {

View File

@@ -52,7 +52,7 @@ class HomeModel: ObservableObject {
init() {
self.damus_state = DamusState.empty
filter_muted()
filter_events()
self.setup_debouncer()
}
@@ -192,7 +192,7 @@ class HomeModel: ObservableObject {
func handle_channel_meta(_ ev: NostrEvent) {
}
func filter_muted() {
func filter_events() {
events.filter { ev in
!damus_state.contacts.is_muted(ev.pubkey)
}
@@ -202,8 +202,11 @@ class HomeModel: ObservableObject {
}
notifications.filter { ev in
!damus_state.contacts.is_muted(ev.pubkey) &&
!damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey)
if damus_state.settings.onlyzaps_mode && ev.known_kind == NostrKind.like {
return false
}
return !damus_state.contacts.is_muted(ev.pubkey) && !damus_state.muted_threads.isMutedThread(ev, privkey: damus_state.keypair.privkey)
}
}
@@ -258,6 +261,10 @@ class HomeModel: ObservableObject {
return
}
if damus_state.settings.onlyzaps_mode {
return
}
switch damus_state.likes.add_event(ev, target: e.ref_id) {
case .already_counted:
break
@@ -322,6 +329,8 @@ class HomeModel: ObservableObject {
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
} else if sub_id == notifications_subid {
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
} else if sub_id == home_subid {
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus_state)
}
self.loading = false
@@ -351,13 +360,13 @@ class HomeModel: ObservableObject {
var friends = damus_state.contacts.get_friend_list()
friends.append(damus_state.pubkey)
var contacts_filter = NostrFilter.filter_kinds([0])
var contacts_filter = NostrFilter.filter_kinds([NostrKind.metadata.rawValue])
contacts_filter.authors = friends
var our_contacts_filter = NostrFilter.filter_kinds([3, 0])
var our_contacts_filter = NostrFilter.filter_kinds([NostrKind.contacts.rawValue, NostrKind.metadata.rawValue])
our_contacts_filter.authors = [damus_state.pubkey]
var our_blocklist_filter = NostrFilter.filter_kinds([30000])
var our_blocklist_filter = NostrFilter.filter_kinds([NostrKind.list.rawValue])
our_blocklist_filter.parameter = ["mute"]
our_blocklist_filter.authors = [damus_state.pubkey]
@@ -376,21 +385,27 @@ class HomeModel: ObservableObject {
our_dms_filter.authors = [ damus_state.pubkey ]
// TODO: separate likes?
var home_filter = NostrFilter.filter_kinds([
var home_filter_kinds = [
NostrKind.text.rawValue,
NostrKind.like.rawValue,
NostrKind.boost.rawValue,
])
NostrKind.boost.rawValue
]
if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(NostrKind.like.rawValue)
}
var home_filter = NostrFilter.filter_kinds(home_filter_kinds)
// include our pubkey as well even if we're not technically a friend
home_filter.authors = friends
home_filter.limit = 500
var notifications_filter = NostrFilter.filter_kinds([
var notifications_filter_kinds = [
NostrKind.text.rawValue,
NostrKind.like.rawValue,
NostrKind.boost.rawValue,
NostrKind.zap.rawValue,
])
]
if !damus_state.settings.onlyzaps_mode {
notifications_filter_kinds.append(NostrKind.like.rawValue)
}
var notifications_filter = NostrFilter.filter_kinds(notifications_filter_kinds)
notifications_filter.pubkeys = [damus_state.pubkey]
notifications_filter.limit = 500
@@ -658,9 +673,7 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) {
if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
DispatchQueue.main.async {
notify(.deleted_account, ())
}
notify(.deleted_account, ())
return
}
@@ -694,20 +707,15 @@ func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: P
// load pfps asap
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
if URL(string: picture) != nil {
DispatchQueue.main.async {
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
let banner = tprof.profile.banner ?? ""
if URL(string: banner) != nil {
DispatchQueue.main.async {
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {

View File

@@ -7,7 +7,7 @@
import Foundation
enum LibreTranslateServer: String, CaseIterable, Identifiable {
enum LibreTranslateServer: String, CaseIterable, Identifiable, StringCodable {
var id: String { self.rawValue }
struct Model: Identifiable, Hashable {
@@ -17,9 +17,19 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable {
var url: String?
}
func to_string() -> String {
return rawValue
}
init?(from string: String) {
guard let libreTranslateServer = LibreTranslateServer(rawValue: string) else {
return nil
}
self = libreTranslateServer
}
case argosopentech
case terraprint
case vern
case custom
var model: Model {
@@ -28,8 +38,6 @@ enum LibreTranslateServer: String, CaseIterable, Identifiable {
return .init(tag: self.rawValue, displayName: "translate.argosopentech.com", url: "https://translate.argosopentech.com")
case .terraprint:
return .init(tag: self.rawValue, displayName: "translate.terraprint.co", url: "https://translate.terraprint.co")
case .vern:
return .init(tag: self.rawValue, displayName: "lt.vern.cc", url: "https://lt.vern.cc")
case .custom:
return .init(tag: self.rawValue, displayName: NSLocalizedString("Custom", comment: "Dropdown option for selecting a custom translation server."), url: nil)
}

View File

@@ -29,4 +29,22 @@ class EventGroup {
func insert(_ ev: NostrEvent) -> Bool {
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
}
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
for ev in events {
if !isIncluded(ev) {
return true
}
}
return false
}
func filter(_ isIncluded: (NostrEvent) -> Bool) -> EventGroup? {
let new_evs = events.filter(isIncluded)
guard new_evs.count > 0 else {
return nil
}
return EventGroup(events: new_evs)
}
}

View File

@@ -30,10 +30,26 @@ class ZapGroup {
}
}
init(zaps: [Zap]) {
self.zaps = zaps
self.msat_total = 0
self.zappers = Set()
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
for zap in zaps {
if !isIncluded(zap.request_ev) {
return true
}
}
return false
}
func filter(_ isIncluded: (NostrEvent) -> Bool) -> ZapGroup? {
let new_zaps = zaps.filter { isIncluded($0.request_ev) }
guard new_zaps.count > 0 else {
return nil
}
let grp = ZapGroup()
for zap in new_zaps {
grp.insert(zap)
}
return grp
}
init() {
@@ -42,6 +58,7 @@ class ZapGroup {
self.zappers = Set()
}
@discardableResult
func insert(_ zap: Zap) -> Bool {
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
return false

View File

@@ -65,6 +65,37 @@ enum NotificationItem {
return reply.created_at
}
}
func would_filter(_ isIncluded: (NostrEvent) -> Bool) -> Bool {
switch self {
case .repost(_, let evgrp):
return evgrp.would_filter(isIncluded)
case .reaction(_, let evgrp):
return evgrp.would_filter(isIncluded)
case .profile_zap(let zapgrp):
return zapgrp.would_filter(isIncluded)
case .event_zap(_, let zapgrp):
return zapgrp.would_filter(isIncluded)
case .reply(let ev):
return !isIncluded(ev)
}
}
func filter(_ isIncluded: (NostrEvent) -> Bool) -> NotificationItem? {
switch self {
case .repost(let evid, let evgrp):
return evgrp.filter(isIncluded).map { .repost(evid, $0) }
case .reaction(let evid, let evgrp):
return evgrp.filter(isIncluded).map { .reaction(evid, $0) }
case .profile_zap(let zapgrp):
return zapgrp.filter(isIncluded).map { .profile_zap($0) }
case .event_zap(let evid, let zapgrp):
return zapgrp.filter(isIncluded).map { .event_zap(evid, $0) }
case .reply(let ev):
if isIncluded(ev) { return .reply(ev) }
return nil
}
}
}
class NotificationsModel: ObservableObject, ScrollQueue {

View File

@@ -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
}

View File

@@ -24,7 +24,7 @@ class SearchHomeModel: ObservableObject {
}
func get_base_filter() -> NostrFilter {
var filter = NostrFilter.filter_kinds([1, 42])
var filter = NostrFilter.filter_kinds([NostrKind.text.rawValue, NostrKind.chat.rawValue])
filter.limit = self.limit
filter.until = Int64(Date.now.timeIntervalSince1970)
return filter
@@ -128,11 +128,14 @@ func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]
var pubkeys = Set<String>()
for ev in events {
if profiles.lookup(id: ev.pubkey) != nil {
continue
// lookup profiles from boosted events
if ev.known_kind == .boost, let bev = ev.inner_event, profiles.lookup(id: bev.pubkey) == nil {
pubkeys.insert(bev.pubkey)
}
pubkeys.insert(ev.pubkey)
if profiles.lookup(id: ev.pubkey) == nil {
pubkeys.insert(ev.pubkey)
}
}
return Array(pubkeys)

View File

@@ -33,7 +33,7 @@ class SearchModel: ObservableObject {
func subscribe() {
// since 1 month
search.limit = self.limit
search.kinds = [1,5,7]
search.kinds = [NostrKind.text.rawValue, NostrKind.like.rawValue]
//likes_filter.ids = ref_events.referenced_ids!

View File

@@ -77,18 +77,23 @@ class ThreadModel: ObservableObject {
var meta_events = NostrFilter()
var event_filter = NostrFilter()
var ref_events = NostrFilter()
//var likes_filter = NostrFilter.filter_kinds(7])
let thread_id = event.thread_id(privkey: nil)
ref_events.referenced_ids = [thread_id, event.id]
ref_events.kinds = [1]
ref_events.kinds = [NostrKind.text.rawValue]
ref_events.limit = 1000
event_filter.ids = [thread_id, event.id]
meta_events.referenced_ids = [event.id]
meta_events.kinds = [9735, 1, 6, 7]
var kinds = [NostrKind.zap.rawValue, NostrKind.text.rawValue, NostrKind.boost.rawValue]
if !damus_state.settings.onlyzaps_mode {
kinds.append(NostrKind.like.rawValue)
}
meta_events.kinds = kinds
meta_events.limit = 1000
/*

View File

@@ -7,7 +7,19 @@
import Foundation
enum TranslationService: String, CaseIterable, Identifiable {
enum TranslationService: String, CaseIterable, Identifiable, StringCodable {
init?(from string: String) {
guard let ts = TranslationService(rawValue: string) else {
return nil
}
self = ts
}
func to_string() -> String {
return self.rawValue
}
var id: String { self.rawValue }
struct Model: Identifiable, Hashable {

View File

@@ -9,210 +9,141 @@ import Foundation
import Vault
import UIKit
func should_show_wallet_selector(_ pubkey: String) -> Bool {
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
}
func pk_setting_key(_ pubkey: String, key: String) -> String {
return "\(pubkey)_\(key)"
}
func default_zap_setting_key(pubkey: String) -> String {
return pk_setting_key(pubkey, key: "default_zap_amount")
}
func set_default_zap_amount(pubkey: String, amount: Int) {
let key = default_zap_setting_key(pubkey: pubkey)
UserDefaults.standard.setValue(amount, forKey: key)
}
func get_default_zap_amount(pubkey: String) -> Int? {
let key = default_zap_setting_key(pubkey: pubkey)
let amt = UserDefaults.standard.integer(forKey: key)
if amt == 0 {
return nil
}
return amt
}
func should_disable_image_animation() -> Bool {
return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool)
?? UIAccessibility.isReduceMotionEnabled
}
func get_default_wallet(_ pubkey: String) -> Wallet {
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
let default_wallet = Wallet(rawValue: defaultWalletName)
{
return default_wallet
} else {
return .system_default_wallet
}
}
func get_media_uploader(_ pubkey: String) -> MediaUploader {
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
return defaultMediaUploader
} else {
return .nostrBuild
}
}
private func get_translation_service(_ pubkey: String) -> TranslationService? {
guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else {
return nil
}
return TranslationService(rawValue: translation_service)
}
private func get_deepl_plan(_ pubkey: String) -> DeepLPlan? {
guard let server_name = UserDefaults.standard.string(forKey: "deepl_plan") else {
return nil
}
return DeepLPlan(rawValue: server_name)
}
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else {
return nil
@propertyWrapper struct Setting<T: Equatable> {
private let key: String
private var value: T
init(key: String, default_value: T) {
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
if let loaded = UserDefaults.standard.object(forKey: self.key) as? T {
self.value = loaded
} else if let loaded = UserDefaults.standard.object(forKey: key) as? T {
// try to load from deprecated non-pubkey-keyed setting
self.value = loaded
} else {
self.value = default_value
}
}
return LibreTranslateServer(rawValue: server_name)
var wrappedValue: T {
get { return value }
set {
guard self.value != newValue else {
return
}
self.value = newValue
UserDefaults.standard.set(newValue, forKey: key)
UserSettingsStore.shared!.objectWillChange.send()
}
}
}
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
if let url = server.model.url {
return url
@propertyWrapper class StringSetting<T: StringCodable & Equatable> {
private let key: String
private var value: T
init(key: String, default_value: T) {
self.key = pk_setting_key(UserSettingsStore.pubkey ?? "", key: key)
if let loaded = UserDefaults.standard.string(forKey: self.key), let val = T.init(from: loaded) {
self.value = val
} else if let loaded = UserDefaults.standard.string(forKey: key), let val = T.init(from: loaded) {
// try to load from deprecated non-pubkey-keyed setting
self.value = val
} else {
self.value = default_value
}
}
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
var wrappedValue: T {
get { return value }
set {
guard self.value != newValue else {
return
}
self.value = newValue
UserDefaults.standard.set(newValue.to_string(), forKey: key)
UserSettingsStore.shared!.objectWillChange.send()
}
}
}
class UserSettingsStore: ObservableObject {
@Published var default_wallet: Wallet {
didSet {
UserDefaults.standard.set(default_wallet.rawValue, forKey: "default_wallet")
}
}
@Published var default_media_uploader: MediaUploader {
didSet {
UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader")
}
}
static var pubkey: String? = nil
static var shared: UserSettingsStore? = nil
@Published var show_wallet_selector: Bool {
didSet {
UserDefaults.standard.set(show_wallet_selector, forKey: "show_wallet_selector")
}
}
@Published var left_handed: Bool {
didSet {
UserDefaults.standard.set(left_handed, forKey: "left_handed")
}
}
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
var default_wallet: Wallet
@Published var always_show_images: Bool {
didSet {
UserDefaults.standard.set(always_show_images, forKey: "always_show_images")
}
}
@Published var zap_vibration: Bool {
didSet {
UserDefaults.standard.set(zap_vibration, forKey: "zap_vibration")
}
}
@Published var zap_notification: Bool {
didSet {
UserDefaults.standard.set(zap_notification, forKey: "zap_notification")
}
}
@Published var mention_notification: Bool {
didSet {
UserDefaults.standard.set(mention_notification, forKey: "mention_notification")
}
}
@Published var repost_notification: Bool {
didSet {
UserDefaults.standard.set(repost_notification, forKey: "repost_notification")
}
}
@Published var dm_notification: Bool {
didSet {
UserDefaults.standard.set(dm_notification, forKey: "dm_notification")
}
}
@Published var like_notification: Bool {
didSet {
UserDefaults.standard.set(like_notification, forKey: "like_notification")
}
}
@Published var notification_only_from_following: Bool {
didSet {
UserDefaults.standard.set(notification_only_from_following, forKey: "notification_only_from_following")
}
}
@StringSetting(key: "default_media_uploader", default_value: .nostrBuild)
var default_media_uploader: MediaUploader
@Published var translate_dms: Bool {
didSet {
UserDefaults.standard.set(translate_dms, forKey: "translate_dms")
}
}
@Published var truncate_timeline_text: Bool {
didSet {
UserDefaults.standard.set(truncate_timeline_text, forKey: "truncate_timeline_text")
}
}
@Setting(key: "show_wallet_selector", default_value: true)
var show_wallet_selector: Bool
@Published var notification_indicators: Int {
didSet {
UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators")
}
}
@Setting(key: "left_handed", default_value: false)
var left_handed: Bool
@Published var truncate_mention_text: Bool {
didSet {
UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text")
}
}
@Setting(key: "always_show_images", default_value: false)
var always_show_images: Bool
@Published var auto_translate: Bool {
didSet {
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
}
}
@Setting(key: "zap_vibration", default_value: true)
var zap_vibration: Bool
@Setting(key: "zap_notification", default_value: true)
var zap_notification: Bool
@Setting(key: "mention_notification", default_value: true)
var mention_notification: Bool
@Published var show_only_preferred_languages: Bool {
didSet {
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
}
}
@Setting(key: "repost_notification", default_value: true)
var repost_notification: Bool
@Setting(key: "dm_notification", default_value: true)
var dm_notification: Bool
@Setting(key: "like_notification", default_value: true)
var like_notification: Bool
@Setting(key: "notification_only_from_following", default_value: false)
var notification_only_from_following: Bool
@Setting(key: "translate_dms", default_value: false)
var translate_dms: Bool
@Setting(key: "truncate_timeline_text", default_value: false)
var truncate_timeline_text: Bool
@Setting(key: "truncate_mention_text", default_value: true)
var truncate_mention_text: Bool
@Setting(key: "notification_indicators", default_value: NewEventsBits.all.rawValue)
var notification_indicators: Int
@Setting(key: "auto_translate", default_value: true)
var auto_translate: Bool
@Published var translation_service: TranslationService {
didSet {
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
}
}
@Setting(key: "show_only_preferred_languages", default_value: false)
var show_only_preferred_languages: Bool
@Published var deepl_plan: DeepLPlan {
didSet {
UserDefaults.standard.set(deepl_plan.rawValue, forKey: "deepl_plan")
}
}
@Setting(key: "onlyzaps_mode", default_value: false)
var onlyzaps_mode: Bool
@Setting(key: "disable_animation", default_value: UIAccessibility.isReduceMotionEnabled)
var disable_animation: Bool
@StringSetting(key: "friend_filter", default_value: .all)
var friend_filter: FriendFilter
@StringSetting(key: "notification_state", default_value: .all)
var notification_state: NotificationFilterState
@Published var deepl_api_key: String {
@StringSetting(key: "translation_service", default_value: .none)
var translation_service: TranslationService
@StringSetting(key: "deepl_plan", default_value: .free)
var deepl_plan: DeepLPlan
var deepl_api_key: String {
didSet {
do {
if deepl_api_key == "" {
@@ -226,31 +157,14 @@ class UserSettingsStore: ObservableObject {
}
}
@Published var libretranslate_server: LibreTranslateServer {
didSet {
if oldValue == libretranslate_server {
return
}
@StringSetting(key: "libretranslate_server", default_value: .terraprint)
var libretranslate_server: LibreTranslateServer
@Setting(key: "libretranslate_url", default_value: "")
var libretranslate_url: String
UserDefaults.standard.set(libretranslate_server.rawValue, forKey: "libretranslate_server")
libretranslate_api_key = ""
if libretranslate_server == .custom {
libretranslate_url = ""
} else {
libretranslate_url = libretranslate_server.model.url!
}
}
}
@Published var libretranslate_url: String {
didSet {
UserDefaults.standard.set(libretranslate_url, forKey: "libretranslate_url")
}
}
@Published var libretranslate_api_key: String {
@Setting(key: "libretranslate_api_key", default_value: "")
var libretranslate_api_key: String {
didSet {
do {
if libretranslate_api_key == "" {
@@ -263,71 +177,8 @@ class UserSettingsStore: ObservableObject {
}
}
}
@Published var disable_animation: Bool {
didSet {
UserDefaults.standard.set(disable_animation, forKey: "disable_animation")
}
}
init() {
// TODO: pubkey-scoped settings
let pubkey = ""
self.default_wallet = get_default_wallet(pubkey)
show_wallet_selector = should_show_wallet_selector(pubkey)
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
default_media_uploader = get_media_uploader(pubkey)
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
zap_notification = UserDefaults.standard.object(forKey: "zap_notification") as? Bool ?? true
mention_notification = UserDefaults.standard.object(forKey: "mention_notification") as? Bool ?? true
repost_notification = UserDefaults.standard.object(forKey: "repost_notification") as? Bool ?? true
like_notification = UserDefaults.standard.object(forKey: "like_notification") as? Bool ?? true
dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true
notification_indicators = UserDefaults.standard.object(forKey: "notification_indicators") as? Int ?? NewEventsBits.all.rawValue
notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") as? Bool ?? false
translate_dms = UserDefaults.standard.object(forKey: "translate_dms") as? Bool ?? false
truncate_timeline_text = UserDefaults.standard.object(forKey: "truncate_timeline_text") as? Bool ?? false
truncate_mention_text = UserDefaults.standard.object(forKey: "truncate_mention_text") as? Bool ?? false
disable_animation = should_disable_image_animation()
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
// Note from @tyiu:
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
// Instead of throwing all Damus users onto feature immediately, allow for discovery of feature organically.
// Also, we are connecting to servers listed as mirrors on the official LibreTranslate GitHub README that do not require API keys.
// However, we have not asked them for permission to use, so we're trying to be good neighbors for now.
// Opportunity: spin up dedicated trusted LibreTranslate server that requires an API key for any access (or higher rate limit access).
if let translation_service = get_translation_service(pubkey) {
self.translation_service = translation_service
} else {
self.translation_service = .none
}
if let libretranslate_server = get_libretranslate_server(pubkey) {
self.libretranslate_server = libretranslate_server
self.libretranslate_url = get_libretranslate_url(pubkey, server: libretranslate_server) ?? ""
} else {
// Choose a random server to distribute load.
libretranslate_server = .allCases.filter { $0 != .custom }.randomElement()!
libretranslate_url = ""
}
do {
libretranslate_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusLibreTranslateKeychainConfiguration())
} catch {
libretranslate_api_key = ""
}
if let deepl_plan = get_deepl_plan(pubkey) {
self.deepl_plan = deepl_plan
} else {
self.deepl_plan = .free
}
do {
deepl_api_key = try Vault.getPrivateKey(keychainConfiguration: DamusDeepLKeychainConfiguration())
} catch {
@@ -374,3 +225,79 @@ struct DamusDeepLKeychainConfiguration: KeychainConfiguration {
var accessGroup: String? = nil
var accountName = "deepl_apikey"
}
func should_show_wallet_selector(_ pubkey: String) -> Bool {
return UserDefaults.standard.object(forKey: "show_wallet_selector") as? Bool ?? true
}
func pk_setting_key(_ pubkey: String, key: String) -> String {
return "\(pubkey)_\(key)"
}
func default_zap_setting_key(pubkey: String) -> String {
return pk_setting_key(pubkey, key: "default_zap_amount")
}
func set_default_zap_amount(pubkey: String, amount: Int) {
let key = default_zap_setting_key(pubkey: pubkey)
UserDefaults.standard.setValue(amount, forKey: key)
}
let fallback_zap_amount = 1000
func get_default_zap_amount(pubkey: String) -> Int {
let key = default_zap_setting_key(pubkey: pubkey)
let amt = UserDefaults.standard.integer(forKey: key)
if amt == 0 {
return fallback_zap_amount
}
return amt
}
func should_disable_image_animation() -> Bool {
return (UserDefaults.standard.object(forKey: "disable_animation") as? Bool)
?? UIAccessibility.isReduceMotionEnabled
}
func get_default_wallet(_ pubkey: String) -> Wallet {
if let defaultWalletName = UserDefaults.standard.string(forKey: "default_wallet"),
let default_wallet = Wallet(rawValue: defaultWalletName)
{
return default_wallet
} else {
return .system_default_wallet
}
}
func get_media_uploader(_ pubkey: String) -> MediaUploader {
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
return defaultMediaUploader
} else {
return .nostrBuild
}
}
private func get_translation_service(_ pubkey: String) -> TranslationService? {
guard let translation_service = UserDefaults.standard.string(forKey: "translation_service") else {
return nil
}
return TranslationService(rawValue: translation_service)
}
private func get_libretranslate_url(_ pubkey: String, server: LibreTranslateServer) -> String? {
if let url = server.model.url {
return url
}
return UserDefaults.standard.object(forKey: "libretranslate_url") as? String
}
private func get_libretranslate_server(_ pubkey: String) -> LibreTranslateServer? {
guard let server_name = UserDefaults.standard.string(forKey: "libretranslate_server") else {
return nil
}
return LibreTranslateServer(rawValue: server_name)
}

View File

@@ -7,7 +7,7 @@
import Foundation
enum Wallet: String, CaseIterable, Identifiable {
enum Wallet: String, CaseIterable, Identifiable, StringCodable {
var id: String { self.rawValue }
struct Model: Identifiable, Hashable {
@@ -20,6 +20,17 @@ enum Wallet: String, CaseIterable, Identifiable {
var image: String
}
func to_string() -> String {
return rawValue
}
init?(from string: String) {
guard let w = Wallet(rawValue: string) else {
return nil
}
self = w
}
// New url prefixes needed to be added to LSApplicationQueriesSchemes
case system_default_wallet
case strike

View File

@@ -22,7 +22,7 @@ class ZapsModel: ObservableObject {
}
func subscribe() {
var filter = NostrFilter.filter_kinds([9735])
var filter = NostrFilter.filter_kinds([NostrKind.zap.rawValue])
switch target {
case .profile(let profile_id):
filter.pubkeys = [profile_id]

View File

@@ -52,6 +52,11 @@ class Profile: Codable {
set_val(key, val)
}
var reactions: Bool? {
get { return get_val("reactions"); }
set(s) { set_val("reactions", s) }
}
var deleted: Bool? {
get { return get_val("deleted"); }
set(s) { set_val("deleted", s) }

View File

@@ -560,7 +560,7 @@ func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
return ev
}
func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEvent? {
func make_metadata_event(keypair: Keypair, metadata: Profile) -> NostrEvent? {
guard let privkey = keypair.privkey else {
return nil
}
@@ -720,11 +720,34 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
return ev
}
func uniq<T: Hashable>(_ xs: [T]) -> [T] {
var s = Set<T>()
var ys: [T] = []
for x in xs {
if s.contains(x) {
continue
}
s.insert(x)
ys.append(x)
}
return ys
}
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []
ids.append(ReferencedId(ref_id: from.id, relay_id: nil, key: "e"))
ids.append(contentsOf: from.referenced_pubkeys.filter { $0.ref_id != our_pubkey })
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey }))
if from.pubkey != our_pubkey {
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
}
return ids
}
func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
var ids: [ReferencedId] = []
if from.pubkey != our_pubkey {
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
}

View File

@@ -41,7 +41,7 @@ struct NostrFilter: Codable, Equatable {
}
public static var filter_text: NostrFilter {
return filter_kinds([1])
return filter_kinds([NostrKind.text.rawValue])
}
public static func filter_ids(_ ids: [String]) -> NostrFilter {
@@ -49,11 +49,11 @@ struct NostrFilter: Codable, Equatable {
}
public static var filter_profiles: NostrFilter {
return filter_kinds([0])
return filter_kinds([NostrKind.metadata.rawValue])
}
public static var filter_contacts: NostrFilter {
return filter_kinds([3])
return filter_kinds([NostrKind.contacts.rawValue])
}
public static func filter_authors(_ authors: [String]) -> NostrFilter {

View File

@@ -1,25 +0,0 @@
//
// NostrMetadata.swift
// damus
//
// Created by William Casarin on 2022-05-21.
//
import Foundation
struct NostrMetadata: Codable {
let display_name: String?
let name: String?
let about: String?
let website: String?
let nip05: String?
let picture: String?
let banner: String?
let lud06: String?
let lud16: String?
}
func create_account_to_metadata(_ model: CreateAccountModel) -> NostrMetadata {
return NostrMetadata(display_name: model.real_name, name: model.nick_name, about: model.about, website: nil, nip05: nil, picture: model.profile_image, banner: nil, lud06: nil, lud16: nil)
}

View File

@@ -35,7 +35,9 @@ let custom_hashtags: [String: CustomHashtag] = [
"coffeechain": CustomHashtag.coffee,
"plebchain": CustomHashtag.plebchain,
"zap": CustomHashtag.zap,
"zaps": CustomHashtag.zap,
"zapathon": CustomHashtag.zap,
"onlyzaps": CustomHashtag.zap,
]
func hashtag_str(_ htag: String) -> CompatibleText {

View File

@@ -113,6 +113,9 @@ extension Notification.Name {
static var local_notification: Notification.Name {
return Notification.Name("local_notification")
}
static var onlyzaps_mode: Notification.Name {
return Notification.Name("hide_reactions")
}
}
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {

View File

@@ -0,0 +1,13 @@
//
// StringCodable.swift
// damus
//
// Created by William Casarin on 2023-04-21.
//
import Foundation
protocol StringCodable {
init?(from string: String)
func to_string() -> String
}

View File

@@ -52,6 +52,10 @@ struct Zap {
public let is_anon: Bool
public let private_request: NostrEvent?
var request_ev: NostrEvent {
return private_request ?? self.request.ev
}
public static func from_zap_event(zap_ev: NostrEvent, zapper: String, our_privkey: String?) -> Zap? {
/// Make sure that we only create a zap event if it is authorized by the profile or event
guard zapper == zap_ev.pubkey else {

View File

@@ -30,6 +30,7 @@ struct EventActionBar: View {
@State var show_share_action: Bool = false
@ObservedObject var bar: ActionBarModel
@ObservedObject var settings: UserSettingsStore
@Environment(\.colorScheme) var colorScheme
@@ -38,12 +39,21 @@ struct EventActionBar: View {
self.event = event
self.test_lnurl = test_lnurl
_bar = ObservedObject(wrappedValue: bar ?? make_actionbar_model(ev: event.id, damus: damus_state))
_settings = ObservedObject(wrappedValue: damus_state.settings)
}
var lnurl: String? {
test_lnurl ?? damus_state.profiles.lookup(id: event.pubkey)?.lnurl
}
var show_like: Bool {
if settings.onlyzaps_mode {
return false
}
return true
}
var body: some View {
HStack {
if damus_state.keypair.privkey != nil {
@@ -72,22 +82,25 @@ struct EventActionBar: View {
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
}
Spacer()
HStack(spacing: 4) {
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
} else {
send_like()
if show_like {
Spacer()
HStack(spacing: 4) {
LikeButton(liked: bar.liked) {
if bar.liked {
notify(.delete, bar.our_like)
} else {
send_like()
}
}
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
.font(.footnote.weight(.medium))
.nip05_colorized(gradient: bar.liked)
}
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
.font(.footnote.weight(.medium))
.nip05_colorized(gradient: bar.liked)
}
if let lnurl = self.lnurl {
Spacer()
ZapButton(damus_state: damus_state, event: event, lnurl: lnurl, bar: bar)
@@ -137,15 +150,7 @@ struct EventActionBar: View {
}
func send_boost() {
guard let privkey = self.damus_state.keypair.privkey else {
return
}
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
self.bar.our_boost = boost
notify(.boost, boost)
notify(.boost, self.event)
}
func send_like() {

View File

@@ -32,7 +32,7 @@ struct EventDetailBar: View {
.buttonStyle(PlainButtonStyle())
}
if bar.likes > 0 {
if bar.likes > 0 && !state.settings.onlyzaps_mode {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
let noun = Text(verbatim: "\(reactionsCountString(bar.likes))").foregroundColor(.gray)
Text("\(Text("\(bar.likes)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")

View File

@@ -89,10 +89,22 @@ extension NSMutableData {
}
}
enum MediaUploader: String, CaseIterable, Identifiable {
enum MediaUploader: String, CaseIterable, Identifiable, StringCodable {
var id: String { self.rawValue }
case nostrBuild
case nostrImg
init?(from string: String) {
guard let mu = MediaUploader(rawValue: string) else {
return nil
}
self = mu
}
func to_string() -> String {
return rawValue
}
var nameParam: String {
switch self {

View File

@@ -0,0 +1,44 @@
//
// FriendsButton.swift
// damus
//
// Created by William Casarin on 2023-04-21.
//
import SwiftUI
struct FriendsButton: View {
@Binding var filter: FriendFilter
var body: some View {
Button(action: {
switch self.filter {
case .all:
self.filter = .friends
case .friends:
self.filter = .all
}
}) {
if filter == .friends {
LINEAR_GRADIENT
.mask(Image(systemName: "person.2.fill")
.resizable()
).frame(width: 30, height: 20)
} else {
Image(systemName: "person.2.fill")
.resizable()
.frame(width: 30, height: 20)
.foregroundColor(DamusColors.adaptableGrey)
}
}
.buttonStyle(.plain)
}
}
struct FriendsButton_Previews: PreviewProvider {
@State static var enabled: FriendFilter = .all
static var previews: some View {
FriendsButton(filter: $enabled)
}
}

View File

@@ -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
}

View File

@@ -17,6 +17,7 @@ struct DirectMessagesView: View {
@State var dm_type: DMType = .friend
@ObservedObject var model: DirectMessagesModel
@ObservedObject var settings: UserSettingsStore
func MainContent(requests: Bool) -> some View {
ScrollView {
@@ -29,12 +30,9 @@ struct DirectMessagesView: View {
EmptyTimelineView()
} else {
let dms = requests ? model.message_requests : model.friend_dms
ForEach(dms, id: \.pubkey) { tup in
MaybeEvent(tup)
ForEach(dms, id: \.pubkey) { dm in
MaybeEvent(dm)
.padding(.top, 10)
Divider()
.padding([.top], 10)
}
}
}
@@ -52,11 +50,15 @@ struct DirectMessagesView: View {
func MaybeEvent(_ model: DirectMessageModel) -> some View {
Group {
if let ev = model.events.last {
let ok = damus_state.settings.friend_filter.filter(contacts: damus_state.contacts, pubkey: model.pubkey)
if ok, let ev = model.events.last {
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
.onTapGesture {
self.model.open_dm_by_model(model)
}
Divider()
.padding([.top], 10)
} else {
EmptyView()
}
@@ -84,10 +86,28 @@ struct DirectMessagesView: View {
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
if would_filter_non_friends_from_dms(contacts: damus_state.contacts, dms: self.model.dms) {
FriendsButton(filter: $settings.friend_filter)
}
}
}
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for view of DMs, where DM is an English abbreviation for Direct Message."))
}
}
func would_filter_non_friends_from_dms(contacts: Contacts, dms: [DirectMessageModel]) -> Bool {
for dm in dms {
if !FriendFilter.friends.filter(contacts: contacts, pubkey: dm.pubkey) {
return true
}
}
return false
}
struct DirectMessagesView_Previews: PreviewProvider {
static var previews: some View {
let ev = NostrEvent(content: "encrypted stuff",
@@ -95,6 +115,6 @@ struct DirectMessagesView_Previews: PreviewProvider {
kind: 4,
tags: [])
let ds = test_damus_state()
DirectMessagesView(damus_state: ds, model: ds.dms)
DirectMessagesView(damus_state: ds, model: ds.dms, settings: ds.settings)
}
}

View File

@@ -48,10 +48,6 @@ struct BuilderEventView: View {
return
}
guard nostr_event.known_kind == .text else {
return
}
if event != nil {
return
}
@@ -78,8 +74,8 @@ struct BuilderEventView: View {
let thread = ThreadModel(event: ev, damus_state: damus)
let dest = ThreadView(state: damus, thread: thread)
NavigationLink(destination: dest) {
EmbeddedEventView(damus_state: damus, event: event)
.padding(8)
EventView(damus: damus, event: event, options: .embedded)
.padding([.top, .bottom], 8)
}.buttonStyle(.plain)
} else {
ProgressView().padding()

View File

@@ -1,47 +0,0 @@
//
// EmbeddedEventView.swift
// damus
//
// Created by William Casarin on 2023-01-23.
//
import SwiftUI
struct EmbeddedEventView: View {
let damus_state: DamusState
let event: NostrEvent
var pubkey: String {
event.pubkey
}
var body: some View {
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey)
HStack {
EventProfile(damus_state: damus_state, pubkey: pubkey, profile: profile, size: .small)
Spacer()
EventMenuContext(event: event, keypair: damus_state.keypair, target_pubkey: event.pubkey, bookmarks: damus_state.bookmarks, muted_threads: damus_state.muted_threads)
.padding([.bottom], 4)
}
.minimumScaleFactor(0.75)
.lineLimit(1)
if event_is_reply(event, privkey: damus_state.keypair.privkey) {
ReplyDescription(event: event, profiles: damus_state.profiles)
}
EventBody(damus_state: damus_state, event: event, size: .small, options: [.truncate_content])
}
}
}
struct EmbeddedEventView_Previews: PreviewProvider {
static var previews: some View {
EmbeddedEventView(damus_state: test_damus_state(), event: test_event)
.padding()
}
}

View File

@@ -10,15 +10,13 @@ import SwiftUI
struct MutedEventView: View {
let damus_state: DamusState
let event: NostrEvent
let scroller: ScrollViewProxy?
let selected: Bool
@State var shown: Bool
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
init(damus_state: DamusState, event: NostrEvent, selected: Bool) {
self.damus_state = damus_state
self.event = event
self.scroller = scroller
self.selected = selected
self._shown = State(initialValue: should_show_event(contacts: damus_state.contacts, ev: event))
}
@@ -89,7 +87,7 @@ struct MutedEventView_Previews: PreviewProvider {
static var previews: some View {
MutedEventView(damus_state: test_damus_state(), event: test_event, scroller: nil, selected: false)
MutedEventView(damus_state: test_damus_state(), event: test_event, selected: false)
.frame(width: .infinity, height: 50)
}
}

View File

@@ -8,7 +8,8 @@
import SwiftUI
struct EventViewOptions: OptionSet {
let rawValue: UInt8
let rawValue: UInt32
static let no_action_bar = EventViewOptions(rawValue: 1 << 0)
static let no_replying_to = EventViewOptions(rawValue: 1 << 1)
static let no_images = EventViewOptions(rawValue: 1 << 2)
@@ -16,6 +17,10 @@ struct EventViewOptions: OptionSet {
static let truncate_content = EventViewOptions(rawValue: 1 << 4)
static let pad_content = EventViewOptions(rawValue: 1 << 5)
static let no_translate = EventViewOptions(rawValue: 1 << 6)
static let small_pfp = EventViewOptions(rawValue: 1 << 7)
static let nested = EventViewOptions(rawValue: 1 << 8)
static let embedded: EventViewOptions = [.no_action_bar, .small_pfp, .wide, .truncate_content, .nested]
}
struct TextEvent: View {
@@ -43,7 +48,7 @@ struct TextEvent: View {
}
func Pfp(is_anon: Bool) -> some View {
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey, size: options.contains(.small_pfp) ? eventview_pfp_size(.small) : PFP_SIZE )
}
func TopPart(is_anon: Bool) -> some View {
@@ -82,7 +87,7 @@ struct TextEvent: View {
EvBody(options: self.options.union(.pad_content))
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
if let mention = get_mention() {
Mention(mention)
.padding(.horizontal)
}
@@ -135,6 +140,14 @@ struct TextEvent: View {
return Rectangle().frame(height: 2).opacity(0)
}
func get_mention() -> Mention? {
if self.options.contains(.nested) {
return nil
}
return first_eref_mention(ev: event, privkey: damus.keypair.privkey)
}
var ThreadedStyle: some View {
HStack(alignment: .top) {
@@ -151,7 +164,7 @@ struct TextEvent: View {
ReplyPart
EvBody(options: self.options)
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
if let mention = get_mention() {
Mention(mention)
}

View File

@@ -7,11 +7,90 @@
import SwiftUI
enum NotificationFilterState: String {
enum FriendFilter: String, StringCodable {
case all
case friends
init?(from string: String) {
guard let ff = FriendFilter(rawValue: string) else {
return nil
}
self = ff
}
func to_string() -> String {
self.rawValue
}
func filter(contacts: Contacts, pubkey: String) -> Bool {
switch self {
case .all:
return true
case .friends:
return contacts.is_in_friendosphere(pubkey)
}
}
}
class NotificationFilter: ObservableObject, Equatable {
@Published var state: NotificationFilterState
@Published var fine_filter: FriendFilter
static func == (lhs: NotificationFilter, rhs: NotificationFilter) -> Bool {
return lhs.state == rhs.state && lhs.fine_filter == rhs.fine_filter
}
init() {
self.state = .all
self.fine_filter = .all
}
init(state: NotificationFilterState, fine_filter: FriendFilter) {
self.state = state
self.fine_filter = fine_filter
}
func toggle_fine_filter() {
switch self.fine_filter {
case .all:
self.fine_filter = .friends
case .friends:
self.fine_filter = .all
}
}
func filter(contacts: Contacts, items: [NotificationItem]) -> [NotificationItem] {
return items.reduce(into: []) { acc, item in
if !self.state.filter(item) {
return
}
if let item = item.filter({ self.fine_filter.filter(contacts: contacts, pubkey: $0.pubkey) }) {
acc.append(item)
}
}
}
}
enum NotificationFilterState: String, StringCodable {
case all
case zaps
case replies
init?(from string: String) {
guard let val = NotificationFilterState(rawValue: string) else {
return nil
}
self = val
}
func to_string() -> String {
self.rawValue
}
func is_other( item: NotificationItem) -> Bool {
return item.is_zap == nil && item.is_reply == nil
}
@@ -31,7 +110,7 @@ enum NotificationFilterState: String {
struct NotificationsView: View {
let state: DamusState
@ObservedObject var notifications: NotificationsModel
@State var filter_state: NotificationFilterState = .all
@StateObject var filter_state: NotificationFilter = NotificationFilter()
@Environment(\.colorScheme) var colorScheme
@@ -44,28 +123,54 @@ struct NotificationsView: View {
}
var body: some View {
TabView(selection: $filter_state) {
TabView(selection: $filter_state.state) {
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
mystery
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
NotificationTab(NotificationFilterState.all)
.tag(NotificationFilterState.all)
NotificationTab(
NotificationFilter(
state: .all,
fine_filter: filter_state.fine_filter
)
)
.tag(NotificationFilterState.all)
NotificationTab(NotificationFilterState.zaps)
.tag(NotificationFilterState.zaps)
NotificationTab(
NotificationFilter(
state: .zaps,
fine_filter: filter_state.fine_filter
)
)
.tag(NotificationFilterState.zaps)
NotificationTab(NotificationFilterState.replies)
.tag(NotificationFilterState.replies)
NotificationTab(
NotificationFilter(
state: .replies,
fine_filter: filter_state.fine_filter
)
)
.tag(NotificationFilterState.replies)
}
.onChange(of: filter_state) { val in
save_notification_filter_state(pubkey: state.pubkey, state: val)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
if would_filter_non_friends_from_notifications(contacts: state.contacts, state: self.filter_state.state, items: self.notifications.notifications) {
FriendsButton(filter: $filter_state.fine_filter)
}
}
}
.onChange(of: filter_state.fine_filter) { val in
state.settings.friend_filter = val
}
.onChange(of: filter_state.state) { val in
state.settings.notification_state = val
}
.onAppear {
self.filter_state = load_notification_filter_state(pubkey: state.pubkey)
self.filter_state.fine_filter = state.settings.friend_filter
self.filter_state.state = state.settings.notification_state
}
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: {
CustomPicker(selection: $filter_state.state, content: {
Text("All", comment: "Label for filter for all notifications.")
.tag(NotificationFilterState.all)
@@ -83,14 +188,14 @@ struct NotificationsView: View {
}
}
func NotificationTab(_ filter: NotificationFilterState) -> some View {
func NotificationTab(_ filter: NotificationFilter) -> some View {
ScrollViewReader { scroller in
ScrollView {
LazyVStack(alignment: .leading) {
Color.white.opacity(0)
.id("startblock")
.frame(height: 5)
ForEach(notifications.notifications.filter(filter.filter), id: \.id) { item in
ForEach(filter.filter(contacts: state.contacts, items: notifications.notifications), id: \.id) { item in
NotificationItemView(state: state, item: item)
}
}
@@ -116,30 +221,22 @@ struct NotificationsView: View {
struct NotificationsView_Previews: PreviewProvider {
static var previews: some View {
NotificationsView(state: test_damus_state(), notifications: NotificationsModel(), filter_state: NotificationFilterState.all)
NotificationsView(state: test_damus_state(), notifications: NotificationsModel(), filter_state: NotificationFilter())
}
}
func notification_filter_state_key(pubkey: String) -> String {
return pk_setting_key(pubkey, key: "notification_filter_state")
}
func load_notification_filter_state(pubkey: String) -> NotificationFilterState {
let key = notification_filter_state_key(pubkey: pubkey)
guard let state_str = UserDefaults.standard.string(forKey: key) else {
return .all
func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
for item in items {
// this is only valid depending on which tab we're looking at
if !state.filter(item) {
continue
}
if item.would_filter({ ev in FriendFilter.friends.filter(contacts: contacts, pubkey: ev.pubkey) }) {
return true
}
}
guard let state = NotificationFilterState(rawValue: state_str) else {
return .all
}
return state
return false
}
func save_notification_filter_state(pubkey: String, state: NotificationFilterState) {
let key = notification_filter_state_key(pubkey: pubkey)
UserDefaults.standard.set(state.rawValue, forKey: key)
}

View File

@@ -1,5 +1,5 @@
//
// ParicipantsView.swift
// ParticipantsView.swift
// damus
//
// Created by Joel Klabo on 1/18/23.

View File

@@ -15,6 +15,23 @@ enum NostrPostResult {
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
enum PostAction {
case replying_to(NostrEvent)
case quoting(NostrEvent)
case posting
var ev: NostrEvent? {
switch self {
case .replying_to(let ev):
return ev
case .quoting(let ev):
return ev
case .posting:
return nil
}
}
}
struct PostView: View {
@State var post: NSMutableAttributedString = NSMutableAttributedString()
@FocusState var focus: Bool
@@ -31,7 +48,7 @@ struct PostView: View {
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
let replying_to: NostrEvent?
let action: PostAction
let damus_state: DamusState
@Environment(\.presentationMode) var presentationMode
@@ -51,7 +68,8 @@ struct PostView: View {
func send_post() {
var kind: NostrKind = .text
if replying_to?.known_kind == .chat {
if case .replying_to(let ev) = action, ev.known_kind == .chat {
kind = .chat
}
@@ -61,25 +79,21 @@ struct PostView: View {
}
}
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
content.append(" " + imagesString + " ")
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) {
content.append(" nostr:" + id)
}
let new_post = NostrPost(content: content, references: references, kind: kind)
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
if let replying_to {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else {
damus_state.drafts.post = NSMutableAttributedString(string: "")
uploadedMedias = []
damus_state.drafts.medias = []
}
clear_draft()
dismiss()
}
@@ -131,17 +145,67 @@ struct PostView: View {
.clipShape(Capsule())
}
var isEmpty: Bool {
self.uploadedMedias.count == 0 &&
self.post.mutableString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
func clear_draft() {
switch action {
case .replying_to(let replying_to):
damus_state.drafts.replies.removeValue(forKey: replying_to)
case .quoting(let quoting):
damus_state.drafts.quotes.removeValue(forKey: quoting)
case .posting:
damus_state.drafts.post = nil
}
}
func load_draft() {
guard let draft = load_draft_for_post(drafts: self.damus_state.drafts, action: self.action) else {
self.post = NSMutableAttributedString("")
self.uploadedMedias = []
return
}
self.uploadedMedias = draft.media
self.post = draft.content
}
func post_changed(post: NSMutableAttributedString, media: [UploadedMedia]) {
switch action {
case .replying_to(let ev):
if let draft = damus_state.drafts.replies[ev] {
draft.content = post
draft.media = media
} else {
damus_state.drafts.replies[ev] = DraftArtifacts(content: post, media: media)
}
case .quoting(let ev):
if let draft = damus_state.drafts.quotes[ev] {
draft.content = post
draft.media = media
} else {
damus_state.drafts.quotes[ev] = DraftArtifacts(content: post, media: media)
}
case .posting:
if let draft = damus_state.drafts.post {
draft.content = post
draft.media = media
} else {
damus_state.drafts.post = DraftArtifacts(content: post, media: media)
}
}
}
var TextEntry: some View {
ZStack(alignment: .topLeading) {
TextViewWrapper(attributedText: $post)
.focused($focus)
.textInputAutocapitalization(.sentences)
.onChange(of: post) { _ in
if let replying_to {
damus_state.drafts.replies[replying_to] = post
} else {
damus_state.drafts.post = post
}
.onChange(of: post) { p in
post_changed(post: p, media: uploadedMedias)
}
if post.string.isEmpty {
@@ -207,6 +271,35 @@ struct PostView: View {
}
}
var has_artifacts: Bool {
if case .quoting = action {
return true
}
return !uploadedMedias.isEmpty
}
func Editor(deviceSize: GeometryProxy) -> some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top) {
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
TextEntry
}
.frame(height: has_artifacts ? deviceSize.size.height*0.4 : deviceSize.size.height)
.id("post")
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
.onChange(of: uploadedMedias) { media in
post_changed(post: post, media: media)
}
if case .quoting(let ev) = action {
BuilderEventView(damus: damus_state, event: ev)
}
}
.padding(.horizontal)
}
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
VStack(alignment: .leading, spacing: 0) {
@@ -217,25 +310,11 @@ struct PostView: View {
ScrollViewReader { scroller in
ScrollView {
if let replying_to = replying_to {
if case .replying_to(let replying_to) = self.action {
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
}
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top) {
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
TextEntry
}
.frame(height: uploadedMedias.isEmpty ? deviceSize.size.height*0.78 : deviceSize.size.height*0.2)
.id("post")
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
.onChange(of: uploadedMedias) { _ in
damus_state.drafts.medias = uploadedMedias
}
}
.padding(.horizontal)
Editor(deviceSize: deviceSize)
}
.frame(maxHeight: searching == nil ? .infinity : 70)
.onAppear {
@@ -262,7 +341,7 @@ struct PostView: View {
} onVideoPicked: { url in
self.mediaToUpload = .video(url)
}
.alert("Are you sure you want to upload this image?", isPresented: $image_upload_confirm) {
.alert(NSLocalizedString("Are you sure you want to upload this image?", comment: "Alert message asking if the user wants to upload an image."), isPresented: $image_upload_confirm) {
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
if let mediaToUpload {
self.handle_upload(media: mediaToUpload)
@@ -281,18 +360,17 @@ struct PostView: View {
}
}
.onAppear() {
if let replying_to {
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
load_draft()
switch action {
case .replying_to(let replying_to):
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
originalReferences = references
if damus_state.drafts.replies[replying_to] == nil {
damus_state.drafts.post = NSMutableAttributedString(string: "")
}
if let p = damus_state.drafts.replies[replying_to] {
post = p
}
} else {
post = damus_state.drafts.post
uploadedMedias = damus_state.drafts.medias
case .quoting(let quoting):
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
originalReferences = references
case .posting:
break
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
@@ -300,11 +378,8 @@ struct PostView: View {
}
}
.onDisappear {
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.post = NSMutableAttributedString(string : "")
damus_state.drafts.medias = uploadedMedias
if isEmpty {
clear_draft()
}
}
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
@@ -342,7 +417,7 @@ func get_searching_string(_ post: String) -> String? {
struct PostView_Previews: PreviewProvider {
static var previews: some View {
PostView(replying_to: nil, damus_state: test_damus_state())
PostView(action: .posting, damus_state: test_damus_state())
}
}
@@ -362,9 +437,19 @@ struct PVImageCarouselView: View {
.frame(width: media.count == 1 ? deviceWidth*0.8 : 250, height: media.count == 1 ? 400 : 250)
.cornerRadius(10)
.padding()
.contextMenu {
if let uploadedURL = media.first(where: { $0.representingImage == image })?.uploadedURL {
Button(action: {
UIPasteboard.general.string = uploadedURL.absoluteString
}) {
Label(NSLocalizedString("Copy URL", comment: "Label for button in context menu to copy URL of the selected uploaded media asset."), systemImage: "doc.on.doc")
}
}
}
Image(systemName: "xmark.circle.fill")
.foregroundColor(.white)
.padding(20)
.shadow(radius: 5)
.onTapGesture {
if let index = media.map({$0.representingImage}).firstIndex(of: image) {
media.remove(at: index)
@@ -378,7 +463,6 @@ struct PVImageCarouselView: View {
}
}
fileprivate func getImage(media: MediaUpload) -> UIImage {
var uiimage: UIImage = UIImage()
if media.is_image {
@@ -418,3 +502,15 @@ struct UploadedMedia: Equatable {
let uploadedURL: URL
let representingImage: UIImage
}
func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts? {
switch action {
case .replying_to(let ev):
return drafts.replies[ev]
case .quoting(let ev):
return drafts.quotes[ev]
case .posting:
return drafts.post
}
}

View File

@@ -63,6 +63,7 @@ struct EditMetadataView: View {
@State var name: String
@State var ln: String
@State var website: String
let profile: Profile?
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@@ -73,6 +74,7 @@ struct EditMetadataView: View {
init (damus_state: DamusState) {
self.damus_state = damus_state
let data = damus_state.profiles.lookup(id: damus_state.pubkey)
self.profile = data
_name = State(initialValue: data?.name ?? "")
_display_name = State(initialValue: data?.display_name ?? "")
@@ -85,27 +87,31 @@ struct EditMetadataView: View {
}
func imageBorderColor() -> Color {
colorScheme == .light ? DamusColors.white : DamusColors.black
}
colorScheme == .light ? DamusColors.white : DamusColors.black
}
func to_profile() -> Profile {
let profile = self.profile ?? Profile()
profile.name = name
profile.display_name = display_name
profile.about = about
profile.website = website
profile.nip05 = nip05.isEmpty ? nil : nip05
profile.picture = picture.isEmpty ? nil : picture
profile.banner = banner.isEmpty ? nil : banner
profile.lud06 = ln.contains("@") ? nil : ln
profile.lud16 = ln.contains("@") ? ln : nil
return profile
}
func save() {
let metadata = NostrMetadata(
display_name: display_name,
name: name,
about: about,
website: website,
nip05: nip05.isEmpty ? nil : nip05,
picture: picture.isEmpty ? nil : picture,
banner: banner.isEmpty ? nil : banner,
lud06: ln.contains("@") ? nil : ln,
lud16: ln.contains("@") ? ln : nil
);
let m_metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: metadata)
if let metadata_ev = m_metadata_ev {
damus_state.postbox.send(metadata_ev)
let profile = to_profile()
guard let metadata_ev = make_metadata_event(keypair: damus_state.keypair, metadata: profile) else {
return
}
damus_state.postbox.send(metadata_ev)
}
func is_ln_valid(ln: String) -> Bool {

View File

@@ -39,8 +39,8 @@ struct EventProfileName: View {
self.size = size
}
var friend_icon: String? {
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
var friend_type: FriendType? {
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
}
var current_nip05: NIP05? {
@@ -50,7 +50,15 @@ struct EventProfileName: View {
var current_display_name: DisplayName {
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
}
var onlyzapper: Bool {
guard let profile else {
return false
}
return profile.reactions == false
}
var body: some View {
HStack(spacing: 2) {
switch current_display_name {
@@ -71,10 +79,13 @@ struct EventProfileName: View {
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: false, clickable: false)
}
if let frend = friend_icon, current_nip05 == nil {
Label("", systemImage: frend)
.foregroundColor(.gray)
.font(.footnote)
if current_nip05 == nil, let frend = friend_type {
FriendIcon(friend: frend)
}
if onlyzapper {
Image("zap-hashtag")
.frame(width: 14, height: 14)
}
}
.onReceive(handle_notify(.profile_updated)) { notif in

View File

@@ -0,0 +1,39 @@
//
// FriendIcon.swift
// damus
//
// Created by William Casarin on 2023-04-20.
//
import SwiftUI
struct FriendIcon: View {
let friend: FriendType
var body: some View {
Group {
switch friend {
case .friend:
LINEAR_GRADIENT
.mask(Image(systemName: "person.fill.checkmark")
.resizable()
).frame(width: 20, height: 14)
case .fof:
Image(systemName: "person.fill.and.arrow.left.and.arrow.right")
.resizable()
.frame(width: 21, height: 14)
.foregroundColor(.gray)
}
}
}
}
struct FriendIcon_Previews: PreviewProvider {
static var previews: some View {
VStack {
FriendIcon(friend: .friend)
FriendIcon(friend: .fof)
}
}
}

View File

@@ -11,17 +11,20 @@ struct MaybeAnonPfpView: View {
let state: DamusState
let is_anon: Bool
let pubkey: String
let size: CGFloat
init(state: DamusState, event: NostrEvent, pubkey: String) {
init(state: DamusState, event: NostrEvent, pubkey: String, size: CGFloat) {
self.state = state
self.is_anon = event_is_anonymous(ev: event)
self.pubkey = pubkey
self.size = size
}
init(state: DamusState, is_anon: Bool, pubkey: String) {
init(state: DamusState, is_anon: Bool, pubkey: String, size: CGFloat) {
self.state = state
self.is_anon = is_anon
self.pubkey = pubkey
self.size = size
}
var body: some View {
@@ -29,10 +32,10 @@ struct MaybeAnonPfpView: View {
if is_anon {
Image(systemName: "person.fill.questionmark")
.font(.largeTitle)
.frame(width: PFP_SIZE, height: PFP_SIZE)
.frame(width: size, height: size)
} else {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: state.profiles)
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles)
}
}
}
@@ -41,6 +44,6 @@ struct MaybeAnonPfpView: View {
struct MaybeAnonPfpView_Previews: PreviewProvider {
static var previews: some View {
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon")
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon", size: PFP_SIZE)
}
}

View File

@@ -7,17 +7,18 @@
import SwiftUI
func get_friend_icon(contacts: Contacts, pubkey: String, show_confirmed: Bool) -> String? {
if !show_confirmed {
return nil
}
enum FriendType {
case friend
case fof
}
func get_friend_type(contacts: Contacts, pubkey: String) -> FriendType? {
if contacts.is_friend_or_self(pubkey) {
return "person.fill.checkmark"
return .friend
}
if contacts.is_friend_of_friend(pubkey) {
return "person.fill.and.arrow.left.and.arrow.right"
return .fof
}
return nil
@@ -53,8 +54,8 @@ struct ProfileName: View {
self.show_nip5_domain = show_nip5_domain
}
var friend_icon: String? {
return get_friend_icon(contacts: damus_state.contacts, pubkey: pubkey, show_confirmed: show_friend_confirmed)
var friend_type: FriendType? {
return get_friend_type(contacts: damus_state.contacts, pubkey: self.pubkey)
}
var current_nip05: NIP05? {
@@ -69,6 +70,14 @@ struct ProfileName: View {
return prefix == "@" ? current_display_name.username : current_display_name.display_name
}
var onlyzapper: Bool {
guard let profile else {
return false
}
return profile.reactions == false
}
var body: some View {
HStack(spacing: 2) {
Text(verbatim: "\(prefix)\(name_choice)")
@@ -77,9 +86,12 @@ struct ProfileName: View {
if let nip05 = current_nip05 {
NIP05Badge(nip05: nip05, pubkey: pubkey, contacts: damus_state.contacts, show_domain: show_nip5_domain, clickable: true)
}
if let friend = friend_icon, current_nip05 == nil {
Image(systemName: friend)
.foregroundColor(.gray)
if let friend = friend_type, current_nip05 == nil {
FriendIcon(friend: friend)
}
if onlyzapper {
Image("zap-hashtag")
.frame(width: 14, height: 14)
}
}
.onReceive(handle_notify(.profile_updated)) { notif in

View File

@@ -245,20 +245,33 @@ struct ProfileView: View {
}
func lnButton(lnurl: String, profile: Profile) -> some View {
Button(action: {
let button_img = profile.reactions == false ? "bolt.brakesignal" : "bolt.circle"
return Button(action: {
if damus_state.settings.show_wallet_selector {
showing_select_wallet = true
} else {
open_with_wallet(wallet: damus_state.settings.default_wallet.model, invoice: lnurl)
}
}) {
Image(systemName: "bolt.circle")
Image(systemName: button_img)
.profile_button_style(scheme: colorScheme)
.contextMenu {
Button {
UIPasteboard.general.string = profile.lnurl ?? ""
} label: {
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
if profile.reactions == false {
Text("OnlyZaps Enabled")
}
if let addr = profile.lud16 {
Button {
UIPasteboard.general.string = addr
} label: {
Label(addr, systemImage: "doc.on.doc")
}
} else if let lnurl = profile.lnurl {
Button {
UIPasteboard.general.string = lnurl
} label: {
Label(NSLocalizedString("Copy LNURL", comment: "Context menu option for copying a user's Lightning URL."), systemImage: "doc.on.doc")
}
}
}

View File

@@ -217,3 +217,7 @@ struct SaveKeysView_Previews: PreviewProvider {
SaveKeysView(account: model)
}
}
func create_account_to_metadata(_ model: CreateAccountModel) -> Profile {
return Profile(name: model.nick_name, display_name: model.real_name, about: model.about, picture: model.profile_image, banner: nil, website: nil, lud06: nil, lud16: nil, nip05: nil)
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -14,17 +14,29 @@ struct ZapSettingsView: View {
@State var default_zap_amount: String
@Environment(\.dismiss) var dismiss
init(pubkey: String, settings: UserSettingsStore) {
self.pubkey = pubkey
let zap_amt = get_default_zap_amount(pubkey: pubkey).map({ "\($0)" }) ?? "1000"
let zap_amt = get_default_zap_amount(pubkey: pubkey).formatted()
_default_zap_amount = State(initialValue: zap_amt)
self._settings = ObservedObject(initialValue: settings)
}
var body: some View {
Form {
Section("Wallet") {
Section(
header: Text(NSLocalizedString("OnlyZaps", comment: "Section header for enabling OnlyZaps mode (hide reactions)")),
footer: Text(NSLocalizedString("Hide all 🤙's", comment: "Section footer describing onlyzaps mode"))
) {
Toggle(NSLocalizedString("Enable OnlyZaps mode", comment: "Setting toggle to hide reactions."), isOn: $settings.onlyzaps_mode)
.toggleStyle(.switch)
.onChange(of: settings.onlyzaps_mode) { newVal in
notify(.onlyzaps_mode, newVal)
}
}
Section(NSLocalizedString("Wallet", comment: "Title for section in zap settings that controls the Lightning wallet selection.")) {
Toggle(NSLocalizedString("Show wallet selector", comment: "Toggle to show or hide selection of wallet."), isOn: $settings.show_wallet_selector).toggleStyle(.switch)
Picker(NSLocalizedString("Select default wallet", comment: "Prompt selection of user's default wallet"),
@@ -36,23 +48,26 @@ struct ZapSettingsView: View {
}
}
Section("Zaps") {
Section(NSLocalizedString("Zaps", comment: "Title for section in zap settings that controls general zap preferences.")) {
Toggle(NSLocalizedString("Zap Vibration", comment: "Setting to enable vibration on zap"), isOn: $settings.zap_vibration)
.toggleStyle(.switch)
}
Section("Default Zap Amount in sats") {
TextField(String("1000"), text: $default_zap_amount)
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Title for section in zap settings that controls the default zap amount in sats.")) {
TextField(fallback_zap_amount.formatted(), text: $default_zap_amount)
.keyboardType(.numberPad)
.onReceive(Just(default_zap_amount)) { newValue in
if let parsed = handle_string_amount(new_value: newValue) {
self.default_zap_amount = String(parsed)
self.default_zap_amount = parsed.formatted()
set_default_zap_amount(pubkey: self.pubkey, amount: parsed)
} else {
self.default_zap_amount = ""
set_default_zap_amount(pubkey: self.pubkey, amount: 0)
}
}
}
}
.navigationTitle("Zaps")
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))
.onReceive(handle_notify(.switched_timeline)) { _ in
dismiss()
}

View File

@@ -29,7 +29,6 @@ struct ThreadView: View {
ForEach(parent_events, id: \.id) { parent_event in
MutedEventView(damus_state: state,
event: parent_event,
scroller: reader,
selected: false)
.padding(.horizontal)
.onTapGesture {
@@ -56,7 +55,6 @@ struct ThreadView: View {
MutedEventView(
damus_state: state,
event: self.thread.event,
scroller: reader,
selected: true
)
.id(self.thread.event.id)
@@ -65,7 +63,6 @@ struct ThreadView: View {
MutedEventView(
damus_state: state,
event: child_event,
scroller: nil,
selected: false
)
.padding(.horizontal)

View File

@@ -25,7 +25,7 @@ struct ZapAmountItem: Identifiable, Hashable {
}
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
let def = get_default_zap_amount(pubkey: pubkey)
return ZapAmountItem(amount: def, icon: "🤙")
}
@@ -181,13 +181,17 @@ struct CustomizeZapView: View {
})
Section(content: {
TextField(String("100000"), text: $custom_amount)
// Use the selected sats amount as the placeholder text so that the UI is less confusing.
// User can type in their custom amount, which hides the placeholder.
TextField(selected_amount.amount.formatted(), text: $custom_amount)
.keyboardType(.numberPad)
.onReceive(Just(custom_amount)) { newValue in
if let parsed = handle_string_amount(new_value: newValue) {
self.custom_amount = String(parsed)
self.custom_amount = parsed.formatted()
self.custom_amount_sats = parsed
} else {
self.custom_amount = ""
self.custom_amount_sats = nil
}
}
}, header: {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -61,9 +61,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ und %1$d andere*r reagierten auf einen Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere*r reagierten auf einen Beitrag in dem du markiert warst</string>
<key>other</key>
<string>%2$@ und %1$d andere reagierten auf einen Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere reagierten auf einen Beitrag in dem du markiert warst</string>
</dict>
</dict>
<key>reacted_your_post_3</key>
@@ -157,9 +157,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ und %1$d andere*r teilten einen Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere*r teilten einen Beitrag in dem du markiert warst</string>
<key>other</key>
<string>%2$@ und %1$d andere teilten ein Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere teilten ein Beitrag in dem du markiert warst</string>
</dict>
</dict>
<key>reposted_your_post_3</key>
@@ -269,9 +269,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ und %1$d andere:r zappten einen Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere:r zappten einen Beitrag in dem du markiert warst</string>
<key>other</key>
<string>%2$@ und %1$d andere zappten einen Beitrag in dem Du markiert warst</string>
<string>%2$@ und %1$d andere zappten einen Beitrag in dem du markiert warst</string>
</dict>
</dict>
<key>zapped_your_post_3</key>

View File

@@ -47,7 +47,7 @@
<key>one</key>
<string>Ακόλουθος</string>
<key>other</key>
<string>Ακολουθείτε</string>
<string>Ακολουθεί</string>
</dict>
</dict>
<key>reacted_tagged_in_3</key>
@@ -127,7 +127,7 @@
<key>one</key>
<string>Διακομιστής Relay</string>
<key>other</key>
<string>Διακομιστές Relays</string>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
@@ -141,9 +141,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d άλλον</string>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d ακόμα</string>
<key>other</key>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d άλλους</string>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d ακόμα</string>
</dict>
</dict>
<key>reposted_tagged_in_3</key>

View File

@@ -45,7 +45,7 @@
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
<note>DM by heading in local notification</note>
</trans-unit>
<trans-unit id="%@ %@" xml:space="preserve">
<source>%@ %@</source>
@@ -188,17 +188,18 @@ Sentence composed of 2 variables to describe how many people are following a use
<trans-unit id="Appearance" xml:space="preserve">
<source>Appearance</source>
<target>Appearance</target>
<note>Section header for text and appearance settings</note>
<note>Navigation title for text and appearance settings.
Section header for text and appearance settings</note>
</trans-unit>
<trans-unit id="Are you lost?" xml:space="preserve">
<source>Are you lost?</source>
<target>Are you lost?</target>
<note>Text asking the user if they are lost in the app.</note>
</trans-unit>
<trans-unit id="Are you sure you want to repost this?" xml:space="preserve">
<source>Are you sure you want to repost this?</source>
<target>Are you sure you want to repost this?</target>
<note>Alert message to ask if user wants to repost a post.</note>
<trans-unit id="Are you sure you want to upload this image?" xml:space="preserve">
<source>Are you sure you want to upload this image?</source>
<target>Are you sure you want to upload this image?</target>
<note>Alert message asking if the user wants to upload an image.</note>
</trans-unit>
<trans-unit id="Automatically translate notes" xml:space="preserve">
<source>Automatically translate notes</source>
@@ -249,8 +250,8 @@ Sentence composed of 2 variables to describe how many people are following a use
Button to cancel a repost.
Button to cancel out of alert that creates a new mutelist.
Button to cancel out of posting a note.
Button to cancel out of reposting a post.
Button to cancel out of view adding user inputted relay.
Button to cancel the upload.
Cancel deleting the user.
Cancel out of logging out the user.</note>
</trans-unit>
@@ -354,6 +355,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Copy Text</target>
<note>Context menu option for copying the text from an note.</note>
</trans-unit>
<trans-unit id="Copy URL" xml:space="preserve">
<source>Copy URL</source>
<target>Copy URL</target>
<note>Label for button in context menu to copy URL of the selected uploaded media asset.</note>
</trans-unit>
<trans-unit id="Copy User Pubkey" xml:space="preserve">
<source>Copy User Pubkey</source>
<target>Copy User Pubkey</target>
@@ -404,11 +410,6 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Custom Zap Amount</target>
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
</trans-unit>
<trans-unit id="DM by %@" xml:space="preserve">
<source>DM by %@</source>
<target>DM by %@</target>
<note>DM by heading in local notification</note>
</trans-unit>
<trans-unit id="DMs" xml:space="preserve">
<source>DMs</source>
<target>DMs</target>
@@ -435,7 +436,7 @@ Sentence composed of 2 variables to describe how many people are following a use
<trans-unit id="Default Zap Amount in sats" xml:space="preserve">
<source>Default Zap Amount in sats</source>
<target>Default Zap Amount in sats</target>
<note>No comment provided by engineer.</note>
<note>Title for section in zap settings that controls the default zap amount in sats.</note>
</trans-unit>
<trans-unit id="Delete" xml:space="preserve">
<source>Delete</source>
@@ -627,7 +628,8 @@ Sentence composed of 2 variables to describe how many people are following a use
<trans-unit id="Keys" xml:space="preserve">
<source>Keys</source>
<target>Keys</target>
<note>Settings section for managing keys</note>
<note>Navigation title for managing keys.
Settings section for managing keys</note>
</trans-unit>
<trans-unit id="Left Handed" xml:space="preserve">
<source>Left Handed</source>
@@ -759,6 +761,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>NIP-05 Verification</target>
<note>Label for NIP-05 Verification section of user profile form.</note>
</trans-unit>
<trans-unit id="New encrypted direct message" xml:space="preserve">
<source>New encrypted direct message</source>
<target>New encrypted direct message</target>
<note>Notification that the user has received a new direct message</note>
</trans-unit>
<trans-unit id="No" xml:space="preserve">
<source>No</source>
<target>No</target>
@@ -802,7 +809,8 @@ Sentence composed of 2 variables to describe how many people are following a use
<trans-unit id="Notifications" xml:space="preserve">
<source>Notifications</source>
<target>Notifications</target>
<note>Toolbar label for Notifications view.</note>
<note>Section header for Damus notifications
Toolbar label for Notifications view.</note>
</trans-unit>
<trans-unit id="Nudity or explicit content" xml:space="preserve">
<source>Nudity or explicit content</source>
@@ -933,6 +941,11 @@ Picker option to indicate that a zap should be sent privately and not identify t
<target>QR Code</target>
<note>Button to view profile's qr code.</note>
</trans-unit>
<trans-unit id="Quote" xml:space="preserve">
<source>Quote</source>
<target>Quote</target>
<note>Title of alert for confirming to make a quoted post.</note>
</trans-unit>
<trans-unit id="Reactions" xml:space="preserve">
<source>Reactions</source>
<target>Reactions</target>
@@ -1021,8 +1034,7 @@ Picker option to indicate that a zap should be sent privately and not identify t
<trans-unit id="Repost" xml:space="preserve">
<source>Repost</source>
<target>Repost</target>
<note>Button to confirm reposting a post.
Title of alert for confirming to repost a post.</note>
<note>Title of alert for confirming to repost a post.</note>
</trans-unit>
<trans-unit id="Reposted" xml:space="preserve">
<source>Reposted</source>
@@ -1273,7 +1285,8 @@ Picker option to indicate that a zap should be sent privately and not identify t
<trans-unit id="Translation" xml:space="preserve">
<source>Translation</source>
<target>Translation</target>
<note>Section header for text and appearance settings</note>
<note>Navigation title for translation settings.
Section header for text and appearance settings</note>
</trans-unit>
<trans-unit id="Translations" xml:space="preserve">
<source>Translations</source>
@@ -1325,6 +1338,11 @@ Picker option to indicate that a zap should be sent privately and not identify t
<target>Unmute conversation</target>
<note>Context menu option for unmuting a conversation.</note>
</trans-unit>
<trans-unit id="Upload" xml:space="preserve">
<source>Upload</source>
<target>Upload</target>
<note>Button to proceed with uploading.</note>
</trans-unit>
<trans-unit id="User has been muted" xml:space="preserve">
<source>User has been muted</source>
<target>User has been muted</target>
@@ -1372,7 +1390,8 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<trans-unit id="Wallet" xml:space="preserve">
<source>Wallet</source>
<target>Wallet</target>
<note>Sidebar menu label for Wallet view.</note>
<note>Sidebar menu label for Wallet view.
Title for section in zap settings that controls the Lightning wallet selection.</note>
</trans-unit>
<trans-unit id="Website" xml:space="preserve">
<source>Website</source>
@@ -1460,8 +1479,10 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<source>Zaps</source>
<target>Zaps</target>
<note>Navigation bar title for the Zaps view.
Navigation title for zap settings.
Section header for zap settings
Setting to enable Zap Local Notification</note>
Setting to enable Zap Local Notification
Title for section in zap settings that controls general zap preferences.</note>
</trans-unit>
<trans-unit id="https://example.com/pic.jpg" xml:space="preserve">
<source>https://example.com/pic.jpg</source>

Binary file not shown.

Binary file not shown.

View File

@@ -17,7 +17,7 @@
<key>many</key>
<string>... %d Outras observações ... </string>
<key>other</key>
<string>... %d outras observações ... </string>
<string>... %d outras notas ... </string>
</dict>
</dict>
<key>followers_count</key>
@@ -71,7 +71,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros reagiram a uma publicação na qual você está marcado</string>
<key>other</key>
<string>%2$@ e %1$d outros reagiram a uma publicação na qual você está marcado</string>
<string>%2$@ e %1$d outros reagiram a um post que você está marcado</string>
</dict>
</dict>
<key>reacted_your_post_3</key>
@@ -89,7 +89,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros reagiram à sua publicação</string>
<key>other</key>
<string>%2$@ e %1$d outros reagiram à sua publicação</string>
<string>%2$@ e %1$d outros reagiram ao seu post</string>
</dict>
</dict>
<key>reacted_your_profile_3</key>
@@ -143,7 +143,7 @@
<key>many</key>
<string>Transmissões</string>
<key>other</key>
<string>Transmissões</string>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
@@ -179,7 +179,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros republicaram uma publicação na qual você está marcado</string>
<key>other</key>
<string>%2$@ e %1$d outros republicaram uma publicação na qual você está marcado</string>
<string>%2$@ e %1$d outros repostaram um post que você está marcado</string>
</dict>
</dict>
<key>reposted_your_post_3</key>
@@ -197,7 +197,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
<key>other</key>
<string>%2$@ e %1$d outros republicaram seu perfil</string>
<string>%2$@ e %1$d outros repostaram seu perfil</string>
</dict>
</dict>
<key>reposted_your_profile_3</key>
@@ -215,7 +215,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
<key>other</key>
<string>%2$@ e %1$d outros republicaram seu perfil</string>
<string>%2$@ e %1$d outros repostaram seu perfil</string>
</dict>
</dict>
<key>reposts_count</key>
@@ -233,7 +233,7 @@
<key>many</key>
<string>Republicações</string>
<key>other</key>
<string>Republicações</string>
<string>Reposts</string>
</dict>
</dict>
<key>sats_count</key>
@@ -305,7 +305,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros eletrizaram uma publicação na qual você está marcado</string>
<key>other</key>
<string>%2$@ e %1$d outros eletrizaram uma publicação na qual você está marcado</string>
<string>%2$@ e %1$d outros zapped um post que você está marcado</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
@@ -323,7 +323,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros eletrizaram sua publicação</string>
<key>other</key>
<string>%2$@ e %1$d outros eletrizaram sua publicação</string>
<string>%2$@ e %1$d outros zapped seu post</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
@@ -341,7 +341,7 @@
<key>many</key>
<string>%2$@ e %1$d muitos outros eletrizaram seu perfil</string>
<key>other</key>
<string>%2$@ e %1$d outros eletrizaram seu perfil</string>
<string>%2$@ e %1$d outros zapped seu perfil</string>
</dict>
</dict>
<key>zaps_count</key>
@@ -359,7 +359,7 @@
<key>many</key>
<string>Muitos iluminaram</string>
<key>other</key>
<string>Iluminaram</string>
<string>Zaps</string>
</dict>
</dict>
</dict>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -13,7 +13,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>... %d 条更多便条...</string>
<string>... %d 条更多笔记...</string>
</dict>
</dict>
<key>followers_count</key>
@@ -55,7 +55,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户回应了提到你的便条</string>
<string>%2$@ 和 %1$d 个其他用户回应了提到你的帖子</string>
</dict>
</dict>
<key>reacted_your_post_3</key>
@@ -69,7 +69,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户回应了你的便条</string>
<string>%2$@ 和 %1$d 个其他用户回应了你的帖子</string>
</dict>
</dict>
<key>reacted_your_profile_3</key>
@@ -139,7 +139,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户转发了提到你的便条</string>
<string>%2$@ 和 %1$d 个其他用户转发了提到你的帖子</string>
</dict>
</dict>
<key>reposted_your_post_3</key>
@@ -153,7 +153,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户转发了你的便条</string>
<string>%2$@ 和 %1$d 个其他用户转发了你的帖子</string>
</dict>
</dict>
<key>reposted_your_profile_3</key>
@@ -237,7 +237,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户电击了提到你的便条</string>
<string>%2$@ 和 %1$d 个其他用户打闪了提到你的帖子</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
@@ -251,7 +251,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户电击了你的便条</string>
<string>%2$@ 和 %1$d 个其他用户打闪了你的帖子</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
@@ -265,7 +265,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 个其他用户电击了你的档案</string>
<string>%2$@ 和 %1$d 个其他用户打闪了你的档案</string>
</dict>
</dict>
<key>zaps_count</key>
@@ -279,7 +279,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>电击</string>
<string>打闪</string>
</dict>
</dict>
</dict>

View File

@@ -13,7 +13,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>...還有%d 条便條...</string>
<string>...還有%d 条筆記...</string>
</dict>
</dict>
<key>followers_count</key>
@@ -237,7 +237,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了提到你的便條</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了提到你的便條</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
@@ -251,7 +251,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了你的便條</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了你的便條</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
@@ -265,7 +265,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了你的檔案</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了你的檔案</string>
</dict>
</dict>
<key>zaps_count</key>
@@ -279,7 +279,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>電擊</string>
<string>打閃</string>
</dict>
</dict>
</dict>

View File

@@ -13,7 +13,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>...還有%d 条便條...</string>
<string>...還有%d 条筆記...</string>
</dict>
</dict>
<key>followers_count</key>
@@ -237,7 +237,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了提到你的便條</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了提到你的便條</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
@@ -251,7 +251,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了你的便條</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了你的便條</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
@@ -265,7 +265,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%2$@ 和 %1$d 個其他用戶電擊了你的檔案</string>
<string>%2$@ 和 %1$d 個其他用戶打閃了你的檔案</string>
</dict>
</dict>
<key>zaps_count</key>
@@ -279,7 +279,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>電擊</string>
<string>打閃</string>
</dict>
</dict>
</dict>

View File

@@ -30,7 +30,7 @@ final class RequestTests: XCTestCase {
}
func testMakeSubscriptionRequest() {
let filter = NostrFilter(kinds: [3], limit: 1, authors: ["d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62"])
let filter = NostrFilter(kinds: [NostrKind.contacts.rawValue], limit: 1, authors: ["d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62"])
let subscribe = NostrSubscribe(filters: [filter], sub_id: "31C737B7-C8F9-41DD-8707-325974F279A4")
let result = make_nostr_req(.subscribe(subscribe))
let expectedResult = "[\"REQ\",\"31C737B7-C8F9-41DD-8707-325974F279A4\",{\"kinds\":[3],\"authors\":[\"d9fa34214aa9d151c4f4db843e9c2af4f246bab4205137731f91bcfa44d66a62\"],\"limit\":1}]"

View File

@@ -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)
}