Compare commits

...

2862 Commits

Author SHA1 Message Date
Vitor Pamplona
a86f64fce5 Fixes release Id for the zap the devs button 2025-11-15 12:01:17 -05:00
Vitor Pamplona
075a92a709 Version 1.04 2025-11-15 11:45:26 -05:00
Vitor Pamplona
67d6b9e350 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-15 11:34:05 -05:00
Vitor Pamplona
f7fd1e79f0 Improves creation of lists for users that had none 2025-11-15 11:26:35 -05:00
Vitor Pamplona
01afb0aeeb Merge pull request #1568 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 11:11:35 -05:00
Crowdin Bot
d777e94aad New Crowdin translations by GitHub Action 2025-11-15 16:09:40 +00:00
Vitor Pamplona
1bd2b2168f Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
  updated cz, de, pt, sv
  New Crowdin translations by GitHub Action
2025-11-15 11:08:00 -05:00
Vitor Pamplona
719e34ea8d Less jarring delete buttons on lists 2025-11-15 11:04:13 -05:00
Vitor Pamplona
78c13a7d29 Minor changes to the change log 2025-11-15 10:36:44 -05:00
David Kaspar
61d4f3000f Merge pull request #1567 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 13:21:09 +01:00
Crowdin Bot
22c8ecefed New Crowdin translations by GitHub Action 2025-11-15 12:15:13 +00:00
David Kaspar
8995c7a51d Merge pull request #1566 from davotoula/update-translations
updated cz, de, pt, sv
2025-11-15 13:14:06 +01:00
David Kaspar
ef267acee2 Merge pull request #1565 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 13:13:49 +01:00
davotoula
cbf9effcf4 updated cz, de, pt, sv 2025-11-15 09:38:00 +01:00
Crowdin Bot
007f512cc4 New Crowdin translations by GitHub Action 2025-11-15 00:20:39 +00:00
Vitor Pamplona
96ee6baa8f Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-14 19:18:33 -05:00
Vitor Pamplona
f400cc6088 Adds a change log 2025-11-14 19:07:52 -05:00
Vitor Pamplona
23299b5ef3 half-way contrast for placeholder texts 2025-11-13 18:25:49 -05:00
David Kaspar
88a3e2a74b Merge pull request #1563 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-13 19:17:56 +01:00
Crowdin Bot
e5fe63d630 New Crowdin translations by GitHub Action 2025-11-13 15:53:22 +00:00
Vitor Pamplona
e6a10e07c4 Correctly adding relays to the filters so that the Global feed can filter only events that came from the relays listed in the Global filter. 2025-11-13 10:50:13 -05:00
Vitor Pamplona
19846e7ff3 Improves the composition of NIP-05 lines 2025-11-13 10:49:29 -05:00
Vitor Pamplona
05af3a78e4 Increases the contrast of placeholder text 2025-11-13 10:45:58 -05:00
Vitor Pamplona
c9370f8d02 Better parse multiple imetas with multiple urls into appropriate properties 2025-11-13 10:45:28 -05:00
Vitor Pamplona
f186b78ca9 Adds test case for empty d-tags in naddr1 2025-11-13 10:30:26 -05:00
Vitor Pamplona
abcaf19d2b removes zapstr.live 2025-11-13 09:11:20 -05:00
Vitor Pamplona
43fbf4f40f Removes filestr 2025-11-13 09:11:09 -05:00
Vitor Pamplona
9f66cd0660 Opens Follow Packs on following.space 2025-11-13 09:10:56 -05:00
Vitor Pamplona
b80f24b587 Upgrades dependencies 2025-11-13 08:42:07 -05:00
Vitor Pamplona
ee52f10192 removes unecessary list of icons option from drawer 2025-11-12 18:19:28 -05:00
Vitor Pamplona
8d41b58167 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-12 17:26:17 -05:00
Vitor Pamplona
ac291c996c Adds on click to the UserProfile selections 2025-11-12 17:21:46 -05:00
Vitor Pamplona
29d11c8281 Moves the List/Pack creation Dialog to a screen and adds an Image upload option 2025-11-12 17:17:22 -05:00
Vitor Pamplona
e2c50847d0 Merge pull request #1562 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-12 14:21:30 -05:00
Crowdin Bot
b339e3a245 New Crowdin translations by GitHub Action 2025-11-12 19:20:01 +00:00
Vitor Pamplona
8963024a95 Fixes Jump on FollowPack screens 2025-11-12 14:18:01 -05:00
Vitor Pamplona
27bc950afb Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-12 14:12:26 -05:00
Vitor Pamplona
0ddbb4616b Adds creation, edits and member update for Follow Packs 2025-11-12 13:50:57 -05:00
Vitor Pamplona
adb484bf56 Improves descriptions of lists and packs 2025-11-12 13:18:18 -05:00
Vitor Pamplona
bd30b526a9 Adds a @Stable marker on UserSuggestionState s 2025-11-12 12:34:49 -05:00
Vitor Pamplona
ce49725771 Allows customization of the Create new list dialog 2025-11-12 12:34:11 -05:00
Vitor Pamplona
6c64297415 Adds an update method for changes in current Event 2025-11-12 12:33:35 -05:00
Vitor Pamplona
90330165d2 Prepares to create follow packs 2025-11-12 12:33:10 -05:00
David Kaspar
fe876de4c9 Merge pull request #1561 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-12 15:13:05 +01:00
Crowdin Bot
b17d709696 New Crowdin translations by GitHub Action 2025-11-12 13:32:00 +00:00
Vitor Pamplona
8554257329 Change reqResultsInOrderAsFlow to reqAsFlow 2025-11-12 08:29:05 -05:00
Vitor Pamplona
9eee5d9e1e slight performance improvement on the reqflow 2025-11-12 08:27:59 -05:00
Vitor Pamplona
b4810fd12c Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-11 19:29:10 -05:00
Vitor Pamplona
bf803384a8 This early decryption doesn't seem needed anymore. The event consumer in the account will take care of decrypting the new DM 2025-11-11 19:01:52 -05:00
Vitor Pamplona
e8eb4c688c Modifies the flow to only output changes in the relay piece that has modified. 2025-11-11 19:01:20 -05:00
Vitor Pamplona
a5207c3f0a adds recursive markAsSeen to mark the inner chat element of NIP-17 DMs for each relay that that message was received. 2025-11-11 19:00:49 -05:00
Vitor Pamplona
ce073553ee Makes sure the edit is Stable 2025-11-11 18:59:34 -05:00
Vitor Pamplona
dd97dfc67f Merge pull request #1560 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-11 17:08:06 -05:00
Vitor Pamplona
1cfe953a65 Refactors the code to avoid using callback lambdas 2025-11-11 16:37:15 -05:00
Vitor Pamplona
d4b91845d5 rename runIOCatching to launchSigner 2025-11-11 16:10:49 -05:00
Crowdin Bot
a114bf9dfa New Crowdin translations by GitHub Action 2025-11-11 21:05:02 +00:00
Vitor Pamplona
b14c588a34 Moves the dispatcher of the save event for server models from each of the viewmodels to the account view model since the scope gets cancelled when the screen closes. 2025-11-11 16:01:19 -05:00
Vitor Pamplona
1589b646a8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
  updated cz, de, pt, sv
2025-11-11 16:00:15 -05:00
Vitor Pamplona
8c3ab80e47 More correctly parses null and default values from NIP-55 using Jackson 2025-11-11 14:00:17 -05:00
David Kaspar
4690f59922 Merge pull request #1556 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-11 09:12:24 +01:00
Crowdin Bot
255389aa3c New Crowdin translations by GitHub Action 2025-11-11 07:58:56 +00:00
David Kaspar
cf756669e5 Merge pull request #1557 from davotoula/update-translations
updated cz, de, pt, sv
2025-11-11 08:57:39 +01:00
davotoula
d7ca9d5a82 updated cz, de, pt, sv 2025-11-11 08:53:04 +01:00
Vitor Pamplona
5f88249be6 Creates a feed filter option for only kind 3 users 2025-11-10 19:45:39 -05:00
Vitor Pamplona
db354b646f Adds context to the highlights 2025-11-10 18:42:17 -05:00
Vitor Pamplona
17fdf05847 Fixes the author of the highlight 2025-11-10 18:00:32 -05:00
Vitor Pamplona
ddfe61eb9f adds description to the follow card ui 2025-11-10 17:36:07 -05:00
Vitor Pamplona
8bdd9d0979 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
  updated cz, de, pt, sv
  New Crowdin translations by GitHub Action
  Update strings.xml

# Conflicts:
#	amethyst/src/main/res/values/strings.xml
2025-11-10 17:12:11 -05:00
Vitor Pamplona
bd1a70039a Creates a feed for follow packs 2025-11-10 17:02:21 -05:00
David Kaspar
87599b7e59 Merge pull request #1554 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-10 18:22:12 +01:00
Crowdin Bot
5762490098 New Crowdin translations by GitHub Action 2025-11-10 17:03:42 +00:00
David Kaspar
e634040504 Merge pull request #1553 from davotoula/update-translations
updated cz, de, pt, sv
2025-11-10 18:02:10 +01:00
davotoula
15e7909222 updated cz, de, pt, sv 2025-11-10 18:01:16 +01:00
Vitor Pamplona
ffaa06fed7 Adds an option to render a user gallery from HexKeys instead of full User objects 2025-11-10 11:18:18 -05:00
Vitor Pamplona
58128d9ad0 Making Channel metadata view model stable for performance 2025-11-10 11:17:22 -05:00
Vitor Pamplona
011aebfc27 Moving preview EmptyNav to a class to avoid Nav auto-imports in production code. 2025-11-10 11:17:04 -05:00
Vitor Pamplona
7230f67782 Merge pull request #1552 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-10 08:02:31 -05:00
Crowdin Bot
93e80dda83 New Crowdin translations by GitHub Action 2025-11-10 12:54:37 +00:00
Vitor Pamplona
0505011eab Merge pull request #1551 from maxblake2015/main
Update strings.xml
2025-11-10 07:53:07 -05:00
Max Blake
ea83dc3fd7 Update strings.xml
a small logical correction
2025-11-08 12:22:45 +00:00
Vitor Pamplona
948180157f pushes exception upward if triggered 2025-11-07 17:37:34 -05:00
Vitor Pamplona
a996065c05 Creates new observer for events with null option 2025-11-07 17:27:41 -05:00
Vitor Pamplona
c328a3aefe Refactors event observer name to a more precise one 2025-11-07 17:26:20 -05:00
Vitor Pamplona
6ccec0681a Marks address as stable 2025-11-07 17:23:53 -05:00
Vitor Pamplona
26a6b5d219 - Inverts public and private tabs
- adds number of users in the tab title
2025-11-06 16:19:43 -05:00
Vitor Pamplona
d17f39e85b Fixes naming cropping the menu icon 2025-11-06 16:18:29 -05:00
Vitor Pamplona
2ac5a57efe Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  optimise imports
  synchronize all cache mutations and supply sinceRelaySet so callers get snapshot copies rework groupByRelayPresence to build relay snapshots via sinceRelaySet,     filtering with immutable lists to prevent ConcurrentModificationException when relays update mid-iteration
  Hardened EOSEAccountFast against concurrent access so callers no longer iterate over live mutable maps
  Adjusted subscription cleanup to avoid mutating the watcher map while iterating it, preventing the ConcurrentModificationException when accounts switch

# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt
2025-11-06 15:15:34 -05:00
Vitor Pamplona
6727bd0430 Improves padding of the ListItem interfaces 2025-11-06 14:15:59 -05:00
Vitor Pamplona
34aca458ed migrates to njump.to 2025-11-06 13:49:15 -05:00
Vitor Pamplona
ba082d86e2 Merge pull request #1550 from davotoula/reduce-errors-ConcurrentModificationException
Reduce errors in log: concurrent modification exception
2025-11-06 09:50:22 -05:00
davotoula
409b3b43f7 optimise imports 2025-11-06 10:19:32 +01:00
davotoula
545cd2ff6d synchronize all cache mutations and supply sinceRelaySet so callers get snapshot copies
rework groupByRelayPresence to build relay snapshots via sinceRelaySet,
    filtering with immutable lists to prevent ConcurrentModificationException when relays update mid-iteration
2025-11-06 10:12:07 +01:00
davotoula
a82d6565fa Hardened EOSEAccountFast against concurrent access so callers no longer iterate over live mutable maps 2025-11-06 10:12:07 +01:00
davotoula
c07202944d Adjusted subscription cleanup to avoid mutating the watcher map while iterating it, preventing the ConcurrentModificationException when accounts switch 2025-11-06 10:12:07 +01:00
Vitor Pamplona
178582f3c4 Showing the list option in all Follow Buttons 2025-11-05 19:03:01 -05:00
Vitor Pamplona
c2c3442b74 Fixes showing the list of people button when the user is blocked 2025-11-05 19:02:45 -05:00
Vitor Pamplona
43942891b7 Changes the following marker to be a follow from all lists and follow sets 2025-11-05 18:02:49 -05:00
Vitor Pamplona
fc488064b7 Changes animations for profile buttons to come from the bottom of the screen 2025-11-05 17:58:56 -05:00
Vitor Pamplona
4535966c32 Refactoring of class names and breaking down bigger files into many 2025-11-05 17:48:16 -05:00
Vitor Pamplona
83f460c01d Selects the full text when adding users to list 2025-11-05 17:06:59 -05:00
Vitor Pamplona
6c630c75c2 Improves design of the lists and User management screen. 2025-11-05 16:40:20 -05:00
Vitor Pamplona
8d33ea8f99 Remove description from top nav bar of the list 2025-11-05 16:39:59 -05:00
Vitor Pamplona
558d952a00 Generalize List counters 2025-11-05 16:39:37 -05:00
Vitor Pamplona
1eb2309952 sort by title instead 2025-11-05 16:39:07 -05:00
Vitor Pamplona
0ffbbee31a Switch to vector graphics 2025-11-05 16:04:03 -05:00
Vitor Pamplona
ed8b285700 Sort lists by identifier 2025-11-05 15:54:01 -05:00
Vitor Pamplona
452082c6cb Fixes multiline list name 2025-11-05 15:53:47 -05:00
Vitor Pamplona
8168d353dd Improves the look of the list of lists of people screen 2025-11-05 14:11:15 -05:00
Vitor Pamplona
59cdb222d5 Removes the Mute List from the All Follows TopNav option 2025-11-05 10:51:10 -05:00
Vitor Pamplona
712965cc8f Fixes the unfollow button on profile (removes the follow signal including all lists for now)
Also fixes spacing of the Follow button
2025-11-04 19:50:57 -05:00
Vitor Pamplona
5741914c2a Fixes User profile banner being off place in short images 2025-11-04 19:41:23 -05:00
Vitor Pamplona
b8cce80d9f Updates all libs 2025-11-04 19:41:05 -05:00
Vitor Pamplona
7dfa343ad8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
  code simplification and deduplication
  code simplification and deduplication
  Add blurhash and dim to Nip96Uploader.kt
  added blurhash to blossom upload
  New Crowdin translations by GitHub Action
  Update README to mark video capture as complete
  Parse for video segments and exclude them from image gallery
  add VideoSegment and failing tests
  Fix location being added to note even after deselecting it
  optimise imports
  New Crowdin translations by GitHub Action
  update cs, de, sv, pt translations
  Revert "update buffer to 64kb"
  update buffer to 64kb
  Change length to Long from Int: avoids potential overflow, Long seems to be used everywhere else
  Create a CountingInputStream utility to avoid duplication Prevent INT overflow in BlossomUploader.kt Connection Cleanup in ImageDownloader.kt Added try-finally in Sha256Hasher.jvmAndroid.kt
  Add streaming hash utility function to quartz multiplatform, follow the existing pool/worker design Change hashing in ImageDownloader.kt to use streaming
  stream file to calculate both hash and size without loading it all at once

# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/followsets/FollowSetScreen.kt
2025-11-04 19:28:45 -05:00
Vitor Pamplona
be804092d7 - Refactors people lists
- Adds people and follow list cache on the account class
- Updates TopNav to use the new caching
- Removes redundant viewModels for list feeds
- Adds the ability to add and remove users from the List screen
2025-11-04 19:24:24 -05:00
Vitor Pamplona
50b551bb80 Updates emoji and user autocomplete state to use Account and avoid linking AccountViewModel 2025-11-04 19:21:04 -05:00
Vitor Pamplona
7c439f2448 Merge pull request #1549 from davotoula/optimise-imports
Optimise imports
2025-11-03 14:06:59 -05:00
davotoula
5ce184d400 Merge branch 'main' into optimise-imports 2025-11-03 19:45:12 +01:00
Vitor Pamplona
bda1fcadf8 Merge pull request #1548 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-03 12:47:34 -05:00
Crowdin Bot
379e539e89 New Crowdin translations by GitHub Action 2025-11-03 17:39:48 +00:00
Vitor Pamplona
f14b545947 Merge pull request #1531 from davotoula/1530-added-streaming-hashing-utility-to-avoid-OOM-on-large-files
Fix OutOfMemoryError for Large File Uploads
2025-11-03 12:38:38 -05:00
Vitor Pamplona
b8fff0c428 Merge pull request #1546 from davotoula/1537-dont-include-video-media-in-image-gallery
dont include video media in image gallery
2025-11-03 12:37:53 -05:00
davotoula
942de50148 code simplification and deduplication 2025-11-02 20:37:20 +01:00
davotoula
76be7b0d58 code simplification and deduplication 2025-11-02 19:56:38 +01:00
davotoula
4b260cfc07 Add blurhash and dim to Nip96Uploader.kt 2025-11-02 18:25:49 +01:00
davotoula
eee1487883 added blurhash to blossom upload 2025-11-02 18:25:49 +01:00
David Kaspar
27380f276c Merge pull request #1547 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-01 19:26:09 +01:00
Crowdin Bot
f62b1667c3 New Crowdin translations by GitHub Action 2025-11-01 18:22:14 +00:00
David Kaspar
eb4f0b9af9 Update README to mark video capture as complete 2025-11-01 18:20:58 +00:00
davotoula
7c374fb586 Parse for video segments and exclude them from image gallery 2025-11-01 16:37:38 +01:00
davotoula
1e12decd36 add VideoSegment and failing tests 2025-11-01 16:23:35 +01:00
Vitor Pamplona
1411de32a0 Adds search of events and addresses in the Deletion Event 2025-10-31 18:43:45 -04:00
Vitor Pamplona
7ea61824c8 Moves filter interfaces to their own files 2025-10-31 18:41:41 -04:00
Vitor Pamplona
f0923fb393 Fixes crash when checking and creating new users from tags. 2025-10-31 18:38:29 -04:00
Vitor Pamplona
3eb662851b Adds a flatten to set utility 2025-10-31 18:36:05 -04:00
Vitor Pamplona
f9bd660774 Merge pull request #1545 from greenart7c3/main
Fix location being added to note even after deselecting it
2025-10-31 08:26:00 -04:00
greenart7c3
bdb2637803 Fix location being added to note even after deselecting it 2025-10-31 06:34:49 -03:00
davotoula
c3c75b46a7 optimise imports 2025-10-30 19:20:02 +01:00
David Kaspar
3c7104fb6a Merge pull request #1544 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-30 19:19:05 +01:00
Crowdin Bot
c111912d2f New Crowdin translations by GitHub Action 2025-10-30 17:56:08 +00:00
David Kaspar
0f4e514308 Merge pull request #1543 from davotoula/update-tr
update cs, de, sv, pt translations
2025-10-30 18:54:44 +01:00
davotoula
ccf9d4ce80 update cs, de, sv, pt translations 2025-10-30 18:19:38 +01:00
Vitor Pamplona
95cc0783fd Adds a req that returns the list of events in order of arrival 2025-10-29 18:30:00 -04:00
Vitor Pamplona
f9f7cdf12f Renames internal Req objects 2025-10-29 18:29:23 -04:00
Vitor Pamplona
40d417038e adds a Start request to the req listener 2025-10-29 18:28:59 -04:00
Vitor Pamplona
895562b4a9 Simplifies readme 2025-10-29 18:28:38 -04:00
Vitor Pamplona
b8511b5ac3 Adds simplified methods to create Requests from NostrClient 2025-10-29 16:51:49 -04:00
Vitor Pamplona
40ce627de0 Merge 2025-10-29 15:57:48 -04:00
Vitor Pamplona
56a856cf71 Better filter for People Lists 2025-10-29 15:55:33 -04:00
Vitor Pamplona
904b8c445f Merge pull request #1540 from davotoula/1535-dont-use-own-follow-sets-for-follows-check
only check follow sets when user1 is the logged-in user
2025-10-29 15:47:30 -04:00
davotoula
3a744b9c1d only check follow sets when user1 is the logged-in user 2025-10-29 20:33:39 +01:00
Vitor Pamplona
6d8b5fa85d Moves message button to Profile Actions 2025-10-29 15:19:11 -04:00
Vitor Pamplona
a913194c41 Updates deprecated Clipboard manager 2025-10-29 15:18:57 -04:00
Vitor Pamplona
a9ebf561e7 Merge pull request #1539 from greenart7c3/main
Fix DecryptZapRequest CommandType
2025-10-29 14:00:05 -04:00
greenart7c3
fb7610cafd Fix DecryptZapRequest CommandType 2025-10-29 14:38:54 -03:00
Vitor Pamplona
cd35c8e30e uses faster spotless check on commits 2025-10-29 12:20:37 -04:00
Vitor Pamplona
f3209d2415 Moves SettingsCategory to take integers instead of Strings 2025-10-29 12:19:38 -04:00
Vitor Pamplona
d44b24936c Activates reverse scrolling for the new post screen in order to avoid hiding the cursor behind the keyboard when typing long texts. 2025-10-29 11:23:09 -04:00
Vitor Pamplona
a9aadb60fc Merge pull request #1538 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-29 09:56:13 -04:00
Crowdin Bot
59f2cdcb2f New Crowdin translations by GitHub Action 2025-10-29 13:21:40 +00:00
Vitor Pamplona
00eb6ee3f1 Adds a missing outbox popup on posting new notes 2025-10-28 19:09:24 -04:00
Vitor Pamplona
09e4b2ea44 Fixes inconsistent relay lists 2025-10-28 18:41:43 -04:00
Vitor Pamplona
f8b920a128 Adds a popup to warn users of the lack of inbox relays 2025-10-28 13:12:36 -04:00
Vitor Pamplona
0705ee9581 Unifying the parser for the tag array 2025-10-28 13:05:57 -04:00
Vitor Pamplona
bf664ea0c5 Avoids interning of large strings and objects where the duplication is not common 2025-10-28 12:18:19 -04:00
Vitor Pamplona
39f3aef7b5 Don't intern signatures, content and tags since they are not usually duplicated 2025-10-28 12:11:34 -04:00
davotoula
7254dce3a9 Revert "update buffer to 64kb"
This reverts commit 4e31a9a5ac.
2025-10-26 10:25:40 +01:00
davotoula
4e31a9a5ac update buffer to 64kb 2025-10-26 00:09:17 +02:00
davotoula
d01f6a2178 Change length to Long from Int: avoids potential overflow, Long seems to be used everywhere else 2025-10-25 23:54:56 +02:00
davotoula
2332623cde Create a CountingInputStream utility to avoid duplication
Prevent INT overflow in BlossomUploader.kt
Connection Cleanup in ImageDownloader.kt
Added try-finally in Sha256Hasher.jvmAndroid.kt
2025-10-25 23:43:24 +02:00
davotoula
93994564f6 Add streaming hash utility function to quartz multiplatform, follow the existing pool/worker design
Change hashing in ImageDownloader.kt to use streaming
2025-10-25 23:22:31 +02:00
davotoula
e1c54f52e3 stream file to calculate both hash and size without loading it all at once 2025-10-25 21:24:12 +02:00
Vitor Pamplona
371d3bf0f7 Improves paddings, language and add stickiness backgrounds 2025-10-24 19:08:26 -04:00
Vitor Pamplona
b0e7082ec6 Solves crash when the same user is in the private and public part of the list 2025-10-24 19:07:31 -04:00
Vitor Pamplona
34971211f1 Better icon padding 2025-10-24 19:06:36 -04:00
Vitor Pamplona
b065956aec Normalize thicknesses of the divisors 2025-10-24 19:05:35 -04:00
Vitor Pamplona
36f79ca581 Correct animations for the list screens 2025-10-24 19:02:52 -04:00
Vitor Pamplona
7aeb4e7582 Enabling more threads in debug 2025-10-24 18:51:18 -04:00
Vitor Pamplona
25d6562500 Solving lack of stream name 2025-10-24 18:08:39 -04:00
Vitor Pamplona
67e3b05a42 Uses the new nostrnwc deep link for connections 2025-10-24 17:53:31 -04:00
Vitor Pamplona
a157c60697 update compose 2025-10-24 14:59:06 -04:00
Vitor Pamplona
50d63a0501 making sure the disappearing modifier is not doing anything when the top or bottom bars are not present. 2025-10-23 19:24:23 -04:00
Vitor Pamplona
e1cbfbb280 Fixes large top bars on these screens 2025-10-23 17:46:26 -04:00
Vitor Pamplona
b6aec40e9a New Disappearing Top, Bottom and Action Scaffold 2025-10-23 17:45:21 -04:00
Vitor Pamplona
e70a191191 No need for disappearing scaffolds in these screens 2025-10-23 17:42:10 -04:00
Vitor Pamplona
59a5a15866 Merge pull request #1528 from davotoula/image-gallery-improve-layout
Image gallery improve layout
2025-10-23 09:13:44 -04:00
davotoula
b82145ac10 Merge branch 'main' into image-gallery-improve-layout 2025-10-23 15:01:29 +02:00
Vitor Pamplona
ee80361478 - Removes the deprecated hacks from Contact Lists
- Removes the use of kind 3 to show hashtags which fixes issues when people unfollowing the hashtag and it would just affect the InterestListEvent
2025-10-22 18:31:00 -04:00
Vitor Pamplona
2d551d4adf Fixes the visual references to communities and hashtags in the top right of the post 2025-10-22 18:27:53 -04:00
Vitor Pamplona
35da3b4efe Makes sure the list of geohashes and hashtags use lowercase words 2025-10-22 18:26:52 -04:00
Vitor Pamplona
5306dd52d7 spotless needs to clean the compiled files to compute all unused imports 2025-10-22 16:29:47 -04:00
Vitor Pamplona
2ada3855d0 Remove imports 2025-10-22 16:28:20 -04:00
Vitor Pamplona
cabdfea8f9 No need to avoid annotations with test 2025-10-22 16:28:10 -04:00
Vitor Pamplona
d9c0a9af43 Reactivates removing imports on ktint by default 2025-10-22 16:27:52 -04:00
Vitor Pamplona
a7c846f641 Removes NIP-04 DMs with hidden words from the Notification screen 2025-10-22 16:16:15 -04:00
davotoula
f7ed4d2aaa NEEDS testing:
Swapped the local var orientation by remember { mutableStateOf(...) } for an explicit MutableState: we now create orientationState = remember(firstImage) { mutableStateOf(...) }, update it with
    orientationState.value = …, and return orientationState.value.
2025-10-22 09:32:26 +01:00
Vitor Pamplona
031e604923 Only avoids sending a message for the request state if it is a new open request 2025-10-21 20:17:04 -04:00
Vitor Pamplona
551bd344e0 Makes sure to trigger null pointer exception on message deserializer 2025-10-21 20:15:48 -04:00
Vitor Pamplona
c21b2ca60f Removes the choice of Dispatchers from Quartz 2025-10-21 20:15:05 -04:00
Vitor Pamplona
db1d5c7e82 Adds the user outbox relays when loading addressables by that user. 2025-10-21 20:09:13 -04:00
Vitor Pamplona
17618ac361 Fixes bug when loading users 2025-10-21 19:29:43 -04:00
Vitor Pamplona
bf94d0d91c For reports, only use the outbox relay if the user has it. (don't use our default bootstrapping relays) 2025-10-21 19:09:58 -04:00
Vitor Pamplona
66096e5b98 Allows an empty metadata to reload everything about the user 2025-10-21 19:09:25 -04:00
Vitor Pamplona
c8a608718c Merge 2025-10-21 18:42:11 -04:00
Vitor Pamplona
df54f7bb67 Moves all Dispatchers.Default to IO because of the amount of Synchronized code that blocks the thread underneath each coroutine and since Default only has the exact number of threads the processor offers, once they block, we lose computing power 2025-10-21 18:30:08 -04:00
David Kaspar
5b230a4c9b Merge branch 'main' into image-gallery-improve-layout 2025-10-22 00:10:34 +02:00
Vitor Pamplona
d8907e3dfb Caches the User object to avoid being deleted by the soft reference on Local Cache 2025-10-21 17:52:45 -04:00
Vitor Pamplona
889e3d63ff Merge pull request #1527 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-21 16:06:01 -04:00
Crowdin Bot
5f7b318647 New Crowdin translations by GitHub Action 2025-10-21 20:04:21 +00:00
Vitor Pamplona
e2b2b10994 - Separates a relay failure tracker to a module
- Updates finding the outbox relay set to include the set of connected relays before all of the relays.
2025-10-21 15:55:32 -04:00
davotoula
a055f473cb prevent race condition: Added a state-aware rememberFirstImageOrientation helper so each gallery watches the cache until the first image’s real aspect ratio is known 2025-10-21 15:56:18 +01:00
davotoula
c735c358bb extracted common logic 2025-10-21 15:47:20 +01:00
davotoula
50cbbb55b6 three images case: test for aspect ratio of first image and adapt gallery based on that 2025-10-21 15:41:40 +01:00
davotoula
103845225b two images case: test for aspect ratio of first image and adapt gallery based on that 2025-10-21 15:19:35 +01:00
Vitor Pamplona
a27df3a9b6 Improving click and long press interactions with the relay list item and status bar items 2025-10-21 09:19:11 -04:00
Vitor Pamplona
d51d1f4453 Updates OkHttp, Tor and Spotless back 2025-10-21 08:38:53 -04:00
Vitor Pamplona
f899d446bc Merge 2025-10-21 08:37:53 -04:00
Vitor Pamplona
c1295ed97f Reverts composable naming conventions on spotless 2025-10-21 08:34:29 -04:00
Vitor Pamplona
774c5412d3 Merge pull request #1526 from davotoula/revert-spotless-plugin
revert spotless plugin  version
2025-10-21 08:32:47 -04:00
davotoula
38d5527b91 revert spotless plugin (need to find a solution for @Composable function names)
Remove unused imports
2025-10-21 12:47:42 +01:00
Vitor Pamplona
1ce352e69f Changes the icon of account preferences to translation for now 2025-10-20 21:00:11 -04:00
Vitor Pamplona
bf3e5c4568 no need for this anymore 2025-10-20 20:57:20 -04:00
Vitor Pamplona
200db73ce4 remove fmt.wiz.biz from bootstrap relays 2025-10-20 20:22:58 -04:00
Vitor Pamplona
2d1fd4a48a Remove companions 2025-10-20 20:22:37 -04:00
Vitor Pamplona
a0cb2a7db6 Creates a default relay list for indexers in new accounts 2025-10-20 20:22:20 -04:00
Vitor Pamplona
382319addf Adds a declared follows state 2025-10-20 20:21:18 -04:00
Vitor Pamplona
37625987c1 Only listens to notification changes when the UI is visible 2025-10-20 19:41:27 -04:00
Vitor Pamplona
63bd0b6cae Watches the okHttp client for web calls (only active when screen is visible) 2025-10-20 19:40:04 -04:00
Vitor Pamplona
48a69f1d26 Watches the correct OKHttp for relays 2025-10-20 19:39:33 -04:00
Vitor Pamplona
f663bb78aa Reduce error log sizes 2025-10-20 18:41:43 -04:00
Vitor Pamplona
a163c4188f Adjustments to the lazy initialization of the app 2025-10-20 18:41:00 -04:00
Vitor Pamplona
fabec35593 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-10-20 17:13:25 -04:00
Vitor Pamplona
e4ce7415a3 Don't use "when" when just one line is needed 2025-10-20 17:13:11 -04:00
Vitor Pamplona
911fdffa95 Turns the EOSE class into a mutable map for speed 2025-10-20 17:12:36 -04:00
Vitor Pamplona
3a73de6828 Separates the new GaleryParser from Composables so that later we can cache it and parse it on the Default thread. 2025-10-20 17:12:12 -04:00
Vitor Pamplona
c4653b147c Adds new benchmark cases 2025-10-20 17:10:08 -04:00
Vitor Pamplona
cbba2fe423 Merge pull request #1525 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-18 18:14:05 -04:00
Crowdin Bot
8aa7c57a24 New Crowdin translations by GitHub Action 2025-10-18 22:13:15 +00:00
Vitor Pamplona
e45b4b11dc Restructuring Relay systems to:
- Maintain order of incoming messages for relay listeners
- Defers all processing of incoming messages to coroutines via channels, freeing OkHttp's thread as soon as possible.
- Simplifies the main relay class by using attached listener modules for each function of the relay client.
- Migrate defaultOnConnect calls to become listener based and moved to NostrClients
- Treat counts as query only, not subscriptions.
- Coordinates REQs so that if an update is required to be sent but the server has not finished processing events, waits for it to finish and sends it later as soon as EOSE or Close arrives
- Correctly sustain the local state of each Req.
- Creates an Account follow list per Relay state that only includes shared relays as a better source of functioning relays
- Changes UserLoading features in a tentative to make them faster since they are used by all functions in the app.
- Correctly marks EOSE for filters that are aligned with the Req State from NostrClient
- Avoid subsequent REQ updates before EOSE or CLOSE calls.
- Refactoring RelayClient listener to be not dependent of which module is active for a relay client.
- Refactors authenticators to do complete operation as a module
- Breaks down Relay Client modules (Auth, Reqs, Counts, Event submissions) in the Relay Pool class.
- Creates listeners just for special REQ situations
- Move statistics to outside the base relay class as a listener
- Move logs to outside the base relay class as a listener
- Better structures a Standalone Relay client
- More appropriately communicate errors to the listeners
- Remove relay state on listeners
2025-10-18 18:10:45 -04:00
Vitor Pamplona
e20afd39a3 Removing onClosing listener because it isn't being used. 2025-10-18 17:12:10 -04:00
Vitor Pamplona
4c633c7dec Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-10-18 08:44:21 -04:00
Vitor Pamplona
97fc10d6ea Increases local video disk cache to 1GB 2025-10-17 18:00:38 -04:00
Vitor Pamplona
b8d0f057c5 Adds a way to remove everyone but some keys from the list of EOSEs 2025-10-17 14:30:13 -04:00
Vitor Pamplona
990cca3d64 Moving the EmptyOTS resolver to Quartz for test cases 2025-10-17 14:29:46 -04:00
Vitor Pamplona
4a0c924d4f Merge pull request #1524 from davotoula/use-runTest-instead-of-runBlocking
change from runBlocking to runTest where appropriate
2025-10-15 18:57:42 -04:00
davotoula
5d6e7d1921 change from runBlocking to runTest where appropriate 2025-10-15 21:00:00 +02:00
Vitor Pamplona
563e985a2b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-10-14 16:13:16 -04:00
David Kaspar
50adf82d90 Merge pull request #1523 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-14 22:11:49 +02:00
Vitor Pamplona
7a05f527dd Moving Log.java to Log.kt 2025-10-14 15:18:45 -04:00
Vitor Pamplona
0668f2caa4 Updates Compose, Firebase, GMS, JNA, mock, okhttp, secp256, spotless, vico, zoomable, androidXCamera 2025-10-14 14:38:28 -04:00
Vitor Pamplona
4af576f5f4 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-10-14 14:15:38 -04:00
Crowdin Bot
fdbf261455 New Crowdin translations by GitHub Action 2025-10-14 17:43:52 +00:00
Vitor Pamplona
e422dcda50 Merge pull request #1519 from davotoula/1516-Private-Public-Bookmark-Removal-Issue
Private public bookmark removal issue
2025-10-14 13:42:42 -04:00
David Kaspar
a210bd1c8f Merge pull request #1522 from davotoula/update-translations
Updated cs, se, de, pt
2025-10-14 19:23:26 +02:00
davotoula
49f77af3e8 Updated cs, se, de, pt 2025-10-14 18:59:54 +02:00
David Kaspar
7d22ef0d12 Merge pull request #1521 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-14 17:58:02 +02:00
Crowdin Bot
deee1a1c85 New Crowdin translations by GitHub Action 2025-10-14 13:50:15 +00:00
Vitor Pamplona
7a1dcbc76e Merge pull request #1520 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-14 09:49:15 -04:00
Vitor Pamplona
68264f2c7f Merge pull request #1518 from davotoula/bugfix-dont-render-gallery-for-mixed-images-and-videos
Bugfix: dont render gallery for mixed images and videos
2025-10-14 09:49:06 -04:00
Vitor Pamplona
b1d3aa968d Merge branch 'main' into bugfix-dont-render-gallery-for-mixed-images-and-videos 2025-10-14 09:48:58 -04:00
Crowdin Bot
2ebdedafaa New Crowdin translations by GitHub Action 2025-10-14 13:47:57 +00:00
Vitor Pamplona
4ffc121003 Merge pull request #1517 from davotoula/fix-streamable-videos
Use latest LightCompressor-enhanced
2025-10-14 09:46:37 -04:00
Vitor Pamplona
a4f1e45aac Merge pull request #1507 from KotlinGeekDev/followset-improvements
More follow set improvements.
2025-10-14 09:46:07 -04:00
davotoula
3a3d1a6f25 bugfix: don't remove from public bookmarks when removing from private 2025-10-14 13:31:27 +02:00
davotoula
85bdf67ac3 added failing test: Public tags should be preserved when removing from private bookmarks 2025-10-14 13:21:46 +02:00
KotlinGeekDev
9e991f8a49 Fix user removal functions, by updating them to take into account backend modifications made earlier. 2025-10-13 21:31:22 +01:00
KotlinGeekDev
e6c841373b String resources. Remove unused string resource in other locale-specific files, to avoid much work. 2025-10-13 21:04:42 +01:00
KotlinGeekDev
ba756c9acd Take into account profile membership when removing profile. 2025-10-13 20:30:18 +01:00
davotoula
349156f380 fix: Only render as gallery if all segments are images 2025-10-13 19:32:25 +02:00
davotoula
133c897731 Added junit and mockK for androidTest UI dependencies
Added failing androidTest for mixed image/video parsing
2025-10-13 18:38:14 +02:00
davotoula
13402fa5d4 Use lightcompressor-enhanced 1.6.0 release 2025-10-13 09:25:11 +02:00
davotoula
2a3c31cc5b Use latest LightCompressor-enhanced:
Fix for Progressive download
Replaced obsolete MP4 parser libraries with native MediaMuxer/MediaCodec
2025-10-12 21:13:12 +02:00
Vitor Pamplona
5fcf0c5938 Using a week as the default cut off for notifications from random relays. 2025-10-10 12:54:58 -04:00
Vitor Pamplona
0fab3afea2 Moves static relay stats to a injectable object 2025-10-10 11:38:22 -04:00
Vitor Pamplona
0bc38c1fa4 Removes sub stats from the Subscription controller to move outside. 2025-10-10 11:34:25 -04:00
Vitor Pamplona
0c0410d1dd Adds an isValid function to all commands so we can check before sending 2025-10-10 11:33:18 -04:00
Vitor Pamplona
fa971b1822 Removes stats from Orchestrator to move out of the inner classes and be an option for app developers instead. 2025-10-10 11:31:32 -04:00
Vitor Pamplona
24f7d76dc4 Adds a Count reply message to relays 2025-10-10 11:17:43 -04:00
Vitor Pamplona
973cfd24fa Creates our own Coil Logger to minimize trash stacks in the logs 2025-10-10 10:56:25 -04:00
Vitor Pamplona
5c09cd0b55 Rejects additional urls with %20 and fixes Wss ones. 2025-10-10 10:55:46 -04:00
Vitor Pamplona
5975130824 Avoids logging at the BasicOkHttp level 2025-10-10 10:51:09 -04:00
Vitor Pamplona
0a23e23801 Moves quartz to Java 21 due to binary inconsistencies between the multiple builds of KMP (tests conflicting with main) 2025-10-10 10:49:35 -04:00
Vitor Pamplona
a0c1b01a4b Adds a build template option for NIP42 auths 2025-10-10 10:48:39 -04:00
Vitor Pamplona
0708936c23 Mocks the android log on test cases 2025-10-10 10:47:59 -04:00
KotlinGeekDev
ac60e3d221 String resources. 2025-10-09 20:13:00 +01:00
KotlinGeekDev
3c622f544c Make a method specially for cloning/copying. Refactor createListWithDescription(). 2025-10-09 19:55:40 +01:00
KotlinGeekDev
a8d01e3667 Put in the relevant code in PeopleListEvent. Use DescriptionTag instead of manual construction. 2025-10-09 17:02:42 +01:00
KotlinGeekDev
9aa5f1bd9f Add support for modifying follow set descriptions. 2025-10-09 16:56:40 +01:00
KotlinGeekDev
192a257780 Merge branch 'vitorpamplona:main' into followset-improvements 2025-10-08 01:53:14 +00:00
Vitor Pamplona
0045426840 Fixes a bug of not loading some notification message kinds 2025-10-06 19:15:48 -04:00
Vitor Pamplona
0ab7dd9797 Adds test cases for the new sub map filters. 2025-10-06 18:55:23 -04:00
Vitor Pamplona
a6e4a8d4fc Performance improvement for the address serializer. 2025-10-06 18:05:03 -04:00
Vitor Pamplona
5f59f1ab41 Adds submap queries to the addressable large cache 2025-10-06 18:04:37 -04:00
KotlinGeekDev
049430cd2e Merge branch 'vitorpamplona:main' into followset-improvements 2025-10-06 21:25:27 +00:00
Vitor Pamplona
b546294078 getOrCreateUser already checks for Hex64. No need to do it twice 2025-10-06 15:44:13 -04:00
Vitor Pamplona
456ef86c85 Speeding up OkHttp startup 2025-10-06 15:17:37 -04:00
Vitor Pamplona
b8ad2961b9 Speeds up loading of the Follow lists in start up by using the new isHex64 method 2025-10-06 15:04:12 -04:00
Vitor Pamplona
0348454e36 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-10-06 14:47:00 -04:00
Vitor Pamplona
b55cdda52a Makes the order of the Tor Relay settings match across data objects 2025-10-06 14:42:25 -04:00
Vitor Pamplona
8c93560058 Fixes trusted relay lists incorrectly only using DM relays 2025-10-06 14:41:56 -04:00
David Kaspar
72b800bfb4 Merge pull request #1514 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-06 19:19:40 +01:00
Crowdin Bot
4a6dbd4ba5 New Crowdin translations by GitHub Action 2025-10-06 18:18:10 +00:00
David Kaspar
52686c3488 Merge pull request #1513 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-06 19:16:56 +01:00
David Kaspar
d8613da3ad Merge branch 'main' into l10n_crowdin_translations 2025-10-06 19:16:34 +01:00
Crowdin Bot
bea393c4ef New Crowdin translations by GitHub Action 2025-10-06 17:34:07 +00:00
Vitor Pamplona
cf4104292c Merge pull request #1511 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-06 13:32:25 -04:00
Vitor Pamplona
deaaa30b92 Merge pull request #1512 from davotoula/fix-nwc-string-deleted-on-restart
BUGFIX: Fix nwc string deleted on restart
2025-10-06 13:32:13 -04:00
davotoula
1fdf869c05 create putOrRemove overload for Nip47URI? 2025-10-06 18:56:12 +02:00
davotoula
701acb004f remove unnecessary suspend 2025-10-06 18:52:14 +02:00
davotoula
341994e505 The putOrRemove(key: String, event: Any?) signature loses type information. When JsonMapper.toJson() tries to serialize, it sees Any? and can't find the serializer for Nip47URI, causing:
SerializationException: Serializer for class 'Any' is not found
2025-10-06 18:50:15 +02:00
KotlinGeekDev
c432a54561 Merge branch 'vitorpamplona:main' into followset-improvements 2025-10-06 10:40:57 +00:00
KotlinGeekDev
55198b1546 Move one last string to string resources. 2025-10-06 11:39:43 +01:00
Crowdin Bot
b9d369a88c New Crowdin translations by GitHub Action 2025-10-06 08:01:52 +00:00
David Kaspar
6c72c36b9a Merge pull request #1510 from davotoula/update-translations
Update local versions of CS, DE since crowdin only uses those?
2025-10-06 09:00:33 +01:00
David Kaspar
369aab2c0c Merge pull request #1509 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-06 09:00:14 +01:00
davotoula
e129796418 Update local versions of CS, DE since crowdin only uses those? 2025-10-06 09:54:40 +02:00
KotlinGeekDev
eb99febb2b Merge branch 'vitorpamplona:main' into followset-improvements 2025-10-04 06:48:59 +00:00
Crowdin Bot
da79d5b0ba New Crowdin translations by GitHub Action 2025-10-03 22:39:37 +00:00
Vitor Pamplona
0843d5db95 Separates EOSE for drafts alone. 2025-10-03 18:37:48 -04:00
Vitor Pamplona
df77bd6633 Only loads drafts if the account is writeable. 2025-10-03 18:18:16 -04:00
Vitor Pamplona
cf8c8e47a2 Creates an option to ignore reconnection relays that should be used when the network settings change. 2025-10-03 18:03:04 -04:00
Vitor Pamplona
26462a150e Makes sure the scope is cancelled when the NostrClient is done 2025-10-03 18:01:34 -04:00
Vitor Pamplona
66a5412961 Migrates fDroid flavor to the new version of the markdown 2025-10-03 17:08:16 -04:00
Vitor Pamplona
93f33cca70 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	gradle/libs.versions.toml
#	quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSendAndWaitExt.kt
2025-10-03 16:46:03 -04:00
Vitor Pamplona
4b959577f0 Corrects follow list logs for performance and uses the right scope 2025-10-03 16:37:20 -04:00
Vitor Pamplona
88747ad1b7 Improves log messages in the relay services 2025-10-03 16:35:10 -04:00
Vitor Pamplona
13d05dc599 Loads the main account in parallel faster before the screen needs it 2025-10-03 15:59:43 -04:00
Vitor Pamplona
b002f9f37e Uses the new isHex64 in the ContactList 2025-10-03 15:58:38 -04:00
Vitor Pamplona
7994945209 30% Faster isHex for strings with 64 bytes. 2025-10-03 15:58:01 -04:00
Vitor Pamplona
9085f4bbea Merge pull request #1508 from greenart7c3/main
Fix sendAndWaitForResponse never receiving a response
2025-10-03 13:03:10 -04:00
greenart7c3
beff7e60b7 Fix sendAndWaitForResponse never receiving a response 2025-10-03 13:30:45 -03:00
KotlinGeekDev
e418a26f41 Add support for copying/cloning follow sets in the UI. 2025-10-03 16:26:20 +01:00
KotlinGeekDev
2df575d4af Modify the models to support creating a new list with several profiles. Use IO dispatcher in FollowSetState. Introduce follow set copying/cloning, with custom names/descriptions for the clones. 2025-10-03 16:02:15 +01:00
KotlinGeekDev
88fbee84dc Merge branch 'vitorpamplona:main' into followset-improvements 2025-10-03 14:39:45 +00:00
Vitor Pamplona
21c1d705a1 Only downloads DMs if the account is writeable 2025-10-03 09:18:53 -04:00
Vitor Pamplona
97668513b5 Fixes passing onClosed messages forward from NostrClient 2025-10-03 09:11:28 -04:00
Vitor Pamplona
f6213c2d9a Slightly better user profile post filter 2025-10-03 08:25:19 -04:00
Vitor Pamplona
85b234d372 Don't show default relays in the relay screen as a user-chosen list 2025-10-03 08:10:13 -04:00
Vitor Pamplona
28df59c00d Expand the number of notes tracked by the EOSE event to 10000 2025-10-02 17:55:43 -04:00
Vitor Pamplona
bc8e18a01b Initial test cases for NostrClient and extensions 2025-10-02 17:54:56 -04:00
Vitor Pamplona
1b36d7e189 Exposes the sample rate on BaseEoseManager class 2025-10-02 17:50:33 -04:00
Vitor Pamplona
5f83379459 Offers an interface to BaseEoseManager objects 2025-10-02 17:50:10 -04:00
Vitor Pamplona
90b682a38f Avoid sending close to subs that are already closed. 2025-10-02 17:48:06 -04:00
Vitor Pamplona
c6107dd784 Starts NostrClient in active instead of waiting for a connect() call 2025-10-02 17:47:39 -04:00
Vitor Pamplona
fd6be80b7e Offers a way to destroy subscriptions 2025-10-02 17:46:54 -04:00
Vitor Pamplona
f901f8ab8f Must use unlimited channels to avoid blocking the OkHttp threads. 2025-10-02 17:46:35 -04:00
Vitor Pamplona
b83ea61522 Moves OKHttp relay implementations to use a Channel in order to guarantee incoming message order. 2025-10-02 17:45:58 -04:00
Vitor Pamplona
168caf0ec6 Fixes comment 2025-10-02 17:42:33 -04:00
Vitor Pamplona
04f5e8738a Forces an updateFilter at the start of new subs. 2025-10-02 16:54:34 -04:00
Vitor Pamplona
c8f51380aa Fixes error messages in BasicRelayClient 2025-10-02 16:41:47 -04:00
Vitor Pamplona
612dd175b3 Creates the relay on sending new requests instead of waiting for updatePool method. 2025-10-02 16:40:45 -04:00
Vitor Pamplona
a56d9631ed Solves the issue of not connecting in the first time reconnect is called 2025-10-02 16:40:16 -04:00
Vitor Pamplona
1fd5ba0451 Adds ease of use functions for the downloadFirst Event extension method 2025-10-02 16:39:47 -04:00
Vitor Pamplona
132c00d434 Fixes suspending on coroutineScope and never returning bug 2025-10-02 16:39:20 -04:00
Vitor Pamplona
47cc8fc4ae Improves log class for JVM 2025-10-02 16:38:34 -04:00
David Kaspar
e12c52ec2c Merge pull request #1506 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-02 21:16:19 +01:00
Crowdin Bot
34cd26b004 New Crowdin translations by GitHub Action 2025-10-02 19:54:33 +00:00
Vitor Pamplona
1c82417a0a Updating the documentation on the use of Quartz 2025-10-02 15:53:10 -04:00
Vitor Pamplona
c3b87e83cb Merge pull request #1505 from davotoula/update-translations
update translations
2025-10-02 13:39:02 -04:00
davotoula
e72a588dab update CS, DE, SE, PT 2025-10-02 19:11:53 +02:00
David Kaspar
3033a4cbc8 Merge pull request #1504 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-10-01 17:10:27 +01:00
Crowdin Bot
ec82f89225 New Crowdin translations by GitHub Action 2025-10-01 15:30:00 +00:00
Vitor Pamplona
d7cf8305d3 Merge pull request #1503 from davotoula/smarter-video-compression--h265
Smarter video compression: h265
2025-10-01 11:28:34 -04:00
davotoula
7c41da0c4c Update lightcompressor to release 1.5.0 2025-10-01 16:57:21 +02:00
davotoula
59be0986eb Fix bug: useH265 parameter wasn't passed to compressor 2025-10-01 15:44:52 +02:00
davotoula
4554813d72 Add codec based multiplier (h265 = 0.7x) 2025-10-01 15:44:52 +02:00
davotoula
7d69122409 remove unused imports 2025-09-30 22:10:24 +02:00
davotoula
a6e306f3de Update to use lightcompressor library version that supports h265
Add h265 toggle to NewMediaView.kt and ImageVideoDescription.kt
Touch many files to add optional h265 boolean (default is false)
2025-09-30 22:02:18 +02:00
KotlinGeekDev
f6b3fdcab4 Refactor UI to adapt to correct way of managing follow sets. 2025-09-30 15:38:16 +01:00
KotlinGeekDev
dfb683e00d Continue refactoring of models to make things correct. 2025-09-30 15:37:02 +01:00
Vitor Pamplona
bb1d805080 Removes the need to use kotlinx.datetime on quartz 2025-09-29 18:06:58 -04:00
Vitor Pamplona
c936e91ec8 Merge pull request #1499 from KotlinGeekDev/follows-and-followsets-unified
Unification of follows and follow sets.
2025-09-29 13:47:39 -04:00
Vitor Pamplona
5d2e718b93 Merge pull request #1502 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-29 13:47:31 -04:00
Crowdin Bot
4cffd6d868 New Crowdin translations by GitHub Action 2025-09-29 17:25:18 +00:00
Vitor Pamplona
26005a062d Updates Quartz instructions to Maven 2025-09-29 13:23:57 -04:00
KotlinGeekDev
80906f2c5a Refactor models to take into account mixed lists, due to misunderstanding on my part. 2025-09-29 11:17:12 +01:00
Vitor Pamplona
631f1d9cfc Makes the relayDB not private in HintIndexer 2025-09-26 16:10:30 -04:00
Vitor Pamplona
a8a6a10d6e With filter serialization, prints kinds before anything else to help debug 2025-09-26 16:10:04 -04:00
Vitor Pamplona
401a3c47d9 Removes unnecessary log of object 2025-09-26 16:09:33 -04:00
Vitor Pamplona
ff0a4eb88f Fixes userList of the FollowList state 2025-09-26 16:09:07 -04:00
Vitor Pamplona
1384a68ee3 Dumps the number of relay hints urls in the DB 2025-09-26 16:08:37 -04:00
Vitor Pamplona
86e50ac984 Logs the early start of the Application for benchmarking purposes 2025-09-26 16:08:22 -04:00
KotlinGeekDev
d2d811f670 Just use remember{} instead of adding derivedStateOf{} to avoid weird state issues. 2025-09-26 16:21:13 +01:00
KotlinGeekDev
61459187d7 Fix duplicate copyright and use derivedStateOf for followSet presence checking variable. 2025-09-26 15:46:29 +01:00
KotlinGeekDev
509d703226 Merge branch 'vitorpamplona:main' into follows-and-followsets-unified 2025-09-26 12:20:47 +00:00
Vitor Pamplona
194ba08df0 Merge pull request #1498 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-26 08:20:19 -04:00
KotlinGeekDev
bf1188addf Merge branch 'vitorpamplona:main' into follows-and-followsets-unified 2025-09-26 12:12:28 +00:00
Crowdin Bot
7ddce17bdd New Crowdin translations by GitHub Action 2025-09-26 12:07:45 +00:00
Vitor Pamplona
ee8ac08845 Merge pull request #1496 from davotoula/smarter-video-compression--sub-1mbps
Smarter video compression - switch to LightCompressor-enhanced
2025-09-26 08:06:19 -04:00
KotlinGeekDev
45192d1cf7 Remove statements to avoid polluting the logs. Modify the timing on the flow producer. 2025-09-26 12:32:44 +01:00
KotlinGeekDev
9c024e334b Replace first hack with a better solution for determining a follow.
Add FollowSetState to MergedFollowListsState and modify it to take into account users from follow sets when displaying a user's follows feed.
2025-09-26 12:24:42 +01:00
KotlinGeekDev
058278956b Merge remote-tracking branch 'origin/follows-and-followsets-unified' into follows-and-followsets-unified 2025-09-26 10:55:48 +01:00
KotlinGeekDev
564cebea9d Merge branch 'vitorpamplona:main' into follows-and-followsets-unified 2025-09-26 09:55:31 +00:00
davotoula
4fc9386393 Combine calculation of videoBitrateInBps, resizer to use compressionRules only once 2025-09-25 23:06:16 +02:00
KotlinGeekDev
6368d17d09 Refactor: Create FollowSetState and move functionality from other places to it. Modify usages accordingly. 2025-09-25 21:39:02 +01:00
KotlinGeekDev
b694e143d0 Finish renaming refactor. 2025-09-25 21:37:33 +01:00
KotlinGeekDev
4b60c3e20b Refactor: From List -> Set, and Viewmodel rename as well. Move FollowSet and related classes to model package. 2025-09-25 21:36:34 +01:00
davotoula
6e4e432adf Use new bps api of lightcompressorlibrary fork 2025-09-25 22:01:25 +02:00
davotoula
4c3eaf972c move to LightCompressor-enhanced fork
corrected image vs video compressor library names
2025-09-25 22:01:25 +02:00
KotlinGeekDev
ba0fc67a94 First hack for bolting follow sets onto follows, for determining if a user follows another. 2025-09-25 18:24:31 +01:00
Vitor Pamplona
f31292a201 Updates compose, navigation, windowCore and tests 2025-09-25 12:59:24 -04:00
Vitor Pamplona
1f6135d45d Updates markdown parser to the latest 2025-09-25 12:53:45 -04:00
Vitor Pamplona
d04cf97418 Better logs on BasicRelayClient 2025-09-24 19:24:27 -04:00
Vitor Pamplona
8466020137 Increase the delay to update filters 2025-09-24 14:34:35 -04:00
Vitor Pamplona
54afc2c4dc Remove logs 2025-09-24 14:34:24 -04:00
Vitor Pamplona
ed7fa2a80f removes log 2025-09-24 12:43:15 -04:00
Vitor Pamplona
06f94150f9 Okhttp log helper class. 2025-09-24 12:43:04 -04:00
Vitor Pamplona
361e46c99f Limits the number of posts coming from big relays with too many authors to 500 max 2025-09-24 12:38:48 -04:00
Vitor Pamplona
6ba60dce3f Throws an error if the RelayStats cannot be created. 2025-09-24 12:38:01 -04:00
Vitor Pamplona
f9e0c82fc8 No need to import 500 communities for each relay it connects to 2025-09-24 12:37:35 -04:00
Vitor Pamplona
45b7b5601b Ups the max requests thread allowed to support people with over 1000 follows 2025-09-24 12:37:04 -04:00
Vitor Pamplona
ccf64687a8 Separates index relays from the default and put it into the hint-based queries for unkown users. 2025-09-24 12:36:39 -04:00
Vitor Pamplona
0ba3385771 Merge pull request #1494 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-24 08:41:08 -04:00
Vitor Pamplona
381692a0a2 Merge pull request #1491 from KotlinGeekDev/set-management-fixes
Set management UI fixes
2025-09-24 08:40:58 -04:00
Crowdin Bot
206f8847fc New Crowdin translations by GitHub Action 2025-09-24 12:39:55 +00:00
Vitor Pamplona
5b969d92f3 Merge pull request #1492 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-24 08:38:36 -04:00
Vitor Pamplona
621290ec33 Merge pull request #1493 from greenart7c3/main
Show a dialog to select a signer when using multiple signers
2025-09-24 08:38:20 -04:00
Crowdin Bot
94a2d9e07d New Crowdin translations by GitHub Action 2025-09-24 12:19:05 +00:00
Vitor Pamplona
8b60aeaa94 Merge pull request #1489 from davotoula/smarter-video-compression
Smarter video compression
2025-09-24 08:17:33 -04:00
greenart7c3
6391ddbbaa Show a dialog to select a signer when using multiple signers 2025-09-24 09:09:51 -03:00
KotlinGeekDev
6ac2a7150f Change appearance of CustomSetItem. Use horizontal dividers instead of rounded borders. Adjust padding. 2025-09-24 11:50:15 +01:00
KotlinGeekDev
ab6d0961e8 Even more size and color adjustments. 2025-09-24 11:37:25 +01:00
KotlinGeekDev
3c87925b38 Make border and padding adjustments to some components. 2025-09-24 10:57:44 +01:00
KotlinGeekDev
54963067aa Adjust colors for some components. 2025-09-24 09:55:53 +01:00
davotoula
ec9d229392 Merge branch 'main' into smarter-video-compression 2025-09-23 23:25:23 +02:00
Vitor Pamplona
ce81157dad Adds a maven publishing to ship quartz 2025-09-23 15:16:26 -04:00
Vitor Pamplona
0499dcc647 Refining the test case to avoid json comparisons 2025-09-23 13:57:34 -04:00
Vitor Pamplona
748085078d Fixes imports for Tlv tests 2025-09-23 13:46:04 -04:00
Vitor Pamplona
fadd9c1940 Creates a shared JVM-test module and moves the jackson inlining tests there 2025-09-23 13:45:48 -04:00
Vitor Pamplona
313e2519b2 Fixes the fdroid version of the new translation component api 2025-09-23 13:33:55 -04:00
Vitor Pamplona
d8c403d3d5 Removes the list view model from the LoginPage 2025-09-23 13:26:04 -04:00
Vitor Pamplona
b30d87266d Improves borders for zap messages in notifications 2025-09-23 13:11:15 -04:00
Vitor Pamplona
f9204d37f2 Updates vico charts 2025-09-23 12:36:12 -04:00
Vitor Pamplona
d3f1db4367 Updates std lib 2025-09-23 12:34:02 -04:00
Vitor Pamplona
9851c4b6be Updates firebase and lifecycleRuntime 2025-09-23 11:32:48 -04:00
Vitor Pamplona
811ad5e00a Merge pull request #1169 from KotlinGeekDev/profiles-list-management
List/Set management: Part 1 - Profiles.
2025-09-23 11:20:21 -04:00
KotlinGeekDev
f2ca1d78da Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-23 14:00:49 +00:00
KotlinGeekDev
00d5e2fed3 Move strings to stringResources. Refactor names for consistency. Some cleanup. 2025-09-23 14:51:05 +01:00
David Kaspar
807eda8401 Merge pull request #1490 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-23 13:21:59 +01:00
Crowdin Bot
6cffda09bb New Crowdin translations by GitHub Action 2025-09-23 11:59:59 +00:00
Vitor Pamplona
1c0766cc76 Merge pull request #1480 from davotoula/add-feed-indicator-to-live-bubbles
Add feed indicator to live bubbles
2025-09-23 07:58:26 -04:00
KotlinGeekDev
5175c61171 Merge branch 'upstream-main' into profiles-list-management 2025-09-23 12:24:28 +01:00
KotlinGeekDev
3f8abd74ba Merge branch 'main' of https://github.com/vitorpamplona/amethyst into upstream-main 2025-09-23 12:22:45 +01:00
KotlinGeekDev
04b8f6cca2 Move Fab back action button to separate component. Adjust colors for the fabs in CustomListsScreen. 2025-09-23 12:19:04 +01:00
KotlinGeekDev
ec10d53195 Add menu option for adding an author to a follow set from a note. 2025-09-23 12:16:58 +01:00
davotoula
ccc5d03d84 converted VideoCompressionHelper to singleton 2025-09-23 11:28:09 +02:00
davotoula
e5df870ace move framerate adjustment into getBitrateMbpsInt to prevent ,precision errors
Clean up logging
2025-09-23 10:24:48 +02:00
davotoula
b030486e7e fixes after rebase 2025-09-23 09:55:03 +02:00
davotoula
544d6d3933 remove plan 2025-09-23 09:25:20 +02:00
davotoula
db9160a6c2 finally block ensures release() is called even if exceptions occur 2025-09-23 09:25:20 +02:00
davotoula
41a233d175 fix for library bug. Temp file name with "_temp" is returned instead of final file name 2025-09-23 09:25:19 +02:00
davotoula
b6dd6a988b change to enum for video resolutions 2025-09-23 09:25:19 +02:00
davotoula
9af1e10ec2 refactor:
Safe file size lookup using OpenableColumns.SIZE
if (continuation.isActive) before resuming.
Better logging levels (Log.e for errors, Log.w for warnings).
Configurable compression timeout
2025-09-23 09:25:19 +02:00
davotoula
ed758ef13e remove unused code 2025-09-23 09:25:18 +02:00
davotoula
947f59fa6f add bitrate multiplier for 60fps content
fix toast when new file larger than old
2025-09-23 09:25:18 +02:00
davotoula
524ead2eac move video compression into helper 2025-09-23 09:25:16 +02:00
davotoula
49dc63c876 Sanity check: if compressed file is larger than original, return original 2025-09-23 09:24:25 +02:00
davotoula
8588c9a6fc update plan 2025-09-23 09:24:25 +02:00
davotoula
f805b70ec0 added log statements and a toast 2025-09-23 09:24:22 +02:00
davotoula
f5202dd8a9 added log statements 2025-09-23 09:22:13 +02:00
davotoula
97d6c791c0 determine resizer and bitrate from input source combined with our compression rules
use 1 as lowest BitrateMbps
2025-09-23 09:22:09 +02:00
davotoula
87f224b6b8 get video resolution, framerate and rotation 2025-09-23 09:21:32 +02:00
davotoula
931c6681c6 added compression rules 2025-09-23 09:21:32 +02:00
davotoula
96b8c77bcd there is a plan 2025-09-23 09:21:32 +02:00
David Kaspar
366534d1d5 Merge pull request #1488 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-23 08:11:41 +01:00
Crowdin Bot
054cfa0de9 New Crowdin translations by GitHub Action 2025-09-23 00:07:59 +00:00
Vitor Pamplona
d93a8d94d2 Reduces number of Notifications from random relays. 2025-09-22 20:05:07 -04:00
Vitor Pamplona
0026668aca Merging with upstream 2025-09-22 19:49:26 -04:00
Vitor Pamplona
7a280a3cde - Migrates Quartz from Android to CommonMain
- Fully converts OpenTimestamp Java codebase to Kotlin, migrating the sync and async HTTP call interfaces to OkHttp and coroutines
- Redesigns parsing of relay commands, messages and filters for performance in Jackson.
- Starts the use of KotlinX Serialization when speed is not a requirement
- Migrates all Jackson field annotations to Kotlin Serialization
- Migrates Regex use in Quarts to Kotlin's Regex class
- Migrates Base64 from Android to Kotlin
- Migrates UUID from Android/Java to Kotlin
- Migrates LRUCache usage from Android/Java to Kotlin collections
- Migrates all String to bytearray conversions to Kotlin methods
- Migrates all System.arraycopy calls to kotlin native ones.
- Separates parsing code from the data classes Companion objects
- Exposes Rfc3986 normalizations to each platform.
- Exposes URI parsing classes to each platform.
- Exposes URL Encoders to each platform.
- Exposes BigDecimal to each platform.
- Exposes the Url Detector to each platform.
- Exposes MacInstances to each platform
- Exposes Diggest instances to each platform.
- Exposes a BitSet to each platform.
- Exposes GZip to each platform.
- Exposes Secp256k1 to each platform.
- Exposes SecureRandom to each platform.
- Exposes Time in seconds to each platform.
- Exposes the LargeCache to each platform.
- Exposes AES CBC and AES GCM encryption/decryption to each platform
- Migrate test assertions to Kotlin Tests
- Exposes Address class to each platform because of the Parceleable
- Creates our own ByteArrayOutputStream.
- Removes Lock features inside the Bloomfilters because we don't need data consistency/
- Migrates UserMetadata parser from Jackson to Kotlin serialization
- Removes the need for Static methods in each tag.
- Adds an event template serializer
- Adds KotlinX Datetime to migrate some of the date-based logs
- Adds support for LibSodium in the JVM platform
- Creates a shared test build for iOS targets
- Fixes several usages of Reflection when serializing classes
- Fixes a bug on loading RelayDB for the HintBloom filter test
- Increases the Bloom filter space to better use hints in the app.
- Removes support for iOS in x86
- Creates a Jackson mapper just for NIP-55, which stays in the Android build only.
- Keeps the event store in the android build as well.
- Removes @Syncronized tags in favor of Mutexes.
- Improved sendAndWaitForResponse NostrClient method to properly account for returns from each relay.
- Removes the need for GlobalScope and async calls in the downloadFirstEvent method.
- Restructures the parser and serialization of the relay messages and commands for performance with Jackson
- Removes the dependency on Jackson's error classes across the codebase.
- Moves the hint to quote tag extension methods to their own packages.
- Speeds up the generation of Bech32 addresses
- Migrates NIP-6 and Blossom uploads to use Kotlin Serialization
2025-09-22 19:44:51 -04:00
KotlinGeekDev
3eb02cbbd8 implement a fix for when a follow set becomes empty(or is empty), which is probably a Nostr limitation. 2025-09-22 15:44:11 +01:00
KotlinGeekDev
1e18edfc02 Refactor FollowSetsManagementDialog UI to correctly adapt to follow set state. Small adjustment in the NostrUserListFeedViewModel. 2025-09-22 15:41:17 +01:00
KotlinGeekDev
9f5301d30a Make a different menu style for new list creation in FollowSetsManagementDialog. Use toBestDisplayName() instead of manual method(which was the same anyways). 2025-09-22 14:24:15 +01:00
KotlinGeekDev
a07ebc6e12 Update addFollowSet() method to optionally create a list with first member. 2025-09-22 14:20:59 +01:00
KotlinGeekDev
3301371d82 Fix color of Fabs in CustomListsScreen. 2025-09-22 14:19:10 +01:00
Vitor Pamplona
7949fc66b2 Fixes test case cast 2025-09-22 09:17:54 -04:00
davotoula
1d0f584c3e use produceState instead of LaunchedEffect 2025-09-22 13:58:48 +02:00
KotlinGeekDev
6a3ebc1d95 Move FollowSetsManagementDialog to correct package. Refactor accordingly. 2025-09-19 18:22:41 +01:00
KotlinGeekDev
e10c429de0 Completely change the UI for list management when adding/removing a profile from lists. 2025-09-19 18:15:18 +01:00
KotlinGeekDev
e4a8a512d2 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-19 15:23:13 +00:00
David Kaspar
8373f6ba19 Merge branch 'vitorpamplona:main' into add-feed-indicator-to-live-bubbles 2025-09-18 21:12:14 +01:00
Vitor Pamplona
d2d5ade84e Merge pull request #1485 from davotoula/fix-for-orientation-issue-following-removing-windowSizeClass-from-global-settings
Fix for properly switch between single and two-pane layouts on rotation
2025-09-18 14:45:23 -04:00
Vitor Pamplona
3acdde7c53 Merge pull request #1483 from davotoula/reduce-sonar-code-quality-warnings
Reduce sonar code quality warnings
2025-09-18 14:45:02 -04:00
Vitor Pamplona
897396ff24 Merge pull request #1484 from davotoula/remove-void-cat-from-default-servers
remove void.cat from default servers
2025-09-18 14:44:00 -04:00
davotoula
ee39b3ae18 Fix for properly switch between single and two-pane layouts on rotation 2025-09-18 20:37:56 +02:00
David Kaspar
6c892ba733 Merge branch 'vitorpamplona:main' into add-feed-indicator-to-live-bubbles 2025-09-18 15:55:20 +01:00
davotoula
64cf81b523 remove void.cat from default servers 2025-09-18 16:33:32 +02:00
KotlinGeekDev
acade3c762 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-18 13:32:24 +00:00
davotoula
4cd86c8b1c return the boolean results from file(npub).delete() 2025-09-18 15:16:40 +02:00
davotoula
fdf372719c Replace with interface delegation using "by" in the class header
Replace abstract class with an interface
2025-09-18 15:16:40 +02:00
davotoula
50f173e406 replace repeated text string with top-level constant 2025-09-18 15:16:40 +02:00
Vitor Pamplona
edc7f49ce2 Merge pull request #1482 from davotoula/investigate-disappearing-ping-in-ms-on-relays-screen
Fix disappearing stats on relay screen
2025-09-18 08:12:06 -04:00
davotoula
b20eb860ff Increase LruCache max size from 100 to 1,000 since we have so many more relays active with outbox 2025-09-17 20:38:40 +02:00
Vitor Pamplona
aba60a87d6 Merge pull request #1481 from davotoula/remove-unused-imports
Remove unused imports and fix a test
2025-09-17 12:16:04 -04:00
davotoula
4f3b7b24f2 remove unused imports 2025-09-17 16:19:40 +02:00
davotoula
6f44d7c28a remove unused imports amethyst
Fix ThreadDualAxisChartAssemblerTest (not sure how it was not breaking the build)
2025-09-17 16:10:23 +02:00
Vitor Pamplona
a96ab764b3 Updates all dependencies 2025-09-17 09:08:49 -04:00
Vitor Pamplona
1be46b7167 Merge pull request #1478 from davotoula/reduce-max-requets-when-in-emulator
Reduce max requests when in emulator
2025-09-17 09:06:14 -04:00
Vitor Pamplona
5c601d74b9 Merge branch 'main' into reduce-max-requets-when-in-emulator 2025-09-17 09:06:00 -04:00
davotoula
2027c7d77d use default settings (64) when in emulator 2025-09-17 15:01:46 +02:00
Vitor Pamplona
faffe2a936 About 30-40% event hashing performance boost 2025-09-17 08:40:06 -04:00
Vitor Pamplona
c375fa0ab8 Adds another catch to get object errors when parsing NIP-28 metadata 2025-09-17 08:40:06 -04:00
Vitor Pamplona
123eca1f90 Fixes NPE on the invoice parser 2025-09-17 08:40:06 -04:00
Vitor Pamplona
83e87c2cf4 Adds an event id hash check that doesn't create a separate bytearray to compare between the incoming hex from the relay and the recalculated byte array from SHA256. 2025-09-17 08:40:06 -04:00
davotoula
f4501d799d Added withContext(Dispatchers.IO) for network call 2025-09-17 14:29:42 +02:00
davotoula
5796052f36 added live/offline status indicator for the DisplayLiveBubbles method
Checks streaming URLs for LiveActivitiesChannel and relay URLs for both channel types
2025-09-17 14:29:42 +02:00
davotoula
c1012bd859 let's remove the maxRequestsPerHost increase, can be done in a separate PR 2025-09-17 11:22:28 +02:00
davotoula
6440b86a5e remove commented out code 2025-09-17 11:10:40 +02:00
davotoula
dddf65e60f Added test for emulator to set maxRequests to 32 to prevent crashing emulator
Increased maxRequestsPerHost in release version to 20 from default 5 to allow for more concurrent connections to a relay (subscribing to timeline + mentions + DMs + profile updates + notifications, etc..)
2025-09-17 11:10:10 +02:00
David Kaspar
80c884a4d1 Merge pull request #1477 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-17 06:10:11 +01:00
Crowdin Bot
497b019093 New Crowdin translations by GitHub Action 2025-09-16 20:57:54 +00:00
Vitor Pamplona
03b320e5b7 Merge pull request #1476 from davotoula/fix-benchmark-apk-location
resolve intermittent CI build failures - fix benchmark apk location
2025-09-16 16:56:43 -04:00
davotoula
00c0472155 remove second identical(?) test run 2025-09-16 18:38:46 +02:00
davotoula
8a66276755 fix benchmark apk location 2025-09-16 18:06:14 +02:00
KotlinGeekDev
6eab098f2e Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-16 13:02:13 +00:00
Vitor Pamplona
eaf8cb3384 Don't check for the max size in string anymore 2025-09-15 17:25:47 -04:00
Vitor Pamplona
cd0b1b65fb Merge expanding and checking Hmac functions to avoid re-creating the Mac instance. 2025-09-15 17:25:36 -04:00
Vitor Pamplona
e329b096ba Moves the message key to the HKDF function 2025-09-15 16:21:11 -04:00
Vitor Pamplona
239cac430e Adds a test for native vs libsodium chacha20 2025-09-15 16:15:07 -04:00
Vitor Pamplona
24a5750155 Generalizes fixed key setup 2025-09-15 16:14:43 -04:00
Vitor Pamplona
cb5299e265 Simple refactoring 2025-09-15 16:14:09 -04:00
Vitor Pamplona
c3c8b87b91 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-15 15:15:56 -04:00
Vitor Pamplona
98aaef61c7 Faster Hkdf functions with less array copying and allocations (which can be impactful if the ciphertext is large) 2025-09-15 15:15:44 -04:00
KotlinGeekDev
1d3dadace2 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-15 18:39:36 +00:00
Vitor Pamplona
bd70058f78 Avoids creating a byte array and recreating it again inside of the Mac instance. 2025-09-15 12:30:56 -04:00
Vitor Pamplona
67f9557081 Reduces the need to keep the memory for checking the Mac and decrypting at the same time, allowing the GC to run after the check was correct. 2025-09-15 12:30:26 -04:00
Vitor Pamplona
689066fd44 Benchmark for NIP-44 operations 2025-09-15 12:29:26 -04:00
David Kaspar
cdd5995513 Merge pull request #1474 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-15 17:11:16 +01:00
Crowdin Bot
0c4cd4f940 New Crowdin translations by GitHub Action 2025-09-15 16:09:54 +00:00
Vitor Pamplona
c84554576c Fixes flow.value warnings. 2025-09-15 12:08:04 -04:00
Vitor Pamplona
7616d01166 Implements NIP-44 extension for bigger payloads https://github.com/nostr-protocol/nips/pull/1907 2025-09-15 11:58:43 -04:00
Vitor Pamplona
60b71a5561 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-15 08:51:41 -04:00
Vitor Pamplona
a7b89cb778 Removing unused imports 2025-09-15 08:50:16 -04:00
David Kaspar
6b18711fe6 Merge pull request #1473 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-15 13:49:59 +01:00
Crowdin Bot
f0b95a3593 New Crowdin translations by GitHub Action 2025-09-15 12:42:18 +00:00
Vitor Pamplona
52cf9e1576 Merge pull request #1468 from davotoula/gallery-view-for-multiple-media
Gallery view for multiple images
2025-09-15 08:40:48 -04:00
davotoula
43f8053b30 Extracted Parsing logic into helper class 2025-09-14 13:19:12 +02:00
davotoula
74cc9535ac Added a data structure for ParagraphImageAnalysis
Single pass analysis
All image-related decisions are made based on the single analysis result
2025-09-14 12:12:44 +02:00
davotoula
7e80ed2af9 Fix for images are mixed with text in a single paragraph
nevent1qqs04pmdf8guxpyhjuvpeg72ccwuzje7vhlm09szxl2d8x28cse5fzgpz9mhxue69uhkummnw3ezuamfdejj7q3qwl89d7yazg500lehg08p45dj2jzhhyqg2erj067458e3wd30djnsxpqqqqqqzmdjgxd
2025-09-14 11:40:20 +02:00
davotoula
1788dfc0b9 Merge branch 'refs/heads/main' into gallery-view-for-multiple-media 2025-09-13 23:32:49 +02:00
davotoula
8f0a829a4d Optimised collectConsecutiveImageParagraphs: continue statements for early loop continuation 2025-09-13 23:32:17 +02:00
davotoula
bfc920ca04 Added logging of image gallery computation time 2025-09-13 21:09:27 +02:00
davotoula
a21b6115c7 Single pass to avoid multiple intermediate lists
O(n) traversal (no drop/takeWhile/filter allocations)
Mutable buffer for collecting images
2025-09-13 20:44:00 +02:00
davotoula
8e39da347a don't use image gallery for secret message 2025-09-13 18:16:43 +02:00
Vitor Pamplona
58277cf717 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-13 10:51:28 -04:00
Vitor Pamplona
7ef6da1a17 Merge pull request #1470 from davotoula/fix-for-trying-to-share-when-image-not-loaded
Fix for crash when attempting to share an image that is still loading
2025-09-13 10:51:11 -04:00
Vitor Pamplona
ec48977a6f Merge pull request #1471 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-13 10:50:29 -04:00
Vitor Pamplona
c5b60c3bd8 Refactor the UISharedPreferences into two files 2025-09-13 10:50:19 -04:00
Crowdin Bot
613978d7ba New Crowdin translations by GitHub Action 2025-09-13 14:48:30 +00:00
Vitor Pamplona
afd20d3f8f Avoids using JSON parsers with DataStore to speed up loading time (loading the parser itself takes ~300ms) 2025-09-13 10:46:27 -04:00
KotlinGeekDev
ea07e52e63 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-12 22:52:03 +00:00
Vitor Pamplona
da469754c4 Adds a All User Follows to fix: https://github.com/vitorpamplona/amethyst/issues/1431 2025-09-12 16:18:12 -04:00
davotoula
6dd2f47fc9 catch exception when sharing image
Show toast
2025-09-12 21:39:57 +02:00
Vitor Pamplona
47d201f936 Fixes unnecessary typecasting from long to int in created ats 2025-09-12 15:08:10 -04:00
Vitor Pamplona
46fec1d480 Adds a payment failed enum 2025-09-12 14:24:11 -04:00
Vitor Pamplona
557ba959e2 Fixes crash on dont translate from 2025-09-12 14:17:41 -04:00
Vitor Pamplona
e121b2849c Fixes subject calculations on rendering 2025-09-12 13:58:37 -04:00
Vitor Pamplona
2e16a1dca6 Fixes a bug when comparing int and long in the comparator 2025-09-12 13:36:25 -04:00
Vitor Pamplona
33155efa99 Bugfix in loading communities from Global 2025-09-12 12:52:36 -04:00
Vitor Pamplona
bd3eed4a29 No need to duplicate these relays anymore 2025-09-12 12:52:13 -04:00
Vitor Pamplona
ac48705980 Fixes showing blog posts in the future 2025-09-12 12:39:12 -04:00
Vitor Pamplona
bc10d7184f Fixes mark as read when drafts are the latest message in the chat 2025-09-12 11:37:58 -04:00
Vitor Pamplona
0886af4386 Migrates memory counters from Long to Int 2025-09-12 11:11:16 -04:00
Vitor Pamplona
3628091511 Applying Memory trimming service to all logged in accounts. 2025-09-12 10:09:25 -04:00
KotlinGeekDev
f5061e470a Merge upstream-adjusted changes. 2025-09-12 14:20:36 +01:00
KotlinGeekDev
a0ba3802d9 Merge upstream changes and adjust. 2025-09-12 14:14:52 +01:00
KotlinGeekDev
4118e3d45d Initialize NostrUserListVM at LoggedInPage level, to reuse it at different places(in prep for other UI changes). 2025-09-12 13:48:59 +01:00
KotlinGeekDev
f850d5291e Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2025-09-12 13:37:53 +01:00
David Kaspar
cb3bc605bc Merge pull request #1469 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-12 11:49:40 +01:00
davotoula
16e3cba651 found a place where image gallery was used for single images 2025-09-12 12:14:05 +02:00
davotoula
ee9e102eb2 removed lazy component from 20+ images case to avoid nesting lazy components 2025-09-12 10:54:17 +02:00
Crowdin Bot
f01895b3e9 New Crowdin translations by GitHub Action 2025-09-11 23:05:11 +00:00
Vitor Pamplona
00a3918d20 Fixes F-droid build 2025-09-11 19:02:58 -04:00
Vitor Pamplona
35a123a185 No need for this log 2025-09-11 18:53:03 -04:00
Vitor Pamplona
c9dc30d976 - Only shows live stream bubbles that are not 24/7
- Only shows user pictures of the active users in the past 15 minutes.
2025-09-11 18:52:14 -04:00
Vitor Pamplona
c06c03928e Fixes the use of index relays and search relays to load users and events as well as become the default for global feeds. 2025-09-11 18:36:42 -04:00
Vitor Pamplona
1f8d0295d5 - Separates Application dependencies into an AppModules class to create only after the OnCreate event.
- Switches TorSettings to be per Application and not per Account anymore
- Since TorSettings is now global, moves the okHttpClient determinations out of the Account-based classes into the Application.
- Since TorSettings is now global, set's up Coil's image loader only once when creating the Application
- Moves UISettings state to App Modules instead of viewmodel
- Migrates TorSettings and UISettings to DataStore
- Accounts now have their own coroutine scopes to allow cancellation when they are unloaded/logged off
- New tor evaluator service for relay connections now uses all account's trusted relays and dm relays at the same time.
- Migrates composable-state-based UISettings to Flow-based UI settings, while observing connectivity status
- Removes the displayFeatures and windowSizeClass from the shared model
- Fixes not requesting Notification Permissions for APIs older than Tiramisu in the FDroid flavor
- Moves the NIP-11 document cache from singleton to the App Modules
- Avoids using AccountViewModel to check NIP-11 Relay documents
- Moves the UI Settings usage in composables to functions that do not observe the state since they don't need to refresh the screen when changed.
- Refactors UI Settings screen to separate components and remove the sharedViewModel
- Only starts Internal Tor if that option is selected in the TorSettings.
- Turn TorSettings into a data class to observe changes to it
- Drops the SharedPreferences ViewModel to use UISettingsFlow directly from App Modules
- Reorganizes OTS Events after simplification of the OkHttp based on TorSettings.
2025-09-11 18:04:10 -04:00
davotoula
8f1027b55d refactor
Removed duplicate GalleryImage boilerplate.
Use .chunked()
Pull out constants (ASPECT_RATIO, IMAGE_SPACING).
2025-09-11 21:52:36 +02:00
davotoula
4dace5aaf8 refactor using takeWhile
Fix edge case with white space before images
2025-09-11 21:03:06 +02:00
davotoula
8cb9d13567 Use constrained LazyVerticalGrid with calculated height for more than 20 images for performance reasons
added vertical spacing for some padding
Fixing scrollable gallery issue
2025-09-11 21:01:12 +02:00
davotoula
f092326dcd Fixing scrollable gallery issue 2025-09-11 20:58:39 +02:00
davotoula
1f6d7d3fd2 Simplify RenderRegularWithGallery
Simplify collectConsecutiveImageParagraphs: just return the end index
Extracting common signature parameters into a RenderContext data class
Extracting GalleryImage helper function
2025-09-11 20:58:15 +02:00
davotoula
062182a7ec fix for when images are separated by empty lines
refactor to reduce duplication:
Extract RenderParagraphWithFlowRow
Extract collectConsecutiveImageParagraphs
2025-09-11 20:50:21 +02:00
davotoula
9534582141 Collect batches of images
Send them to new image gallery
2025-09-11 20:25:05 +02:00
Vitor Pamplona
a078df6159 Fixes parameters on the findEarliestOtsForNote 2025-09-11 11:28:46 -04:00
Vitor Pamplona
90a5c15ee9 Moves the OTS processor from Account's decrypt process to Application since it doesn't need the account information. 2025-09-11 11:27:29 -04:00
Vitor Pamplona
bb9a03ec5b Fixes invalid hexes on preview 2025-09-11 11:23:45 -04:00
Vitor Pamplona
825d890921 Removes the otsFinding method from accountViewModel, since it is not account dependent. 2025-09-11 11:19:27 -04:00
Vitor Pamplona
05c364eff4 removes unnecessary import 2025-09-11 11:17:08 -04:00
Vitor Pamplona
404d0ab40e Removes the dependency on windowSizeClass and displayFeatures to be passed in the global settings of the app (which is activity independenty) 2025-09-11 11:16:26 -04:00
Vitor Pamplona
bf88e376de Generalizes the dependencies from Application to Context 2025-09-11 11:12:20 -04:00
Vitor Pamplona
1e74a5769d Passes appcontext down on MediaSession Pool to avoid the direct dependency on Amethyst 2025-09-11 11:09:36 -04:00
Vitor Pamplona
e1f7fae70e Removes dependency in the Amethyst class from Playback calculator 2025-09-11 11:08:04 -04:00
Vitor Pamplona
5127907acd Makes the root okhttp client for an Empty manager a lazy property to allow loading in Preview screens 2025-09-11 11:07:00 -04:00
Vitor Pamplona
2eda4d40e8 Makes max exoplayer pool size to a variable in the class 2025-09-11 11:06:09 -04:00
Vitor Pamplona
7f428a9976 Switches account cache to a flow 2025-09-11 11:05:03 -04:00
Vitor Pamplona
4347a1b104 Generalizes the type of OtsResolver needed by OtsState 2025-09-11 11:03:05 -04:00
Vitor Pamplona
abd6e6f2ac Makes the torControl option nullable to allow an Orbot-based Active flow 2025-09-11 11:02:25 -04:00
Vitor Pamplona
1af5daffcf Fixes the default Tor Settings 2025-09-11 10:58:35 -04:00
Vitor Pamplona
0e04425095 Fixes the option switching animations when loading the screen. 2025-09-11 10:57:49 -04:00
Vitor Pamplona
bbbdc05243 Fixes animations when selecting Tor options in the privacy screen 2025-09-11 10:54:22 -04:00
Vitor Pamplona
d4c39bd9cb Adds a flow to TorSettings so that any change to the inner properties can be tracked. 2025-09-11 10:46:26 -04:00
Vitor Pamplona
11597d2448 Documents OTS methods 2025-09-11 10:45:35 -04:00
Vitor Pamplona
5c3ea0a302 removes duplicated copyright notice 2025-09-11 10:45:18 -04:00
Vitor Pamplona
0c65176ecc Adds a DefaultOtsResolver builder using the internal web request classes from java. 2025-09-11 10:45:02 -04:00
KotlinGeekDev
e72bb5044b Adjust size of delete button for a Follow set item. 2025-09-11 15:02:38 +01:00
KotlinGeekDev
e8d04a2205 Modify Follow and Unfollow buttons to only change looks when in profile actions. 2025-09-11 15:01:27 +01:00
KotlinGeekDev
31328089cb FollowSetScreen: -Make a separate component for the title and description.
- Fix onBroadcastList, making sure to get the latest changes to the set before broadcast.
-Make a separate component for FollowSet list items, and make the delete button look a bit better(?).
NostrUserListVM: - Minor cleanup.
2025-09-11 14:32:58 +01:00
KotlinGeekDev
bd41354009 Make some UI adjustments for the list creation dialog. 2025-09-11 14:05:28 +01:00
KotlinGeekDev
bac3c575cb Make some UI adjustments for the list rename dialog. 2025-09-11 14:03:29 +01:00
KotlinGeekDev
3c4865c4b0 Parse follow set descriptions as-is. 2025-09-11 14:01:12 +01:00
Vitor Pamplona
ba2707e7b4 Changes the name of the nav to avoid confusing the auto import 2025-09-10 19:59:48 -04:00
Vitor Pamplona
f9590055b0 Removes dependency on the application class from DecryptAndIndexProcessor 2025-09-09 12:17:28 -04:00
Vitor Pamplona
dc97e5004e Uses local copy of cache instead of global singleton 2025-09-09 12:16:52 -04:00
Vitor Pamplona
4fb629e893 Fixes not sending the live stream events anywhere when the stream doesn't have a relay set declared. 2025-09-09 11:25:59 -04:00
Vitor Pamplona
44a34ffcfe Removes livestream chats from the home bubbles that are placed after the live stream is done 2025-09-09 11:25:30 -04:00
Vitor Pamplona
f00207d53c Fixes reply routes when clicking in the Conversations tab when the event is a PublicChat, LiveStream or Ephemeral Chat 2025-09-09 10:28:49 -04:00
Vitor Pamplona
d25e6cd07b Small speed up with contains of a char and not a string 2025-09-09 10:28:01 -04:00
Vitor Pamplona
bbec0d93d2 Avoids breaking the contract (changes during sorting) 2025-09-08 19:38:18 -04:00
Vitor Pamplona
4158d3bd45 Fixes logs for account 2025-09-08 18:47:31 -04:00
Vitor Pamplona
49e4f7bb46 Fixes feed filter update when changing the top nav filter 2025-09-08 18:03:01 -04:00
Vitor Pamplona
4249b9f872 Fixes not having author picture in the long form previews 2025-09-08 18:01:26 -04:00
Vitor Pamplona
eac2e54953 Merge pull request #1467 from davotoula/prevent-resource-leaks-with-streams
Prevent resource leaks with streams
2025-09-08 17:27:47 -04:00
Vitor Pamplona
2455a34987 Moves account saving from StateViewModel to Account 2025-09-08 17:22:54 -04:00
Vitor Pamplona
ba0ce3366c Fixes f-droid code 2025-09-08 16:58:14 -04:00
Vitor Pamplona
ccf9f47305 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-08 16:50:06 -04:00
Vitor Pamplona
95cf3fb6eb Changing the DVM feed to sort by follows that liked or zapped the DVM 2025-09-08 16:49:30 -04:00
Vitor Pamplona
81dbebc5a4 Fixes deletion computation in the update of feeds (it was not considering the a-tag's date and deleting newer events than the deletion request) 2025-09-08 16:49:09 -04:00
Vitor Pamplona
26599fe24f Removes dependency on the viewModel on AccountFeedStates 2025-09-08 15:58:50 -04:00
Vitor Pamplona
ed1ea4d289 Removing the dependency on an application class from AccountViewModel 2025-09-08 15:50:54 -04:00
Vitor Pamplona
e0764da095 Creates an interface for the DualHttpClientManager 2025-09-08 15:50:28 -04:00
Vitor Pamplona
4c8456c4af Fixes the crash on starting when the contentResolver is not ready yet. 2025-09-08 15:50:07 -04:00
Vitor Pamplona
ac2609b4f6 Finish migration to INostrClient 2025-09-08 15:45:50 -04:00
Vitor Pamplona
67e2189676 Moves the event processor to the Account class 2025-09-08 15:06:15 -04:00
Vitor Pamplona
222615a116 Establishing a scope for each account loaded. 2025-09-08 14:37:45 -04:00
davotoula
8f623ebb91 add TODO to investigate. Double closing of stream most often is ignored. 2025-09-08 20:32:28 +02:00
davotoula
adc4aa7906 cleaner code 2025-09-08 20:32:28 +02:00
davotoula
ec362ea0ee Use .use for streams
cleaner code
2025-09-08 20:32:28 +02:00
davotoula
e66e71a05c clener code 2025-09-08 20:32:28 +02:00
davotoula
9be55eda07 close FileInputStreams to prevent resource leak 2025-09-08 20:32:28 +02:00
David Kaspar
16b5d44c3d Merge pull request #1466 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-08 18:38:01 +01:00
Vitor Pamplona
93a639fea5 Modifies the Notification processor to use full accounts and their cached structrues again. 2025-09-08 13:29:02 -04:00
Vitor Pamplona
360f71c79a Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-08 13:28:23 -04:00
Crowdin Bot
1f38ae5deb New Crowdin translations by GitHub Action 2025-09-08 17:26:27 +00:00
Vitor Pamplona
f633d57277 Merge pull request #1465 from greenart7c3/main
When using intents check if it's rejected
2025-09-08 13:25:19 -04:00
greenart7c3
196a7de6a3 When using intents check if it's rejected 2025-09-08 14:05:00 -03:00
David Kaspar
4c1d0ac60c Merge pull request #1464 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-08 07:58:20 +01:00
Crowdin Bot
1bdf7d9884 New Crowdin translations by GitHub Action 2025-09-08 06:57:03 +00:00
David Kaspar
9e1d2c89bb Merge pull request #1463 from davotoula/update-translations
added CZ, DE translations
2025-09-08 07:55:46 +01:00
davotoula
a58dc09b80 added CZ, DE translations 2025-09-08 08:34:16 +02:00
David Kaspar
91f85a1e1c Merge pull request #1462 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-08 07:05:42 +01:00
Crowdin Bot
cf9d630032 New Crowdin translations by GitHub Action 2025-09-08 06:03:52 +00:00
David Kaspar
f55b0f528c Merge pull request #1461 from davotoula/update-translations
updated PT, CZ, SE, GE translations
2025-09-08 07:02:34 +01:00
davotoula
463762f268 added PT, CZ, SE, GE translations 2025-09-07 19:17:53 +02:00
Vitor Pamplona
82770c204c Merge pull request #1460 from davotoula/update-secp256k1
update secp256k1KmpJniAndroid to 0.19.0
2025-09-07 12:56:13 -04:00
Vitor Pamplona
0621cfb46c Small correction in the method name 2025-09-07 12:23:34 -04:00
davotoula
1f1a7eb12c update secp256k1KmpJniAndroid to 0.19.0 2025-09-07 17:59:08 +02:00
Vitor Pamplona
93e2f66559 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-06 11:59:38 -04:00
Vitor Pamplona
cc2b836cd5 Migrates Account management to an Application class. 2025-09-06 11:59:01 -04:00
Vitor Pamplona
fd2a227674 Fixes the new theme parameter 2025-09-06 11:59:01 -04:00
Vitor Pamplona
67937b9fbc Nicer way to create the account view model 2025-09-06 11:59:01 -04:00
Vitor Pamplona
ea58b4fa3d Adds a dependency on Account and to the NWC state to avoid linking directly with the Application class 2025-09-06 11:59:01 -04:00
Vitor Pamplona
7087fb85f0 Changes the Theme class to only take the preferred theme directly. 2025-09-06 11:59:01 -04:00
Vitor Pamplona
caf325f240 Sets the instance of the Application class before anything else 2025-09-06 11:59:01 -04:00
Vitor Pamplona
18b2fdb7e4 Removes one of the Application dependencies in viewmodel 2025-09-06 11:59:01 -04:00
Vitor Pamplona
66eef96ccb Offers way to load the Application class from Context 2025-09-06 11:59:01 -04:00
Vitor Pamplona
757806341c Adds an isMobileOrFalse flow in connectivity 2025-09-06 11:59:01 -04:00
Vitor Pamplona
69e3d2d0d8 Creates an interface for NostrClient 2025-09-06 11:59:01 -04:00
Vitor Pamplona
a03ba7b260 Refactors zap the devs card view 2025-09-06 11:59:01 -04:00
Vitor Pamplona
51f81dce73 Merge pull request #1459 from davotoula/main
Setting JvmTarget to 21 for quartz
2025-09-06 11:57:43 -04:00
davotoula
32d2ee698a fix for missing lazysodium 2025-09-06 15:53:11 +02:00
davotoula
8aaea960f0 Set quartz JVM target to 1.8 to allow for lower requirements for building library 2025-09-06 15:34:06 +02:00
davotoula
ab9ee36263 Setting JvmTarget to 21 for quartz to fix build/push issue due to mixed Java versions when using Java 23 locally 2025-09-05 20:01:49 +02:00
KotlinGeekDev
f9cf348f8f Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-05 12:56:23 +00:00
Vitor Pamplona
254ac1a1cb Trying to remove firebase analytics 2025-09-04 21:05:06 -04:00
Vitor Pamplona
2ffa029790 Small refactoring on NotificationUtils 2025-09-04 21:04:43 -04:00
Vitor Pamplona
b0e27d0748 Forces relay status updates when connecting and disconnecting 2025-09-04 21:02:20 -04:00
Vitor Pamplona
a4569675c2 Merge pull request #1458 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-04 11:16:18 -04:00
Crowdin Bot
88457eb101 New Crowdin translations by GitHub Action 2025-09-04 15:13:49 +00:00
Vitor Pamplona
103ef01be4 Adds support for NIP-53 Meeting room to quartz 2025-09-04 11:10:54 -04:00
Vitor Pamplona
fcef6df7db Merge pull request #1457 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-09-03 18:10:34 -04:00
Crowdin Bot
19c24af592 New Crowdin translations by GitHub Action 2025-09-03 22:05:06 +00:00
Vitor Pamplona
7de119dd4f removes logs 2025-09-03 17:48:24 -04:00
Vitor Pamplona
8d73bf2cc4 Makes the new Video events non replaceable, while keeping the old ones in the replaceable 2025-09-03 17:44:40 -04:00
Vitor Pamplona
8131f3db39 Update AGP 2025-09-03 16:57:38 -04:00
Vitor Pamplona
e4e52ec4e6 Unify outbox calculation in the RelayOutbox loader. 2025-09-03 16:51:27 -04:00
Vitor Pamplona
43c9df23c4 Removes common mistakes from the outbox list from follows 2025-09-03 16:37:30 -04:00
Vitor Pamplona
85fb6dcc83 Avoids breaking line in the relay info top nav bar title 2025-09-03 16:35:10 -04:00
Vitor Pamplona
c9cdc74c91 Removes duplicated Loading of Relay Info 2025-09-03 15:45:21 -04:00
Vitor Pamplona
c64e65ddb4 Migrates to use suspending routines for OTS, statuses and edits 2025-09-02 19:20:44 -04:00
Vitor Pamplona
c46cf79501 Correctly sorts by online status when the app has already cached the status of a streaming 2025-09-02 18:34:21 -04:00
Vitor Pamplona
eef0adc6f9 Resets streaming url check if offline and entering a chat of that streaming 2025-09-02 18:30:47 -04:00
Vitor Pamplona
480ef63aed adds a border on the users of a given relay in the connected relay list 2025-09-02 18:30:01 -04:00
Vitor Pamplona
7a807e94af Exposes active filters per relay on the Nostr Client for debugging purposes 2025-09-02 18:28:41 -04:00
Vitor Pamplona
116bd1ae29 Removes any relay url that has the null byte, regardless of size 2025-09-02 18:28:14 -04:00
Vitor Pamplona
fb5c20d8da Abandoning the precision of Mills to seconds on the isOnline Check 2025-09-02 18:27:46 -04:00
Vitor Pamplona
0bc0f503be Reduces errors when trying to parse unparseable NIP-11s 2025-09-02 17:34:15 -04:00
KotlinGeekDev
70c0668962 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-02 21:23:29 +00:00
Vitor Pamplona
f6d283dafb Refining the performance of the QuickAction menu bar 2025-09-02 15:32:28 -04:00
Vitor Pamplona
7048a1da50 Fixes invalid dependency on Quartz 2025-09-02 15:30:57 -04:00
Vitor Pamplona
36b541e0c7 Fixes NPE when updating a metadata with null name 2025-09-02 14:35:43 -04:00
Vitor Pamplona
501fa25e98 v1.03.0 2025-09-02 14:01:15 -04:00
Vitor Pamplona
b2516bbae9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-09-02 13:43:56 -04:00
Vitor Pamplona
fcd56fdb3a Improves list of visible authors on live stream bubbles 2025-09-02 13:26:19 -04:00
Vitor Pamplona
8db90728aa Removes wss://%2A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00.isekco.re/ 2025-09-02 12:37:38 -04:00
Vitor Pamplona
35146cc794 Adds the error code to avoid testing it again 2025-09-02 12:36:43 -04:00
KotlinGeekDev
bc395a09bd Merge branch 'vitorpamplona:main' into profiles-list-management 2025-09-01 17:28:50 +00:00
Vitor Pamplona
b6cd8d7b05 Merge pull request #1453 from KotlinGeekDev/quartz-kmp-build-fixes
Fix Quartz build issues.
2025-09-01 12:29:58 -04:00
KotlinGeekDev
822edf1273 Re-enable publish in androidTarget KMP section, as it works in conjunction with the Android publish block. 2025-09-01 16:47:56 +01:00
KotlinGeekDev
2f2919090e Disable publishing in androidTarget KMP configuration. Re-enable in Android block, and use updated syntax. 2025-09-01 16:40:36 +01:00
KotlinGeekDev
edf5b28e06 Verifying some changes. 2025-09-01 16:26:57 +01:00
KotlinGeekDev
c828299793 Update sourceset folder names. 2025-09-01 15:24:22 +01:00
KotlinGeekDev
cf13d24c93 Revert to using androidLibrary plugin until KMP publishing guidance is updated. Use default sourcesets for Android. 2025-09-01 15:22:53 +01:00
Vitor Pamplona
56f93170e5 Fixes product title when in a quoted post 2025-08-31 21:25:08 -04:00
Vitor Pamplona
ed4839bc4d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-31 12:27:57 -04:00
Vitor Pamplona
b8c39ca5b2 Updating firebase 2025-08-31 12:23:32 -04:00
Vitor Pamplona
5397e4e22e Moves SoftCache logs to the app 2025-08-31 12:10:51 -04:00
Vitor Pamplona
ea7a2f3426 Fixes live flag when the URL is not available anymore. 2025-08-31 12:09:01 -04:00
Vitor Pamplona
f2ae8afe3b Merge pull request #1450 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-31 11:26:43 -04:00
Crowdin Bot
ee235ce682 New Crowdin translations by GitHub Action 2025-08-31 15:17:12 +00:00
Vitor Pamplona
408a9725e0 Fixes bug on opening the Relay Settings page with duplicated Trusted and Blocked relays. 2025-08-31 11:13:56 -04:00
Vitor Pamplona
1112fa96aa Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-30 14:22:45 -04:00
Vitor Pamplona
7da309c9cf Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-30 14:22:33 -04:00
Vitor Pamplona
caf4b5ca0b Merge pull request #1449 from newrizen/English-strings.xml
Update english strings.xml
2025-08-30 14:18:32 -04:00
Neil Rizen
c8d6a46444 Update english strings.xml
According to PR #1444, commit 64df323, which changed the hyphen that was being applied to all languages ​​to a space, this is the adjustment made in English and will be followed for other languages.

[pt_rbr already adjusted]
2025-08-30 15:08:35 -03:00
Vitor Pamplona
dd5ea51575 Updates the User Profile's Relay List to an outbox version 2025-08-30 13:49:24 -04:00
Vitor Pamplona
9d4c690760 Adds a follow list state from kind 3 to keep following users in memory. 2025-08-30 13:18:04 -04:00
Vitor Pamplona
d7a3cf94b7 Moves from RelationshipStatus to ContactCard as per NIP update 2025-08-30 13:11:29 -04:00
Vitor Pamplona
a063107a15 Adds a normalized parser to all nip65 relays 2025-08-30 12:45:32 -04:00
Vitor Pamplona
f06fc8a9cb Merge pull request #1448 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-30 11:05:21 -04:00
Crowdin Bot
0c386811e0 New Crowdin translations by GitHub Action 2025-08-30 15:02:29 +00:00
Vitor Pamplona
4c34c6b8e3 Simple fix on url normalization 2025-08-30 10:59:53 -04:00
David Kaspar
4c43399cce Merge pull request #1447 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-30 15:44:31 +01:00
Crowdin Bot
b8ffb01073 New Crowdin translations by GitHub Action 2025-08-30 13:35:16 +00:00
Vitor Pamplona
50275548b8 Merge pull request #1443 from davotoula/capture-and-post-video
Capture and post video
2025-08-30 09:34:14 -04:00
Vitor Pamplona
64df3236ce Merge pull request #1444 from newrizen/Fix-TranslatableRichTextViewer.kt
Fix hyphen applied in all languages in TranslatableRichTextViewer.kt
2025-08-30 09:33:45 -04:00
Vitor Pamplona
c5a7293120 Merge pull request #1445 from davotoula/remove-unused-imports-amethyst
remove unused imports [amethyst]
2025-08-30 09:33:24 -04:00
Vitor Pamplona
4a969cb77c Merge pull request #1446 from davotoula/remove-unused-imports-quartz
remove unused imports [quartz]
2025-08-30 09:33:17 -04:00
davotoula
8fffe3f662 remove unused imports 2025-08-30 09:10:28 +01:00
davotoula
f5722eb963 remove unused imports 2025-08-30 08:25:30 +01:00
Neil Rizen
f5289cb3d3 Update TranslatableRichTextViewer.kt
Changed the hyphen to a space to move the word "auto-translated" (and its translations) to "translations_auto" string and "from" (and its translations) to "translated_from" string, as this hyphen only exists in some languages.
2025-08-29 21:52:48 -03:00
Vitor Pamplona
0064a4c596 Trying to avoid an NPE in the maxOfOrNull iterator 2025-08-29 17:58:39 -04:00
Vitor Pamplona
e9d9ef197f Activates live streams to the top feed bubble 2025-08-29 16:55:29 -04:00
Vitor Pamplona
a5a6c875fa Adds a listener to check the use of custom TTS Engines 2025-08-29 12:27:17 -04:00
Vitor Pamplona
8e8521fd6d Assemble report properties as a table and crash as a code block 2025-08-29 10:02:39 -04:00
davotoula
e387e04b6d Added "record and post video" button to GenericCommentPostScreen.kt (GeoHashTag post and HashTag post) 2025-08-29 14:47:25 +01:00
davotoula
001167ff23 Added "record and post video" button to NewProductScreen 2025-08-29 14:38:19 +01:00
Vitor Pamplona
449a3f541e Improves the design of the Crash Report permission screen with a cancel button.
Deleted copy stack to clipboard
2025-08-29 09:36:33 -04:00
Vitor Pamplona
3b59b11e8b Ignores out of memory exceptions because the stack trace is irrelevant in those cases. 2025-08-29 09:35:05 -04:00
Vitor Pamplona
aaae806505 Makes the exception name the first line in the report 2025-08-29 09:34:41 -04:00
Vitor Pamplona
458a7c7377 Adds support for expirations in DMs 2025-08-29 09:34:26 -04:00
davotoula
c95ab2d6b7 Added "record and post video" button to NewPublicMessageScreen (I'm not sure it's used) 2025-08-29 14:27:59 +01:00
davotoula
6a3aae7dbc Added "record and post video" button to NewGroupDMScreen 2025-08-29 14:14:03 +01:00
davotoula
9179e18b06 Added "record and post video" button to media tab 2025-08-29 14:06:39 +01:00
Vitor Pamplona
ecf530d512 Merge pull request #1442 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-29 08:02:42 -04:00
Crowdin Bot
51f7081214 New Crowdin translations by GitHub Action 2025-08-29 11:41:25 +00:00
Vitor Pamplona
7f7debbb73 v1.02.1 2025-08-29 07:38:42 -04:00
Vitor Pamplona
60377c0c35 Fixes inability to delete relays from certain relay lists. 2025-08-29 07:38:18 -04:00
Vitor Pamplona
a594d741b1 Creates an additional pointer for Account lists to make sure the GC doesn't clear instances that are only stored in the LocalCache's WeakReference DB. 2025-08-29 07:37:24 -04:00
davotoula
3dd791fbbe Change LOW video compression to use VERY_LOW from lightcompressorlibrary for better streaming experience
LOW: 20% of original
VERY_LOW: 10% of original
2025-08-29 10:35:27 +01:00
davotoula
87afb7f491 refactor getVideoUri and getPhotoUri to reduce duplication 2025-08-29 10:33:39 +01:00
davotoula
5f8519d737 added translations 2025-08-29 09:19:58 +01:00
davotoula
f83166e576 Add take video functionality
Add take video button to new post screen
I18N
2025-08-29 09:13:26 +01:00
Vitor Pamplona
df85c3969c Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-28 18:19:49 -04:00
Vitor Pamplona
e4311ddfa3 Creates a bootstrap relay list for inbox that doesn't have nostr.wine to avoid sending posts there and getting the notify 2025-08-28 18:12:55 -04:00
Vitor Pamplona
3a9fd925e1 Merge pull request #1440 from davotoula/upgrade-agp
Upgrade agp and add JvmTarget.JVM_21 to Quartz
2025-08-28 18:08:39 -04:00
davotoula
dbcca9cbd2 Use JvmTarget JVM_1_8 for quartz to be more forgiving about requirements. 2025-08-28 22:48:18 +01:00
davotoula
ef250eb0f1 Consistent JvmTarget JVM_21 across all modules 2025-08-28 22:17:44 +01:00
davotoula
8c26af7d40 upgrade agp to 8.12.2 2025-08-28 22:17:44 +01:00
Vitor Pamplona
0fe01696a5 v1.02.0 2025-08-28 17:02:55 -04:00
Vitor Pamplona
624bef21b1 Adds NIP-17 crash report. 2025-08-28 16:59:46 -04:00
Vitor Pamplona
8493c95e07 Fixes the wrong import 2025-08-28 15:54:14 -04:00
Vitor Pamplona
f7eb043aac Avoids comparisons with the torConnection as part of the data class 2025-08-28 14:23:26 -04:00
Vitor Pamplona
b80685dcfb Protects this flow against lots of similar emissions 2025-08-28 14:17:58 -04:00
Vitor Pamplona
0dc18cc98c Reduces the size of log messages 2025-08-28 13:53:22 -04:00
Vitor Pamplona
c366384a83 ignores notebook file 2025-08-28 13:09:08 -04:00
KotlinGeekDev
3cfe195561 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-28 15:43:27 +00:00
Vitor Pamplona
c298147ad9 updates google services json 2025-08-28 11:18:01 -04:00
Vitor Pamplona
16e9d43da8 Removes DB migration 2025-08-28 11:17:49 -04:00
KotlinGeekDev
e3cfa76843 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-28 15:08:48 +00:00
Vitor Pamplona
da5816a6c6 Fixes crash when pasting an invalid NIP-47 uri into the zap settings 2025-08-28 10:41:22 -04:00
Vitor Pamplona
4be1a143e6 Solving parsing issue with NWC 2025-08-28 10:35:44 -04:00
Vitor Pamplona
85ee48e003 Fixes crash when loading a user without a name 2025-08-28 10:32:29 -04:00
Vitor Pamplona
f536f8e88e Fixes readonly crash on DVMs 2025-08-28 10:32:16 -04:00
Vitor Pamplona
918a575cfd Speeds up first time logins by not waiting for Tor 2025-08-28 10:24:54 -04:00
KotlinGeekDev
a23bc875cf Merge latest upstream changes. 2025-08-28 14:42:53 +01:00
KotlinGeekDev
92fac53705 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2025-08-28 14:42:09 +01:00
KotlinGeekDev
1b011acbd7 Merge branch 'main' of https://github.com/vitorpamplona/amethyst ite resolve branch. 2025-08-28 14:38:56 +01:00
KotlinGeekDev
f651dd964d Refactor FollowSetScreen parameters. Add new toast message type that includes an action to perform on dismissal. 2025-08-28 14:27:50 +01:00
KotlinGeekDev
53b74e6f82 Add Nav route for FollowSetScreen. Refactor other places accordingly. Make minor cleanup. 2025-08-28 14:25:39 +01:00
KotlinGeekDev
c1a5052cbb Fix bug in addFollowSet, and refactor to use Boolean for checking list visibility. Use runBlocking in getFollowSetNOte(for now). 2025-08-28 14:20:07 +01:00
Vitor Pamplona
56d8a83ebe Fixing a parser benchmark 2025-08-28 08:55:15 -04:00
Vitor Pamplona
5f33fb2cea removes unnecessary native lib 2025-08-27 21:09:42 -04:00
Vitor Pamplona
170b6cfe69 Moves test assets to resources to make it work with kmp 2025-08-27 21:09:21 -04:00
Vitor Pamplona
ce991a8aea - Migrates NIP-49 from java to kotlin for kmp 2025-08-27 21:08:34 -04:00
Vitor Pamplona
bd5b61ee64 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-27 18:32:43 -04:00
Vitor Pamplona
494d7edc1f Testing proguard rules on KMP quartz 2025-08-27 18:32:33 -04:00
Vitor Pamplona
8e2d684cb1 Merge pull request #1439 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-27 17:38:55 -04:00
Crowdin Bot
e994839468 New Crowdin translations by GitHub Action 2025-08-27 21:36:13 +00:00
Vitor Pamplona
76d97d6d16 Removes nostr.bg from relay options 2025-08-27 17:32:54 -04:00
Vitor Pamplona
442f75fc41 - Migrates Quartz to a KMP project
- Converts OpenTimestamps from java to Kotlin
- Moves OTS OkHttp setup to Quartz
2025-08-27 17:32:39 -04:00
KotlinGeekDev
3b23b9eeef Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-27 14:35:06 +00:00
Vitor Pamplona
d2214889b6 Version 1.01.0 2025-08-27 09:10:11 -04:00
Vitor Pamplona
d4da14e4fa Moves draft deletion calculations to the default thread 2025-08-27 08:57:10 -04:00
Vitor Pamplona
56dec11901 Intercepts backhandler to save drafts 2025-08-27 08:56:40 -04:00
Vitor Pamplona
8624dcf2d4 Merge pull request #1437 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-27 08:15:39 -04:00
Crowdin Bot
7edccdb236 New Crowdin translations by GitHub Action 2025-08-27 12:02:53 +00:00
Vitor Pamplona
d8fb49bd52 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-27 08:00:35 -04:00
Vitor Pamplona
108d8a2e57 Improves draft deletes in the feeds 2025-08-27 08:00:08 -04:00
Vitor Pamplona
7b6bd3579c Merge pull request #1436 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-26 20:33:52 -04:00
Crowdin Bot
9f398b5ef8 New Crowdin translations by GitHub Action 2025-08-27 00:20:01 +00:00
Vitor Pamplona
568a175735 fixes fdroid build 2025-08-26 20:18:10 -04:00
Vitor Pamplona
aa80d1b84a - Upgrades NIP-72 communities to NIP-22 comments
- Adds moderation queue feed
- Adds in-app approval procedures
- Adds drafts for community posts
- Redesigns About us section of communities.
2025-08-26 20:10:04 -04:00
KotlinGeekDev
685365eb12 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-25 20:34:00 +00:00
Vitor Pamplona
fd752a4997 Adds a restart of the registration once the Post_notification permission changes 2025-08-25 15:43:56 -04:00
Vitor Pamplona
7302d260d9 avoids sending expired events to the cloud 2025-08-25 15:43:12 -04:00
Vitor Pamplona
823540160a Fixes some DM notification issues 2025-08-25 15:42:57 -04:00
Vitor Pamplona
0187529f8d Removes default and bootstrapping lists from the "Trusted Relay set". This should fix the unwanted NOTIFY requests. 2025-08-25 12:16:40 -04:00
Vitor Pamplona
98453c7b65 Fixes unable to delete relays from some lists. 2025-08-25 10:26:39 -04:00
KotlinGeekDev
34a75dd28f Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-25 14:18:23 +00:00
Vitor Pamplona
f43e88eee7 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-25 10:10:06 -04:00
Vitor Pamplona
4baa5bf659 Fixes https://github.com/vitorpamplona/amethyst/issues/1070 2025-08-25 10:09:39 -04:00
David Kaspar
ce1db52abb Merge pull request #1433 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-25 14:25:14 +01:00
Crowdin Bot
cc6a9f1818 New Crowdin translations by GitHub Action 2025-08-25 13:14:18 +00:00
Vitor Pamplona
f11ab08f92 Merge pull request #1428 from davotoula/1403-accessibility-talkback-issues
accessibility talkback issues
2025-08-25 09:12:41 -04:00
davotoula
c93cc04409 updated imports 2025-08-23 22:58:16 +01:00
davotoula
56f299569e added translations CS, DE, PT, SV 2025-08-23 22:24:48 +01:00
davotoula
4006aebf35 added I18n for new strings 2025-08-23 18:24:10 +01:00
davotoula
b0aa2a8c31 added accessibility Description
TODO: add translations
2025-08-23 17:12:23 +01:00
davotoula
03483c932c reduce warnings:
remove unused nav parameter
remove unnecessary ? operator
replace .filter().first() with .first
2025-08-23 17:08:17 +01:00
David Kaspar
f3f8beeebe Added semantics to the invisible box
contentDescription - provides current state info
TODO: add translations
2025-08-23 17:06:26 +01:00
David Kaspar
0381e22198 Corrected TextSpinner package 2025-08-23 17:06:05 +01:00
Vitor Pamplona
d90618f7f3 Corrected version 1.00.5 2025-08-23 11:06:45 -04:00
Vitor Pamplona
41ca6d281e Revert "Version 1.00.5"
This reverts commit 83870cd06b.
2025-08-23 11:06:11 -04:00
Vitor Pamplona
1f4f1d4822 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-23 11:05:50 -04:00
Vitor Pamplona
83870cd06b Version 1.00.5 2025-08-23 11:05:22 -04:00
Vitor Pamplona
0d4689b064 Merge pull request #1427 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-23 10:58:18 -04:00
Crowdin Bot
d2e7e171a1 New Crowdin translations by GitHub Action 2025-08-23 14:57:56 +00:00
Vitor Pamplona
aea8573693 Merge pull request #1425 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-23 10:56:53 -04:00
Vitor Pamplona
36b14f93cb Removing multiple relays in the same line. 2025-08-23 10:49:40 -04:00
Vitor Pamplona
356a805681 Adds a child safety standards section 2025-08-22 18:15:12 -04:00
Crowdin Bot
c9e5646df2 New Crowdin translations by GitHub Action 2025-08-22 20:46:42 +00:00
Vitor Pamplona
f50a3c5bbb Merge pull request #1424 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-22 16:45:32 -04:00
Vitor Pamplona
46fe3c2f4a Merge pull request #1423 from newrizen/New-translation
Update pt_rbr strings.xml
2025-08-22 16:45:08 -04:00
Crowdin Bot
5ed0bf48a6 New Crowdin translations by GitHub Action 2025-08-22 20:31:42 +00:00
Vitor Pamplona
0f59befb03 Version 1.00.4 2025-08-22 16:29:56 -04:00
Vitor Pamplona
c1d8aac030 Fixes https://github.com/vitorpamplona/amethyst/issues/1422 2025-08-22 16:29:49 -04:00
Vitor Pamplona
0d3c60b9c2 Fixes the disappearance of drafts. 2025-08-22 16:11:30 -04:00
Vitor Pamplona
fa80912db2 Uses a separate okhttp for DM relays 2025-08-22 13:50:28 -04:00
Vitor Pamplona
f9ea1270fa creates a separate okhttp for relays 2025-08-22 13:35:46 -04:00
Vitor Pamplona
e0ffa6d4fb In case a user does not have an outbox list, defaults to all hints seen for that user. 2025-08-22 12:42:40 -04:00
Vitor Pamplona
3df7129f00 Deletes unused state class 2025-08-22 12:41:03 -04:00
Neil Rizen
692f77dcd1 Update pt_rbr strings.xml
More compact translation, with corrected spelling (there is no "automaticamente-traduzido" with dash in Portuguese, so I changed it to 'autotraduzido' and moved the dash away to use as a ':' ) and visually more pleasing.
2025-08-22 07:56:00 -03:00
Vitor Pamplona
273186ef35 version 1.00.3 2025-08-21 20:13:52 -04:00
Vitor Pamplona
b64d6c24c8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-21 20:11:54 -04:00
Vitor Pamplona
1f2a153f47 Fixes lack of feed updates for those that didn't follow any communities 2025-08-21 20:11:44 -04:00
Vitor Pamplona
a36f2cc93f Merge pull request #1421 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-21 18:29:13 -04:00
Vitor Pamplona
499316687f Avoids parsing bad NIP-28 objects 2025-08-21 17:19:22 -04:00
Vitor Pamplona
693a75b9ec Don't try to decrypt appData unless it is a writeable account 2025-08-21 17:18:47 -04:00
Crowdin Bot
d0b3c9b019 New Crowdin translations by GitHub Action 2025-08-21 21:12:11 +00:00
Vitor Pamplona
d810aaa2d0 streamlines function calls on AccountViewModel 2025-08-21 17:10:10 -04:00
Vitor Pamplona
965db8b91b trying to remove HEAD from the release build name 2025-08-21 15:13:53 -04:00
Vitor Pamplona
5f7088b7bb Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-21 14:33:27 -04:00
Vitor Pamplona
d7c80ca138 Merge pull request #1420 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-21 14:33:04 -04:00
Vitor Pamplona
75da18a30b Version 1.00.2 2025-08-21 14:32:48 -04:00
Crowdin Bot
897e9d84cb New Crowdin translations by GitHub Action 2025-08-21 18:31:11 +00:00
Vitor Pamplona
fe141d1ee3 Fixes crash when typing a new post without permissions to do a draft post. 2025-08-21 14:29:34 -04:00
Vitor Pamplona
4a7a09c43a Tries to avoid crash when lacking google services in the play version. 2025-08-21 14:20:55 -04:00
Vitor Pamplona
fd26ae5543 Fixes branch name 2025-08-21 14:11:56 -04:00
Vitor Pamplona
631270fc37 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-21 14:09:43 -04:00
Vitor Pamplona
816de5bf03 Fixes community join/leave issue 2025-08-21 14:06:47 -04:00
Vitor Pamplona
1ef9902ece Migrates top nav list to hashtag, geohash and community lists 2025-08-21 13:51:33 -04:00
Vitor Pamplona
704a08e7bb Fixes follow/unfollow from hashtags and geohashes 2025-08-21 13:51:10 -04:00
Vitor Pamplona
ffbf2b4024 Merge pull request #1419 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-21 12:25:23 -04:00
Crowdin Bot
a33ab5c1e9 New Crowdin translations by GitHub Action 2025-08-21 16:22:29 +00:00
Vitor Pamplona
b699b30954 Version 1.00.1 to fix app name 2025-08-21 12:21:48 -04:00
Vitor Pamplona
1d1ee8670e Fixes app name 2025-08-21 12:20:16 -04:00
Vitor Pamplona
bcdf6e5ff7 Sends only play files to the zap store. 2025-08-21 12:17:31 -04:00
Vitor Pamplona
fbb50f4147 version 1.0.0 2025-08-21 11:08:23 -04:00
Vitor Pamplona
4e13c5ab9c Improves timeouts to download and upload files 2025-08-21 08:49:25 -04:00
Vitor Pamplona
e1a5b9c079 Waits for the player to load and sets the current position of the audio 2025-08-20 21:26:59 -04:00
Vitor Pamplona
a27acd9dc6 YakBak in PictureInPicture mode
New audio player UI for YakBak
2025-08-20 21:00:09 -04:00
KotlinGeekDev
a3be27723d Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-20 21:54:12 +00:00
Vitor Pamplona
2f82233b25 Higher quality audio recordings 2025-08-20 17:37:44 -04:00
Vitor Pamplona
b232f484bf Saves all NIP-51 lists as nip-44 2025-08-20 17:32:58 -04:00
Vitor Pamplona
679f9b66ed Allows the height of the preview to adjust to the width of the preview image 2025-08-20 17:22:04 -04:00
Vitor Pamplona
da015e3298 Enabling the display for amplitudes in an array of floats for YakBak 2025-08-20 16:00:06 -04:00
Vitor Pamplona
4065adac83 Correctly manages airplane mode changes: reconnects Tor and Relays 2025-08-20 12:16:01 -04:00
Vitor Pamplona
7339f0ea3b Merge pull request #1418 from davotoula/update-n71-video-events
Support updated video events (Kind 21/22)
2025-08-20 08:32:20 -04:00
David Kaspar
1bbc67fed7 Added VideoNormalEvent (21) and VideoShortEvent (22)
Updated Account to post events as new 21 / 22 kinds.
Keep showing old event kinds for backwards compatibility
2025-08-20 11:16:02 +01:00
Vitor Pamplona
64f46a55f9 Because the indexer processes Notes in bulk and after the fact, it could be the case where a new draft and a deletion event happen one after the other. If we don't re-check for deletions, the app will reindex the old draft that was already deleted by the LocalCache, creating a memory leak. 2025-08-19 18:54:28 -04:00
Vitor Pamplona
5662eda54c Finishes the routing for note edits for live, public and ephemeral chats as well as public messages. 2025-08-19 18:21:27 -04:00
Vitor Pamplona
f56d927f72 Adds 90 day expiration to all drafts. 2025-08-19 12:40:53 -04:00
Vitor Pamplona
5685f227f9 Fixes relay hints when they disagree with our findings. 2025-08-19 12:20:49 -04:00
Vitor Pamplona
d539eca8bb Avoids chats going behind the status bar up top 2025-08-19 10:42:42 -04:00
Vitor Pamplona
415bf9f522 Corrects some of the keys for Launched effects 2025-08-19 09:02:15 -04:00
Vitor Pamplona
667c15b3a1 Checks if the relay list in NIP-28 groups is empty and uses default when needed. 2025-08-18 19:24:57 -04:00
Vitor Pamplona
f31fa4a9c6 Adds notes to channels to facilitate their loading. 2025-08-18 19:24:23 -04:00
Vitor Pamplona
3bf6e80650 update kotlin, serialization plugin, tor 2025-08-18 19:22:56 -04:00
Vitor Pamplona
3bf2f858f7 Fixes the unable to recreate user after the GC deletes it. 2025-08-18 17:24:34 -04:00
Vitor Pamplona
5c0d71f034 Avoiding concurrent errors when deleting weak references 2025-08-18 16:52:17 -04:00
KotlinGeekDev
62f2c8c01c Remove followSetsFlow from AccountViewModel in favor of using the NostrUserListFeedViewModel. Remove unused code, and refactor accordingly. 2025-08-18 21:23:22 +01:00
Vitor Pamplona
d4d60cb1ff Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-18 15:21:06 -04:00
Vitor Pamplona
b2932cd7e3 Updates agp 2025-08-18 15:07:35 -04:00
Vitor Pamplona
9d9b5f03db Merge pull request #1417 from greenart7c3/main
Change BasicOkHttpWebSocket to be like the docs
2025-08-18 13:27:52 -04:00
greenart7c3
e076f97714 Change BasicOkHttpWebSocket to be like the docs 2025-08-18 13:51:40 -03:00
KotlinGeekDev
c06408537a Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-18 16:23:06 +00:00
KotlinGeekDev
850b9afc66 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2025-08-18 17:22:26 +01:00
KotlinGeekDev
909c704bda Improved version of NostrUserListFeedViewModel. Remove unneeded LaunchedEffect in CustomListsScreen. 2025-08-18 17:21:26 +01:00
Vitor Pamplona
f5247a5443 Moves reconnections to 1 minute not 10 seconds 2025-08-18 11:37:32 -04:00
Vitor Pamplona
92e9978859 Moves to parcelable.
Fixes: https://github.com/vitorpamplona/amethyst/issues/1413
2025-08-18 11:25:57 -04:00
Vitor Pamplona
3520fe89ae Merge pull request #1414 from davotoula/1413-use-pubkeyhex-for-lazy-column-key
Use pubkeyhex for lazy column key instead of Address
2025-08-18 11:13:23 -04:00
Vitor Pamplona
01ed0f3fc1 Merge pull request #1415 from davotoula/target-field-specifically-for-constructor-params-for-serialization
Use explicit field annotation target for constructor params
2025-08-18 11:13:08 -04:00
David Kaspar
41545a156b Use explicit annotation target for constructor params
See https://youtrack.jetbrains.com/issue/KT-73255 for more details
2025-08-18 10:44:42 +01:00
David Kaspar
b38e9247ce Use pubkeyhex for lazy column key instead of Address 2025-08-17 23:02:25 +01:00
Vitor Pamplona
45ec9e3a62 Merge pull request #1411 from davotoula/reduce-duplications-large-cache
Reduce duplications: large cache
2025-08-16 16:14:13 -04:00
David Kaspar
c1004dc643 Use .get( instead of index to keep to code base coding style 2025-08-16 18:56:48 +01:00
David Kaspar
a72fff5f8f Lifted out collectors from CacheCollectors object 2025-08-16 18:50:01 +01:00
Vitor Pamplona
fca4aee5f5 Adds a user list to each connected relay to know where this is coming from 2025-08-16 13:21:10 -04:00
David Kaspar
a82f4aacbe Use val for references that do not change (eg results) 2025-08-16 18:17:27 +01:00
David Kaspar
f21c7bd258 Lint issues: remove unused methods, use indexing operator instead of .get() 2025-08-16 11:21:19 +01:00
David Kaspar
533d8b18f0 Remove duplicate functionality from Cache implementations and use instead common methods in CacheOperations utility class
Create override for forEach and size as they are needed internally and do slightly different things
2025-08-16 10:45:37 +01:00
David Kaspar
b069f283ac Move common cache operations to new utility class 2025-08-16 10:38:57 +01:00
David Kaspar
eab2f96f02 remove duplicate colletors and Use collectors from new utility class 2025-08-16 10:20:52 +01:00
David Kaspar
c4b71a0aea Move common collectors to new utility class 2025-08-16 10:09:56 +01:00
KotlinGeekDev
bcebfa58f1 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-16 01:34:30 +00:00
Vitor Pamplona
88933fba8d Fixes the time to reconnect interval due to an error between seconds and milliseconds. 2025-08-15 18:07:12 -04:00
Vitor Pamplona
ef049e88ba Adds a new mechanism to wake up all connections when movement is made in the app. 2025-08-15 18:06:21 -04:00
Vitor Pamplona
6f27c96aed changes the sendEvent from forcing a connection to waiting for the time to connect. 2025-08-15 18:04:57 -04:00
Vitor Pamplona
5b605700a3 Adds 10 seconds ago 2025-08-15 18:03:57 -04:00
Vitor Pamplona
e867571ea6 Improves version finder 2025-08-15 14:39:53 -04:00
Vitor Pamplona
f4759c283e Adds a basic OkHttp builder to Quartz (new dependency :( )
Adds some instructions to the basic readme.md
2025-08-15 14:33:05 -04:00
Vitor Pamplona
4887e6f3a1 Sends a disconnect signal to the clients 2025-08-15 09:43:37 -04:00
Vitor Pamplona
1392833179 no need for the scope on the cache connector 2025-08-15 09:18:42 -04:00
Vitor Pamplona
529a1f403e Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-15 08:41:22 -04:00
Vitor Pamplona
c9b16309e2 Clears the socket from memory upon closing. 2025-08-15 08:40:33 -04:00
Vitor Pamplona
81db455bfb Updates compose and fragment 2025-08-15 08:36:08 -04:00
Vitor Pamplona
c017b9cace Merge pull request #1410 from greenart7c3/main
Fix non ssl connections
2025-08-15 06:57:29 -04:00
greenart7c3
25f5551a98 Fix non ssl connections 2025-08-15 07:35:01 -03:00
Vitor Pamplona
d8461c2a74 Growing the number of possibilities to download profile information from 2025-08-14 19:47:20 -04:00
Vitor Pamplona
25dec49e97 Reactivates event log 2025-08-14 18:58:33 -04:00
Vitor Pamplona
424d5ccc9c Refactoring 2025-08-14 17:32:28 -04:00
Vitor Pamplona
422a018a1f Fixes when posts have published_at in milliseconds. 2025-08-14 17:32:15 -04:00
Vitor Pamplona
5b8afb6f60 Updates logging for auth 2025-08-14 17:06:26 -04:00
Vitor Pamplona
38827a4db6 Uses relays where the event was seen as a source of reactions and replies 2025-08-14 16:20:53 -04:00
Vitor Pamplona
97bad5707f Improves filter by last date of the feed 2025-08-14 16:09:26 -04:00
Vitor Pamplona
58c8ff646f Moves the feed update to 5 seconds. 2025-08-14 16:04:27 -04:00
Vitor Pamplona
613a3b4912 Adds a limit of 1 week to load videos and hashtags on shorts 2025-08-14 16:03:48 -04:00
Vitor Pamplona
a67155ac25 Only loads notifications from random relays after the notification feed is done 2025-08-14 16:03:19 -04:00
Vitor Pamplona
ba27b68c14 Minor refactoring 2025-08-14 15:43:43 -04:00
Vitor Pamplona
dfe5f86a23 Faster PublishedAt providers 2025-08-14 11:28:03 -04:00
Vitor Pamplona
abf0c4fd70 Removes broadcasting relays from the group of relays used to find events. 2025-08-14 10:49:30 -04:00
Vitor Pamplona
ed675a4973 Merge pull request #1408 from davotoula/reduce-duplication-resuable-zap-button
Deduplication of code: ZapTheDevsCard, DvmContentDiscoveryScreen
2025-08-14 09:51:38 -04:00
Vitor Pamplona
6514bacf69 Merge pull request #1409 from davotoula/1406-display-app-picture-when-clicked-on-app-picture
Display app picture when clicked on app picture
2025-08-14 09:51:06 -04:00
David Kaspar
e682a95822 Changed ZoomableImageDialog to show app picture rather than banner
Moved it inside picture?.let to guard against null picture
2025-08-14 14:34:11 +01:00
David Kaspar
d367a1f281 Specify return type as non-nullable asJackson will either return a non-null value or throw an exception. 2025-08-14 11:23:23 +01:00
KotlinGeekDev
8585620045 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-14 04:22:54 +00:00
Vitor Pamplona
f5881d4248 avoids using broadcasting relays to build filters to download home data 2025-08-13 19:11:54 -04:00
Vitor Pamplona
4c0f223673 Draft event refactoring 2025-08-13 19:11:29 -04:00
David Kaspar
aae520d2e4 Created ReusableZapButton with common zap button logic
ZapDVMButton refactored to use ReusableZapButton with DVM-specific configuration
2025-08-13 20:59:56 +01:00
Vitor Pamplona
251e5535d0 Removes hash logs 2025-08-12 18:57:11 -04:00
Vitor Pamplona
a034b495e8 Removes checks 2025-08-12 18:57:01 -04:00
Vitor Pamplona
41c2097f3c reduces logs 2025-08-12 18:55:55 -04:00
Vitor Pamplona
22dc037bf7 Moves to disable tor if on the background 2025-08-12 18:55:47 -04:00
Vitor Pamplona
346fb61507 Small refactoring 2025-08-12 17:55:24 -04:00
Vitor Pamplona
c17d66b7e9 Small optimization 2025-08-12 17:28:22 -04:00
Vitor Pamplona
478b2d12eb Speeding up the parsing of relay urls 2025-08-12 17:16:14 -04:00
Vitor Pamplona
c14d976ad1 Minor refactoring 2025-08-12 17:15:23 -04:00
Vitor Pamplona
d605eb3b0a Fixes lack of update on chats and channels due to the new indexer being decoupled form LocalCache. 2025-08-12 16:37:32 -04:00
Vitor Pamplona
25aaed80b0 Better name for the newestMessage of a chat 2025-08-12 11:43:57 -04:00
Vitor Pamplona
14582fe9b6 Recalculating subject and last message when deletes happen 2025-08-12 11:29:58 -04:00
Vitor Pamplona
7a38269449 removing unused libs 2025-08-12 11:29:34 -04:00
Vitor Pamplona
77bb34eaf8 Updates tor to 16KB-page files and firebase 2025-08-12 08:47:51 -04:00
Vitor Pamplona
d0f3dde08b Migrates to a more obvious open/close subscription model for NostrClient 2025-08-11 10:34:10 -04:00
Vitor Pamplona
d5b9d5e80b Closes subscription when finished by timeout. 2025-08-11 10:33:21 -04:00
KotlinGeekDev
2238f4a807 Use runBlocking in mapEventToSet(). Rework and simplify fetching in FollowSetFeedFilter. Make FollowSetActions dropdown menu more reliable. 2025-08-10 02:22:20 +01:00
KotlinGeekDev
653494e922 Cleanup/Refactor:Remove followSetNotes property from User, and refactor accordingly. Make getFollowSetNotes() return the notes. 2025-08-10 02:18:03 +01:00
Vitor Pamplona
549074ef13 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-07 18:04:42 -04:00
Vitor Pamplona
2315051504 - Makes zap calculation a sync routine
- Optimizes the display of the yellow zap when the zap event comes before the pay payment confirmation
- Fixes animations for a new zap over an existing zap.
2025-08-07 18:02:31 -04:00
Vitor Pamplona
8ba96ca527 Simplifies Zap animation sizes 2025-08-07 16:39:44 -04:00
Vitor Pamplona
10c5db8281 Fixes the encryption of NWC 2025-08-07 16:07:15 -04:00
KotlinGeekDev
9e7d3146fa Merge branch 'vitorpamplona:main' into profiles-list-management 2025-08-07 19:52:15 +00:00
Vitor Pamplona
86b4957067 Removes NWC events from notificatuions 2025-08-07 15:35:59 -04:00
Vitor Pamplona
fe19b8b4af Merge pull request #1405 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-07 14:37:59 -04:00
Crowdin Bot
3532a0cadd New Crowdin translations by GitHub Action 2025-08-07 18:27:02 +00:00
Vitor Pamplona
58277d9697 Moves build tools of github actions to 36 2025-08-07 14:24:45 -04:00
Vitor Pamplona
cf76ab9f0e Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-07 10:13:18 -04:00
Vitor Pamplona
51d7fa3aac Fixes missing method on build 2025-08-07 10:09:43 -04:00
Vitor Pamplona
10832e10c8 Fixes the background color behind the show more button on light mode 2025-08-07 09:50:20 -04:00
David Kaspar
1fce054c9f Merge pull request #1404 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-07 15:27:14 +02:00
Crowdin Bot
f7b3dada05 New Crowdin translations by GitHub Action 2025-08-07 13:25:23 +00:00
Vitor Pamplona
f35376a7da Small refactoring 2025-08-06 19:13:15 -04:00
Vitor Pamplona
d0f3630abb Changes to show the user name before the text on public messages. 2025-08-06 18:26:11 -04:00
Vitor Pamplona
d22fc2874b Don't show public messages on the feed, just on notifications 2025-08-06 18:25:20 -04:00
Vitor Pamplona
23b3ff9fe8 Adds missing methods to display as Public message 2025-08-06 18:09:14 -04:00
Vitor Pamplona
52ac0c184d Passes reply preview to the new Public message screen 2025-08-06 18:07:30 -04:00
Vitor Pamplona
923ddf1c83 Shows all public message events on conversations 2025-08-06 18:05:14 -04:00
Vitor Pamplona
cc186ef91b Fixes route maker 2025-08-06 18:04:44 -04:00
Vitor Pamplona
02def3e0b1 Doesn't pass deleted drafts ahead to the indexer anymore. It's not needed. 2025-08-06 18:02:54 -04:00
Vitor Pamplona
036b385f38 Adds a check for deletion before processing inner posts 2025-08-06 18:02:26 -04:00
Vitor Pamplona
fdff92dff9 New Public Message screen: to field should get focus if it is empty, otherwise the message field should have the focus 2025-08-06 18:01:19 -04:00
Vitor Pamplona
8c854c43be Adds p-tags to the display of public messages so that users know they are being cited 2025-08-06 17:58:51 -04:00
Vitor Pamplona
d707a951c3 Adds an option to pass a request focus parameter to the MessageField in new post screens 2025-08-06 17:58:18 -04:00
Vitor Pamplona
b1566d8f8b Update log descriptions to match the new deleted flow 2025-08-06 17:57:46 -04:00
Vitor Pamplona
481db7ebeb Loads and shows public messages in the user's profile as replies to others 2025-08-06 17:57:06 -04:00
Vitor Pamplona
a18c6c0bc8 Shows public messages in the profile's mutual feed 2025-08-06 17:56:26 -04:00
Vitor Pamplona
4d6f6d0ed5 removes a launch call from an already launched coroutine 2025-08-06 17:55:52 -04:00
Vitor Pamplona
1b043603f9 Adds a method to create nprofiles without creating a relay list first 2025-08-06 17:54:34 -04:00
KotlinGeekDev
2ff56e8af8 Rename FollowList ViewModel to match it's actual use. 2025-08-06 22:17:06 +01:00
Vitor Pamplona
dddc9e1346 Simplifies the startWith check 2025-08-06 17:13:37 -04:00
KotlinGeekDev
b1e4f65be8 Remove code that was reintroduced during merge with upstream. Fix Lists route. 2025-08-06 21:45:34 +01:00
KotlinGeekDev
1ce829ba23 Bring back some lost changes, and fix styling. 2025-08-06 20:26:03 +01:00
KotlinGeekDev
e92e36ceda Sync with upstream 2025-08-06 19:11:07 +00:00
KotlinGeekDev
b8b4377891 Upstream + adjustments. 2025-08-06 20:09:02 +01:00
Vitor Pamplona
b5662fd05a normalizes the parameter name across all adaptive filters 2025-08-06 14:09:06 -04:00
Vitor Pamplona
726f9e2f4f Moves target compatibility to java 21 2025-08-06 14:07:51 -04:00
KotlinGeekDev
981486f09a Add other functions and adapt them to the code already present. 2025-08-06 18:56:15 +01:00
Vitor Pamplona
ca55472010 Increases the limit for notification events to fill up a week's worth of inbox 2025-08-06 13:20:00 -04:00
Vitor Pamplona
71bbae2f9b Simple refactoring 2025-08-06 13:18:45 -04:00
Vitor Pamplona
1971956443 Setting up the boundary for one week ago for notifications 2025-08-06 13:18:13 -04:00
Vitor Pamplona
282935c5f3 Adds search relays as fallback for event finding activities 2025-08-06 08:55:35 -04:00
KotlinGeekDev
7f8b2a6ccf Fix unresolved conflicts. 2025-08-06 13:34:08 +01:00
Vitor Pamplona
d4d7d58378 Maybe separate assemblies work 2025-08-05 17:42:57 -04:00
Vitor Pamplona
5d1e3e804f Trying to see if we can get benchmark flavors out of github actions 2025-08-05 17:20:25 -04:00
Vitor Pamplona
9475978612 Fixes background of transparent posts. 2025-08-05 16:42:13 -04:00
Vitor Pamplona
c32a64fbfe avoids indexing via DraftTags and removes notes from the reply list when they get updated. 2025-08-05 16:30:36 -04:00
Vitor Pamplona
da40de49cd Refactors the DecryptAndIndex to avoid a service class 2025-08-05 16:30:00 -04:00
Vitor Pamplona
e237853a8f Making the new signer work for readonly accounts 2025-08-05 16:06:52 -04:00
Vitor Pamplona
63f62167a4 Simplifying the refactoring of the DecryptAndIndexProcessor 2025-08-05 15:04:23 -04:00
Vitor Pamplona
4675212e1d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-05 12:06:17 -04:00
Vitor Pamplona
f1b15cc3c5 Cleaning up the saving draft concurrency issues. 2025-08-05 11:48:02 -04:00
Vitor Pamplona
a747f25906 Adds exception handlers for managed coroutine scopes 2025-08-05 11:47:03 -04:00
Vitor Pamplona
e72ae565d3 Adds an extra catch in case there is a decryption error 2025-08-05 11:46:29 -04:00
Vitor Pamplona
be41627c2b Fixes geohash cache saving 2025-08-05 10:45:20 -04:00
Vitor Pamplona
426378a7ee Merge pull request #1402 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-05 09:55:06 -04:00
Crowdin Bot
f9efc0ae2d New Crowdin translations by GitHub Action 2025-08-05 13:51:50 +00:00
Vitor Pamplona
09dfa79dea Merge pull request #1395 from davotoula/refactor-decrypt-and-index-processor-light
Refactor decrypt and index processor
2025-08-05 09:50:12 -04:00
Vitor Pamplona
8af955ce20 Adds an extra layer on the url normalization cache to avoid recomputing failed attempts before. 2025-08-04 18:18:39 -04:00
Vitor Pamplona
5b0fccb9e0 Merge pull request #1401 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-04 11:44:47 -04:00
Crowdin Bot
bef607ec41 New Crowdin translations by GitHub Action 2025-08-04 15:14:07 +00:00
Vitor Pamplona
bfbdc65bef Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-08-04 11:09:24 -04:00
Vitor Pamplona
cfb2f98f66 Merge pull request #1396 from davotoula/dynamic-version-number-based-on-branch-name
Dynamic version number based on branch name
2025-08-03 12:29:56 -04:00
David Kaspar
fb0597b2f4 Merge pull request #1399 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-03 18:24:23 +02:00
Crowdin Bot
c663fd0f5a New Crowdin translations by GitHub Action 2025-08-03 16:22:02 +00:00
David Kaspar
2a8b258f80 Merge pull request #1398 from davotoula/update-translations
update DE
2025-08-03 18:20:53 +02:00
David Kaspar
9c0663db69 update DE 2025-08-03 18:16:24 +02:00
David Kaspar
8d8edbaf90 executes a Git command to get the current branch name.
If the branch is "main" or "master", returns the base version unchanged
For feature branches, appends the branch name to the version
2025-08-03 11:47:26 +02:00
David Kaspar
5420ddd338 lets pass LocalCache instead of account.cache, as it was before 2025-08-03 10:35:40 +02:00
David Kaspar
fa80cb297d reduce lint warnings 2025-08-03 10:32:04 +02:00
David Kaspar
cdfb1db253 refactor DecryptAndIndexProcessor
EventProcessor: Orchestrates the processing flow
DecryptionService: Handles all decryption operations
IndexingService: Manages event indexing and relationships
ChatroomService: Manages chatroom operations
2025-08-03 10:29:05 +02:00
David Kaspar
52b87fa17a Merge pull request #1394 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-03 10:11:31 +02:00
Crowdin Bot
f209b17811 New Crowdin translations by GitHub Action 2025-08-03 07:36:52 +00:00
David Kaspar
a3d38aab1b Merge pull request #1391 from davotoula/update-translations
Update translations. CZ, SE, PT, DE
2025-08-03 09:35:39 +02:00
Vitor Pamplona
52df3cba02 Updates junit, benchmark, media3, expresso, compose, and crypto 2025-08-01 18:48:31 -04:00
David Kaspar
bb8a71f47b Update translations. CZ, SE, PT, DE 2025-08-01 23:37:14 +02:00
Vitor Pamplona
d78d3aeab3 Merge pull request #1390 from davotoula/upgrade-agp
Upgrade agp to 8.12.0
2025-08-01 17:30:59 -04:00
Vitor Pamplona
7f6435ba57 Updates build tools to java 21 2025-08-01 17:07:31 -04:00
Vitor Pamplona
1a58603477 Merge pull request #1389 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-08-01 16:58:20 -04:00
David Kaspar
4b36da59ec Upgrade agp to 8.12.0 2025-08-01 22:55:28 +02:00
Crowdin Bot
9257305820 New Crowdin translations by GitHub Action 2025-08-01 20:40:51 +00:00
Vitor Pamplona
e414c4be98 Merge pull request #1388 from vitorpamplona/full-outbox
Full outbox
2025-08-01 16:39:30 -04:00
Vitor Pamplona
340760513f Better handling index cleanup when draft events get deleted 2025-08-01 16:11:24 -04:00
Vitor Pamplona
eed4350bca Simplifying badge composables 2025-08-01 16:10:34 -04:00
Vitor Pamplona
6b946a57c2 Moves the exceptions to our own signing exceptions 2025-08-01 16:09:52 -04:00
Vitor Pamplona
26cadfee88 Fixes the index after deletions 2025-08-01 11:40:19 -04:00
Vitor Pamplona
4cd24e5363 No need for not null checks 2025-08-01 09:57:33 -04:00
Vitor Pamplona
e715a74bc4 Better indexes notes in channels and chatrooms 2025-08-01 08:30:44 -04:00
Vitor Pamplona
42738e72f7 fixes test case 2025-07-31 19:14:43 -04:00
Vitor Pamplona
add8f8eba2 Improves the rendering of the node id 2025-07-31 19:12:10 -04:00
Vitor Pamplona
e82c09e1c3 Finishes the migration of adressable ids on the local cache. 2025-07-31 19:11:55 -04:00
Vitor Pamplona
e1b5f0f7b5 Renames chatroom and message fields 2025-07-31 19:11:08 -04:00
Vitor Pamplona
7e9d5d6063 Uses new tag model on the ZapEvent 2025-07-31 19:06:34 -04:00
Vitor Pamplona
141f0ed63b Removes unused items from Node 2025-07-31 19:06:09 -04:00
Vitor Pamplona
c5645b0ae7 Removes suspend of NostrSignerSync-based functions 2025-07-31 17:45:44 -04:00
Vitor Pamplona
fe8f519dbf Correctly logs off from the current account when creating new accounts from inside an existing account. 2025-07-31 17:45:21 -04:00
Vitor Pamplona
14e860928b Improves names of the repost methods 2025-07-31 17:44:04 -04:00
Vitor Pamplona
ba460e011e Adds a lower boundary for inbox from random relays. 2025-07-31 12:22:22 -04:00
Vitor Pamplona
12f793ad51 The gentle shutdown doesn't really work. There could be too many send messages waiting to be processed. 2025-07-31 12:21:44 -04:00
Vitor Pamplona
504d582d54 Tuning limits for Global feeds 2025-07-31 11:31:36 -04:00
Vitor Pamplona
a59cd590bc Deletes these very confusing mocks into account viewmodels for the test 2025-07-30 20:20:56 -04:00
Vitor Pamplona
2e9696745a Avoids sending filters on new connections if the client is not active 2025-07-30 18:23:02 -04:00
Vitor Pamplona
5d4b16641e Fixes count call 2025-07-30 18:11:11 -04:00
Vitor Pamplona
1ddc9c216b Marks which relays have been sending unsupported events. 2025-07-30 18:06:24 -04:00
Vitor Pamplona
349dfe3d42 Slowdown updates to random relay requests. 2025-07-30 18:05:53 -04:00
Vitor Pamplona
521a41d81b avoids logging with sub ms times 2025-07-30 17:50:21 -04:00
Vitor Pamplona
c6e103ff87 Delete unnecessary logs 2025-07-30 17:33:56 -04:00
Vitor Pamplona
e70f1fedc6 Adds a coroutine to the relay class so that we can defer processing events 2025-07-30 17:33:12 -04:00
Vitor Pamplona
651ca9a3ef No need to update status on EOSE 2025-07-30 17:31:38 -04:00
Vitor Pamplona
bd7dba1a31 Fixes more random relay hint errors 2025-07-30 17:31:24 -04:00
Vitor Pamplona
cf1d66bc01 new parameter to only log when a given amount of ms have passed. 2025-07-30 16:50:36 -04:00
Vitor Pamplona
414e933686 Only trigger new filters if this has changed 2025-07-30 16:49:48 -04:00
Vitor Pamplona
fa53ff2c78 Adds an iMeta cache for Picture and Video kinds and speeds up the procedure to verify if a video or picture is supported. 2025-07-30 12:47:31 -04:00
Vitor Pamplona
2c6a084808 Makes sure the NostrClient stays off in the background 2025-07-30 10:37:30 -04:00
Vitor Pamplona
8abba9246c Adds support to reply to Yaks with another voice message. 2025-07-29 20:07:28 -04:00
Vitor Pamplona
c9de32c3fa fixes voice track bugs 2025-07-29 15:08:47 -04:00
Vitor Pamplona
35a0b04020 Adds view support for public messages 2025-07-29 15:03:44 -04:00
Vitor Pamplona
4cc5aaa49d Adds view support for YakBak 2025-07-29 15:02:16 -04:00
Vitor Pamplona
bbf6e02609 Beginnings for the migration to DataStore 2025-07-28 17:13:57 -04:00
Vitor Pamplona
7ba2810423 Quick event store using SQLite directly. 2025-07-28 16:49:39 -04:00
Vitor Pamplona
88f9beafd3 Faster expiration calculation and tag array serialization 2025-07-28 16:48:42 -04:00
Vitor Pamplona
0116bc30e4 Creates a TagArray deserializer that interns all strings to avoid duplicated memory 2025-07-28 10:53:55 -04:00
Vitor Pamplona
a59fe59657 Sending eventDB companion to avoid recreating it all the time 2025-07-27 16:52:28 -04:00
Vitor Pamplona
e6dfa07526 Fixes expiration builder 2025-07-27 16:51:59 -04:00
Vitor Pamplona
8e3594cd75 Adds a way to delete events by address only 2025-07-27 16:25:24 -04:00
Vitor Pamplona
d5bbf4b00b Adds basic events to the seacheable interface 2025-07-27 16:25:09 -04:00
Vitor Pamplona
dc68d6025e Adds support for the Request to Vanish NIP - 62 2025-07-27 16:24:10 -04:00
Vitor Pamplona
f8f59abdf1 Small refactoring 2025-07-25 14:33:14 -04:00
Vitor Pamplona
3a37d5e42d Fixes the benchmark testing cases 2025-07-25 14:29:14 -04:00
Vitor Pamplona
ca9777be83 Creates a kind type alias 2025-07-25 12:42:19 -04:00
Vitor Pamplona
e7a9890eb1 Fixes quartz test cases for the new structure 2025-07-25 12:41:43 -04:00
Vitor Pamplona
39152d4e90 Makes the metadata event and contact list addressables 2025-07-25 12:40:55 -04:00
Vitor Pamplona
96e26cfb7d Adds a parser and assembler for NIP47 2025-07-25 12:39:55 -04:00
Vitor Pamplona
8844bb73ea Defines a searcheable interface for events to implement and provide content for a full text search 2025-07-25 12:39:38 -04:00
Vitor Pamplona
180a1eb1db Refactors the use of Gzip 2025-07-25 12:39:11 -04:00
Vitor Pamplona
b77127df77 Minor adjustment 2025-07-24 11:05:08 -04:00
Vitor Pamplona
803b231aaa Moves firebase to the main module 2025-07-24 10:16:56 -04:00
Vitor Pamplona
b27a12b9b5 Finally solved the issue with @aar in gradle 2025-07-24 10:11:36 -04:00
Vitor Pamplona
e7f04ad38d updates spotless 2025-07-24 10:01:20 -04:00
Vitor Pamplona
25e92666e0 Adjusts the gradle DSL language to the latest deprecations 2025-07-24 09:53:00 -04:00
Vitor Pamplona
e76bffd048 Updates coil, compose, firebase, jackson, lifecyle, mockk, navigation 2025-07-24 09:37:40 -04:00
Vitor Pamplona
558a060994 Merge remote-tracking branch 'origin/HEAD' into full-outbox
# Conflicts:
#	gradle/libs.versions.toml
2025-07-24 09:27:01 -04:00
Vitor Pamplona
14055894dc Refactoring in the relay url normalizer 2025-07-24 09:22:24 -04:00
Vitor Pamplona
a494257a27 Adds Proxy relay list and adds support for fixed list of relays. 2025-07-23 19:09:04 -04:00
Vitor Pamplona
1084d2a063 Adds an indexer relay list 2025-07-23 13:19:46 -04:00
Vitor Pamplona
57e4d76da0 Adds broadcasting relay section 2025-07-23 11:35:25 -04:00
Vitor Pamplona
5ac4314e2a Improvements to the online check 2025-07-23 11:33:43 -04:00
Vitor Pamplona
a7dbd787f4 Creates a garbage collector-friendly note cache. 2025-07-23 10:50:51 -04:00
Vitor Pamplona
3a0d7ddac1 Removes unnecessary spec in Channel 2025-07-23 09:26:07 -04:00
Vitor Pamplona
c221506e78 Fixes square loading image on Follow packs 2025-07-23 08:50:14 -04:00
Vitor Pamplona
064a732ec8 Fixes leaving and joining channels 2025-07-23 08:44:41 -04:00
Vitor Pamplona
95f463b0aa Showing ephemeral chats in the messages list.
Fixing a bug of the last message with unloaded event
2025-07-22 20:12:43 -04:00
Vitor Pamplona
6d4708bbeb Implements a partial page system to load feeds. 2025-07-22 19:02:23 -04:00
Vitor Pamplona
1d9b2cffa2 Filtering Addressable notes from Notification's note stack 2025-07-22 15:05:17 -04:00
Vitor Pamplona
6c77bf94a8 Fixing mark as read 2025-07-22 14:09:47 -04:00
Vitor Pamplona
37ff0070ee Solves the hanging AccountViewModel scope when Amethyst is forced closed. 2025-07-22 13:38:30 -04:00
Vitor Pamplona
0f13202f84 Corrects a bug of not showing the user's own messages on chat 2025-07-22 12:46:05 -04:00
Vitor Pamplona
a3ec4d4260 Completing Intent writing implementation 2025-07-22 10:54:24 -04:00
Vitor Pamplona
e6b5d51cda Fixes NIP-55 return as a list of results on the intent. 2025-07-22 10:34:42 -04:00
Vitor Pamplona
8fe13717dd Fixes trying to parse empty addresses 2025-07-22 10:34:09 -04:00
Vitor Pamplona
c7e81d1378 Refactoring Channel modules 2025-07-22 10:33:50 -04:00
Vitor Pamplona
8e6d71e422 Minimizes the dependency on the Account further by moving Tor settings to a tor Privacy State 2025-07-21 19:09:06 -04:00
Vitor Pamplona
95c6dcce5e Moves Ots to its own state class 2025-07-21 17:34:48 -04:00
Vitor Pamplona
6066c7d06d Removes relay lists from notifications 2025-07-21 17:33:31 -04:00
Vitor Pamplona
1c5acc00f3 More appropriate limits 2025-07-21 10:11:03 -04:00
David Kaspar
d8fd9f127d Merge pull request #1387 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-07-20 16:12:05 +02:00
Crowdin Bot
0037d642b0 New Crowdin translations by GitHub Action 2025-07-20 14:11:35 +00:00
Vitor Pamplona
3b0c1b6d4f Merge pull request #1386 from davotoula/upgrade-agp
Upgrade agp to 8.11.1
2025-07-20 10:10:29 -04:00
David Kaspar
4855e36e4c Upgrade agp to 8.11.1 2025-07-20 11:46:26 +02:00
Vitor Pamplona
8a3bb6e1ae Significantly reduces the limit in our filters to reduce the amount of duplicated events coming from multiple relays. 2025-07-18 18:35:52 -04:00
Vitor Pamplona
13ce0e7311 Breaks down the amount of reactions for each post to download from the amount of reactions to communities and chats themselves (which is much larger) 2025-07-18 18:34:54 -04:00
Vitor Pamplona
ee67f2ded9 Adds a subscription to download notifications from random relays in case users are not outbox ready yet. 2025-07-18 18:33:50 -04:00
Vitor Pamplona
19327b682c Improves external signer logs 2025-07-18 17:51:43 -04:00
Vitor Pamplona
85dd9dd5a4 Fixes screen clean up in Login 2025-07-18 17:47:56 -04:00
Vitor Pamplona
9d9963d324 Fixes saving old version of event in to the cache. 2025-07-18 17:47:11 -04:00
Vitor Pamplona
58e353fb40 Moves NIP-55 calls to be suspending functions.
Moves NIP-55 calls to include an ID per call, not per event.
Adds error handling facilities to the Signer functions.
Moves the indexing of the decrypted objects to outside the LocalCache
Migrates Signers to become suspending functions.
Migrates Decryption caching systems to outside the Events themselves.
Migrates all NIP-51 lists to the new structure.
Migrates Drafts and NIP-04 and NIP-17 DMs to the new structure
Migrates Bookmarks to the new structure.
Changes the Room route to avoid using hashcode.
2025-07-18 17:17:27 -04:00
Vitor Pamplona
7bdb3a1a18 Fixing method name 2025-07-14 11:50:15 -04:00
Vitor Pamplona
850277fdc1 Optimize imports 2025-07-14 11:29:21 -04:00
Vitor Pamplona
597fec5c43 Removes lazy StateFlows due to inconsistent starting values. 2025-07-14 11:19:12 -04:00
Vitor Pamplona
909eb62db6 Moves private zap decryption to async, all at once. 2025-07-14 11:18:56 -04:00
Vitor Pamplona
510583e72e Rewrites NIP-55 to start offering better error handling. 2025-07-14 11:17:59 -04:00
Vitor Pamplona
f93b08a272 Refactors master serializer name from EventMapper to JsonMapper 2025-07-11 17:10:51 -04:00
Vitor Pamplona
bb5ae5d09e Fixes F-droid compilation due to a duplicated class from a unifiedpush crypto bug 2025-07-11 14:55:13 -04:00
Vitor Pamplona
02a9563de6 Updates spotless 2025-07-11 14:33:43 -04:00
Vitor Pamplona
15efd671ec Reorganizes navigation and unifies topbar designs 2025-07-11 14:05:31 -04:00
Vitor Pamplona
e37d6ebd8b Moves media server from Dialog to Screen with full route. 2025-07-11 09:22:27 -04:00
Vitor Pamplona
2ecfeb6e7f Moves subscription management from Ammolite to Quatrz 2025-07-11 08:51:02 -04:00
Vitor Pamplona
a94c8673d5 Fixing Ephemeral Chat interface 2025-07-10 19:42:18 -04:00
Vitor Pamplona
e3acf4c250 Fixes some of the live stream flags when the event doesn't update. 2025-07-10 18:22:38 -04:00
Vitor Pamplona
dc61c5a8e4 Keeps cache of the content inside Public chat forums 2025-07-10 18:08:32 -04:00
Vitor Pamplona
f3e08bd297 No need for this OkHttp log anymore 2025-07-10 18:08:09 -04:00
Vitor Pamplona
62253d8601 Finish the migration to separate channels 2025-07-10 18:07:57 -04:00
Vitor Pamplona
5daaa6364d Fixes issue with unable to parse text-based content in the event. 2025-07-10 18:07:01 -04:00
Vitor Pamplona
a706005cfd Fixes recent label reuse 2025-07-10 18:06:30 -04:00
Vitor Pamplona
4eb348654f Providing a delete fix for empty drafts. 2025-07-10 17:04:19 -04:00
Vitor Pamplona
191f1344db Starts diskcache together with video cache 2025-07-10 17:02:31 -04:00
Vitor Pamplona
a207d80d55 Separating Reports from Metadata watching 2025-07-10 15:51:57 -04:00
Vitor Pamplona
94a7d9cf14 Makes sure the list is not empty before building the filter 2025-07-10 14:52:27 -04:00
Vitor Pamplona
f1d39b8517 Logs the relays used for deletion events and checks if it hasn't sent the deleted event to each relay already 2025-07-10 14:36:31 -04:00
Vitor Pamplona
7a14d95973 Adds more relays to the DVM inbox.
Sends the new EVENT only after the screen updates.
2025-07-10 14:34:09 -04:00
Vitor Pamplona
8dbb1a889e Avoids displaying loading symbol on our cached AsyncImage implementation 2025-07-10 11:49:16 -04:00
Vitor Pamplona
ca41d4e10a Refactoring 2025-07-10 11:13:52 -04:00
Vitor Pamplona
ee8ffcd03d Fixes some borders of preview cards 2025-07-10 11:08:39 -04:00
Vitor Pamplona
f9189e9ec1 Further separates channels by kind in the UI 2025-07-10 10:57:42 -04:00
Vitor Pamplona
ec985d58e7 Fixes matching image borders in the loading step of preview cards 2025-07-10 09:43:36 -04:00
Vitor Pamplona
a2d16c173c Fixing crash when the last : is not present in an address 2025-07-10 09:38:53 -04:00
Vitor Pamplona
123803edd3 Fixes reply count 2025-07-09 17:48:19 -04:00
Vitor Pamplona
35005dddad Adds checks in the filter to highlight issues with empty or invalid filters 2025-07-09 17:20:59 -04:00
Vitor Pamplona
accc8d4439 Brute forces the finding of events. 2025-07-09 16:57:26 -04:00
Vitor Pamplona
267d4c505a Moves OkHttp to the coroutines version and turns all their usage into suspend functions 2025-07-09 15:14:58 -04:00
Vitor Pamplona
b502ab7de8 Fixes issue on saving NIP47 address 2025-07-09 15:14:25 -04:00
Vitor Pamplona
7eb39b3c4c Readjusts relay view on profile 2025-07-09 09:03:56 -04:00
Vitor Pamplona
17e33c61e7 Refactoring base EOSE managers to provide better logging 2025-07-09 09:03:45 -04:00
Vitor Pamplona
6851b1bb51 Only closes after removing the subscriptions to avoid resubscribing while closing. 2025-07-09 08:37:21 -04:00
Vitor Pamplona
b12e675b3a Refactors the name of the simple relay client 2025-07-09 08:36:50 -04:00
Vitor Pamplona
cc02806390 Fixing weird version number on the left drawer 2025-07-08 19:27:43 -04:00
Vitor Pamplona
2ee1285ea1 Removes old follows in Contact lists. 2025-07-08 19:16:28 -04:00
Vitor Pamplona
c5ed0cfa60 Improves Image load screens in the preview cards 2025-07-08 18:43:10 -04:00
Vitor Pamplona
4084a72178 Minor refactoring in Account 2025-07-08 18:17:18 -04:00
Vitor Pamplona
2c3e641639 Merge branch 'full-outbox' of nostr://vitorpamplona.com/relay.ngit.dev/amethyst into full-outbox
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
2025-07-08 18:06:38 -04:00
Vitor Pamplona
8e0b94646a Includes Trusted Relays in all my relays 2025-07-08 18:05:54 -04:00
Vitor Pamplona
8c4ec1dc7d Includes Trusted Relays in all my relays 2025-07-08 17:52:56 -04:00
Vitor Pamplona
e89a04eb56 Makes sure the metadata and the Advertised Relay list goes to every connected relay. 2025-07-08 17:21:59 -04:00
Vitor Pamplona
8d14f3615d Improves Bech32 parser to consider only the 58 chars of nsec1, npub1 and note1. 2025-07-08 15:22:55 -04:00
Vitor Pamplona
dbb9ee5379 Removing all the default since logic 2025-07-08 15:22:31 -04:00
Vitor Pamplona
0be9447bee Reusing the parsed event to reuse cache when computing relay hints. 2025-07-08 15:22:09 -04:00
Vitor Pamplona
0d174aae30 Refining the outbox relay selection with all hints. 2025-07-08 14:34:52 -04:00
Vitor Pamplona
994363602d Turns the relay information dialog into a route 2025-07-08 12:00:17 -04:00
Vitor Pamplona
296fa201f7 Delete Relay Selection dialog from all new post screens. 2025-07-08 11:28:30 -04:00
Vitor Pamplona
a05b2681ad 1. Fixes crash when opening the relay selection dialog
2. Fixes color and size of ping labels on relays
3. Removes delete button for connected relay view.
2025-07-08 10:20:54 -04:00
Vitor Pamplona
1b86c3d85b Fixes the sending of multiple old events back to the relays that send old events to Amethyst. 2025-07-08 09:22:33 -04:00
Vitor Pamplona
2c90aa4b1e Continues the migration to use full parsed Addresses instead of just addressIds on the Local Cache. Moves the community route to take a full address. 2025-07-08 08:54:13 -04:00
Vitor Pamplona
0fe8f6c78c Improves debouncing when changing relays. 2025-07-07 20:40:29 -04:00
Vitor Pamplona
b800b5a664 Early support for Blocked and Trusted Relay Lists 2025-07-07 19:49:23 -04:00
Vitor Pamplona
d084714a21 Avoids logging all REQs by default. 2025-07-07 17:19:37 -04:00
Vitor Pamplona
ed2d7f57c5 Adds the incoming relay as a hint to all related keys, addresses and event ids. 2025-07-07 17:02:42 -04:00
Vitor Pamplona
eb5d113298 new map of set api 2025-07-07 13:48:39 -04:00
Vitor Pamplona
cf48516602 Makes sure Filters are not updated if the since is the only change 2025-07-07 13:37:57 -04:00
Vitor Pamplona
28d182cc55 Updates tor, okhttp and navigation compose 2025-07-03 18:26:57 -04:00
Vitor Pamplona
7eeb2ed66a Fixes count call mistake 2025-07-03 18:26:34 -04:00
Vitor Pamplona
1e8ca6c5f0 Merge remote-tracking branch 'origin/HEAD' into full-outbox 2025-07-03 17:22:38 -04:00
Vitor Pamplona
9e72f5d55e Fixes icon size cache on the User Profile 2025-07-03 17:14:07 -04:00
David Kaspar
1db628719b Merge pull request #1384 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-07-03 22:04:11 +02:00
Vitor Pamplona
e4ebca29f4 Improves RelayHint normalization to make sure the NormalizedRelayUrls are indeed relays 2025-07-03 15:45:46 -04:00
Crowdin Bot
67ae61e5d4 New Crowdin translations by GitHub Action 2025-07-03 19:13:48 +00:00
Vitor Pamplona
64bad2b476 Better logs for the basic relay 2025-07-03 13:08:50 -04:00
Vitor Pamplona
cfbf113910 Fixes crash on lazy flows when uploading images before the relay list is loaded. 2025-07-03 13:08:27 -04:00
Vitor Pamplona
47e69ce045 Improves caching on the allRelays call on the Nostr Client 2025-07-03 10:30:53 -04:00
Vitor Pamplona
da3dbb0321 Fixes the Account switching issue that was calling onDispose AFTER a new user composition was run and thus closing all connections on switching 2025-07-03 10:30:19 -04:00
Vitor Pamplona
1f4ad32d23 Fixes the clearing of the mapping function between users and subscription ids when subscriptions end 2025-07-03 10:29:17 -04:00
Vitor Pamplona
69db9bdcd5 Reviews onDispose calls to make sure the onDispose is disposing the data from the previous composable, since it can happen AFTER a new composition already took place. So the references must match the old composition. 2025-07-03 10:27:47 -04:00
Vitor Pamplona
5bcaf065a3 Fixing logs for the new list of connected relays. 2025-07-03 10:19:33 -04:00
Vitor Pamplona
1fa2592563 Improves the performance and logging structures for the anti-spam filter. 2025-07-02 19:36:33 -04:00
Vitor Pamplona
ad481e1928 Removes another local relay from the list 2025-07-02 17:59:14 -04:00
Vitor Pamplona
5525bc4bd9 Increasing the thread pool for OkHttp in order to sustain more connections. 2025-07-02 17:42:46 -04:00
Vitor Pamplona
0b102df117 Removes local relays from NIP65 lists. 2025-07-02 15:50:50 -04:00
Vitor Pamplona
826718d688 Fixes author id on the outbox loader filter. 2025-07-02 15:35:47 -04:00
Vitor Pamplona
3b260d98ad Marks if it is using Tor in each relay of the list. 2025-07-02 15:15:19 -04:00
Vitor Pamplona
e499410ddb Fixes bug when the string has emojis and is trying to check if it is a hex or not 2025-07-02 15:12:48 -04:00
Vitor Pamplona
11d9af41bd Normalizes the click to open relay info dialog 2025-07-02 14:52:16 -04:00
Vitor Pamplona
67dc396588 Displays all available and connected relays in the relay screen 2025-07-02 14:52:03 -04:00
Vitor Pamplona
d0e66504e4 Improves Relay Icon flow 2025-07-02 14:16:26 -04:00
Vitor Pamplona
8c11ae5c87 Avoids adding local relays to the hint index. 2025-07-02 14:13:36 -04:00
Vitor Pamplona
abd895480d adds local servers to the "local definition" of the Normalized Url 2025-07-02 14:13:09 -04:00
Vitor Pamplona
a1a2e86c94 Reports don't offer hints 2025-07-02 14:12:24 -04:00
Vitor Pamplona
83899bda5a Improves RelayInformation document with an empty one 2025-07-02 14:11:45 -04:00
Vitor Pamplona
29b70a52d4 Improves logs 2025-07-02 14:11:26 -04:00
Vitor Pamplona
dd5c2ca704 Making sure the order of params is right 2025-07-02 13:35:01 -04:00
Vitor Pamplona
ce7965d00e Improves starting value of flows 2025-07-02 12:51:08 -04:00
Vitor Pamplona
bb55aea4a4 Fixing log messages 2025-07-02 12:50:17 -04:00
Vitor Pamplona
546d238db3 Reformats due to new lint 2025-07-02 10:51:13 -04:00
Vitor Pamplona
774ee20a20 Updates all dependencies 2025-07-02 10:51:03 -04:00
Vitor Pamplona
1c4b2e9d1a Recover Send and Wait 2025-07-02 08:59:12 -04:00
Vitor Pamplona
b236938ef5 Simplify the unregistering instead of relying on errors and other states that might come from separate filters to the same relay 2025-07-02 08:59:03 -04:00
Vitor Pamplona
1615337a2c Simplifies new try 2025-07-02 08:58:16 -04:00
Vitor Pamplona
936b17f54a Merge remote-tracking branch 'origin/HEAD' into full-outbox
# Conflicts:
#	ammolite/src/main/java/com/vitorpamplona/ammolite/relays/NostrClient.kt
#	gradle/libs.versions.toml
2025-07-02 08:23:54 -04:00
Vitor Pamplona
0cfcfaf899 3rd large migration to outbox. 2025-07-01 20:38:18 -04:00
Vitor Pamplona
f788d031c9 Merge pull request #1380 from davotoula/684-add-auto-translate-config-to-settings
Add auto translate config to new user specific settings
2025-07-01 08:43:46 -04:00
Vitor Pamplona
ddc50f379d Merge pull request #1381 from greenart7c3/main
Propagate Auth event to `sendAndWaitForResponse`
2025-06-30 08:11:58 -04:00
greenart7c3
bfe5fcf0b1 - Propagate Auth event to sendAndWaitForResponse
- Pass feed types to `sendSingle` when sending signed event
2025-06-30 08:11:10 -03:00
David Kaspar
fb9c070ee6 One more line for description 2025-06-29 18:33:30 +01:00
David Kaspar
285603cc0f added trailing x icon to menu items to clarify removal 2025-06-29 18:28:57 +01:00
David Kaspar
65cca8aa34 Don't show users own languages
TODO:
Add red x to items
Rename Settings to AppSettings (routes, classes)
Add explainer
2025-06-29 17:30:02 +01:00
David Kaspar
6015ea04ce remove selected language from dropdown
TODO:
Rename Settings to AppSettings (routes, classes)
Don't show users own languages
Add explainer
2025-06-26 13:27:32 +02:00
David Kaspar
fdf11d3ca3 revert dropdown
TODO:
Rename Settings to AppSettings (routes, classes)
Don't show users own languages
Remove language from drop down
Add explainer
2025-06-26 09:14:58 +02:00
David Kaspar
5620eb023b added preview for UserSettingsScreen.kt
TODO:
Rename Settings to AppSettings (routes, classes)
Don't show users own languages
Remove language from drop down
Add explainer
2025-06-26 08:56:23 +02:00
David Kaspar
759ef44440 Remove previous implementation
TODO:
Rename Settings to AppSettings (routes, classes)
Don't show users own languages
Remove language from drop down
Add explainer
Change to dropdown with Xs
2025-06-26 08:16:09 +02:00
David Kaspar
f0670971ef Wrap update call in IO scope
TODO:
Don't show users own languages
Remove language from drop down
Add explainer
2025-06-25 20:51:11 +02:00
David Kaspar
c59086068b Move dont translate settings to the dedicated user settings page 2025-06-25 20:42:29 +02:00
David Kaspar
3524f7e412 Added support for multiple icons for a single row (backwards compatible) 2025-06-25 20:18:11 +02:00
David Kaspar
2e2a9bb917 Added user preferences screen 2025-06-25 18:13:43 +02:00
David Kaspar
a5c370133c Change to show excluded languages in dropdown, no rows below
TODO:
Load actually excluded languages
Optimise code
Improve UI
2025-06-25 17:34:14 +02:00
David Kaspar
0495dc88fc Add dropdown with auto translate language excludes to app settings
Persist changes to settings

TODO:
Load actually excluded languages
Optimise code
Improve UI
2025-06-25 17:34:14 +02:00
Vitor Pamplona
f536abb22a Merge pull request #1378 from davotoula/main
Upgrade AGP and Gradle
2025-06-25 11:22:33 -04:00
David Kaspar
0bae8adebc Upgrade AGP to 8.11.0
Upgrade Gradle to 8.13
2025-06-25 17:18:05 +02:00
Vitor Pamplona
3270254f47 Merge pull request #1377 from davotoula/strip-codec-from-mime-type-before-saving-video
bugfix: only user part before ";" in media-type before passing to ContentResolver
2025-06-25 10:53:16 -04:00
David Kaspar
dacc9f236b strip anything after ";" in media-type before passing to ContentResolver 2025-06-25 16:33:28 +02:00
David Kaspar
cd316fb52f Merge pull request #1376 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-06-25 14:20:16 +02:00
Crowdin Bot
bf7638eaa9 New Crowdin translations by GitHub Action 2025-06-25 12:02:19 +00:00
Vitor Pamplona
3f049a0536 Merge pull request #1375 from davotoula/fix-build-issue-android-kotlin-geohash
fix build: change to version b481c6a64e of android-kotlin-geohash
2025-06-25 08:00:55 -04:00
David Kaspar
86bfca99e6 change to version b481c6a64e of android-kotlin-geohash as 1.0 has disappeared from jitpack.io 2025-06-25 11:41:28 +02:00
Vitor Pamplona
e7dec1702d Merge pull request #1369 from alltheseas/patch-2
Update README.md with deepwiki link
2025-06-19 09:48:04 -04:00
Vitor Pamplona
4a848b3296 Merge pull request #1372 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-06-19 09:47:52 -04:00
Crowdin Bot
2d9348b640 New Crowdin translations by GitHub Action 2025-06-19 13:46:40 +00:00
Vitor Pamplona
44108c7410 Merge pull request #1371 from greenart7c3/main
Add a UnsignedEventDeserializer
2025-06-19 09:45:19 -04:00
greenart7c3
9e58c2d3b9 Use EventTemplate instead of Event in BunkerRequestSign 2025-06-18 17:24:43 -03:00
greenart7c3
acf426ccdc Add a UnsignedEventDeserializer 2025-06-18 13:28:43 -03:00
alltheseas
2889c5dae5 Update README.md with deepwiki link
added link to amethyst deepwiki
2025-06-09 17:13:35 -05:00
KotlinGeekDev
a38d198b70 Use background coroutines for modification operations. Use FollowSet in callbacks, rather than just the set identifier. 2025-06-07 02:42:27 +01:00
KotlinGeekDev
af2b2328cf Generalise the implementation of PeopleListEvent.removeTag() to also support public list events. Use defaults in already dependent code. 2025-06-06 18:34:45 +01:00
Vitor Pamplona
8276156f0a Merge pull request #1367 from davotoula/upgrade-agp
Upgrade AGP to 8.10.1
2025-06-05 13:02:06 -04:00
David Kaspar
6a544340cb Upgrade AGP to 8.10.1 2025-06-05 18:52:36 +02:00
KotlinGeekDev
94243f4060 Make adjustments to UI code following the ViewModel method signatures. 2025-06-04 21:41:53 +01:00
Vitor Pamplona
5ee64b31d2 Merge pull request #1366 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-06-02 11:36:03 -04:00
Crowdin Bot
ec1dd3cc6e New Crowdin translations by GitHub Action 2025-06-02 15:28:22 +00:00
Vitor Pamplona
ef6313696d Merge pull request #1365 from davotoula/remove-unused-resource-ids-from-translated-files
remove unused string ids from translations
2025-06-02 11:27:07 -04:00
David Kaspar
ffeeebfe1e remove unused string ids from translations 2025-06-02 16:12:42 +02:00
KotlinGeekDev
63177b15a1 Make adjustments to UI code following the ViewModel method signatures. 2025-05-30 20:26:42 +01:00
KotlinGeekDev
69ae42d344 Implement relevant methods in NostrListFeedViewModel. Remove unnecessary subclass and bring ViewModelProvider in the actual VM itself. 2025-05-30 20:21:45 +01:00
Vitor Pamplona
fcde05c91e Merge pull request #1363 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-30 14:42:48 -04:00
Crowdin Bot
6b1662da24 New Crowdin translations by GitHub Action 2025-05-30 18:40:22 +00:00
Vitor Pamplona
5925a91e62 Merge pull request #1360 from davotoula/update-string-ids
update string ids to force new translations
2025-05-30 14:39:03 -04:00
KotlinGeekDev
0b3380ac6e Use dTag instead of the note's address. 2025-05-30 19:03:21 +01:00
KotlinGeekDev
af0061ae09 Add new methods for list management to PeopleListEvent. 2025-05-30 13:57:23 +01:00
David Kaspar
d83e8007dc Merge pull request #1362 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-28 08:29:43 +02:00
Crowdin Bot
173a27f085 New Crowdin translations by GitHub Action 2025-05-28 06:20:54 +00:00
David Kaspar
a970080478 Merge pull request #1361 from davotoula/remove-unused-imports
remove unused imports amethyst
2025-05-28 08:19:40 +02:00
David Kaspar
293bd2557b remove unused imports amethyst 2025-05-28 07:42:20 +02:00
David Kaspar
dac1c3717a update string ids to force new translations 2025-05-27 18:49:35 +02:00
Vitor Pamplona
e10332c957 - Only saves draft after the first change in the UI.
- Adjusts coroutine scopes to make sure it cannot be cancelled while posting
2025-05-20 18:21:19 -04:00
Vitor Pamplona
de881c23ec Migrates button objects out of the new post screen file 2025-05-20 17:01:55 -04:00
Vitor Pamplona
fcfa2bb05a Making NewPost Screen to be about Kind 1 and polls only 2025-05-20 16:30:35 -04:00
Vitor Pamplona
00048606fa Small refactoring 2025-05-20 15:22:53 -04:00
Vitor Pamplona
65bab50a6d Slight improvements on paddings and spacings 2025-05-20 15:18:32 -04:00
Vitor Pamplona
6d0b05e34d Activates NIP-22 reply for everything but kind 1s. 2025-05-20 15:00:00 -04:00
Vitor Pamplona
195e268865 Normalizes NIP-22 viewmodel to its own entity 2025-05-20 14:23:51 -04:00
Vitor Pamplona
bb1dfee35b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-05-20 11:54:29 -04:00
Vitor Pamplona
63340d79e2 - Adds support for creating and replying to NIP-22 geo scope posts
- Adds support for creating and replying to NIP-22 hashtag scope posts
- Refactors NIP-73 and NIP-22 on quartz to better support scoped replies
- Creates New post screens for both hashtags and geolocated posts.
2025-05-20 11:54:21 -04:00
Vitor Pamplona
041aa1c461 Fixes hashtag search i18n and padding 2025-05-20 11:50:24 -04:00
Vitor Pamplona
1594971a60 Merge pull request #1359 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-19 18:45:48 -04:00
Crowdin Bot
12b7cf410d New Crowdin translations by GitHub Action 2025-05-19 22:16:05 +00:00
Vitor Pamplona
a5e8bdd43a Fixes NIP-22 identifier for geotags 2025-05-19 17:42:16 -04:00
Vitor Pamplona
ba71c95bec Removes youtube links from the video feed 2025-05-19 17:41:50 -04:00
Vitor Pamplona
e5230d734c Merge pull request #1358 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-17 08:18:40 -04:00
Crowdin Bot
1a45e26eb8 New Crowdin translations by GitHub Action 2025-05-17 12:17:19 +00:00
Vitor Pamplona
1b81dfd832 Merge pull request #1357 from davotoula/remove-unused-imports
remove unused imports
2025-05-17 08:16:03 -04:00
David Kaspar
7ee2a02227 remove unused imports 2025-05-17 08:50:04 +02:00
Vitor Pamplona
8a59904bfc Fixes local cache strategy to create an option and not verify events from Seals. 2025-05-16 19:27:39 -04:00
Vitor Pamplona
049e1d15ce Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-05-16 17:03:04 -04:00
Vitor Pamplona
203b5e5f39 Align specific inner events with the new verification procedures inside the consume function 2025-05-16 17:02:35 -04:00
David Kaspar
0bb8349a46 Update README.md
Long form content view only
2025-05-16 21:33:05 +02:00
David Kaspar
725fb11ec5 Merge pull request #1356 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-16 20:29:04 +02:00
Crowdin Bot
c429a97bb4 New Crowdin translations by GitHub Action 2025-05-16 18:15:50 +00:00
Vitor Pamplona
ad12f80679 Upgrade Media3 to 1.7.1 2025-05-16 14:14:11 -04:00
Vitor Pamplona
d35e3f8daf Removing deprecated functions 2025-05-16 14:11:12 -04:00
Vitor Pamplona
780a1ef65e Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-05-16 14:05:20 -04:00
David Kaspar
c413659c87 Merge pull request #1355 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-16 19:48:21 +02:00
Crowdin Bot
23f42a025a New Crowdin translations by GitHub Action 2025-05-16 17:40:35 +00:00
David Kaspar
ef1ac76839 Update strings.xml
Fixed typo
2025-05-16 18:39:14 +01:00
Vitor Pamplona
141713bd35 Minor refactoring 2025-05-16 12:55:29 -04:00
Vitor Pamplona
b5a1b84fa8 Fixes logging of the memory cache usage 2025-05-16 12:48:01 -04:00
Vitor Pamplona
1a681e1b53 Deletes encrypted component of the GiftWrap after unwrapping to avoid consuming memory needlessly 2025-05-16 12:47:35 -04:00
Vitor Pamplona
07434eb4b6 Fixes newStack bug that was only resetting the stack until the home and not to its own route 2025-05-15 17:02:15 -04:00
Vitor Pamplona
ac7084ce84 Removing debug methods from the main binary 2025-05-15 16:40:44 -04:00
Vitor Pamplona
dc6f982ae9 Adds a byte count in the event per second logger 2025-05-15 15:33:53 -04:00
Vitor Pamplona
ea9d25d13b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-05-15 11:49:34 -04:00
Vitor Pamplona
afe04969ee Improves edge to edge margins and colors for the RelayInfoDialog 2025-05-15 11:49:25 -04:00
Vitor Pamplona
3823acef0b Merge pull request #1354 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-15 08:38:34 -04:00
Crowdin Bot
12ac1c48bb New Crowdin translations by GitHub Action 2025-05-15 12:37:54 +00:00
Vitor Pamplona
b345eca8ad Merge pull request #1350 from davotoula/share-media-to-other-apps
Share images to other apps
2025-05-15 08:36:23 -04:00
David Kaspar
c3c8b56a39 Merge branch 'main' into share-media-to-other-apps 2025-05-15 07:41:28 +02:00
Vitor Pamplona
42e20efaa5 removing unused imports 2025-05-14 20:39:01 -04:00
Vitor Pamplona
2ef1cb5fea Merge pull request #1353 from kirpy0x/NostrLinks
Open Njump, Yakihonne and Primal links in Amethyst
2025-05-14 20:33:55 -04:00
Vitor Pamplona
df2f0feef8 Improvement to the logs of RelaySpeedLogger 2025-05-14 20:32:33 -04:00
Vitor Pamplona
700813dab7 Moves LocalCache bundler to the default thread 2025-05-14 19:27:57 -04:00
kirpy
b925b12278 Open Njump, Yakihonne and Primal links in Amethyst
fixes: 1217
2025-05-14 17:23:58 -06:00
Vitor Pamplona
6367e9ddc9 Moving logs from SubscriptionController to the Manager to be able to distinguish between the multiple managers 2025-05-14 19:19:58 -04:00
Vitor Pamplona
dfdee1ae96 - Migrating a mix of tags from the old Tag array methods to the new Tag Object parsers.
- Starting to use fast methods for Tag array loops instead of the native kotlin ones.
2025-05-14 19:19:29 -04:00
Vitor Pamplona
c0ecda94a3 Uses fastAny to avoid creating an iterator 2025-05-14 18:29:03 -04:00
Vitor Pamplona
acf944ebac Avoids item drawing mishandling of multiple incognitos in the same chat screen 2025-05-14 18:28:49 -04:00
Vitor Pamplona
3c5b9dbca6 Fixes an issue with two home screens interfering with flow collections. For some reason it was only a problem because this route matches the starting route. 2025-05-14 17:00:36 -04:00
Vitor Pamplona
e0449a9d44 Adds a list state saver to the messages screen 2025-05-14 16:52:19 -04:00
Vitor Pamplona
4ae86861f8 Removes unnecessary labels 2025-05-14 15:17:31 -04:00
Vitor Pamplona
9e13e88ae9 removes log 2025-05-14 15:00:35 -04:00
Vitor Pamplona
1ed9aa64bf Gets rid of the update bundler for Notes, Users and Channel. 2025-05-14 15:00:06 -04:00
Vitor Pamplona
803decf5ff Move the EOSE cache from Note to the sub assembler 2025-05-14 14:59:27 -04:00
Vitor Pamplona
6a91b68b4b Migrates missing User filter to the new no EOSE cache manager 2025-05-14 14:56:47 -04:00
Vitor Pamplona
0f018140f0 Minor refactoring 2025-05-14 14:56:08 -04:00
Vitor Pamplona
979e02e7fa Migrates event finder and channel finder to the new No Eose cache manager 2025-05-14 14:55:53 -04:00
Vitor Pamplona
4fc2d3a7d1 Adds a new EOSE manager that keeps one sub but no EOSE cache 2025-05-14 14:53:54 -04:00
Vitor Pamplona
d0f138e992 Generalizes the FastEOSE cache for Users 2025-05-14 14:53:30 -04:00
Vitor Pamplona
b7ac32d768 Removes unused since param for missing events in thread assemblers 2025-05-14 14:50:52 -04:00
David Kaspar
3b5d12b477 make helper method suspend, use different scope in View 2025-05-14 19:15:16 +02:00
Vitor Pamplona
6f7f8e296b Removes the time logger for note rendering 2025-05-14 12:53:56 -04:00
David Kaspar
d6ee09ecdb Merge branch 'main' into share-media-to-other-apps 2025-05-14 18:20:57 +02:00
David Kaspar
61970b8ce5 Update README.md
Split video and image capture from phone. Ticket image capture
2025-05-14 17:17:27 +01:00
Vitor Pamplona
b045d0e122 Migrates lint to the newer default version and copyright to 2025 2025-05-14 11:59:15 -04:00
Vitor Pamplona
47de821f6d Updates coil, compose, immutable, lifecycle, navigation and zoomable 2025-05-14 11:14:33 -04:00
Vitor Pamplona
2c06df7e50 Simple refactoring for the name of the subscription cache 2025-05-14 10:43:42 -04:00
Vitor Pamplona
52d31502e6 Migrates the Subscription controller to use filters from the NostrClient instead of the past version in the controller to avoid out of sync comparisons 2025-05-14 10:08:56 -04:00
Vitor Pamplona
d7d8f02a80 Moves Content Sensitivity setting to the Security filter screen 2025-05-13 20:09:14 -04:00
Vitor Pamplona
f3e2327676 Modifies the behavior of new posts to clear the background color after 5 seconds 2025-05-13 17:21:22 -04:00
Vitor Pamplona
dba51acbd5 Merge pull request #1351 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-13 15:32:53 -04:00
Crowdin Bot
20c775913a New Crowdin translations by GitHub Action 2025-05-13 19:24:27 +00:00
Vitor Pamplona
a42afdc78b merge 2025-05-13 14:53:00 -04:00
Vitor Pamplona
6ecb25b8e1 - Refactors the entire EOSE caching structure.
- Refactors all Datasource classes into a 3 layer design with composeSubscriptions being registered, passed to a group of EOSE managers that can change eose caching capabilities based on needs, and migrates the old subscription Orchestrator into a simpler controller
- Breaks down all Datasource into multiple filter functions
- Improves EOSE support for many of the base filters like on NIP-04 DMs, notifications, etc
- Moves EOSE cache for public, non-user-dependent items like channels, hashtags, etc to their own caching system.
- Improves the Relay speed logger to better track how many events are being received, and check for duplications
2025-05-13 14:52:26 -04:00
Vitor Pamplona
0e68031227 Removes the need to track EOSE at the base user class 2025-05-13 14:47:20 -04:00
Vitor Pamplona
466f52245b Adds an isDifferent function to avoid resending NormalFilters 2025-05-13 14:46:09 -04:00
Vitor Pamplona
09946c19b9 Turns all base Hint classes into data classes 2025-05-13 14:44:03 -04:00
Vitor Pamplona
4279ad004c Only updates EOSE log if the time is bigger 2025-05-13 14:43:44 -04:00
Vitor Pamplona
50cde1bc3e - Bugfix to mark relay connections as AfterEOSE when the relay client or server closes the subscription
- Adds an arrival time to log the time EOSEs were received in case the listener is running async processes later.
2025-05-13 14:43:28 -04:00
Vitor Pamplona
2d5732bcce Normalizes hashtag transformations for filters. 2025-05-13 14:40:03 -04:00
David Kaspar
a848ea6135 add content param when making call using a MediaPreloadedContent. Just in case 2025-05-13 09:43:23 +02:00
David Kaspar
095d054d62 add support for gifs 2025-05-13 09:39:33 +02:00
David Kaspar
2e4c9f77e6 refactor 2025-05-13 09:39:33 +02:00
David Kaspar
7efec03056 Return file extension from ShareHelper just in case mimetype is null
Use content to determine whether it's an image
2025-05-12 22:26:24 +02:00
KotlinGeekDev
44b567b01f Write down implementation for FollowSetScreen. Introduce functions(hacks, really) for making sure of the UI/UX design.
- Implement dialog for creating a new list.
2025-05-12 20:47:12 +01:00
David Kaspar
91b6bf6e88 localisation 2025-05-12 21:03:17 +02:00
David Kaspar
35001c0b00 moved sharing intent back to view 2025-05-12 20:56:16 +02:00
David Kaspar
ce96ab873f added todos 2025-05-12 18:42:56 +02:00
David Kaspar
8c1888b4d5 added error handling
.use {} for automatic resource management with snapshots
2025-05-12 17:40:28 +02:00
David Kaspar
0192a4eb20 Add test to only share images. Other media not supported atm 2025-05-12 17:13:29 +02:00
David Kaspar
fee85a6111 remove scope launch 2025-05-12 17:06:05 +02:00
David Kaspar
13ee0a23de Use disk cache reference from app
Detect image type based on magic number
2025-05-12 16:44:31 +02:00
David Kaspar
ebf1571b92 share images from coil cache to other apps 2025-05-08 18:05:06 +02:00
Vitor Pamplona
a5c63dd311 Merge pull request #1349 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-08 10:58:07 -04:00
Crowdin Bot
245b767dc8 New Crowdin translations by GitHub Action 2025-05-08 14:56:55 +00:00
Vitor Pamplona
be62d5ad7f Merge pull request #1347 from davotoula/use-updated-markdown-to-sanitise-url-labels
Use new version of Markdown to sanitise url labels from # and @
2025-05-08 10:55:33 -04:00
Vitor Pamplona
dc132de18d Merge pull request #1348 from davotoula/upgrade-agp-to-8.10.0
Upgrade agp to 8.10.0
2025-05-08 10:52:52 -04:00
David Kaspar
5d1515659e remove unused imports 2025-05-08 13:13:02 +02:00
David Kaspar
6206d550e6 Upgrade agp to 8.10.0 2025-05-08 13:09:08 +02:00
David Kaspar
d7055062f3 Use new version of Markdown to sanitise url labels from # and @ 2025-05-08 11:58:02 +02:00
Vitor Pamplona
bddd8914ae Adds an EOSE manager to Channel Filters 2025-05-07 13:54:56 -04:00
Vitor Pamplona
2fc7c37270 Adds a watcher of MetadataChanges for NIP-28 that includes a since parameter for the last metadata time + 1 2025-05-07 13:48:54 -04:00
Vitor Pamplona
bc22b78f19 Refactors EOSE management for Chatroom List 2025-05-07 13:47:34 -04:00
Vitor Pamplona
e6b0b41e80 Adds a Normal Nostr filter as a PerRelay filter 2025-05-07 13:46:39 -04:00
Vitor Pamplona
8a7ce8dc07 Makes EOSE cache per user cleanable 2025-05-07 13:46:14 -04:00
Vitor Pamplona
cb4e57d6f6 remove the filter invalidation after each EOSE 2025-05-06 19:42:23 -04:00
Vitor Pamplona
a54835e4f9 Adds Eose management for the UserProfile Screen 2025-05-06 19:42:09 -04:00
Vitor Pamplona
be0a559b7d Prints better logs for relay subscriptions 2025-05-06 19:38:13 -04:00
Vitor Pamplona
b801125ff5 Builds a speed logger to better understand data usage 2025-05-06 19:37:44 -04:00
Vitor Pamplona
4ea9d2db7d Updates AGP to 8.10 2025-05-06 18:21:15 -04:00
Vitor Pamplona
54532de88a Extending AsyncImage to correctly use pre-loaded aspect ratio and avoid screen jittering 2025-05-06 16:46:25 -04:00
Vitor Pamplona
ba6a7bb07f Fixes missing content alignment on the crossfade API migration 2025-05-06 16:18:21 -04:00
Vitor Pamplona
fcd70001bf Adds a Reads feed to the discovery tab 2025-05-06 15:48:03 -04:00
Vitor Pamplona
80d4971548 - Adds FollowSets to the discovery tabs.
- Refactors discovery screen, creating a package for each feature.
- Fixes Android studio previews
2025-05-06 13:23:43 -04:00
Vitor Pamplona
fbe20029f7 Move to a cache model where we defer the event's verification until the last moment before adding to cache 2025-05-06 09:45:01 -04:00
Vitor Pamplona
a05a465f69 Avoids sending an NWC filter when there are no keys subscribed to it 2025-05-06 08:56:47 -04:00
Vitor Pamplona
49c131a5cd Adding more constraints to the channel filter 2025-05-05 19:11:54 -04:00
Vitor Pamplona
1c23600c57 Assigns the first relay when a gift-wrapped message is received 2025-05-05 18:51:45 -04:00
Vitor Pamplona
7a8766551f unused checks 2025-05-05 18:36:13 -04:00
Vitor Pamplona
7accb5e2b9 Adds onStart calculator for live bubble authors 2025-05-05 18:34:54 -04:00
Vitor Pamplona
504f365ab3 Fixes code to mark messages as received by relays 2025-05-05 18:12:16 -04:00
Vitor Pamplona
d9b784bee5 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-05-05 17:22:53 -04:00
Vitor Pamplona
aff95853d8 Merge pull request #1346 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-05 17:21:15 -04:00
Vitor Pamplona
adcbbec5be Removing unnecessary logs 2025-05-05 17:21:03 -04:00
Crowdin Bot
ceabf8184a New Crowdin translations by GitHub Action 2025-05-05 21:10:39 +00:00
Vitor Pamplona
7aa71fad3f Caches app icons as painters 2025-05-05 17:08:47 -04:00
Vitor Pamplona
15dfbb2f31 Fixes the test case for emojis 2025-05-03 16:33:28 -04:00
Vitor Pamplona
0d53a30371 Disable event logger for now 2025-05-03 16:33:08 -04:00
Vitor Pamplona
38bb39a407 Fixes bug when trying to detect the charset of the first 1024 bytes of the page. Since this is an invalid HTML, it was crashing here. 2025-05-03 16:15:20 -04:00
Vitor Pamplona
560a9ba471 Fixes some log messages 2025-05-02 21:23:44 -04:00
Vitor Pamplona
38d3194e87 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
2025-05-02 21:19:14 -04:00
Vitor Pamplona
462fda1b5c Updates accompanist, jackson, mockk, secp256, zoomable 2025-05-02 21:18:40 -04:00
Vitor Pamplona
63a009f36d - Adds support for Ephemeral Chats from coolr.chat
- Adds support for following ephemeral chats
- Adds support for live events at the top of the feed.
- Adds support for NIP-51, kind:10005 public chat lists
- Adds support for Channel feeds
- Moves following of NIP-28 chats from the Contact List to kind: 10005
- Disables following of events at the Contact list
- Improves gallery display to slightly override profile pictures when in list
- Starts the Account refactoring by moving custom Emoji, EphemeralList and PublicChat lists to their own packages
- Refactors NIP-51 lists to use common classes of private tags instead of general list classes.
- Starts to separate all Public chats into their own database.
- Removes old account upgrades from the local storage
- Refactors url NIP-11 loading and unifies icon
- Reduces the dependency of Relay classes in the LocalCache, Notes and User classes
2025-05-02 21:18:10 -04:00
David Kaspar
c3dc8104e9 Merge pull request #1340 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-05-02 15:27:21 +02:00
Crowdin Bot
23fbb53c96 New Crowdin translations by GitHub Action 2025-05-02 13:20:35 +00:00
Vitor Pamplona
1fe55ece5d Merge pull request #1339 from davotoula/missing-at-for-profile-links-in-markdown
Missing at for profile links in markdown and fix typo preventing opening of hashtags from markdown notes
2025-05-02 09:19:03 -04:00
David Kaspar
bb9435e637 Remove unused imports 2025-05-02 15:05:20 +02:00
David Kaspar
67e3a92d68 cleanup 2025-05-02 15:00:27 +02:00
David Kaspar
32439b5feb bugfix: fix typo preventing opening of hashtags from markdown notes 2025-05-02 15:00:06 +02:00
David Kaspar
136a4061d7 prepend clickable user with @ sign 2025-05-02 14:59:32 +02:00
Vitor Pamplona
b06e53f59d Fixes lack of update for Discovery filters when resuming 2025-04-30 15:24:00 -04:00
Vitor Pamplona
9c79ef08c7 Moves logging out of quartz Relay class 2025-04-30 14:00:08 -04:00
KotlinGeekDev
5a6cfad848 Separate components out into their logical groups(by file). Add new icons for public/private list creation, and add FloatingActionButtons for each. Add test code for iterating on changes quickly(TEMPORARY). 2025-04-30 17:11:28 +01:00
Vitor Pamplona
43b761c98c Slight improvement in the UI to view follow packs 2025-04-29 17:38:00 -04:00
Vitor Pamplona
626bfc95cb - Adds support for Follow packs
- Enables top filter by pack as well.
2025-04-29 17:24:03 -04:00
Vitor Pamplona
9df0f6c368 Moves Coil's image loader choice to a subscription on account. 2025-04-29 16:04:16 -04:00
Vitor Pamplona
7c68ffaf6e Delete unused flows 2025-04-29 15:55:57 -04:00
Vitor Pamplona
ee1d37dd5b Merge pull request #1338 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-29 14:39:17 -04:00
Crowdin Bot
ed6d0f6c0f New Crowdin translations by GitHub Action 2025-04-29 18:37:53 +00:00
Vitor Pamplona
533e7d661f Presubscribe to the bottom navigation items in the start up of the app. 2025-04-29 14:36:01 -04:00
Vitor Pamplona
932664c9f9 Avoids crashes when fetching base64 and blurhash images 2025-04-29 14:33:55 -04:00
David Kaspar
3d89ed567b Merge pull request #1337 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-29 06:11:07 +02:00
Crowdin Bot
62e2e2ac65 New Crowdin translations by GitHub Action 2025-04-29 01:16:39 +00:00
Vitor Pamplona
1c8a6d1234 - Separates new Product Screen from the generic new Post screen.
- Adds New product button on the Marketplace tab
- Adds imeta tags for images and urls inside the content of the Classifieds.
- Shows multiple images on the post and thread view.
- Removes the option for NIP-95 images on Classifieds.
- Creates a new route for new products
- Adjusts quarts to process images with iMeta tags
2025-04-28 21:13:41 -04:00
Vitor Pamplona
c07592f399 Migrates Relay selection dialog to full bleed layout 2025-04-28 20:58:23 -04:00
Vitor Pamplona
96b1565de7 Removes duplicated draft save on DM 2025-04-28 20:57:11 -04:00
Vitor Pamplona
ee675d06f6 Merge pull request #1336 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-26 11:05:50 -04:00
Crowdin Bot
7c02b5b58f New Crowdin translations by GitHub Action 2025-04-26 15:00:51 +00:00
Vitor Pamplona
4c7ef8ecff Caches the processing of preview urls for the new post screen
Fixes a bug on saving MediaType on Android's stack
2025-04-26 10:58:45 -04:00
Vitor Pamplona
99cef643e1 Improves the visual of multiple previews on the new post screen 2025-04-26 10:58:17 -04:00
Vitor Pamplona
08025f7df7 Adds a special case for loading previews with just 1 item in the new post screen 2025-04-25 17:41:06 -04:00
Vitor Pamplona
92eff02f0a Removes the hack to reduce memory usage by deleting the .content when saving the event to cache. 2025-04-25 17:04:16 -04:00
Vitor Pamplona
539d20b397 Adds an error log for the new on account view model processor 2025-04-25 17:03:26 -04:00
Vitor Pamplona
b51e5f4383 better relay reconnecting logic 2025-04-25 17:02:58 -04:00
Vitor Pamplona
0c54f9aa10 Renders the new comment tag on highlights. 2025-04-25 16:58:41 -04:00
Vitor Pamplona
c98e23add7 Merging 2025-04-25 15:07:52 -04:00
Vitor Pamplona
e4cc65969a Merge pull request #1332 from davotoula/reduce-compiler-warnings-amethyst
Reduce compiler warnings: amethyst module
2025-04-25 12:16:40 -04:00
Vitor Pamplona
aef7d91a6b Moves privacy option from dialog to route 2025-04-25 12:14:44 -04:00
David Kaspar
d874e798c6 remove unused imports 2025-04-25 17:10:25 +02:00
Vitor Pamplona
390e0615d1 Removing these disabled lock behavior code until we get it working 2025-04-25 11:10:04 -04:00
David Kaspar
e40ca09f7c Use default locale for capitalisation in UI strings (use ROOT for IDs/Hashes for consistency) 2025-04-25 17:00:16 +02:00
David Kaspar
96436912bb Safe checked casting 2025-04-25 16:03:16 +02:00
David Kaspar
97290cea48 Replace String.capitalize with replaceFirstChar 2025-04-25 16:02:59 +02:00
David Kaspar
ad2387f8e9 Add @OptIns
Change to auto mirrored icons
Remove unnecessary inline
2025-04-25 15:59:54 +02:00
KotlinGeekDev
0693e4412f Re-arrange components so as to better fit the ListOptionsButton. 2025-04-25 14:20:24 +01:00
KotlinGeekDev
b52943a826 Add stub for GeneralBookmarks tab. Use a different icon for representing private lists. Add dropdown menu for follow set component(for actions such as delete, rename, etc). 2025-04-25 13:42:39 +01:00
Vitor Pamplona
d25e89f41d Removes pull to refresh hack since we can now fully migrate 2025-04-24 19:36:47 -04:00
Vitor Pamplona
04712468aa Removing unused nip-05 2025-04-24 19:14:35 -04:00
Vitor Pamplona
354fc34fbb New default banner 2025-04-24 19:08:58 -04:00
Vitor Pamplona
026943439a reverting x 2025-04-24 18:51:34 -04:00
Vitor Pamplona
46cb7eb640 Removes text select icon 2025-04-24 18:50:16 -04:00
Vitor Pamplona
f465844009 Removes old icons and migrates some to the Icons package 2025-04-24 18:49:05 -04:00
Vitor Pamplona
eebbda547d Trying again to fix the hyphen 2025-04-24 18:25:09 -04:00
Vitor Pamplona
122603080e Removed unused function 2025-04-24 18:21:30 -04:00
Vitor Pamplona
aff0369b04 updating firebase 2025-04-24 18:14:02 -04:00
Vitor Pamplona
1c1db6fa1e Removes unused copytext dialog 2025-04-24 18:10:52 -04:00
Vitor Pamplona
187d77c138 Marks GlobalScope as optin 2025-04-24 18:07:22 -04:00
Vitor Pamplona
950d0bcc93 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-04-24 18:06:49 -04:00
Vitor Pamplona
0d66ceb1a4 Merge pull request #1330 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-24 18:06:39 -04:00
Vitor Pamplona
8e0563ed3a Migrate animateItemPlacement to new API 2025-04-24 17:46:13 -04:00
Crowdin Bot
02870d0cd3 New Crowdin translations by GitHub Action 2025-04-24 21:41:05 +00:00
Vitor Pamplona
9d3a6d3f1d migrates compose bom and media3 2025-04-24 17:40:38 -04:00
Vitor Pamplona
2c85260ab1 Removes the old ic_verified 2025-04-24 17:39:00 -04:00
Vitor Pamplona
ecebc68e11 avoiding deprecated .put api 2025-04-24 17:36:27 -04:00
Vitor Pamplona
07c3a3ffe5 Fixes unchecked cast and other opt-in tags on observers 2025-04-24 17:35:34 -04:00
Vitor Pamplona
c0f486db82 Hyphen can be replaced with dash 2025-04-24 17:35:11 -04:00
Vitor Pamplona
e0990ae38e Merge pull request #1329 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-24 17:22:02 -04:00
Crowdin Bot
7005158821 New Crowdin translations by GitHub Action 2025-04-24 21:19:22 +00:00
Vitor Pamplona
9922fa117f Reverting svg sizes because of app icon issues 2025-04-24 17:16:31 -04:00
Vitor Pamplona
723b4b5e9f Removes dependency on a modifier for SettingsCategory 2025-04-24 16:58:40 -04:00
Vitor Pamplona
2d64eb0cb3 Refactors Add and Remove button to remove dependency on a specific modifier. 2025-04-24 16:58:22 -04:00
Vitor Pamplona
6a91ede54c Fixes the lack of locale when creating percentages on zap split screens. 2025-04-24 16:57:44 -04:00
Vitor Pamplona
8d91b97962 Migrates CircularProgresIndicator to the newer version 2025-04-24 16:57:24 -04:00
Vitor Pamplona
bf47c1b431 No need to check for API version in this function, since we check outside of it 2025-04-24 16:56:46 -04:00
Vitor Pamplona
f5a504077c Keeps the transparency settings for statusbar and navigation for now. 2025-04-24 16:55:21 -04:00
Vitor Pamplona
95afed5045 Only request for notification permissions after API 33 2025-04-24 16:54:52 -04:00
Vitor Pamplona
dcccc1de3a Last Uri.parse fix 2025-04-24 16:54:31 -04:00
Vitor Pamplona
5095c9f480 Migrating from ContextCompat.startActivity to context.startActivity 2025-04-24 16:54:17 -04:00
Vitor Pamplona
30c4e3c646 Fixes deprecated URI parse function warnings 2025-04-24 16:52:25 -04:00
Vitor Pamplona
d04f6c977e Migrates to the new capitalization function 2025-04-24 16:50:48 -04:00
Vitor Pamplona
7d97b73356 Fixes mutable state builders with primitive types warning. 2025-04-24 16:48:56 -04:00
Vitor Pamplona
be3eb4314b Fixes casting warnings on viewmodels 2025-04-24 16:46:34 -04:00
Vitor Pamplona
f7abc6e1ee Suppress modifiers on ColorScheme. 2025-04-24 16:44:30 -04:00
Vitor Pamplona
c16b93c06a Reduces viewport to 200dp for performance 2025-04-24 16:44:10 -04:00
Vitor Pamplona
82af2644a6 Removes unnecessary label to v26 2025-04-24 16:43:50 -04:00
Vitor Pamplona
7d2b2ee674 normalizing strings 2025-04-24 16:43:28 -04:00
Vitor Pamplona
fdc95a656f Ignore permissions to use our services. 2025-04-24 16:42:43 -04:00
Vitor Pamplona
7b040933d5 Merge 2025-04-24 14:35:57 -04:00
Vitor Pamplona
067f353e50 Finish the migration from LiveData to Flow
Moves most of the flow updates away from the Main thread.
Removes the need to use the Main thread to access Account.userProfile
2025-04-24 14:35:22 -04:00
Vitor Pamplona
2f3c282ef5 Merge pull request #1328 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-24 08:45:03 -04:00
Crowdin Bot
0c8564157b New Crowdin translations by GitHub Action 2025-04-24 12:43:11 +00:00
Vitor Pamplona
d6160ea6c2 Merge pull request #1327 from davotoula/upgrade-agp
upgrade agp to 8.9.2
2025-04-24 08:41:41 -04:00
David Kaspar
0fc04490fd upgrade agp to 8.9.2 2025-04-24 10:44:11 +02:00
Vitor Pamplona
e375b09a24 Fixes missing gallery of reactions 2025-04-23 18:52:43 -04:00
Vitor Pamplona
802e04445d Adds logging message to hunt down some slow screens 2025-04-23 17:15:21 -04:00
Vitor Pamplona
ca8faa062f Caches translations to avoid blinking text every time the user comes back to an older screen. 2025-04-23 16:44:35 -04:00
Vitor Pamplona
d6d34b2a88 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-04-23 16:24:57 -04:00
Vitor Pamplona
a84371a0b3 - Restructures the old static datasource model into dynamic filter assemblers.
- Moves filter assemblers, viewModels and DAL classes to their own packages.
- Creates Composable observers for Users and Notes
- Deletes most of the secondary LiveData objects in the move to Flow
- Manipulates nostr filters depending on the account of the current screen, not a global account.
- Unifies all FilterAssembly lifecycle watchers to a few classes.
- Prepares to separate The Nostr Client as an Engine of Amethyst.
- Moves the pre-caching processor of new events from the datasource to the accountViewModel
- Reorganizes search to be per-screen basis
- Moves authentication to a fixed Coordinator class for all accounts in all relays.
- Moves NOTIFY command to its own coordinator class for all accounts
- Moves the connection between filters and cache to its own class.
- Significantly reduces the dependency on a single ServiceManager class.
2025-04-23 16:24:41 -04:00
Vitor Pamplona
411b49d68f Merge pull request #1326 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-23 16:04:46 -04:00
Crowdin Bot
f8fd935a4e New Crowdin translations by GitHub Action 2025-04-23 19:45:34 +00:00
Vitor Pamplona
62be469cd8 Merge pull request #1325 from greenart7c3/main
Filter special characters when decoding bech32
2025-04-23 15:44:08 -04:00
greenart7c3
b42d05ae7a Filter unicode characters 2025-04-23 16:17:13 -03:00
KotlinGeekDev
3a5e6dbe90 Use a tabbed layout for the different types of lists/sets, and migrate follow sets. Use empty info for next tab(labeled bookmarks). 2025-04-21 18:27:25 +01:00
KotlinGeekDev
2aa2605b48 Migrate Lists route to new navigation setup. 2025-04-18 14:24:51 +01:00
KotlinGeekDev
f97c97e00e Merge modified upstream changes. 2025-04-15 11:43:10 +01:00
KotlinGeekDev
dc22c7ea76 Keep local changes for later refactoring. Apply styling fixes. 2025-04-15 11:42:41 +01:00
KotlinGeekDev
acc59d2b40 Manual merge, due to other unfinished work. 2025-04-15 11:39:25 +01:00
KotlinGeekDev
4d45f86e06 Add CuratedBookmarkList type. Introduce FollowSetScreen. 2025-04-15 11:17:31 +01:00
KotlinGeekDev
0dae538c31 Use better naming for the view models, reflecting their true nature, and preparing for future work here. 2025-04-14 17:45:44 +01:00
Vitor Pamplona
e007a02d84 Merge pull request #1320 from davotoula/circle-around-profile-picture-in-drawer
contrasting circle around profile picture
2025-04-13 11:13:03 -04:00
David Kaspar
cbd5a8a7ee contrasting circle around profile picture
remove unused import
2025-04-13 09:59:43 +02:00
Vitor Pamplona
fd1c5d5887 Merge pull request #1319 from davotoula/1318-profile-qr-code-screen-changes
corrected profile picture border colour
2025-04-12 11:18:12 -04:00
David Kaspar
043196be10 Update bug_report.md
Added app flavour field (play vs fdroid)
bump versions
2025-04-12 15:56:51 +01:00
David Kaspar
66927ca70c corrected profile picture border colour 2025-04-12 16:02:35 +02:00
Vitor Pamplona
faf0f6dc1a Updates mockk, coroutines, zoomable and vico 2025-04-10 14:06:25 -04:00
Vitor Pamplona
5abb5f2f80 Merge pull request #1317 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-10 10:00:00 -04:00
Crowdin Bot
37b84108a7 New Crowdin translations by GitHub Action 2025-04-10 13:48:08 +00:00
Vitor Pamplona
5edc945341 Updates Compose, CoreKtx and SecurityKtx 2025-04-10 09:45:21 -04:00
Vitor Pamplona
3833f5f822 - Refactoring services to be part of the App Lifecycle,
- Migrated services to Flows that are active while their flows remain subscribed
- Improved OTS Decoupling
- Moved OTS verification procedures from the local cache to the data source
- Migrated the forceProxy options to lambdas that return an OkHttpClient
- Isolated Connectivity services, from Compose to Flow
- Isolated Tor services, from Compose to Tor (only starts if the Tor option is marked as internal)
- Isolated Memory trimming services, from Compose to Flow
- Isolated Image Caching services, from Compose to Flow
- Isolated Video Caching services
- Isolated Logging services
- Isolated NIP-95 Caching services
- Isolated Pokey receiver services
- Improved support for Tor in push notifications
- Isolated OkHttpClient services as flows
- Reduced the coupling of Context objects with singleton objects.
- Migrated UserFeedStates, StringFeedStates and ZapFeedStates to MutableStateFlow, avoiding feed updates on Main
- Forces a reconnect into relays that lost connection if any tor, connectivity or account changes
- Speeds up Base64 parser
2025-04-09 18:51:17 -04:00
Vitor Pamplona
652373bd01 Remove the warning saying the app name is being replaced on benchmark. 2025-04-09 16:28:18 -04:00
Vitor Pamplona
e883a46f53 Fixes deprecated warning 2025-04-09 16:20:14 -04:00
Vitor Pamplona
2b7cfa5560 Better name for the Blurhash class to avoid mistakes with its field. 2025-04-03 20:50:43 -04:00
Vitor Pamplona
ed860454a7 Moves activities to be singleInstance to avoid multiple startups when receiving share events from other apps. 2025-04-03 20:46:39 -04:00
Vitor Pamplona
ed0435061c Moves to cover all classes in the proguard. 2025-04-03 20:45:25 -04:00
Vitor Pamplona
223865827f Fixes tagging issue on new post. 2025-04-03 13:54:23 -04:00
Vitor Pamplona
ec25ef3f82 Adds flowerstr 2025-04-03 09:02:31 -04:00
Vitor Pamplona
7c4911f2dc Converts old ClickableTexts into new Compose Texts with clickable spans 2025-04-02 20:37:14 -04:00
Vitor Pamplona
4ac046fe39 Migrates Navigation to Compose Navigation. 2025-04-02 17:39:16 -04:00
Vitor Pamplona
bfd5845022 Updates Firebase and CBOR 2025-04-01 19:26:10 -04:00
Vitor Pamplona
d288010a83 Move the stringresource setup out of the Activity 2025-04-01 19:13:35 -04:00
Vitor Pamplona
834efe02bc Refactors SharedSettings state to:
- Minimize recompositions
- Improve code quality
2025-04-01 18:59:53 -04:00
Vitor Pamplona
1bd32c854c Makes sure the tryLogin function only runs if it is not already logged in (compostables might call it more times) 2025-04-01 18:58:15 -04:00
Vitor Pamplona
d08998736f Removes unecessary preview items from Cashu components. 2025-04-01 18:57:40 -04:00
Vitor Pamplona
a5f9b865ec Makes ZapAmount viewmodel stable 2025-04-01 18:57:17 -04:00
Vitor Pamplona
375931d4f9 Modernizing the edit access to LocalPreferences. 2025-04-01 18:53:30 -04:00
Vitor Pamplona
7e3f64fc61 Performance: Avoids saving the first collection on login 2025-04-01 18:51:29 -04:00
Vitor Pamplona
09024fbc61 Performance: Avoids creating OkHttp proxies directly on the OnCreate 2025-04-01 18:50:08 -04:00
Vitor Pamplona
a530ced594 Performance: Avoids creating a SharedSettings ViewModel on Galery 2025-04-01 18:49:26 -04:00
Vitor Pamplona
3089524a1f Cleaning up from the previous commit 2025-04-01 16:50:55 -04:00
Vitor Pamplona
5aa2aa4d54 Merge translations 2025-04-01 16:36:03 -04:00
Vitor Pamplona
12235216ff Moves The WIFI/Mobile data connection watcher to the compose.
Moves Relay Connection Manager to the Compose
Adds a 30 second delay to approve events on Amber
2025-04-01 16:35:46 -04:00
Vitor Pamplona
35b9648bab Merge pull request #1316 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-04-01 15:06:31 -04:00
Vitor Pamplona
cf2959d42e Refactors logged in page into its own package 2025-04-01 14:03:17 -04:00
Crowdin Bot
b5164d5dd0 New Crowdin translations by GitHub Action 2025-04-01 18:01:18 +00:00
Vitor Pamplona
05cb055b1d Migrates FDroid's push system to match Play services 2025-04-01 13:58:57 -04:00
Vitor Pamplona
f6bd12058f removes firebase logging 2025-04-01 11:56:55 -04:00
Vitor Pamplona
99ef1fc168 Removes Tor leaks when loading thumbnails for vides automatically from the ExoPlayer 2025-04-01 11:56:47 -04:00
Vitor Pamplona
bd02515209 Moves push registration notification into Compose after login to make sure we use the tor configuration of the active user. 2025-04-01 11:18:15 -04:00
Vitor Pamplona
b3f1b093ed Tor now listens to port changes and restart connections when it does change. 2025-03-31 20:32:15 -04:00
KotlinGeekDev
7d90b9f87b Remove map in FollowSetFeedFilter, in favor of retrieving note from cache if modification is needed. Amend FollowSet to carry the address of the note. Add Refreshable for the Lists screen. 2025-03-31 21:31:50 +01:00
KotlinGeekDev
28093256e7 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2025-03-31 21:19:28 +01:00
KotlinGeekDev
d7d8554626 Make naming clearer. Some refactoring. Add a function to retrieve the not corresponding to a follow set. 2025-03-31 21:19:05 +01:00
KotlinGeekDev
bfa673ca2c Make naming clearer. Some refactoring. Add a function to retrieve the not corresponding to a follow set. 2025-03-31 21:15:06 +01:00
KotlinGeekDev
82f8fe96a1 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-31 17:50:33 +00:00
Vitor Pamplona
491d269f75 Merge pull request #1315 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-31 11:30:04 -04:00
Crowdin Bot
c79243a912 New Crowdin translations by GitHub Action 2025-03-31 15:11:49 +00:00
Vitor Pamplona
b762907d69 adds a mutex to make sure memory clearing only happens once at a time. 2025-03-31 11:09:23 -04:00
Vitor Pamplona
396831456c - Tor Cleanup:
- Only starts Tor when the "Internal" option is selected on the privacy settings
- Adds a mutex to make sure tor only starts once.
- Fixes bug when setting port before the service is running
- Moves tor start and stop to Compose actions, instead of activity
- Adds a 5 second delay before disconnecting Tor on the App's onPause
2025-03-31 11:09:04 -04:00
KotlinGeekDev
c57153221c Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-30 23:05:31 +00:00
Vitor Pamplona
31e961c64f Fixes Video cropping due to rotated Exif metadata. 2025-03-30 10:51:32 -04:00
Vitor Pamplona
8baf9ad914 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-29 16:07:19 -04:00
Vitor Pamplona
c2b0154bb5 Fixes mark as seen when drafts are the last post in messages 2025-03-29 16:07:11 -04:00
Vitor Pamplona
c2b19c670b Merge pull request #1314 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-29 15:33:38 -04:00
Crowdin Bot
f97cfa81ca New Crowdin translations by GitHub Action 2025-03-29 19:22:34 +00:00
Vitor Pamplona
21643b781c Fixing language codes 2025-03-29 15:02:52 -04:00
Vitor Pamplona
5961db5c77 Testing the removal of generic Arabic translation to a language-country pair 2025-03-29 14:43:23 -04:00
Vitor Pamplona
99d5e9f1f7 Merge pull request #1313 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-29 14:38:55 -04:00
Crowdin Bot
818116d57b New Crowdin translations by GitHub Action 2025-03-29 18:36:37 +00:00
Vitor Pamplona
ef1e743fe2 removes language mapping for crowdin 2025-03-29 14:35:28 -04:00
Vitor Pamplona
df9568b667 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-29 12:25:54 -04:00
Vitor Pamplona
b5649e2607 Fixes: https://github.com/vitorpamplona/amethyst/issues/1309 2025-03-29 12:25:48 -04:00
Vitor Pamplona
92033b16d2 Updates Media, Camera, Compose and benchmark libs 2025-03-29 12:23:20 -04:00
Vitor Pamplona
9a440513ef Merge pull request #1312 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-29 11:11:05 -04:00
Crowdin Bot
89aa4a6f95 New Crowdin translations by GitHub Action 2025-03-29 15:02:23 +00:00
Vitor Pamplona
5ac73bbeba - Retries to reverse geolocate if fails due to the services long startup time.
- Adds support for the new and old ways to use reverse geolocation services.
- Hits all existing providers for location updates instead of just the network ones
- Uses past last known locations to kick start the cache.
- Improves logging to check potential failures in production.
2025-03-29 11:00:37 -04:00
KotlinGeekDev
ab32518b8e Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-28 23:02:13 +00:00
Vitor Pamplona
76eee177a8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-28 13:48:31 -04:00
Vitor Pamplona
4124724fb1 - Creates a new Group DM screen and moves DMs (NIP-17 and NIP-04) away from the New Post Screen.
- Separates all DM attachments as their own objects and holds them in cache until the message is sent.
- Adds previews for any number of urls, events and media uploads on new post screens.
- Adds zap split, zap raiser, geolocation symbols for DMs and channel messages
- Separates each component that can be added to a new post into its own package.
- Unifies processing for User Suggestions, Emoji Suggestions, Url Previews, location, draft versions, among text inputs
2025-03-28 13:48:22 -04:00
KotlinGeekDev
24a9d7659a Merge branch 'upstream-main' into profiles-list-management
Merge upstream changes.
2025-03-28 12:12:25 +01:00
KotlinGeekDev
85fb56bb22 Updates to conflict-causing files. 2025-03-28 12:10:58 +01:00
Vitor Pamplona
7921745fd9 Merge pull request #1311 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-27 17:54:50 -04:00
Crowdin Bot
c24c23f003 New Crowdin translations by GitHub Action 2025-03-27 21:41:53 +00:00
Vitor Pamplona
8a6b71e913 Fixing google services.json for benchmark app 2025-03-27 17:39:10 -04:00
KotlinGeekDev
c1f14cea2e Use a general type to represent lists, NostrList. Make FollowSet a subclass. Rename ListType to ListVisibility. Refactor accordingly. 2025-03-27 18:47:57 +01:00
Vitor Pamplona
29d046a094 Updates zoomable, vico, firebase, jackson and jna 2025-03-26 12:36:38 -04:00
Vitor Pamplona
c0423032c3 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-26 11:24:17 -04:00
Vitor Pamplona
fd7ff57a37 Merge pull request #1307 from davotoula/1301-use-note-renderer-for-file-header-and-video-events
Use CheckNewAndRenderNote method for FileHeaderEvent, FileStorageHeaderEvent and VideoEvent(s)
2025-03-26 11:23:41 -04:00
Vitor Pamplona
9a0a164ab5 Merge pull request #1308 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-26 11:22:51 -04:00
Vitor Pamplona
b35fd24a93 Fixes QR Code screen padding 2025-03-26 11:22:38 -04:00
Vitor Pamplona
aac366c690 - Migrates "Show Anyway" and "Show More" buttons to lighter version (Tonal)
- Improves tonal rendering on Light theme
- Lightens card surfaces
- Adjusts surfaceTint on Dark theme to match the elevation differences on the Light theme
2025-03-26 11:22:27 -04:00
Crowdin Bot
04a161dc47 New Crowdin translations by GitHub Action 2025-03-26 15:20:27 +00:00
David Kaspar
0d623b2ecc Use CheckNewAndRenderNote method for FileHeaderEvent, FileStorageHeaderEvent and VideoEvent(s) 2025-03-26 12:20:59 +00:00
Vitor Pamplona
aa9f1b6c4f Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-25 19:36:47 -04:00
Vitor Pamplona
8f77ecd891 Improves the rendering of the channel metadata changes
Redesigns the expandable top bar for NIP-28 chats
2025-03-25 19:36:39 -04:00
Vitor Pamplona
c4ab506c87 Merge pull request #1305 from davotoula/1292-url-decode-text-shared-to-app
use existing helper function that URL decodes message text
2025-03-25 18:06:42 -04:00
Vitor Pamplona
e75b451b79 Merge pull request #1306 from davotoula/remove-fdroid-from-readme
Update README.md
2025-03-25 18:05:44 -04:00
David Kaspar
7ad47bb137 Update README.md
remove fdroid from readme
2025-03-25 21:55:12 +00:00
David Kaspar
5518293813 use existing helper function that URL decodes message text 2025-03-25 20:38:08 +00:00
Vitor Pamplona
a23ad2dde7 Moves relay screens to it's own master package on the UI and reorganizes subpackages 2025-03-25 15:17:59 -04:00
Vitor Pamplona
b74bf44141 - Migrates NIP-28 creation to its own route.
- Adds picture upload for NIP-28 metadata
- Adds support for relay hints on NIP-28
- Improves edit field for chats
- Improves keyboard support for relay lists edit fields
2025-03-25 14:36:12 -04:00
KotlinGeekDev
f2a06184b4 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-25 10:53:49 +00:00
Vitor Pamplona
84f10da006 Forces the root post in a thread to be observed at all times. That guarantees thread loading even if relays are slow and the user is scrolling. 2025-03-24 18:02:55 -04:00
Vitor Pamplona
d2c19cbbb4 Merge pull request #1300 from davotoula/sniff-content-type-from-extension-when-empty-in-response-headers
Sniff content type from extension when empty in response headers
2025-03-24 17:50:22 -04:00
Vitor Pamplona
ef582eceb6 Merge pull request #1303 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-24 17:49:27 -04:00
Crowdin Bot
258815d349 New Crowdin translations by GitHub Action 2025-03-24 21:36:42 +00:00
Vitor Pamplona
1b40e70058 Fixing the Notification order for multiple playbacks 2025-03-24 17:33:53 -04:00
Vitor Pamplona
43f1796058 Updates AGP 2025-03-24 17:33:17 -04:00
Vitor Pamplona
53803eb878 Merge pull request #1302 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-24 16:13:11 -04:00
Crowdin Bot
7b5a2d1312 New Crowdin translations by GitHub Action 2025-03-24 20:12:36 +00:00
Vitor Pamplona
8f9a4557b5 Enable Picture in Picture videos 2025-03-24 16:10:14 -04:00
KotlinGeekDev
2ae9d272f4 Use Set rather than List, to avoid duplication. Introduce follow sets flow, and use it in FollowSetsActionMenu. 2025-03-22 21:02:10 +01:00
KotlinGeekDev
1d90e37f71 Fix mixup in follow set categorization logic. 2025-03-22 19:55:53 +01:00
KotlinGeekDev
c802c395a6 Unify the underlying implementation for follow sets and use it in both profile actions and CustomListsScreen. 2025-03-21 13:04:16 +01:00
KotlinGeekDev
fa7425e114 Remove unnecessary delay call. 2025-03-20 17:30:01 +01:00
KotlinGeekDev
5af094ab9b Re-introduce the follow sets action menu(with test data) in ProfileActions. 2025-03-20 15:56:11 +01:00
KotlinGeekDev
74ccbb3d07 Re-introduce the follow sets action menu(in a separate file, this time). 2025-03-20 15:47:23 +01:00
KotlinGeekDev
d9c3badf8a Re-introduce button styling changes(border shape). 2025-03-20 15:11:33 +01:00
David Kaspar
cdf2b10fd2 trim inline meta data to fix sniffing (required for DM messages) 2025-03-18 12:55:31 +00:00
KotlinGeekDev
dde59d4741 Switch to nameOrTitle() for follow set titles. 2025-03-18 13:49:35 +01:00
David Kaspar
2f29a21d72 cleaner code saveContentDefault: use apply for clarity 2025-03-18 12:29:03 +00:00
David Kaspar
93ed81dedb cleaner code saveDownloadingIfNeeded: refactor to use when and remove unnecessary null-safe calls 2025-03-18 12:14:57 +00:00
KotlinGeekDev
3e0f8a81b4 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-18 12:05:14 +00:00
David Kaspar
bcd636b46e sniff content type from extension when empty in response 2025-03-18 11:50:33 +00:00
Vitor Pamplona
6fd5f36ff5 Fix: Removes kind 30078 from search 2025-03-17 20:22:11 -04:00
Vitor Pamplona
4b79122899 Fixes the content type after image compression. 2025-03-17 20:07:25 -04:00
Vitor Pamplona
1df4d1db10 Removes debugging red border for full screen video 2025-03-17 14:30:17 -04:00
Vitor Pamplona
804194c3f5 Opens the fullscreen video playback with the right aspect ratio 2025-03-17 14:27:36 -04:00
Vitor Pamplona
fc159c4d1a Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-17 14:27:20 -04:00
Vitor Pamplona
754b648877 Removing unnecessary playback logs 2025-03-17 14:27:13 -04:00
Vitor Pamplona
c1606c54be Merge pull request #1299 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-17 14:04:48 -04:00
Crowdin Bot
82864c2069 New Crowdin translations by GitHub Action 2025-03-17 17:58:07 +00:00
Vitor Pamplona
3dc2a4a2d3 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-17 13:55:50 -04:00
Vitor Pamplona
7d1a8652cf Merge pull request #1298 from davotoula/1297-trim-inline-meta-data-before-saving-file
Trim meta data from file name before saving file to disk
2025-03-17 13:55:40 -04:00
Vitor Pamplona
96ac674eaa Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-17 13:55:18 -04:00
Vitor Pamplona
664ea86e6c Adds tor classes to the proguard exceptions 2025-03-17 13:55:12 -04:00
Vitor Pamplona
5aa2eb4499 Makes the bechmark build its own package name to facilitate testing in real phones. 2025-03-17 13:54:57 -04:00
Vitor Pamplona
0b9a44ab9b adds icon for Android studio 2025-03-17 13:53:42 -04:00
Vitor Pamplona
e3e35fa1eb - Refactoring playback service and its client.
- Created a pool of ExoPlayers to avoid creating new instances when scrolling.
- More accurately tracks MediaSessions
- Splits Caching layers into their own files
- Splits VideoView into multiple files
- Specializes the old Playback Manager into pools.
- Reorganizes video playback dependencies inside a single package.
- Structures callback uris to be per media item as opposed to per controller.
2025-03-17 13:53:26 -04:00
David Kaspar
221c58126c moved the trimming of inline meta-data to where File is created 2025-03-16 21:36:13 +00:00
David Kaspar
2cdf5d6673 trim meta data from file name before saving file to disk 2025-03-16 21:05:09 +00:00
Vitor Pamplona
2d5697cc9a Merge pull request #1296 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-15 14:25:09 -04:00
Crowdin Bot
d4ae290364 New Crowdin translations by GitHub Action 2025-03-15 16:46:06 +00:00
Vitor Pamplona
a690359541 Merge pull request #1295 from davotoula/1294-failed-to-save-the-image
Failed to save the image
2025-03-15 12:44:50 -04:00
David Kaspar
b3fa6401f6 Remove accidental import of okio.use 2025-03-15 13:58:04 +00:00
David Kaspar
b58d74138d cleaner code 2025-03-15 13:27:46 +00:00
David Kaspar
e6c413b176 Get mime type from extension if mime type in response header is null for a application/octet-stream content type 2025-03-15 13:13:46 +00:00
KotlinGeekDev
c9dbe7d203 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-14 23:18:09 +00:00
Vitor Pamplona
c1399d129e Migrates from old keyboard option interface 2025-03-14 14:20:08 -04:00
Vitor Pamplona
337a5e3cae Adds a flag to avoid new linting warnings 2025-03-14 14:12:12 -04:00
Vitor Pamplona
32b184a9cd Improves lightning error messages 2025-03-14 13:57:44 -04:00
Vitor Pamplona
fd538ebde1 Improves multiple error dialogs appearing when zapping from the reactions bar in the feed. 2025-03-14 13:57:33 -04:00
Vitor Pamplona
d76de0123a Forces aspect ratio of when loading an image to minimize resizing of the UI after the image is loaded. 2025-03-14 10:27:13 -04:00
Vitor Pamplona
de2b1a6c95 Optimizes caching for hidden chars on emoji 2025-03-14 10:24:03 -04:00
Vitor Pamplona
311964ce33 Caches video and image aspect ratios to recompose with the correct size 2025-03-13 19:30:57 -04:00
Vitor Pamplona
4d4c7d5b72 Moves DVM's last announcement restriction from 90 days to 365 days 2025-03-13 17:38:05 -04:00
Vitor Pamplona
66535e11ce - Removes the TAG_SIZE constant on all tags because each parser might use a different size. There is no minimum size for tags.
- Moves the tag parsing checks to use ensure.
- Explicitly writes matching methods to avoid hiding complexity beyond utility functions.
2025-03-13 17:24:20 -04:00
Vitor Pamplona
682dc62703 Updates zoomable, vico and mockk 2025-03-13 10:47:31 -04:00
Vitor Pamplona
ba7bd0bd8a Migrates to AGP 8.9 2025-03-13 09:21:50 -04:00
Vitor Pamplona
64b42ede7c Updates compose, firebase, navigation 2025-03-13 09:04:54 -04:00
Vitor Pamplona
8afe1ae537 Marks PrivateOutbox contents as encrypted 2025-03-13 09:04:39 -04:00
Vitor Pamplona
258a550e15 updating android studio 2025-03-13 09:00:44 -04:00
Vitor Pamplona
1603a7473a Adds support for Harassment reports. 2025-03-12 15:51:17 -04:00
Vitor Pamplona
ba6786cdcf Updates nip70 Protected Tags to the new structure 2025-03-12 14:51:47 -04:00
Vitor Pamplona
b017e03728 Updates NIP-56 to the newest tag structure for Quartz 2025-03-12 14:51:23 -04:00
Vitor Pamplona
5766bb1887 First idea without changing NIP-44 2025-03-12 09:41:18 -04:00
KotlinGeekDev
aae0e1bb4b Merge branch 'vitorpamplona:main' into profiles-list-management 2025-03-11 23:59:51 +00:00
Vitor Pamplona
40bc651860 Making sure iMetas are unique 2025-03-11 14:27:58 -04:00
Vitor Pamplona
8a779e7f3c Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-11 14:26:29 -04:00
Vitor Pamplona
350e2a5a90 Fixes loading iMeta from drafts and reset them after use 2025-03-11 14:17:59 -04:00
Vitor Pamplona
a75e268466 Merge pull request #1291 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-11 14:00:21 -04:00
Crowdin Bot
2fd456e33d New Crowdin translations by GitHub Action 2025-03-11 17:58:06 +00:00
Vitor Pamplona
64f7401b46 Fixes not including iMeta tags on public chats 2025-03-11 13:57:56 -04:00
Vitor Pamplona
5dcb1350ac Losing the test case for unit tests in CI 2025-03-11 13:57:39 -04:00
Vitor Pamplona
3aa370d746 Fixes bug that the room changes after citing a user on NIP-17 2025-03-11 13:46:55 -04:00
Vitor Pamplona
583855de4f Generalizes the chat upload UI and applies it to channels 2025-03-11 13:46:36 -04:00
KotlinGeekDev
4855126d6d Fix some imports and remove unneeded functions(as per the upstream code). 2025-03-11 13:56:16 +01:00
KotlinGeekDev
3b140b16eb Merge pull request #4 from vitorpamplona/main
Merge upstream changes.
2025-03-11 11:47:57 +00:00
Vitor Pamplona
05acc19c9a Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-10 20:34:42 -04:00
Vitor Pamplona
71f6fe2375 Merge pull request #1290 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-10 20:33:58 -04:00
Vitor Pamplona
286730c50d Adds name to the rendering of people lists in the feed 2025-03-10 20:33:42 -04:00
Crowdin Bot
57d6145677 New Crowdin translations by GitHub Action 2025-03-11 00:06:10 +00:00
Vitor Pamplona
94c78c6327 Minor refactoring of chat screen components. 2025-03-10 20:04:18 -04:00
Vitor Pamplona
7d2b7789d3 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-10 19:33:19 -04:00
Vitor Pamplona
fe55ee1818 Starts the migration of public chats to the new structure
Starts the migration of drafts to the new structure
2025-03-10 19:33:12 -04:00
Vitor Pamplona
58b6be73ee as per https://github.com/nostr-protocol/nips/pull/1836 2025-03-10 18:32:43 -04:00
KotlinGeekDev
9f23bd034c Merge changes from upstream. 2025-03-08 00:52:38 +01:00
KotlinGeekDev
33d690c403 Updates to conflict-causing files. 2025-03-08 00:52:10 +01:00
KotlinGeekDev
1dcbcc04d2 Fix merge-related conflicts. 2025-03-08 00:49:18 +01:00
David Kaspar
cd646951cf Merge pull request #1289 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-07 20:42:39 +00:00
Crowdin Bot
3789ba6c07 New Crowdin translations by GitHub Action 2025-03-07 20:13:11 +00:00
Vitor Pamplona
429f9f81c4 re organizing the private dm composables 2025-03-07 15:11:17 -05:00
Vitor Pamplona
639e2bb645 Updates our base textfield and moves to it's a more general package 2025-03-07 10:52:28 -05:00
David Kaspar
797bea9e72 Merge pull request #1285 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-06 21:40:09 +00:00
Crowdin Bot
1c28aa30f0 New Crowdin translations by GitHub Action 2025-03-06 21:28:31 +00:00
Vitor Pamplona
52e67c9ab6 Uses NIP-22 to reply to Highlights 2025-03-06 16:25:57 -05:00
Vitor Pamplona
fa9ff0c52a Uses NIP-22 to reply to Long form posts. 2025-03-06 16:25:47 -05:00
Vitor Pamplona
9cd73c2663 Adding secret emojis to reactions 2025-03-05 18:24:03 -05:00
Vitor Pamplona
8722f37fa5 Improving the UI of the Custom Emoji page. 2025-03-05 16:56:34 -05:00
Vitor Pamplona
987d8fa5dc Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt
#	quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt
#	quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt
#	quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt
#	quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt
2025-03-05 16:14:50 -05:00
Vitor Pamplona
f4d06eee6d Simplifying conditionals for the ATag parser 2025-03-05 16:13:33 -05:00
Vitor Pamplona
e2459e3fbd Simplifying conditionals for the ATag parser 2025-03-05 15:58:55 -05:00
Vitor Pamplona
9adc7a7adf Fixing padding to match the line padding 2025-03-05 15:40:49 -05:00
Vitor Pamplona
d474bb0016 fixes misaligned mute card on notifications 2025-03-05 15:37:57 -05:00
Vitor Pamplona
3942847d16 Refactors chats into public, private and list views for tablets and phones 2025-03-05 15:30:02 -05:00
Vitor Pamplona
2b2b219536 no message 2025-03-05 15:29:16 -05:00
Vitor Pamplona
91c19dc2a1 Adds a wait and display function to make sure the image url doesn't appear while the image is loading from disk 2025-03-05 15:26:52 -05:00
Vitor Pamplona
0536c141b7 Speeding up the loading of emojis 2025-03-05 15:23:37 -05:00
Vitor Pamplona
16331d268d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-03-05 13:40:59 -05:00
Vitor Pamplona
7e2724ea31 adds support for secret emojis 2025-03-05 13:40:51 -05:00
Vitor Pamplona
8facd02e33 Merge pull request #1284 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-03-04 18:02:13 -05:00
Crowdin Bot
90872b2160 New Crowdin translations by GitHub Action 2025-03-04 23:00:16 +00:00
Vitor Pamplona
0580aef4a2 - Migrates NIP-17 DM new message model to its own model and disable some of the generic behavior from new posts.
- Fixes a bug on edits showing a previous edit.
- Refactors user suggestions and lastword procedures
- Adds a DM inline reply from other parts of the app, like on notification.
- Hides Retweet button from DMs on Notifications
- Fixes colors for drafts in chats.
- Adds a nav function that computes the destination only after the user clicks on it and on a coroutine.
2025-03-04 17:57:47 -05:00
Vitor Pamplona
1a001e7bb3 Moves GeohashPrecision class to Quartz 2025-03-01 18:24:55 -05:00
Vitor Pamplona
4b91cfe809 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-28 15:26:58 -05:00
Vitor Pamplona
3d5d7b4987 adds nostr elites as a default global relay 2025-02-27 19:15:55 -05:00
Vitor Pamplona
ca0b6ef2ba Fixing error message on relay notices 2025-02-27 19:15:40 -05:00
Vitor Pamplona
3103b51cf4 Merge pull request #1281 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-27 18:45:33 -05:00
Crowdin Bot
8bbc017f91 New Crowdin translations by GitHub Action 2025-02-27 23:37:03 +00:00
Vitor Pamplona
0df07ddfcb - Adds support for https://github.com/nostr-protocol/nips/pull/1770
- Improves signer utils for testing
- Improves test cases for User metadata
2025-02-27 18:34:10 -05:00
Vitor Pamplona
8f0879b8e5 Updates agp, activity compose, accompanist, navigation and zoomable 2025-02-26 15:55:39 -05:00
Vitor Pamplona
f5ac8faa45 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-25 19:27:20 -05:00
Vitor Pamplona
40aa469866 Fixes unable to click in the last tab of the profile 2025-02-25 18:26:01 -05:00
Vitor Pamplona
d2590b969b Merge pull request #1280 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-25 18:20:05 -05:00
Crowdin Bot
2de3ba82cc New Crowdin translations by GitHub Action 2025-02-25 23:18:41 +00:00
Vitor Pamplona
b3dec691d7 losing up the probability to make tests pass 2025-02-25 18:16:20 -05:00
Vitor Pamplona
b40b37b6ac Adds the logged in user' posts to a user in the user's profile.
Refactors the user profile screen in many packages.
2025-02-25 18:09:04 -05:00
Vitor Pamplona
e6cd3a99c5 Fixes bug on saving Encrypted files on DMs.
Fixes bug when using partial downloads to display videos on DMs.
2025-02-25 16:43:15 -05:00
Vitor Pamplona
d5d289a834 Uses note.toEventUri to make sure the relay hint is correct
Avoids crash when adding an event to gallery without a relay hint.
2025-02-24 20:46:33 -05:00
Vitor Pamplona
a2d9f31a8a Assigning hints to NIPs until NIP-53
Speeding up Bech32 and Hex processing
Updating Boosts and Reposts to the new way of declaring tags
Renaming the SHA256 hash tag from NIP94 to not confuse with # hashtags
2025-02-24 17:09:08 -05:00
Vitor Pamplona
3790325a0f uses const val for faster MurMur 2025-02-22 16:19:01 -05:00
Vitor Pamplona
96f57b2c90 Fixes tag name on ImageTag 2025-02-22 16:18:46 -05:00
Vitor Pamplona
4b10dd58df It's slightly faster to reject params in one line than on many. 2025-02-22 16:17:10 -05:00
Vitor Pamplona
dc4240a8f5 Increasing the amount of addresses 2025-02-20 18:06:17 -05:00
Vitor Pamplona
3220d793b4 Using a linked set for the relayDB is faster to loop through on mightContain 2025-02-20 17:44:36 -05:00
Vitor Pamplona
84a52a1ce0 Speeding MurMur up
Adding more test cases
Mapping out memory used by filter
2025-02-20 17:10:24 -05:00
Vitor Pamplona
aaf86bf53e Adds a Bloom-based hint indexer with MurMur hash 2025-02-20 14:07:10 -05:00
Vitor Pamplona
94e0f4ed02 Performance improvements on the PoW miner 2025-02-18 20:44:24 -05:00
Vitor Pamplona
af29133272 Simplifies the benchmark for PoW 2025-02-18 19:00:05 -05:00
Vitor Pamplona
06240b1f15 Updates libsecp256k 2025-02-18 18:14:52 -05:00
Vitor Pamplona
d47ec06b19 Deleting the experimental serializer test 2025-02-18 17:51:34 -05:00
Vitor Pamplona
9689acb5f3 Deletes experimental serializer on NIP-01
Moves classes to the appropriate packages on NIP-01
Adds a PoW miner to quartz.
Adds a PoW commit evaluator as a bytearray of the ID
2025-02-18 17:47:34 -05:00
Vitor Pamplona
bcb3838e85 Fixes isTagged for events 2025-02-18 12:04:39 -05:00
Vitor Pamplona
4fcf962c71 Fixes r-tag migration 2025-02-18 12:00:22 -05:00
Vitor Pamplona
2de4bb1adc Reverses ATag Generalization (It just made things more complicated) 2025-02-18 11:51:16 -05:00
Vitor Pamplona
9e1a2bb1e4 Removes the notion of generic Address references 2025-02-18 11:29:52 -05:00
Vitor Pamplona
8720b00d4f Separates the random benchmark from the Sign/Verify benchmarks 2025-02-18 10:19:51 -05:00
Vitor Pamplona
9c35008fc4 Moves Sha256 to a pool of digests to double the hashing performance 2025-02-18 09:40:17 -05:00
Vitor Pamplona
fad08830a1 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-17 19:13:14 -05:00
Vitor Pamplona
6d9964a1cb Decoupling NIP01 methods from CryptoUtils
Decoupling Encryption and Decryptions from CryptoUtils
Decoupling live instances of JNI bindings for Secp and LibSodium from CryptoUtils
Decoupling key cache from CryptoUtils
Reorganizes NIP-04 to match new package structure
Adjusts test structures to match
2025-02-17 19:13:05 -05:00
Vitor Pamplona
70ced84dc3 Merge pull request #1278 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-17 18:59:34 -05:00
Crowdin Bot
50da982b36 New Crowdin translations by GitHub Action 2025-02-17 19:14:58 +00:00
Vitor Pamplona
7ad8b2ce46 Migrates LocalCache from being based on ATags to Addresses, removing the need to use memory space to store relay hints 2025-02-17 14:12:54 -05:00
Vitor Pamplona
94ffe783e9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-17 11:18:47 -05:00
Vitor Pamplona
41e47fa71d Fixes running new events on the main thread. 2025-02-17 11:17:38 -05:00
Vitor Pamplona
08dd634eae Fixes crash when evaluating the isHex function for an emoji 2025-02-17 11:12:47 -05:00
David Kaspar
cb3aa6c329 Merge pull request #1277 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-16 19:42:57 +01:00
Crowdin Bot
4e2e24c7ab New Crowdin translations by GitHub Action 2025-02-16 16:55:21 +00:00
Vitor Pamplona
67106e8a11 Renames PoW processor to parser 2025-02-16 11:53:23 -05:00
Vitor Pamplona
06c7ea0123 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-14 16:31:33 -05:00
Vitor Pamplona
8022ac525f Makes sure deletions happen in the background thread. 2025-02-14 16:29:10 -05:00
Vitor Pamplona
534f1ed8cd Adds Tim Bourma's test case 2025-02-14 16:28:56 -05:00
Vitor Pamplona
793b3fcd16 Renames PoW Parser to open space for the PoW creator 2025-02-14 16:28:40 -05:00
Vitor Pamplona
a4f84227b6 Merge pull request #1276 from MoonDusk1996/main
fix wrong word
2025-02-14 14:48:26 -05:00
Dusk
e921f26d5f fix wrong word 2025-02-14 13:34:59 -04:00
David Kaspar
0c3a792a84 Merge pull request #1275 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-14 17:15:44 +01:00
Crowdin Bot
6481fcbecd New Crowdin translations by GitHub Action 2025-02-14 16:07:00 +00:00
Vitor Pamplona
0f6c3b9a3a Moves to vico chart 2.0 2025-02-14 11:05:00 -05:00
Vitor Pamplona
dcb0e31cd6 Updating activity compose, coil, combosebom, firebasebom, fragmentktx, kotlinserialization, mockk and navigation compose 2025-02-13 19:02:15 -05:00
Vitor Pamplona
09cff7c930 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-02-13 19:01:37 -05:00
Vitor Pamplona
cb121835f5 Stops using vico's forEach custom function (it's going to be deleted in the next version) 2025-02-13 19:01:30 -05:00
Vitor Pamplona
0610416f61 Updates AGP to 8.8.1 2025-02-13 17:59:23 -05:00
Vitor Pamplona
f0116ad06e Merge pull request #1274 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-13 17:55:17 -05:00
Crowdin Bot
89664a2813 New Crowdin translations by GitHub Action 2025-02-13 22:49:05 +00:00
Vitor Pamplona
9f465d3296 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	amethyst/src/main/res/values-uz-rUZ/strings.xml
2025-02-13 17:40:00 -05:00
Vitor Pamplona
9c21a8a7da Massive refactoring of Quartz to:
- Define each tag in their own class.
- Allow extension functions to additional responsibilities to other classes
- Migrate from hardcoded tag filters in events to the Tag's parser and assemble functions.
- Migrate hardcoded event.create to builders that use extension functions
- Restructures threading infrastructure for NIP-10
- Decouple the event signing from the Event building functions via event templates
- Create classes to represent Tags and TagArrays and use extension functions to add domain-related methods to the tag array of each nip.
- Uses external functions on event template builders to better point to which functions and which tags can be used in which event kinds.
- Separates Event kinds in packages inside each nip.
- Improves support for NIP-89
- Correctly establishes which imeta params can be used in each nip (video, picture, files)
- Decouples the iMeta builder from any nip.
- Fixes mute list word and user removal when inserted from a different client.
- Migrates the Account class to avoiding having to build each Event inside of it
2025-02-13 17:39:13 -05:00
Vitor Pamplona
0f3b3c4b2d Adds support for NIP-73 2025-02-13 17:19:09 -05:00
Vitor Pamplona
68d4686424 Adds NostrMedia as a paid blossom relay option 2025-02-13 17:10:51 -05:00
Vitor Pamplona
e2a812a416 Merge pull request #1272 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-02-10 08:48:44 -05:00
Crowdin Bot
2eece49b9b New Crowdin translations by GitHub Action 2025-02-10 13:43:23 +00:00
Vitor Pamplona
c34c107a7c Merge pull request #1270 from believethehype/gallery_options
add option for 2 profile gallery views
2025-02-10 08:41:37 -05:00
KotlinGeekDev
0fe6fd40be Add component for displaying the number of people in the follow list. Make some minor refactoring. 2025-02-06 14:12:09 +01:00
KotlinGeekDev
e25dce32e4 Refactor to adjust to previous change. Make other little changes. 2025-02-05 15:42:46 +01:00
KotlinGeekDev
606f630cb6 Use data classes/objects for state. Only update follow sets through exposed functions. 2025-02-05 15:31:48 +01:00
dbth
e9861d80f6 add option for 2 profile gallery views 2025-02-01 21:48:26 +01:00
Vitor Pamplona
7b84c81d9c Merge pull request #1267 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-30 14:49:30 -05:00
Crowdin Bot
9caf0d28f5 New Crowdin translations by GitHub Action 2025-01-30 19:48:11 +00:00
Vitor Pamplona
78e5f5e277 Merge pull request #1265 from davotoula/1258-exlude-FileHeaderEvent-from-search-results
Exclude file header events from search results
2025-01-30 14:46:55 -05:00
KotlinGeekDev
c2857664d3 Just use simple List. Remove explicit dispatcher calls in unneeded places. 2025-01-30 14:50:05 +01:00
KotlinGeekDev
a300a4b25c Remove the tick as list indicator in favour of textual indication. Make add/remove button look slightly better. 2025-01-30 13:52:07 +01:00
KotlinGeekDev
fad545a094 Introduce user removal functionality. 2025-01-30 10:00:25 +01:00
KotlinGeekDev
a066285667 Make some visual changes to the add/remove user button, and move the add callback to the right place. 2025-01-29 23:45:52 +01:00
David Kaspar
1bd52f163f extract logical expression for which notes to exclude from search results 2025-01-28 11:55:17 +01:00
KotlinGeekDev
d13bf43de0 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2025-01-28 11:42:40 +01:00
KotlinGeekDev
7325644689 Fix some code after upstream changes. 2025-01-28 11:42:05 +01:00
David Kaspar
d825995ef4 Exclude FileHeaderEvent from note search results (direct note id search and free text) 2025-01-28 11:39:46 +01:00
KotlinGeekDev
4b7b5f1276 Merge branch 'upstream' into profiles-list-management
Sync with upstream
2025-01-28 11:20:12 +01:00
KotlinGeekDev
467b8d4184 Merge upstream, cont'd. 2025-01-28 10:18:03 +01:00
Vitor Pamplona
3e1e3e80fb Merge pull request #1264 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-25 08:55:44 -05:00
Crowdin Bot
951a3a73d0 New Crowdin translations by GitHub Action 2025-01-25 13:54:41 +00:00
Vitor Pamplona
8a9fdd2c34 Merge pull request #1263 from davotoula/ugrade-gradle-and-agp
upgrade gradle to 8.10.2
2025-01-25 08:53:29 -05:00
David Kaspar
5dbba6d06d upgrade gradle to 8.10.2
upgrade agp to 8.8.0
2025-01-25 09:18:57 +00:00
Vitor Pamplona
5ee754c71a Allows the choice of limits on Count 2025-01-15 13:16:44 -05:00
Vitor Pamplona
a739329ab8 Merge pull request #1257 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-15 11:54:44 -05:00
Crowdin Bot
c12febf980 New Crowdin translations by GitHub Action 2025-01-15 16:49:57 +00:00
Vitor Pamplona
c25d64558e - Normalizes the use of ETag
- Creates a BaseReplaceable event to avoid duplicates of empty d tags.
2025-01-15 11:48:01 -05:00
Vitor Pamplona
5a3c821da4 make sure the pow functions are static in java 2025-01-15 10:11:17 -05:00
Vitor Pamplona
b90aac496e Moves PoW tag to the new style of tag builders. 2025-01-15 10:10:10 -05:00
Vitor Pamplona
4b52cdb101 Updates NIP-14 subjects to the new style of extension functions 2025-01-15 10:09:52 -05:00
Vitor Pamplona
4a4f03c12b Refactor proxyTag function names 2025-01-15 09:56:15 -05:00
Vitor Pamplona
1f9aeddb57 Adds support for NIP-48 to Quartz 2025-01-15 09:17:32 -05:00
Vitor Pamplona
f3b049382e Renames Gossip to Rumor to match names on the NIP-59 2025-01-15 08:58:08 -05:00
Vitor Pamplona
d1c64a0d15 Removing additional dependencies in the base Event class 2025-01-15 08:53:13 -05:00
Vitor Pamplona
8e25271bd6 Improves the documentation of the TagArray functions 2025-01-15 08:39:41 -05:00
Vitor Pamplona
0029df4575 Merge pull request #1256 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-14 19:44:32 -05:00
Crowdin Bot
b0e029cd59 New Crowdin translations by GitHub Action 2025-01-15 00:37:17 +00:00
Vitor Pamplona
d19ae56afa - Migrates Zap splits, zapraisers, subject, alts and content warning to their own packages.
- Adds protected event tags
2025-01-14 19:35:16 -05:00
Vitor Pamplona
502d39c893 Generalizes the hashtag and index tag parser for the content as well as the way to pass params to build them on create. 2025-01-14 16:59:53 -05:00
Vitor Pamplona
4f774ae3c5 Simple refactoring to avoid empty challenges. 2025-01-14 16:58:16 -05:00
Vitor Pamplona
86a9fb2af5 Moves the relay structure to quartz 2025-01-14 12:09:25 -05:00
Vitor Pamplona
e36e49cc88 Breakes the Command and Message parsers out of the Relay class 2025-01-14 11:47:52 -05:00
Vitor Pamplona
3ac303c724 Starts the work to filter events that return from the relay to make sure they match the subscription submitted 2025-01-13 19:42:43 -05:00
Vitor Pamplona
e3e90229ce Separates Typed Relay class from a SimpleRelay class to prepare to move to Quartz 2025-01-13 19:15:20 -05:00
Vitor Pamplona
9b0f24ba94 Removing duplicated permission for the camera 2025-01-13 13:03:40 -05:00
Vitor Pamplona
7f6b0ef7c2 Fixes package names for filters that moved to quartz 2025-01-13 13:03:22 -05:00
Vitor Pamplona
5c805d7551 Fixes greek name for Backup keys 2025-01-13 12:46:23 -05:00
Vitor Pamplona
57fe3740fc Moves limits and Filters to quartz 2025-01-13 12:46:15 -05:00
Vitor Pamplona
b871954293 Moves NIP96 parsing test to Quartz 2025-01-13 12:23:57 -05:00
Vitor Pamplona
d66e8d4cde refactoring android tests to match refactoring of package names
moving some nip96 stuff to quartz
2025-01-13 12:20:02 -05:00
Vitor Pamplona
103210ade4 Moves pow test to pow package 2025-01-13 10:44:15 -05:00
Vitor Pamplona
0d4936cf4b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2025-01-13 10:38:53 -05:00
Vitor Pamplona
d2b731e372 Massive refactoring of quartz to prepare for nip-based packages. 2025-01-13 10:38:45 -05:00
Vitor Pamplona
ceba31ec61 Merge pull request #1255 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-13 09:51:50 -05:00
Crowdin Bot
9684940265 New Crowdin translations by GitHub Action 2025-01-13 14:49:44 +00:00
Vitor Pamplona
dd7387a3f5 Merge pull request #1254 from greenart7c3/main
Move profile edit dialog to a screen
2025-01-13 09:48:13 -05:00
greenart7c3
020ead3c3f Move profile edit dialog to a screen 2025-01-08 10:47:32 -03:00
Vitor Pamplona
1430ba4745 Moves Event ID hashing to a new class 2025-01-06 15:13:14 -05:00
Vitor Pamplona
5f577df819 Refactors SHA-256 methods to its own class 2025-01-06 12:56:57 -05:00
Vitor Pamplona
0971b716e1 Refactors secure random to extension class 2025-01-06 12:56:29 -05:00
Vitor Pamplona
53458ab484 Avoids showing youtube videos on the feed since we can't play them 2025-01-06 12:54:08 -05:00
Vitor Pamplona
879f70fcbb Merge pull request #1252 from greenart7c3/main
Add some missing functions to NostrClient
2025-01-05 14:23:40 -05:00
greenart7c3
eb93664968 Add getRelay 2025-01-05 14:43:44 -03:00
Vitor Pamplona
9816577f00 v0.94.3 2025-01-05 12:16:06 -05:00
Vitor Pamplona
306ce9a36c Adds iMeta tags to GIF urls 2025-01-05 12:14:28 -05:00
greenart7c3
10459b0a7a Add getAll 2025-01-05 13:58:48 -03:00
Vitor Pamplona
f1d61daa04 Merge pull request #1250 from geovnn/main
Keep note state across LazyColumns recompositions
2025-01-05 11:23:43 -05:00
Vitor Pamplona
1ca0abf30d Minor refactoring: uploaders can do image and videos. 2025-01-05 11:22:40 -05:00
Vitor Pamplona
d0a9c1ccd0 Fixes: Only close the upload screen if the video upload is a success on the Media tab 2025-01-05 11:22:15 -05:00
Vitor Pamplona
617f1ba2a6 Fixes the extra empty kind 20 post when uploading videos. 2025-01-05 11:21:32 -05:00
Giovanni Gatti
f1141e698e Use rememberSaveable to keep note state across LazyColumns recompositions 2025-01-05 00:52:53 +01:00
Vitor Pamplona
f94feebbd7 v0.94.2 2025-01-04 17:47:13 -05:00
Vitor Pamplona
82d006f65e letter change to avoid mixing with other log messages 2025-01-04 17:44:43 -05:00
Vitor Pamplona
3a553fa550 Updates JNA and URI Normalizer 2025-01-04 17:41:47 -05:00
Vitor Pamplona
1e9c3a47a4 Merge pull request #1249 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-04 17:40:46 -05:00
Crowdin Bot
3d9b4431ad New Crowdin translations by GitHub Action 2025-01-04 22:37:25 +00:00
Vitor Pamplona
6522d74253 Fixes the Tor leaks for relays and some image content. 2025-01-04 17:35:34 -05:00
KotlinGeekDev
410888b271 Merge branch 'vitorpamplona:main' into profiles-list-management 2025-01-04 21:34:56 +00:00
Vitor Pamplona
8a8ae4fb8b Merge pull request #1248 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-03 20:39:08 -05:00
Crowdin Bot
6cac5fa5e4 New Crowdin translations by GitHub Action 2025-01-04 01:38:36 +00:00
Vitor Pamplona
aa3063acae v0.94.1 2025-01-03 20:36:26 -05:00
Vitor Pamplona
46de90ea65 Removing the unecessary loading of the emoji suggestions when creating the new post view model 2025-01-03 20:36:18 -05:00
Vitor Pamplona
aa44560714 Adds support for Custom Reaction selection on new posts and direct GIF url usage. 2025-01-03 20:29:31 -05:00
Vitor Pamplona
c9a6b63f67 v0.94.0 2025-01-03 11:02:15 -05:00
David Kaspar
47211e9136 Merge pull request #1247 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-03 15:29:44 +00:00
Crowdin Bot
cfbcc49940 New Crowdin translations by GitHub Action 2025-01-03 15:28:37 +00:00
Vitor Pamplona
b4a497fd11 Merge pull request #1246 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-03 10:27:22 -05:00
Crowdin Bot
9aa287f96a New Crowdin translations by GitHub Action 2025-01-03 15:16:06 +00:00
David Kaspar
a6880f339c Merge pull request #1245 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-03 15:14:36 +00:00
Crowdin Bot
43996499ba New Crowdin translations by GitHub Action 2025-01-03 15:13:40 +00:00
Vitor Pamplona
5c0c1acc4a Allowing multiple images on a single event to be displayed in the Profile gallery 2025-01-03 10:11:10 -05:00
KotlinGeekDev
920241abab Merge branch 'vitorpamplona:main' into profiles-list-management 2025-01-03 14:38:02 +00:00
Vitor Pamplona
4bfacbc11e Improves the spacing in the layout of the gallery 2025-01-02 19:42:17 -05:00
Vitor Pamplona
633c9e8dcf Merge pull request #1244 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-02 19:04:53 -05:00
Crowdin Bot
0f2a4d104c New Crowdin translations by GitHub Action 2025-01-02 23:55:24 +00:00
Vitor Pamplona
fb92d2da2f Merge pull request #1243 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-02 18:54:30 -05:00
Vitor Pamplona
d29e90fe8f Adjusts the top bar when uploading media to DMs to match the chat top bar 2025-01-02 18:53:44 -05:00
Crowdin Bot
27329b7a0f New Crowdin translations by GitHub Action 2025-01-02 23:11:04 +00:00
Vitor Pamplona
577ef6437f removes logs 2025-01-02 18:09:27 -05:00
Vitor Pamplona
dbe593db98 Adds a server and compression selection screen when uploading images on DMs 2025-01-02 18:06:41 -05:00
Vitor Pamplona
ff9509b649 Refactors user suggestion views 2025-01-02 17:58:06 -05:00
Vitor Pamplona
2772bdff8b Increases the number of active playbacks on the screen 2025-01-02 10:52:17 -05:00
Vitor Pamplona
c6869eaff6 Merge pull request #1242 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-01-02 09:04:49 -05:00
Crowdin Bot
663f5f282e New Crowdin translations by GitHub Action 2025-01-02 13:57:39 +00:00
Vitor Pamplona
9f147f03c7 Fixes https://github.com/vitorpamplona/amethyst/issues/1241 2025-01-02 08:55:42 -05:00
KotlinGeekDev
8ca827a816 Merge branch 'vitorpamplona-main' into profiles-list-management
Merge upstream changes.
2025-01-01 13:39:19 +01:00
KotlinGeekDev
b2dd2a2adb Merge upstream with local changes. 2025-01-01 13:29:00 +01:00
KotlinGeekDev
7166439b97 Arrange the placement of list status indicators. 2025-01-01 13:06:56 +01:00
Vitor Pamplona
0ceccb23c6 Avoids cutting off some of the payables in the Pay intent screen 2024-12-31 16:21:35 -05:00
Vitor Pamplona
ee65169896 Final touches of the new error message dashboard 2024-12-31 16:21:14 -05:00
Vitor Pamplona
6ac930c241 Don't show the "message the user" button when the problem is local 2024-12-31 16:20:53 -05:00
Vitor Pamplona
a09b8c5862 Moves to using cancellable coroutines to avoid cancelling the parent group all together. 2024-12-31 16:20:10 -05:00
Vitor Pamplona
160d4722c0 Refactoring Zap Error message screen to allow sending messages directly to each receiver with an error 2024-12-31 15:51:31 -05:00
Vitor Pamplona
497ae937fd Fixes the navigation padding on Samsung devices for the Shorts tab. 2024-12-30 20:32:53 -05:00
Vitor Pamplona
b366aee1a9 Makes sure to keep the chat's input on top of the system nav 2024-12-30 20:13:38 -05:00
Vitor Pamplona
09539ac22a adds new rules for proguard 2024-12-30 19:57:31 -05:00
Vitor Pamplona
e1e3a0e6a0 Expands the Around me filter to 50km 2024-12-30 19:57:20 -05:00
Vitor Pamplona
54e6be8ef3 Fixes notes appearing of replies for some recipes 2024-12-30 16:03:00 -05:00
Vitor Pamplona
e8462fe67f Fully removing the swipe to delete from right to left. 2024-12-30 15:56:24 -05:00
Vitor Pamplona
626daa9967 Refactoring 2024-12-30 15:43:09 -05:00
Vitor Pamplona
266d4cb34f Solves crashing when a p-tag contains only "ffff" 2024-12-30 15:42:54 -05:00
Vitor Pamplona
fc72e20da7 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-30 08:51:24 -05:00
Vitor Pamplona
594abd1d7a Updates zoomable 2024-12-30 08:51:14 -05:00
Vitor Pamplona
a0d8a770b7 Merge pull request #1239 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-30 08:50:31 -05:00
Crowdin Bot
fc7b44b9d0 New Crowdin translations by GitHub Action 2024-12-30 13:39:56 +00:00
Vitor Pamplona
00eb4e364a Merge pull request #1238 from davotoula/display-toast-on-media-download-start
Display toast on media download start
2024-12-30 08:38:31 -05:00
David Kaspar
e88a36187d added translations 2024-12-30 14:16:30 +01:00
David Kaspar
21d80518c9 strings refactor 2024-12-30 14:07:06 +01:00
David Kaspar
7999608f28 added translatable strings 2024-12-30 14:03:28 +01:00
David Kaspar
ee20508e4b added video download started toast 2024-12-30 13:56:39 +01:00
David Kaspar
1231a1483f Corrected "added to profile gallery" toast text 2024-12-30 13:45:02 +01:00
David Kaspar
f8dd82f592 added a first toast for start of image download 2024-12-30 13:21:11 +01:00
Vitor Pamplona
ca00e6a302 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-28 16:43:34 -05:00
Vitor Pamplona
aeba5d1b51 delete unnecessary comment 2024-12-28 16:43:05 -05:00
Vitor Pamplona
3e014e7b0f Merge pull request #1237 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-28 15:32:06 -05:00
Crowdin Bot
95cda8ec08 New Crowdin translations by GitHub Action 2024-12-28 20:20:54 +00:00
Vitor Pamplona
1f3a818922 Remove crossfades and double composition on image success 2024-12-28 15:15:52 -05:00
Vitor Pamplona
7fe02fcf2c Disables delete draft from swipe from right to left. Only left to right remains available. 2024-12-27 18:25:24 -05:00
Vitor Pamplona
1c52887e3a Merge pull request #1236 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-27 17:02:07 -05:00
Crowdin Bot
62d95e2128 New Crowdin translations by GitHub Action 2024-12-27 21:52:27 +00:00
Vitor Pamplona
d0961d08da Fixes edge to edge issues when the keyboard is shown. 2024-12-27 16:49:43 -05:00
Vitor Pamplona
f38383d4e4 Removes debugging logs 2024-12-25 11:09:20 -05:00
Vitor Pamplona
739a073d4e Updates Vico, mockk, kotlin and adaptive acompanist 2024-12-24 18:22:53 -05:00
Vitor Pamplona
a195167a56 Fixes keyboard overriding the relay settings screen 2024-12-24 17:55:26 -05:00
Vitor Pamplona
72c089858d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-24 16:05:43 -05:00
Vitor Pamplona
92fcf05557 Fixes double quotes on NIP-28 Channel messages 2024-12-24 15:53:45 -05:00
Vitor Pamplona
73d38f6257 Merge pull request #1233 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-24 11:31:53 -05:00
Crowdin Bot
48d8e418e8 New Crowdin translations by GitHub Action 2024-12-24 16:30:04 +00:00
Vitor Pamplona
ff427d0fde Fixes image upload testing. 2024-12-24 11:27:44 -05:00
Vitor Pamplona
6807554d1d Merge pull request #1232 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-23 20:40:49 -05:00
Crowdin Bot
433a15cf0a New Crowdin translations by GitHub Action 2024-12-24 01:33:04 +00:00
Vitor Pamplona
94c74a1e0c Adds support for encrypted media uploads on NIP-17 DMs 2024-12-23 20:30:22 -05:00
Vitor Pamplona
f839565152 Refactors Ammolite to remove the dependency on OkHttp to prepare for KTor and multiplatform settings.
- This also reduces the Singleton coupling between Client and RelayPool.

To migrate, create a NostrClient instance on your Application class and update your code to access that `client` instance.
2024-12-20 15:36:50 -05:00
Vitor Pamplona
d9c14a78a7 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-18 20:55:55 -05:00
Vitor Pamplona
a9d87883d2 Integrates with Pokey's Broadcast receiver. 2024-12-18 20:55:45 -05:00
Vitor Pamplona
66e7559ddb Merge pull request #1231 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-18 20:32:34 -05:00
Crowdin Bot
2cd9a7dca6 New Crowdin translations by GitHub Action 2024-12-19 01:10:26 +00:00
Vitor Pamplona
123239443d organize imports 2024-12-18 20:08:27 -05:00
Vitor Pamplona
76ce8f2974 Fixes bug that resets localhost relay settings. 2024-12-18 18:38:26 -05:00
Vitor Pamplona
e002e5c10f Merge pull request #1229 from davotoula/account-backup-qr-code
Account backup qr codes
2024-12-18 18:28:13 -05:00
Vitor Pamplona
d101397166 Merge pull request #1230 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-18 17:58:02 -05:00
Crowdin Bot
2fd96da7c7 New Crowdin translations by GitHub Action 2024-12-18 22:48:35 +00:00
Vitor Pamplona
06bc1922cb Fixes tests 2024-12-18 17:27:20 -05:00
David Kaspar
9f1b5a3c1a added content description to QR code icons 2024-12-18 23:23:57 +01:00
David Kaspar
4f4da12aa7 refactor QrCodeButton methods to avoid duplications 2024-12-18 23:06:37 +01:00
Vitor Pamplona
17495631f7 Simplifies a bit of the game controller icon 2024-12-18 17:01:29 -05:00
Vitor Pamplona
0041d87645 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-18 16:53:11 -05:00
Vitor Pamplona
579f63935a - Adds support for multiple media uploads at the same time.
- Adds support to display PictureEvents with multiple images at the same time
- Improves Uploading feedback for the NewPost screen
- 10x better performance on Blurhash generation
- Fixes cosine caching on Blurhash
- Removes troublesome dependency on blurhash encoder liberary
- Restructures contentScale for Images and Video dialogs
- Refactors Media Uploaders to improve code reuse
- Refactors iMeta usage on Quartz to move away from NIP-94
2024-12-18 16:52:50 -05:00
David Kaspar
b273e6dd4f rearrange code for easier diff 2024-12-18 22:52:01 +01:00
David Kaspar
eec8212070 added keyguard to qr code encrypted key export 2024-12-18 20:54:14 +01:00
David Kaspar
af5f136e6c added keyguard to qr code key export 2024-12-18 20:18:23 +01:00
David Kaspar
12d646e8a9 Move QR code next to button 2024-12-18 15:08:06 +01:00
David Kaspar
bd26fcfafd Clean up layout 2024-12-18 14:54:09 +01:00
David Kaspar
ca05b768df Added QR code private key export dialog 2024-12-18 14:43:23 +01:00
Vitor Pamplona
b75024e1fb Merge pull request #1227 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-16 10:10:44 -05:00
Crowdin Bot
816c598971 New Crowdin translations by GitHub Action 2024-12-16 15:08:47 +00:00
Vitor Pamplona
5636748cf8 Merge pull request #1223 from davotoula/add-custom-hashtag-icon-gamestr
Added Gamestr custom hashtag icon
2024-12-16 10:07:25 -05:00
Vitor Pamplona
3defc4d45b Merge pull request #1226 from greenart7c3/main
Fix download and loading icon not showing when text overflow
2024-12-16 10:06:17 -05:00
greenart7c3
ee16324673 Fix download and loading icon not showing when text overflow 2024-12-16 10:40:18 -03:00
David Kaspar
dfabd7afdd Added Gamestr custom hashtag icon 2024-12-15 21:52:30 +01:00
Vitor Pamplona
728768632c Update Compose Bom, firebase, navigation compose and android camera libs 2024-12-12 14:16:58 -05:00
Vitor Pamplona
c2243c53cf Optimizes user search to account for names that start with the typed prefix 2024-12-10 19:07:37 -05:00
Vitor Pamplona
5aed1cee2c Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-10 18:46:08 -05:00
Vitor Pamplona
8809593bbc - adds an account switcher to the notification click
- removes the use of nostr: for notifications
- enables a new screen to be routed when new logins happen
2024-12-10 18:45:59 -05:00
Vitor Pamplona
83e942a7be Fixes lack of notification when a new account is logged into the app and before the app restarts. 2024-12-10 18:39:45 -05:00
Vitor Pamplona
1ee6316e92 Merge pull request #1221 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-10 15:28:07 -05:00
Crowdin Bot
357ca9b6eb New Crowdin translations by GitHub Action 2024-12-10 20:16:01 +00:00
Vitor Pamplona
b0f5e77077 Merge pull request #1220 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-10 15:14:39 -05:00
Crowdin Bot
1b7b8fa268 New Crowdin translations by GitHub Action 2024-12-10 19:22:17 +00:00
Vitor Pamplona
82d8411ac6 Merge pull request #1219 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-10 14:21:00 -05:00
Crowdin Bot
684b78d2df New Crowdin translations by GitHub Action 2024-12-10 19:19:46 +00:00
Vitor Pamplona
8b80ac13db Fixes displaying an old result when coming back to a DVM screen 2024-12-10 14:17:50 -05:00
Vitor Pamplona
83235dab69 Removes contract of the old image picker 2024-12-10 14:17:15 -05:00
Vitor Pamplona
b01f46b2ee Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-09 14:59:44 -05:00
Vitor Pamplona
6a787f2774 Moves relayToAdd from the Object to a param in the relay screen 2024-12-09 14:59:35 -05:00
Vitor Pamplona
53206cc0b2 Merge pull request #1218 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-09 14:43:09 -05:00
Crowdin Bot
7cb6854489 New Crowdin translations by GitHub Action 2024-12-09 19:32:52 +00:00
Vitor Pamplona
95966e5874 Merge pull request #1214 from greenart7c3/main
Move relay dialog to a route
2024-12-09 14:31:24 -05:00
Vitor Pamplona
723979f185 Merge pull request #1216 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-07 18:10:42 -05:00
Crowdin Bot
b989bc0236 New Crowdin translations by GitHub Action 2024-12-07 23:01:56 +00:00
Vitor Pamplona
54ab8f1448 Merge pull request #1215 from davotoula/improve-accessibility-add-content-descriptions
Improve accessibility add content descriptions
2024-12-07 18:00:18 -05:00
David Kaspar
1b0ff52c61 translations 2024-12-07 21:29:34 +01:00
David Kaspar
46b70b719a re-add key to LaunchedEffect 2024-12-07 21:21:47 +01:00
David Kaspar
1972cdeafb Add content description to custom emojis 2024-12-07 21:06:21 +01:00
David Kaspar
39a7d98651 Add content description to icon in app definition 2024-12-07 16:56:24 +01:00
greenart7c3
a93910f8cb Move relay dialog to a route 2024-12-07 11:17:38 -03:00
David Kaspar
314cbe1a32 Add content description to recommended apps icons - refactored 2024-12-07 15:14:46 +01:00
David Kaspar
54d0ec806d Add content description to recommended apps icons 2024-12-07 15:12:27 +01:00
David Kaspar
6faab55a55 Add content description to pushpin icon (just in case) 2024-12-07 14:38:58 +01:00
David Kaspar
8b5366f9e2 Add description to relay list selector in NewMediaView 2024-12-07 14:11:09 +01:00
Vitor Pamplona
15b527c58d Fixes blurhash rendering 2024-12-06 18:13:40 -05:00
Vitor Pamplona
e501101062 Merge pull request #1213 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-06 15:57:41 -05:00
Crowdin Bot
15411edfa6 New Crowdin translations by GitHub Action 2024-12-06 20:07:57 +00:00
Vitor Pamplona
e65346d268 Move gallery previews to it's own package. Avoid reusing VideoView 2024-12-06 15:05:59 -05:00
Vitor Pamplona
01b880c4a5 Simplifies the Gallery stack 2024-12-06 13:41:03 -05:00
Vitor Pamplona
0d6760360f Fixes the bugs from migrating video events to imeta tags 2024-12-06 10:19:55 -05:00
Vitor Pamplona
4610519bb3 - Removes pull to refresh from gallery 2024-12-06 10:17:37 -05:00
Vitor Pamplona
905655461d Adds kind picture and video events to the user profile gallery subscription. 2024-12-06 10:16:54 -05:00
Vitor Pamplona
f759acf66e Merge pull request #1210 from believethehype/olas-profile-with-legacy
Extend NIP93 Profile Gallery with Nip68 Events
2024-12-06 08:22:22 -05:00
Vitor Pamplona
8979706a81 Merge pull request #1212 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-06 08:21:02 -05:00
Crowdin Bot
0db664fabc New Crowdin translations by GitHub Action 2024-12-06 13:19:06 +00:00
Vitor Pamplona
c33fee59a2 Delete unused constant 2024-12-05 18:03:40 -05:00
Vitor Pamplona
eb36489094 Adds basic support for RelationshipStatus to Quartz 2024-12-05 17:55:59 -05:00
Vitor Pamplona
51ce509928 Simple refactoring to newer versions of the clickable text 2024-12-05 17:55:36 -05:00
Vitor Pamplona
0dd306b7fa Separates event class that manages general lists and abstracts another intermediary class that manages private tag arrays in its content. 2024-12-05 17:55:10 -05:00
Vitor Pamplona
8a6f830e29 Removes invalid import 2024-12-05 17:26:47 -05:00
Vitor Pamplona
bcb9ba26ab Improves search by npubs to use all relays. 2024-12-05 17:14:16 -05:00
Vitor Pamplona
3aa15889c1 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-12-05 17:12:50 -05:00
Vitor Pamplona
b47d9ad4d9 - Migrates Video events to imeta tags
- Removes youtu.be links from the video feed.
- Checks for video file types in uppercase as well as lowercase
2024-12-05 16:41:43 -05:00
David Kaspar
b504a2c0d4 Merge pull request #1211 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-05 17:33:30 +01:00
Crowdin Bot
69c9e84dc2 New Crowdin translations by GitHub Action 2024-12-05 15:54:05 +00:00
Vitor Pamplona
81591d6f76 v0.93.1 2024-12-05 10:48:52 -05:00
Believethehype
d2187995b8 Update strings.xml 2024-12-04 18:06:33 +01:00
Believethehype
a3257a365b hybrid solution nip68 / nip93
it keeps the nip93 gallery as before, but also adds all nip68 media to the gallery
2024-12-04 17:56:06 +01:00
Vitor Pamplona
9180eeb59f Merge pull request #1209 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-03 16:10:21 -05:00
Crowdin Bot
b24983b232 New Crowdin translations by GitHub Action 2024-12-03 20:56:03 +00:00
Vitor Pamplona
fe3246c94e Fixes video rotation screen in full size while playing 2024-12-03 15:53:17 -05:00
Vitor Pamplona
b9883093ac Improving performance of the Hex encoder. 2024-12-03 14:07:13 -05:00
Vitor Pamplona
869debaf9d Fixes Satellite's blossom upload 2024-12-02 20:05:13 -05:00
Vitor Pamplona
6a522f5c92 updates AGP 2024-12-02 18:03:47 -05:00
Vitor Pamplona
ae13725db5 Improves the Discovery block UI 2024-12-02 17:45:44 -05:00
Vitor Pamplona
95774ddca5 Merge pull request #1207 from believethehype/main
Content DVMS: show amount and personalized status in feed
2024-12-02 16:04:39 -05:00
Believethehype
56450cf6c5 Merge branch 'main' of https://github.com/believethehype/amethyst 2024-12-02 22:02:49 +01:00
Believethehype
2da1dda019 Merge remote-tracking branch 'upstream/main' 2024-12-02 22:00:24 +01:00
dbth
fb73f6153f Merge branch 'main' into main 2024-12-02 22:00:16 +01:00
Believethehype
62807336fd DVM: show amount and personalized in feed 2024-12-02 21:56:13 +01:00
Vitor Pamplona
fddb9b16ab updates jackson and secp256k1 2024-12-02 15:32:19 -05:00
Vitor Pamplona
0dcce5a987 Fixes lack of reactions on video feeds 2024-12-02 15:31:59 -05:00
Vitor Pamplona
16cf3c850a Merge pull request #1205 from believethehype/main
DVM: adapt nip89 naming, show picture then image (legacy)
2024-12-02 14:09:23 -05:00
Vitor Pamplona
2b17e9303a Merge pull request #1206 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-12-02 14:05:51 -05:00
Crowdin Bot
61d9145310 New Crowdin translations by GitHub Action 2024-12-02 18:59:15 +00:00
Vitor Pamplona
40921d4b03 fixes badge crash 2024-12-02 13:56:38 -05:00
Believethehype
e0e898d4b1 DVM: adapt nip89 naming, show picture then image (legacy) 2024-12-02 19:55:02 +01:00
Vitor Pamplona
bd2005ec6e Fixes screen mispositioning when rotating full screen video/image dialogs. 2024-11-29 16:40:07 -05:00
Vitor Pamplona
2c825d5380 Remove unecessary log 2024-11-29 16:32:04 -05:00
Vitor Pamplona
7fe522307a Fixes the bug on not having the video feed at the top when loading the app from scratch. 2024-11-29 15:12:54 -05:00
Vitor Pamplona
9660c739f3 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-29 14:50:39 -05:00
Vitor Pamplona
9025741c13 deletes unecessary logs 2024-11-29 14:50:28 -05:00
Vitor Pamplona
0ef851e4bb Merge pull request #1201 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-29 14:41:13 -05:00
Crowdin Bot
7204d337e8 New Crowdin translations by GitHub Action 2024-11-29 19:39:34 +00:00
Vitor Pamplona
cb538bc6a3 Fixes missing reply order when the direct reply is also included as a quote. 2024-11-29 14:31:31 -05:00
Vitor Pamplona
d0b0f441b1 Fixes image testing. 2024-11-27 15:42:31 -05:00
Vitor Pamplona
ddd7f19f38 Merge pull request #1198 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 12:09:28 -05:00
Crowdin Bot
e46c8f4c15 New Crowdin translations by GitHub Action 2024-11-27 17:02:57 +00:00
Vitor Pamplona
6b390dad30 Merge pull request #1197 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 12:01:24 -05:00
Crowdin Bot
50e067fbf0 New Crowdin translations by GitHub Action 2024-11-27 16:43:19 +00:00
Vitor Pamplona
04da076d95 Adds NIP-22 as replies to Interactive Stories. 2024-11-27 11:41:13 -05:00
David Kaspar
fd88dfed7d Merge pull request #1196 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 17:04:41 +01:00
Crowdin Bot
22905bc04e New Crowdin translations by GitHub Action 2024-11-27 15:40:29 +00:00
Vitor Pamplona
c02ced7855 Merge pull request #1195 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 10:39:14 -05:00
Crowdin Bot
ea1145c890 New Crowdin translations by GitHub Action 2024-11-27 15:36:35 +00:00
Vitor Pamplona
043c8eff57 Merge pull request #1194 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 10:35:14 -05:00
Vitor Pamplona
fbb036da4a v0.93.0 2024-11-27 10:33:59 -05:00
Crowdin Bot
71d30f83ea New Crowdin translations by GitHub Action 2024-11-27 15:26:37 +00:00
Vitor Pamplona
9a0c998654 Fixes "null" strings on profile fields. 2024-11-27 10:24:09 -05:00
Vitor Pamplona
88082b1ba3 Updates media and coil 2024-11-27 09:19:17 -05:00
Vitor Pamplona
ad2b2e6d02 Merge pull request #1193 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-27 09:16:25 -05:00
Crowdin Bot
5044a687fa New Crowdin translations by GitHub Action 2024-11-27 14:02:42 +00:00
Vitor Pamplona
298d4bb237 removes unnecessary metadata 2024-11-27 08:59:23 -05:00
Vitor Pamplona
9c180cb87a Merge pull request #1192 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-26 19:10:54 -05:00
Crowdin Bot
2e3f2044d5 New Crowdin translations by GitHub Action 2024-11-27 00:09:32 +00:00
Vitor Pamplona
bdf012f641 Adds support for Interactive Stories 2024-11-26 19:07:12 -05:00
Vitor Pamplona
034ee46543 Refactors additional factories 2024-11-26 09:28:42 -05:00
Vitor Pamplona
6a17f94cb1 Merge pull request #1190 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-25 14:31:56 -05:00
Crowdin Bot
233059d544 New Crowdin translations by GitHub Action 2024-11-25 19:31:18 +00:00
Vitor Pamplona
7bd9b6a1ec Moves to the new Android Image/Video Picker 2024-11-25 14:24:17 -05:00
Vitor Pamplona
04e77daaab Light chat bubbles 2024-11-22 19:17:50 -05:00
Vitor Pamplona
308048c352 Adds date separators on chats 2024-11-22 18:08:30 -05:00
Vitor Pamplona
3f1173a42a Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-22 17:01:57 -05:00
Vitor Pamplona
39726142ae Fixes account creation that follows itself. 2024-11-22 17:01:50 -05:00
Vitor Pamplona
025cbeed07 Merge pull request #1189 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-22 16:33:33 -05:00
Crowdin Bot
8a1550cbd9 New Crowdin translations by GitHub Action 2024-11-22 21:20:34 +00:00
Vitor Pamplona
49148ddc76 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-22 16:18:01 -05:00
Vitor Pamplona
d0e870e1b8 Adds Custom Empty Feed Screen for notifications 2024-11-22 16:17:55 -05:00
Vitor Pamplona
dbb2bfa512 Merge pull request #1188 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-22 12:26:35 -05:00
Crowdin Bot
d6b66c65fa New Crowdin translations by GitHub Action 2024-11-22 16:36:21 +00:00
Vitor Pamplona
9a3d365243 Fixes: https://github.com/vitorpamplona/amethyst/issues/1186 2024-11-22 11:33:51 -05:00
Vitor Pamplona
3b30d5493d Merge pull request #1184 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-21 18:58:17 -05:00
Crowdin Bot
7bfd1a9107 New Crowdin translations by GitHub Action 2024-11-21 23:53:47 +00:00
Vitor Pamplona
2c9e2de524 Adds support for Blossom media servers. 2024-11-21 18:51:43 -05:00
Vitor Pamplona
c89c5eb4b0 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-21 15:46:16 -05:00
Vitor Pamplona
5c5eb53395 Fixes translations of http urls 2024-11-21 15:46:07 -05:00
Vitor Pamplona
55f0092e03 Merge pull request #1185 from davotoula/enable-unfollow-in-note-dropdown
Enable unfollow in note dropdown
2024-11-21 15:32:44 -05:00
Vitor Pamplona
54d9ac5730 no need for identifier anymore 2024-11-21 15:15:03 -05:00
David Kaspar
d9a9137d89 cleaner code 2024-11-21 21:02:31 +01:00
David Kaspar
7beff8c835 Add unfollow to NoteDropDownMenu 2024-11-21 20:58:40 +01:00
Vitor Pamplona
4a80a16473 Adds support for pronouns when the user makes them available. 2024-11-21 11:19:13 -05:00
Vitor Pamplona
b3e3b70dd0 Marks which events can receive Comment replies as root scopes. 2024-11-19 18:01:56 -05:00
Vitor Pamplona
03662b9d1b Migrates Video feed new posts from NIP-94 to Video and Picture events. 2024-11-19 17:54:51 -05:00
Vitor Pamplona
d33421fce4 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-19 15:37:11 -05:00
Vitor Pamplona
4db6f5531c Adds support for Instagram feeds. https://github.com/nostr-protocol/nips/pull/1551 2024-11-19 15:36:30 -05:00
Vitor Pamplona
72ea4a44a2 Merge pull request #1183 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-19 09:31:20 -05:00
Crowdin Bot
022a9f6f6c New Crowdin translations by GitHub Action 2024-11-19 14:25:24 +00:00
Vitor Pamplona
248848799c Fully Deprecates note1, removing the last usages of the standard 2024-11-19 09:22:37 -05:00
Vitor Pamplona
72f317717b Remove the unused encryption field from NIP-94 2024-11-18 17:29:59 -05:00
Vitor Pamplona
4651074774 Fixes bug on searching for geohashes on addresses 2024-11-18 16:48:21 -05:00
Vitor Pamplona
fb058f93b6 Makes NIP-22 posts only visible to the main feed if inside the scope they were placed. 2024-11-18 16:47:47 -05:00
Vitor Pamplona
9e34e9d720 Fixes issue with the order of same-author events in a thread 2024-11-18 16:00:48 -05:00
Vitor Pamplona
04d6da28af Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-11-18 15:39:19 -05:00
Vitor Pamplona
192a43dc21 Updates compose, coil, firebase and navigation 2024-11-18 15:39:12 -05:00
Vitor Pamplona
61510ae7e3 Adds support for NIP-22 exclusive geo posts 2024-11-18 15:38:57 -05:00
KotlinGeekDev
966936bba3 Use FilterChip instead of Custom Text component for presence indication. Add some test code(to be removed later). 2024-11-18 19:57:23 +01:00
Vitor Pamplona
488286ce20 Merge pull request #1181 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-18 08:25:31 -05:00
Crowdin Bot
fab6c65e98 New Crowdin translations by GitHub Action 2024-11-18 13:22:15 +00:00
Vitor Pamplona
accde8622c Merge pull request #1179 from davotoula/1172-hide-drafts-from-other-accounts-on-device
Hide drafts from other accounts on device
2024-11-18 08:20:55 -05:00
David Kaspar
4a590d67a6 move filtering of "not your drafts" to ThreadFeedFilter 2024-11-17 10:48:03 +01:00
David Kaspar
6c3200a9cc cleaner code, change cascading ifs to when 2024-11-16 10:34:01 +01:00
David Kaspar
f16143bec3 cleaner code, fewer warnings 2024-11-16 09:29:45 +01:00
David Kaspar
8216476916 cleaner code, fewer warnings 2024-11-16 08:48:30 +01:00
David Kaspar
ffd43e448b filter out drafts that are not same author as current account 2024-11-15 12:54:59 +01:00
Vitor Pamplona
3a566dad6b Improves (Show More) presence to only when it actually makes a difference. 2024-11-12 13:36:59 -05:00
Vitor Pamplona
bf861d6bc5 - Improves thread preloading
- Fixes jumping of scroll when the thread updates
2024-11-12 12:54:41 -05:00
Vitor Pamplona
b31ace79c3 App hangs when switching to an account that was logged into with the npub just because it waits to decrypt the hidden user list. 2024-11-11 18:29:38 -05:00
Vitor Pamplona
37a92c25f0 - Unifies Location Flows and GeoHash Flows into one
- Make them react to changing location permissions
- Adds UI for when the location permission is rejected.
2024-11-11 17:51:42 -05:00
Vitor Pamplona
062e4af118 Updates JNA, Coil, Jackson, and Kotlin Collections 2024-11-11 14:54:38 -05:00
Vitor Pamplona
c083295f6a Fixes error message for the signup page when the display name is blank 2024-11-11 14:53:39 -05:00
Vitor Pamplona
924baf0de3 Merge pull request #1177 from greenart7c3/main
Fix initial decryption of mute lists when using amber
2024-11-11 09:48:46 -05:00
greenart7c3
6015c968f5 Fix initial decryption of mutelists when using amber 2024-11-11 11:33:14 -03:00
Vitor Pamplona
3e85991f19 Merge pull request #1176 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-11 09:04:33 -05:00
Crowdin Bot
893b23e4fb New Crowdin translations by GitHub Action 2024-11-11 13:54:10 +00:00
Vitor Pamplona
cb5d69e358 Merge pull request #1171 from davotoula/crashfix-test-for-empty-string-in-mentions
Crashfix: test for empty string in mentions
2024-11-11 08:52:39 -05:00
Vitor Pamplona
43688c4117 Merge pull request #1174 from davotoula/1173-add-nip05-to-hidden-words-filtering
Added nip05 field from user profile to be used as part of hidden words filtering
2024-11-11 08:52:28 -05:00
David Kaspar
4e4521ecef Added nip05 field from user profile to be used as part of hidden words filtering 2024-11-10 06:51:34 +00:00
David Kaspar
7bc9205758 Added a test around NewPostViewModel.load with no mentions, valid mentions and invalid empty mentions 2024-11-08 19:15:04 +00:00
KotlinGeekDev
732ce790c2 Introduce FollowSetItem, with funny defaults for now. 2024-11-07 20:20:48 +01:00
KotlinGeekDev
276b6cbf46 Use a different icon for public lists. 2024-11-07 19:21:02 +01:00
KotlinGeekDev
c485f75206 Make custom button border naming clearer, and fix list dropdown button border. 2024-11-07 12:27:00 +01:00
David Kaspar
abf6cc00ae test for empty string in mentions 2024-11-07 09:24:30 +00:00
KotlinGeekDev
883f7a975f Experiment with Icon as a button. Refactor FollowButton and UnfollowButton to take into account the modified design, without affecting other components. 2024-11-06 10:56:33 +01:00
KotlinGeekDev
aa64e42bfe Introduce FollowSetsActionMenu. 2024-11-05 21:17:08 +01:00
Vitor Pamplona
7146838f63 Better names for github jobs 2024-11-04 11:56:25 -05:00
Vitor Pamplona
1037ab33e3 fixes names of uploaded files 2024-11-04 11:31:04 -05:00
Vitor Pamplona
230fd553ae fixes paths on github actions 2024-11-04 11:12:40 -05:00
Vitor Pamplona
eac078c42d updates build github action to test and upload the right assets 2024-11-04 10:54:14 -05:00
Vitor Pamplona
39c9b75bf4 Moves zapstore to local image and first in the list 2024-11-04 10:15:31 -05:00
Vitor Pamplona
e076a2dbe3 Merge pull request #1167 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-04 10:12:31 -05:00
Crowdin Bot
5705a26836 New Crowdin translations by GitHub Action 2024-11-04 15:12:18 +00:00
Vitor Pamplona
740c279825 Merge pull request #1165 from davotoula/1164-launch-in-scope-to-persist-translation-prefs
Crash fix: Launch in Dispatchers.IO when changing translations prefs in account
2024-11-04 10:10:48 -05:00
Vitor Pamplona
718605e44b Merge pull request #1163 from ilcompratoreconsapevole/banner
Added ZapStore's SVG banner that says "Get it on" linking to its repo
2024-11-04 10:09:49 -05:00
Vitor Pamplona
8df2ff839c Merge pull request #1162 from davotoula/bugfix-images-reported-saved-as-video
Bugfix: images reported saved as video
2024-11-04 10:09:36 -05:00
ilcompratoreconsapevole
00be9fc595 Rewritten connection link to ZapStore's banner image to ensure reliable connection to specific repo 2024-11-03 21:38:01 +01:00
David Kaspar
7d25ab13a9 Launch in Dispatchers.IO when changing translations prefs in account 2024-11-03 16:15:54 +00:00
ilcompratoreconsapevole
d7888096e9 Added ZapStore's SVG banner that says "Get it on" linking to its repository. 2024-11-03 12:50:46 +01:00
David Kaspar
aad5aaa930 rename inner it to avoid shadowed it 2024-11-03 11:48:12 +00:00
David Kaspar
b877e4105a Replace nested ifs with a when 2024-11-03 10:30:07 +00:00
David Kaspar
d1cd15c389 Remove unused val 2024-11-03 10:24:20 +00:00
David Kaspar
69e139e382 Remove unused @OptIn(ExperimentalFoundationApi::class) 2024-11-03 10:23:32 +00:00
David Kaspar
b2628764c9 Remove unused shadowed orientation val 2024-11-03 10:20:30 +00:00
David Kaspar
f7f0d74be1 Correct boolean operation from AND to OR 2024-11-03 10:19:29 +00:00
Vitor Pamplona
b47122d79a Updates accompanist, kotlin serialization and mockk 2024-11-01 18:41:00 -04:00
Vitor Pamplona
635ad30de9 Let it throw in the giftwrap parser to make sure Notifications work 2024-11-01 18:40:34 -04:00
Vitor Pamplona
0884318a55 Merge pull request #1161 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-01 18:27:38 -04:00
Crowdin Bot
d8c89317d2 New Crowdin translations by GitHub Action 2024-11-01 22:20:09 +00:00
Vitor Pamplona
772987dffd coroutines test to 1.9 2024-11-01 18:17:32 -04:00
Vitor Pamplona
61c51ee9a8 Updates AGP 2024-11-01 18:17:07 -04:00
Vitor Pamplona
f157f8eb9b - Fixes lack of zap updates when there is a private zap the user cannot decrypt or when a nwc payment fails.
- Moves the parallel processing to a utils class
2024-11-01 18:16:57 -04:00
KotlinGeekDev
aed914dfcf Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-11-01 22:28:20 +01:00
KotlinGeekDev
d763f43b40 Restore function for getting follow sets(due to merge of upstream). 2024-11-01 22:27:55 +01:00
Vitor Pamplona
d96f5953d5 Moving animation size to a constant. 2024-11-01 17:27:19 -04:00
KotlinGeekDev
2fcd6af24b Merge branch 'vitorpamplona:main' into profiles-list-management 2024-11-01 15:42:11 +00:00
Vitor Pamplona
ffb5cce509 Merge pull request #1160 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-11-01 10:35:40 -04:00
Crowdin Bot
b441292413 New Crowdin translations by GitHub Action 2024-11-01 14:16:09 +00:00
Vitor Pamplona
91748d5c39 - Adds a cache for reply levels when viewing threads.
- Moves the lazy list state to the view model to saving the scrolling position when navigating between multiple threads.
2024-11-01 10:10:05 -04:00
Vitor Pamplona
a825f575de Fixes rendering of notes when the modifier changes due to new posts found and the reply level gets recalculated. 2024-11-01 10:06:09 -04:00
Vitor Pamplona
0581a3e836 Fixes reply level calculation caching. 2024-11-01 10:05:25 -04:00
KotlinGeekDev
4b76f56293 Merge branch 'vitorpamplona:main' into profiles-list-management 2024-10-31 22:15:32 +00:00
Vitor Pamplona
82f0ed8a0d Upgrades to Coil 3 2024-10-31 16:34:22 -04:00
KotlinGeekDev
3ad26f0039 Merge branch 'vitorpamplona-main' into profiles-list-management
Merging upstream changes/resolving conflicts.
2024-10-31 19:42:05 +01:00
KotlinGeekDev
2366f26ac7 Merge branch 'main' of https://github.com/vitorpamplona/amethyst into vitorpamplona-main
merging upstream changes.
2024-10-31 19:36:20 +01:00
KotlinGeekDev
f2640deeaf Minor refactor. 2024-10-31 19:27:15 +01:00
Vitor Pamplona
116bc108c3 Merge pull request #1158 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-31 14:07:13 -04:00
Crowdin Bot
44868ac4e6 New Crowdin translations by GitHub Action 2024-10-31 18:05:03 +00:00
Vitor Pamplona
2a24ca0787 Adds support for q tags on addresses 2024-10-31 14:02:39 -04:00
KotlinGeekDev
8f798d1003 Add some error handling in the feed filter. 2024-10-31 19:02:38 +01:00
KotlinGeekDev
a39834dc87 Use FollowSetState in in CustomListsScreen. Make other fixes. 2024-10-31 18:43:07 +01:00
KotlinGeekDev
00b7ace00b Change followSets signature again. Refactor followSetNotesFLow(). 2024-10-31 18:39:05 +01:00
KotlinGeekDev
ee8148ae50 Just use NavigationRow instead of IconRow. 2024-10-31 16:58:36 +01:00
KotlinGeekDev
63654b742a Refactor NostrUserFollowSetFeedViewModel to use the FollowSetViewModel. Change follow sets signature. Might be changed later. 2024-10-31 16:55:13 +01:00
KotlinGeekDev
f3edc2d43b Introduce FollowSetFeedViewModel and FollowSetState. 2024-10-31 16:48:45 +01:00
KotlinGeekDev
0e9cc54245 Refactor FollowSetFeedFilter and Account. 2024-10-31 16:45:12 +01:00
KotlinGeekDev
3d4483ed48 Move FollowSet to it's own class. 2024-10-31 16:37:05 +01:00
Vitor Pamplona
03225fc696 Merge pull request #1157 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-30 18:00:57 -04:00
Crowdin Bot
a0ab4a824b New Crowdin translations by GitHub Action 2024-10-30 21:59:32 +00:00
Vitor Pamplona
0ba4ca13bf Updates compose, corektx, fragment, kotlin, runtime, and android camera libs 2024-10-30 17:56:28 -04:00
Vitor Pamplona
060a242a0f Removes logs 2024-10-30 17:55:35 -04:00
Vitor Pamplona
ae530ed813 adds extra padding for the zap setup screen 2024-10-30 16:02:00 -04:00
Vitor Pamplona
a5c4a53afe - Adds an Around ME feed
- Refactors location to operate as a flow
- Refactors FeedStructures to prepare for custom feeds
- Moves Account to operate feeds with location
2024-10-30 16:01:39 -04:00
KotlinGeekDev
ddbaca383f Navigation: Add custom lists to main app navigation control helper. 2024-10-30 15:01:27 +01:00
KotlinGeekDev
5b51e97594 Replace TODOs with provided feed state implementations. 2024-10-30 14:57:27 +01:00
KotlinGeekDev
d348e7a4ad Introduce ListsScreen. Current implementation is focused on follows. 2024-10-30 14:51:46 +01:00
KotlinGeekDev
0b014c5c7c Move mapping function from FollowSetFeedFilter to FollowSet. 2024-10-30 14:48:58 +01:00
KotlinGeekDev
bc7d9eef9b Make FollowSet stable. The class may be moved to another location. 2024-10-30 13:31:37 +01:00
Vitor Pamplona
fb6137f99a Missing string 2024-10-29 12:31:07 -04:00
Vitor Pamplona
de99c50639 Adds search.nos.today to bootstrapping relays for search 2024-10-29 12:23:09 -04:00
Vitor Pamplona
d5f8994128 Merge pull request #1156 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-29 12:16:33 -04:00
Crowdin Bot
cf02d808d6 New Crowdin translations by GitHub Action 2024-10-29 16:10:36 +00:00
Vitor Pamplona
f6aa732558 Adds DM and Search default buttons to the dialogs. 2024-10-29 12:07:15 -04:00
Vitor Pamplona
d321df79e8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-10-28 16:11:09 -04:00
Vitor Pamplona
d1b4f81083 Fixes jumpy thread view 2024-10-28 16:10:47 -04:00
Vitor Pamplona
0205f1fd12 Merge pull request #1155 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-28 10:14:42 -04:00
Crowdin Bot
d739fdac2d New Crowdin translations by GitHub Action 2024-10-28 14:11:31 +00:00
Vitor Pamplona
4a43a87a79 Moves templates to explain that bounties matter. 2024-10-28 10:08:53 -04:00
KotlinGeekDev
7dec46f3fd Create Viewmodel for Follow sets. Refactor User.followSets to be a collection of Notes. Adjust FollowSetFeedFilter. 2024-10-27 20:19:23 +01:00
KotlinGeekDev
caad5066ff Refactor followSetNotesFlow() and introduce FollowSetFeedFilter. 2024-10-27 17:48:52 +01:00
KotlinGeekDev
c86ce1b1ce Load and populate follow sets in Account(since Account is initialized in AccountViewModel anyway). 2024-10-27 00:20:35 +01:00
KotlinGeekDev
aa1175ce1d Implement method for retrieving follow sets. 2024-10-26 18:00:09 +01:00
KotlinGeekDev
afca104b87 Add FollowSet class, and property in User. 2024-10-26 13:15:43 +01:00
Vitor Pamplona
6f31898cdc Updates Firebase 2024-10-24 17:34:13 -04:00
Vitor Pamplona
6ebc878312 Fixes poll's background rendering with the right percentages. 2024-10-24 17:34:00 -04:00
KotlinGeekDev
c79157f415 Add description() for list events. 2024-10-24 18:12:50 +01:00
Vitor Pamplona
518e1c88ce Adds hidden words filter to search, hashtag and geotag feeds
Applies hidden words even to hashtags that were not included in the content of the event.
Pre-process search to avoid showing and hiding posts after hidden words where processed by the UI.
2024-10-24 11:00:07 -04:00
Vitor Pamplona
f88985b2bf Adds an icon to the repo 2024-10-23 18:00:23 -04:00
KotlinGeekDev
5a38267fc1 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-23 15:29:35 +01:00
KotlinGeekDev
0c9bea3c19 Add function to get all follow sets from cache. 2024-10-23 15:28:50 +01:00
Vitor Pamplona
89e1e20b40 Improves algorithm for placing the clicked note in a visible position when opening a new threads that need to load dynamically 2024-10-22 17:26:53 -04:00
Vitor Pamplona
c2e725ef5f Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-10-22 17:14:36 -04:00
Vitor Pamplona
6e8c348cbc Moves NIP-44 test model classes to a new file 2024-10-22 17:14:29 -04:00
Vitor Pamplona
84316d320d Merge pull request #1150 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-22 15:26:36 -04:00
Crowdin Bot
87fb63af06 New Crowdin translations by GitHub Action 2024-10-22 19:00:17 +00:00
Vitor Pamplona
a3b0436d91 Uses NIP-78 to start syncing some data between instances of Amethyst 2024-10-22 14:52:11 -04:00
KotlinGeekDev
27d9b6384e Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-20 10:42:58 +01:00
Vitor Pamplona
bb430605bc Merge pull request #1148 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-19 18:50:37 -04:00
Crowdin Bot
4c0d92f89f New Crowdin translations by GitHub Action 2024-10-19 18:19:59 +00:00
Vitor Pamplona
f0b14edef6 v0.92.7 2024-10-19 14:17:42 -04:00
Vitor Pamplona
aae2ffebd7 Fixes the bug of unable to decrypt mute lists. 2024-10-19 14:17:17 -04:00
KotlinGeekDev
bcd220191e Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-18 09:10:46 +01:00
Vitor Pamplona
7b11d0ce81 v0.92.6 2024-10-17 17:21:20 -04:00
Vitor Pamplona
26ba75c7c9 Fixes array too big because strfry blocks more than 20 filters 2024-10-17 17:18:35 -04:00
Vitor Pamplona
67fb3bf488 Fixes margin of poll options with translation 2024-10-17 15:22:34 -04:00
Vitor Pamplona
24b29396cb Reduces the font size for the translation label 2024-10-17 15:22:19 -04:00
Vitor Pamplona
25c3532637 Fixes poll rendering 2024-10-17 14:55:07 -04:00
KotlinGeekDev
1076e00aa0 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-17 19:39:23 +01:00
Vitor Pamplona
cff6ee457d Fixes zapraiser progress bar 2024-10-17 14:18:40 -04:00
Vitor Pamplona
c0ba6f5b00 If the Share target intent opens with the new post screen already active, just updates the post instead of creating a new one. 2024-10-17 13:59:52 -04:00
KotlinGeekDev
9bca9187a6 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-17 09:39:33 +01:00
Vitor Pamplona
44d8b8ff33 v0.92.5 2024-10-16 17:34:10 -04:00
Vitor Pamplona
f495acb570 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-10-16 17:33:58 -04:00
Vitor Pamplona
ecfc8bcd87 Merge pull request #1144 from geovnn/main
Prevent clicks outside boost popup
2024-10-16 17:33:39 -04:00
Vitor Pamplona
724bea6bbb Updates compose, benchmark, navigation, and activity 2024-10-16 17:32:33 -04:00
Giovanni Gatti
6826db13a4 Prevent clicks outside boost popup 2024-10-16 23:11:51 +02:00
Vitor Pamplona
c5dd2c5651 Fixes recreating the new post screen from intent when switching accounts. 2024-10-16 13:33:46 -04:00
KotlinGeekDev
bbeec18e0e Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-16 16:28:01 +01:00
Vitor Pamplona
236ff2d680 v0.92.4 2024-10-16 11:13:56 -04:00
Vitor Pamplona
13e2546d36 Makes Amethyst a share target for texts, images and videos. 2024-10-16 10:54:57 -04:00
KotlinGeekDev
3d11845396 Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-16 15:50:14 +01:00
Vitor Pamplona
549b9f53e5 Fixes new media post dialog for the edge to edge borders 2024-10-16 09:41:02 -04:00
Vitor Pamplona
974c022aed Fixes padding of the new new post screen 2024-10-15 17:20:16 -04:00
Vitor Pamplona
a57566dd84 Changes the new post screen to use the non-disappearing version of the scaffold. 2024-10-15 16:55:09 -04:00
Vitor Pamplona
72e86cf391 Merge pull request #1143 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 16:25:21 -04:00
Crowdin Bot
3b23cded56 New Crowdin translations by GitHub Action 2024-10-15 20:04:18 +00:00
Vitor Pamplona
121337eb83 Makes new post screen come in and out from the bottom 2024-10-15 16:02:20 -04:00
Vitor Pamplona
fe87d642c9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt
2024-10-15 15:54:26 -04:00
Vitor Pamplona
b8112ff1aa Correctly maps the write status of the outbox relays. 2024-10-15 15:53:00 -04:00
Vitor Pamplona
1eee0bd1ae Merge pull request #1142 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 13:55:09 -04:00
Crowdin Bot
f48335e3be New Crowdin translations by GitHub Action 2024-10-15 17:46:27 +00:00
Vitor Pamplona
fcc4befb22 Merge pull request #1141 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 13:44:54 -04:00
Crowdin Bot
e28a1896ec New Crowdin translations by GitHub Action 2024-10-15 17:38:52 +00:00
Vitor Pamplona
f10e66e6a5 Merge pull request #1137 from geovnn/main
Change NewPostView to NewPostScreen to make it route based
2024-10-15 13:37:33 -04:00
Vitor Pamplona
825a227e2d Merge pull request #1140 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 12:09:51 -04:00
Crowdin Bot
3446fdaf7f New Crowdin translations by GitHub Action 2024-10-15 16:08:45 +00:00
Vitor Pamplona
9c29bf3f25 v0.92.3 2024-10-15 12:06:36 -04:00
Vitor Pamplona
9642e45775 Merge pull request #1139 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 12:06:23 -04:00
Crowdin Bot
58788d2cd5 New Crowdin translations by GitHub Action 2024-10-15 16:00:54 +00:00
Vitor Pamplona
d7c18341cd Uses the same Reaction animation to also do Boosts 2024-10-15 11:58:40 -04:00
Vitor Pamplona
ee16f6c143 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-10-15 11:28:41 -04:00
Vitor Pamplona
d78b55d134 Reverts hack for fling behavior on the Vertical Pager 2024-10-15 11:28:33 -04:00
Vitor Pamplona
ff5e7359e2 Merge pull request #1138 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-15 11:05:39 -04:00
Crowdin Bot
4fd0f38402 New Crowdin translations by GitHub Action 2024-10-15 14:56:33 +00:00
Vitor Pamplona
5b516f4101 Updates to AGP 8.7.1 and SDK 35 2024-10-15 10:53:21 -04:00
Vitor Pamplona
1fc390c215 Do not bring the keyboard when the search field reappears. 2024-10-15 10:52:54 -04:00
Vitor Pamplona
2bb09f583b Fixes Thread view when loading a reply of a thread that is not cached yet. 2024-10-15 10:45:03 -04:00
Giovanni Gatti
89de272ea1 Change NewPostView to NewPostScreen to make it route based 2024-10-14 23:19:43 +02:00
Vitor Pamplona
b4eba2da02 0.92.2 2024-10-11 19:35:52 -04:00
Vitor Pamplona
786a173ba5 Merge pull request #1133 from greenart7c3/external_signer_result
Check for the result field when using external signer
2024-10-11 08:08:30 -04:00
greenart7c3
5ef3c8f00e Check for the result field when using external signer 2024-10-11 07:22:34 -03:00
Vitor Pamplona
d832351bcd Fixes the ability to see muted lists in Shorts 2024-10-10 17:52:24 -04:00
Vitor Pamplona
9ae611831f Enables new fields for vision prescriptions 2024-10-10 16:48:33 -04:00
Vitor Pamplona
6db287a008 Merge pull request #1131 from davotoula/prioritise-name-when-searching
Prioritise search results with user name starting with search term
2024-10-10 09:47:28 -04:00
David Kaspar
99641e1a12 Added comparison between search term and display name to comparator to prioritise results with search term in name 2024-10-10 14:11:13 +02:00
Vitor Pamplona
abb2f0557d Merge pull request #1118 from davotoula/refactor-media-compressor
Refactor media compressor as prep for replacing 3rd party compression library
2024-10-09 16:38:29 -04:00
David Kaspar
76a66e7bd3 added test for when exception happens during compression 2024-10-09 21:46:15 +02:00
David Kaspar
6cd50a115b Merge branch 'vitorpamplona:main' into refactor-media-compressor 2024-10-09 14:16:14 +01:00
KotlinGeekDev
34a6be67cf Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-09 13:54:29 +01:00
Vitor Pamplona
0ed34cec09 Fixes https://github.com/vitorpamplona/amethyst/issues/1130 2024-10-09 08:42:33 -04:00
David Kaspar
c5e3657bfe added missing dot for file extension 2024-10-09 01:45:50 +02:00
David Kaspar
da47965d76 Merge branch 'main' into refactor-media-compressor 2024-10-09 00:16:35 +01:00
David Kaspar
a66417ec82 Added Unknown media type test 2024-10-09 01:07:28 +02:00
David Kaspar
62c3c93d2e Removed splitFileName function as both file name and extension are known separately 2024-10-09 01:02:41 +02:00
David Kaspar
fbecc8b6d1 Use "use" blocks to close resources automatically 2024-10-09 00:52:35 +02:00
Vitor Pamplona
e95afa89ff Merge pull request #1129 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-08 18:38:36 -04:00
Crowdin Bot
d6f2560c1c New Crowdin translations by GitHub Action 2024-10-08 22:33:54 +00:00
Vitor Pamplona
d320b951ae Fixes long term issue with the video in the Shorts feed not aligning with the padding of the screen 2024-10-08 18:31:54 -04:00
David Kaspar
af28e6d466 Use copyTo to copy streams 2024-10-09 00:15:19 +02:00
Vitor Pamplona
86e6d56dcd Removes old logs used for debuging the previous issue 2024-10-08 17:19:35 -04:00
Vitor Pamplona
c66fa1073b Fixes intent reopening the Zap Setup screen because it was saved in the backstack of the Home screen. 2024-10-08 16:09:09 -04:00
Vitor Pamplona
6d9bffe9e7 Disabling #1122 until we solve those little issues. 2024-10-08 15:00:25 -04:00
Vitor Pamplona
04158cec6e Merge pull request #1128 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-08 14:43:07 -04:00
Crowdin Bot
76376a7c40 New Crowdin translations by GitHub Action 2024-10-08 18:32:53 +00:00
Vitor Pamplona
67ea0dcf7f Faster logout processing without closing the account switcher dialog. 2024-10-08 13:59:51 -04:00
David Kaspar
cdc8850894 Refactor out file operations into seperate helper
Additional tests for MediaCompressor
2024-10-08 17:47:27 +02:00
David Kaspar
ce0c7c842d Merge branch 'main' into refactor-media-compressor 2024-10-08 14:51:53 +01:00
Vitor Pamplona
f51f929079 Merging 2024-10-08 09:24:10 -04:00
Vitor Pamplona
d87d2e0ef8 Merge pull request #1127 from geovnn/main
Added animations
2024-10-08 09:22:40 -04:00
Giovanni Gatti
a3166f4ff3 Add animation to notification chart 2024-10-08 12:13:47 +02:00
Giovanni Gatti
9340c440f5 Add animation to FABs 2024-10-08 11:54:42 +02:00
Giovanni Gatti
a7343d5b54 Add animation to zap and reaction popups 2024-10-08 11:06:29 +02:00
Vitor Pamplona
0551b82bd9 Updates AGP and compose, fragment, navigation, benchmarking and firebase libraries. 2024-10-07 17:02:09 -04:00
Vitor Pamplona
18fca8adac Merge pull request #1122 from KotlinGeekDev/conditional-rotation
Re-enable tap to fullscreen behaviour with stricter orientation checks.
2024-10-07 12:57:28 -04:00
Vitor Pamplona
5d788ed669 Merge pull request #1125 from geovnn/main
Prevent clicks outside reaction and zap popups
2024-10-07 12:56:57 -04:00
Giovanni Gatti
54d52e0b77 Prevent clicks outside reaction and zap popups 2024-10-07 17:38:05 +02:00
Vitor Pamplona
28c92675b2 Merge pull request #1124 from greenart7c3/main
Support for login with hex key when using amber
2024-10-07 08:32:59 -04:00
greenart7c3
baa9ee0562 Support for login with hex key when using amber 2024-10-07 08:50:06 -03:00
KotlinGeekDev
7326d6be93 Rotation:actually rotate if the device orientation is not locked. 2024-10-05 17:06:36 +01:00
KotlinGeekDev
3c22129c4f Merge remote-tracking branch 'origin/profiles-list-management' into profiles-list-management 2024-10-05 13:50:00 +01:00
David Kaspar
e187e5392f refactor "from" method 2024-10-03 14:56:03 +02:00
David Kaspar
7c8f6eb239 formatting 2024-10-03 14:56:03 +02:00
David Kaspar
6294c65ad6 Create private functions for image vs video compression and when statement to select on content type 2024-10-03 14:56:03 +02:00
David Kaspar
51d9657371 Use Pair instead of Array to split up file name 2024-10-03 14:56:03 +02:00
David Kaspar
32783794ee Exit early if UNCOMPRESSED is selected 2024-10-03 14:56:03 +02:00
David Kaspar
e3858dc830 added kotlinx.coroutines.test library
added MediaCompressor first tests
2024-10-03 14:56:03 +02:00
Vitor Pamplona
38a9030963 Merge pull request #1117 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-10-03 08:28:43 -04:00
Crowdin Bot
29c0c4cf5f New Crowdin translations by GitHub Action 2024-10-03 12:23:07 +00:00
Vitor Pamplona
dcbde81a23 Merge pull request #1116 from davotoula/update-tests-for-new-forceProxy-parameter
Update tests for new forceProxy param
2024-10-03 08:21:47 -04:00
David Kaspar
56e6f06fe7 add "forceProxy = false" to tests 2024-10-03 10:02:31 +02:00
KotlinGeekDev
c5be20b4d2 Add icon for Lists and create Drawer/Route entry. 2024-10-01 20:18:11 +01:00
Vitor Pamplona
342b64f0eb Merge pull request #1114 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-30 19:18:57 -04:00
Crowdin Bot
a64a640165 New Crowdin translations by GitHub Action 2024-09-30 23:05:25 +00:00
Vitor Pamplona
6433830c32 Fixes language setting toggles 2024-09-30 18:47:45 -04:00
Vitor Pamplona
3a58819b52 Merge pull request #1112 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-28 11:54:58 -04:00
Crowdin Bot
5c7be44369 New Crowdin translations by GitHub Action 2024-09-28 15:43:33 +00:00
Vitor Pamplona
6e66d008dc Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-28 11:41:30 -04:00
Vitor Pamplona
c61c4ce16c Improves names and icons of the left drawer. 2024-09-28 11:01:56 -04:00
Vitor Pamplona
246ce55253 Merge pull request #1111 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-28 09:49:21 -04:00
Crowdin Bot
a704c81d24 New Crowdin translations by GitHub Action 2024-09-28 13:43:32 +00:00
Vitor Pamplona
9963245ec9 Improves zap store descriptions 2024-09-28 09:40:02 -04:00
Vitor Pamplona
64016aa940 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-27 18:02:33 -04:00
Vitor Pamplona
c6462f102a Merge pull request #1110 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 17:23:41 -04:00
Vitor Pamplona
b05083863d removes the aab files 2024-09-27 17:13:44 -04:00
Crowdin Bot
86fffe6539 New Crowdin translations by GitHub Action 2024-09-27 21:13:01 +00:00
Vitor Pamplona
f94a4cfa65 Merge pull request #1109 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 17:11:24 -04:00
Crowdin Bot
3825642ae7 New Crowdin translations by GitHub Action 2024-09-27 19:40:05 +00:00
Vitor Pamplona
f90f81b2a5 Merge pull request #1108 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 15:38:46 -04:00
Crowdin Bot
bc042e52b0 New Crowdin translations by GitHub Action 2024-09-27 19:28:52 +00:00
Vitor Pamplona
d80b22fa29 Merge pull request #1107 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 15:27:38 -04:00
Crowdin Bot
b63ed4fdbd New Crowdin translations by GitHub Action 2024-09-27 19:24:11 +00:00
Vitor Pamplona
397acce85b Merge pull request #1106 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 15:23:01 -04:00
Crowdin Bot
5305929df3 New Crowdin translations by GitHub Action 2024-09-27 19:22:04 +00:00
Vitor Pamplona
6d4ba6d507 v0.92.1 2024-09-27 15:20:06 -04:00
Vitor Pamplona
a028c2c3fc Fixes missing zaps on the first connection to a NWC wallet with a split payment. 2024-09-27 15:19:59 -04:00
Vitor Pamplona
86e5dfa799 Fixes access to Citrine 2024-09-27 14:38:14 -04:00
Vitor Pamplona
3aa316d262 v0.92.0 2024-09-27 11:14:45 -04:00
Vitor Pamplona
3f4efdb6d9 Merge pull request #1105 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-27 08:26:41 -04:00
Crowdin Bot
a178e5f589 New Crowdin translations by GitHub Action 2024-09-27 12:23:29 +00:00
Vitor Pamplona
cc5a6cdaf2 Merge pull request #1104 from davotoula/update_translations
Update de, cs, se, pt translations
2024-09-27 08:21:49 -04:00
David Kaspar
d9596335de Update de, cs, se, pt translations 2024-09-27 09:51:41 +02:00
Vitor Pamplona
44c600dfa8 Fixes icon position on chat user pictures 2024-09-26 18:36:42 -04:00
Vitor Pamplona
a92fe0bca2 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-26 17:20:36 -04:00
Vitor Pamplona
dea9d698df Adds privacy presets for Tor Settings 2024-09-26 17:20:29 -04:00
Vitor Pamplona
94ae7b3da1 Merge pull request #1103 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-26 15:53:03 -04:00
Crowdin Bot
da1c94f77b New Crowdin translations by GitHub Action 2024-09-26 19:26:05 +00:00
Vitor Pamplona
64ff33e745 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-26 15:23:47 -04:00
Vitor Pamplona
8810cfde37 Adjusts Login and Sign up screens for the new Tor Settings 2024-09-26 15:23:39 -04:00
Vitor Pamplona
c7b7ad78b7 Adds mempool to verify OTS via Tor. 2024-09-26 15:23:13 -04:00
Vitor Pamplona
ff9f25ccdb Removes unnecessary log 2024-09-26 14:42:40 -04:00
Vitor Pamplona
498595b892 Merge pull request #1102 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-26 13:48:48 -04:00
Vitor Pamplona
56878900b0 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-26 13:46:52 -04:00
Vitor Pamplona
d8d6736d55 Caches OTS web calls to avoid pinging the server repeatedly for the same event. 2024-09-26 13:45:01 -04:00
Crowdin Bot
ddc68e61d5 New Crowdin translations by GitHub Action 2024-09-26 17:43:05 +00:00
Vitor Pamplona
d4e8fc5737 Merge pull request #1101 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-26 13:41:24 -04:00
Crowdin Bot
3a7dfe7d03 New Crowdin translations by GitHub Action 2024-09-26 15:57:17 +00:00
Vitor Pamplona
d23a859f34 Improves OTS Verification error message 2024-09-26 11:55:29 -04:00
Vitor Pamplona
824cb4e5f1 @Suppress("ProduceStateDoesNotAssignValue") until we figure out what's wrong with this lint. 2024-09-26 11:36:52 -04:00
Vitor Pamplona
35aeeb571c Fixes setting the value in the main thread 2024-09-26 11:20:51 -04:00
Vitor Pamplona
70d68c89c8 Updates compose, benchmark, firebase, navigation and lifecycle dependencies. 2024-09-26 11:20:33 -04:00
Vitor Pamplona
35a6331da2 Fixes test case 2024-09-26 10:59:50 -04:00
Vitor Pamplona
6229a0ad63 making sure to only use .onion urls if the option is active 2024-09-26 10:41:36 -04:00
Vitor Pamplona
620285596d Show buffering when loading videos 2024-09-26 10:39:12 -04:00
Vitor Pamplona
fd2637ff42 Removes unused tor setting 2024-09-26 10:38:36 -04:00
Vitor Pamplona
afe8a06486 - Adds a new Settings page for Tor
- Adjusts all web calls to use Tor if that setting is enabled.
- Allows .onion urls to use Tor
- Blocks localhost from using Tor
- Moves OTS web calls to use the Tor Proxy as well.
- Starts to build all OkHttp clients from a main root node to keep the same thread pool
- Refactors the URL Preview code
- Changes ammolite to force proxy.
- Changes NIP-47 implementation to force relay for the NWC connection.
2024-09-25 17:58:37 -04:00
Vitor Pamplona
b682e90f81 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-23 21:51:23 -04:00
Vitor Pamplona
ec700ca61f Refactoring 2024-09-23 21:51:15 -04:00
Vitor Pamplona
9b26ffd92a Updates to AGP 8.6.1 2024-09-23 21:48:13 -04:00
Vitor Pamplona
a8ca9ab727 Merge pull request #1091 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-20 09:00:43 -04:00
Crowdin Bot
e5832f6a45 New Crowdin translations by GitHub Action 2024-09-20 12:57:12 +00:00
Vitor Pamplona
8b3035f635 Merge pull request #1090 from greenart7c3/main
Fix for notifications when using amber
2024-09-20 08:56:00 -04:00
greenart7c3
9f17cc50f8 Fix for notifications when using amber 2024-09-20 06:48:27 -03:00
Vitor Pamplona
d0633d07fa Fixes inability to go back after opening a nostr link from the web into Amethyst 2024-09-18 19:47:09 -04:00
Vitor Pamplona
97b88c7ceb Merge pull request #1088 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-18 17:38:59 -04:00
Crowdin Bot
4961a22753 New Crowdin translations by GitHub Action 2024-09-18 21:23:22 +00:00
Vitor Pamplona
45b568f6a7 updates amy bg 2024-09-18 17:21:44 -04:00
Vitor Pamplona
446859e8bd adds amy 2024-09-18 17:16:52 -04:00
Vitor Pamplona
e1b24c22b9 adds phone mockups 2024-09-18 17:16:38 -04:00
Vitor Pamplona
e0b1c4dc3f Sending jpg and webp version of the image 2024-09-18 16:28:07 -04:00
Vitor Pamplona
5722507b3d Adds two designed images for the homepage 2024-09-18 16:23:13 -04:00
Vitor Pamplona
74df309527 Improving bug report template 2024-09-17 18:07:41 -04:00
Vitor Pamplona
740c6a8c33 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-17 18:07:29 -04:00
Vitor Pamplona
929a263665 Adds zapstore yaml 2024-09-17 18:07:12 -04:00
Vitor Pamplona
6f44c02b5c Makes sure if the d component of an a-tag has :, it stays in the d-tag after parse. 2024-09-16 18:10:54 -04:00
Vitor Pamplona
bce01dc264 Merge pull request #1083 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-16 11:26:36 -04:00
Crowdin Bot
7919637e12 New Crowdin translations by GitHub Action 2024-09-16 15:25:34 +00:00
Vitor Pamplona
28da06ef01 Fixes #1084. Opengraph parser fails when value-less properties are present. 2024-09-16 11:23:41 -04:00
Vitor Pamplona
428ed464af Adds salt to the bloom filter 2024-09-15 14:28:49 -04:00
Vitor Pamplona
5921b712b0 Merge pull request #1082 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-13 14:17:46 -04:00
Crowdin Bot
5c9a37f01c New Crowdin translations by GitHub Action 2024-09-13 18:12:12 +00:00
Vitor Pamplona
514a69deec Bloom filter test case for NIP76 2024-09-13 14:06:45 -04:00
Vitor Pamplona
356cbc24d4 Merge pull request #1081 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-12 16:09:38 -04:00
Crowdin Bot
f4a2f247f7 New Crowdin translations by GitHub Action 2024-09-12 20:05:59 +00:00
Vitor Pamplona
9ee28e28b8 If already logged in and an NFC tag shows up with ncryptsec, logs in as a transient account 2024-09-12 15:57:35 -04:00
Vitor Pamplona
2457429470 Merge pull request #1078 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-12 08:15:27 -04:00
Crowdin Bot
b998381bb6 New Crowdin translations by GitHub Action 2024-09-12 12:04:40 +00:00
Vitor Pamplona
74725235b7 Merge pull request #1074 from davotoula/add-media-compression-level-uncompressed
Add media compression level uncompressed
2024-09-12 08:02:51 -04:00
David Kaspar
ef4d975c7c Merge branch 'vitorpamplona:main' into add-media-compression-level-uncompressed 2024-09-12 12:40:46 +01:00
Vitor Pamplona
3fdd3f9b69 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-11 17:15:33 -04:00
Vitor Pamplona
53aa9b0017 Trying to fix upload-artifact. 2024-09-11 17:15:23 -04:00
Vitor Pamplona
a52d87a555 Merge pull request #1076 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-11 17:15:10 -04:00
Crowdin Bot
39f062fe32 New Crowdin translations by GitHub Action 2024-09-11 21:03:20 +00:00
Vitor Pamplona
b2c13089ce adds NFC-Based transient accounts.
Refactors the login screen
2024-09-11 16:49:35 -04:00
David Kaspar
4ddf307b9f added translations 2024-09-11 17:27:08 +02:00
David Kaspar
3db36d8c67 added skipping of media compressor when quality is UNCOMPRESSED
added media compressor logging
2024-09-11 17:27:08 +02:00
David Kaspar
bba7d3455c enhanced nip05 logging 2024-09-11 17:27:08 +02:00
Vitor Pamplona
f086c0fe10 Merge pull request #1043 from greenart7c3/camera
Take pictures with camera
2024-09-10 15:32:01 -04:00
Vitor Pamplona
abc803953e Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-09 19:44:33 -04:00
Vitor Pamplona
8841aa9a10 Adds Tor 2024-09-09 19:42:43 -04:00
greenart7c3
7e417f09f7 Merge remote-tracking branch 'origin/camera' into camera 2024-09-09 16:10:47 -03:00
greenart7c3
0fde6ffd30 Move camera dependencies to version catalogs 2024-09-09 16:03:35 -03:00
greenart7c3
6f291139d8 Merge branch 'main' into camera 2024-09-09 15:40:56 -03:00
greenart7c3
1e3104d489 Add camera button in the stories screen 2024-09-09 15:37:30 -03:00
greenart7c3
2e110422c8 after enabling permissions open the camera 2024-09-09 14:59:05 -03:00
Vitor Pamplona
7b7aa1932f Merge pull request #1072 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-09 13:37:59 -04:00
Crowdin Bot
773c96997c New Crowdin translations by GitHub Action 2024-09-09 17:37:07 +00:00
Vitor Pamplona
8f8199f489 Merge pull request #1071 from greenart7c3/main
Fix edit draft not working when using the quick action menu
2024-09-09 13:33:54 -04:00
greenart7c3
a63d50c24f Merge remote-tracking branch 'origin/camera' into camera 2024-09-09 14:32:55 -03:00
greenart7c3
6af2b48d23 Add a button to take a picture from camera 2024-09-09 14:32:37 -03:00
greenart7c3
37197e1f8a Fix edit draft not working when using the quick action menu 2024-09-09 14:25:11 -03:00
Vitor Pamplona
8ede4eb3d6 Updates navigation compose, lifecycle, fragment, activity and composeBoms 2024-09-06 17:29:06 -04:00
Vitor Pamplona
c7fcc47b11 Adds an isRefreshing status for all feeds. 2024-09-06 17:19:10 -04:00
Vitor Pamplona
f12cf93e9f Merge pull request #1065 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-06 14:20:11 -04:00
Crowdin Bot
00e354394f New Crowdin translations by GitHub Action 2024-09-06 18:12:34 +00:00
Vitor Pamplona
bbb548fe21 Fixes the zap options available for the Zap the Devs button. 2024-09-06 11:36:01 -04:00
Vitor Pamplona
c19e1fc56d Fixes #1064 2024-09-06 11:19:01 -04:00
Vitor Pamplona
065cf83b68 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-06 11:00:04 -04:00
Vitor Pamplona
ca0da1dcc8 v0.91.0 2024-09-06 10:59:54 -04:00
Vitor Pamplona
58a5cbc624 Merge pull request #1063 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-06 10:12:13 -04:00
Crowdin Bot
554fef9eae New Crowdin translations by GitHub Action 2024-09-06 14:10:20 +00:00
Vitor Pamplona
7dd76f866a Enables the use of hidden words for all visible properties of the user 2024-09-06 10:07:41 -04:00
Vitor Pamplona
84bcc33e87 minor refactoring on variables inside the chat screen rendering. 2024-09-06 10:03:49 -04:00
Vitor Pamplona
6309aa1711 Fixes size of default banner when the profile is not loaded yet. 2024-09-06 10:02:04 -04:00
Vitor Pamplona
a8a3bc8a6b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-05 18:00:25 -04:00
Vitor Pamplona
d2b4a859bf Fixes Community Load up with Global filter 2024-09-05 18:00:17 -04:00
Vitor Pamplona
92d11d84a7 Merge pull request #1061 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-05 17:49:28 -04:00
Crowdin Bot
1cba6e6cdb New Crowdin translations by GitHub Action 2024-09-05 21:41:52 +00:00
Vitor Pamplona
20115c7080 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-05 17:39:51 -04:00
Vitor Pamplona
4b3d986c5b Moves has-new-item bottom navigation computations to use the new feed states and last read database in flows. 2024-09-05 17:39:36 -04:00
Vitor Pamplona
21112d3d46 Merge pull request #1060 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-05 15:23:45 -04:00
Crowdin Bot
ec3f307e0b New Crowdin translations by GitHub Action 2024-09-05 19:17:01 +00:00
Vitor Pamplona
e17cfb4fdb Simplifies the "and 2 more" translations 2024-09-05 15:11:00 -04:00
Vitor Pamplona
010305fbeb - Disables saving m3u8 files (streaming can't be saved)
- Changes Error messages to not include stack traces and move them to a copy to clipboard action instead.
2024-09-05 14:36:23 -04:00
Vitor Pamplona
5af473870e Fixes Search layout after the transition to nav 2024-09-05 14:00:39 -04:00
Vitor Pamplona
80f805617a Adds sliding animations in all inner screens 2024-09-05 13:53:52 -04:00
Vitor Pamplona
3e1184562f Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-05 13:47:33 -04:00
Vitor Pamplona
39edbd22cc fixes #1058 language update issue 2024-09-05 13:47:23 -04:00
Vitor Pamplona
c90e615d5b Merge pull request #1059 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-05 13:00:43 -04:00
Crowdin Bot
5e6e7a3b5f New Crowdin translations by GitHub Action 2024-09-05 16:08:57 +00:00
Vitor Pamplona
3e23d6e526 Merge pull request #1051 from davotoula/configurable-media-compression
Configurable media compression
2024-09-05 12:07:33 -04:00
David Kaspar
08054e226e Merge branch 'vitorpamplona:main' into configurable-media-compression 2024-09-05 16:02:52 +01:00
David Kaspar
cfe668ab0d move slider value label to above slider 2024-09-05 16:58:39 +02:00
Vitor Pamplona
8f19276fac Fixes blinking on crossfades when the system's light/dark theme is different than the app's theme. 2024-09-05 10:50:16 -04:00
Vitor Pamplona
3a3c0d74d2 Fixes the Messages layout on foldables. 2024-09-05 10:30:58 -04:00
David Kaspar
3ad21e72a9 Clean up by using Int instead of Float for compression level 2024-09-05 16:10:26 +02:00
David Kaspar
790b9359bd Merge branch 'vitorpamplona:main' into configurable-media-compression 2024-09-05 14:32:59 +01:00
David Kaspar
e52e2bfdc9 Localise and add czech, swedish, german and PT translations 2024-09-05 15:09:19 +02:00
Vitor Pamplona
a34c763656 Merge pull request #1056 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-05 08:42:51 -04:00
Crowdin Bot
adf5bbe80f New Crowdin translations by GitHub Action 2024-09-05 12:39:02 +00:00
Vitor Pamplona
c4c542c739 Moves the last npub to hexkey for https://github.com/nostr-protocol/nips/pull/1341 2024-09-05 08:37:15 -04:00
David Kaspar
5f0236c015 change MediaCompressor API: mandatory mediaQuality 2024-09-05 14:28:12 +02:00
David Kaspar
023c0592e8 Add compression configuration to VideoScreen uploads 2024-09-05 13:09:17 +02:00
David Kaspar
c6d2f05f15 Styling 2024-09-05 11:50:52 +02:00
David Kaspar
08e9f6625a Merge branch 'main' into configurable-media-compression 2024-09-05 08:32:49 +01:00
David Kaspar
fcf5e972d6 Passing media quality from Slider to Compressor
setting media quality to MEDIUM where ui has no option for setting it (ChannelScreen, ChatroomScreen, NewUserMetadataViewModel)
2024-09-05 09:09:10 +02:00
Vitor Pamplona
20bf4dbc29 Merge pull request #1053 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-04 22:28:12 -04:00
Crowdin Bot
990c5df2b9 New Crowdin translations by GitHub Action 2024-09-05 02:21:58 +00:00
Vitor Pamplona
71af88725a Fixes play version 2024-09-04 22:19:28 -04:00
Vitor Pamplona
fb5f5c3eba no message 2024-09-04 22:18:00 -04:00
Vitor Pamplona
ead588314e - Inverts Layouts to place Navigation on top of Scaffold and allow custom scaffolds per route.
- Finishes Edge to Edge transition
- Fixes moving buttons on the full screen dialog for images and videos.
- Restructures screens into their own packages.
- Restructures navigation functions as a single object
- Fixes padding of the backup screen
- Refactors all TopBars to use default material 3 ones.
- Refactors navigation to improve clarity
- Removes unnecessary observers from the transition in the bottom nav layouts.
2024-09-04 22:17:42 -04:00
Vitor Pamplona
fb447e441a Merge pull request #1052 from greenart7c3/gcompatup
Fix token sanitization when using gcompatup
2024-09-04 13:37:42 -04:00
greenart7c3
3d61343905 Fix token sanitization when using gcompatup 2024-09-04 13:58:55 -03:00
David Kaspar
7e4d9fb708 added media quality slider 2024-09-04 12:32:33 +02:00
Vitor Pamplona
0217fd3eb3 Moves the profile zap to threads 2024-09-03 17:29:41 -04:00
Vitor Pamplona
96fa6f39b0 Fixes ime padding issues when using physical keyboards on the message screens. 2024-09-03 17:28:07 -04:00
Vitor Pamplona
b6bb214532 Fixes text field recompositions because of new keyboard actions 2024-09-03 15:55:52 -04:00
Vitor Pamplona
ee160b76a8 Updates zoomable and kotlin serialization 2024-09-03 15:54:54 -04:00
Vitor Pamplona
2fd9d6610a Normalizes cache directories in the Application class 2024-09-03 15:31:16 -04:00
David Kaspar
272f99be17 added logging 2024-09-03 20:44:39 +02:00
David Kaspar
b8246d7cd4 fixing type 2024-09-03 19:23:47 +02:00
David Kaspar
981d93f2fc making MediaCompressor configurable 2024-09-03 19:18:10 +02:00
Vitor Pamplona
1166fe92e9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-09-03 11:45:27 -04:00
Vitor Pamplona
b700d4e9bf Fixes missing Private Home/Outbox relay list after loading from backup 2024-09-03 11:43:17 -04:00
Vitor Pamplona
0b41d09fda Merge pull request #1050 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-02 16:00:29 -04:00
Crowdin Bot
61c145a68f New Crowdin translations by GitHub Action 2024-09-02 19:29:46 +00:00
Vitor Pamplona
4103342f99 Updates to AGP 8.6.0 2024-09-02 15:24:39 -04:00
Vitor Pamplona
e48ed607ff Fixes #1049 2024-09-02 14:56:24 -04:00
Vitor Pamplona
d5a98db67c v0.90.6 2024-09-02 10:05:07 -04:00
Vitor Pamplona
72412b9d03 Merge 2024-09-02 10:04:38 -04:00
Vitor Pamplona
b5b90c80e9 Revert "v0.90.6"
This reverts commit 2ca5186aef.
2024-09-02 10:03:15 -04:00
Vitor Pamplona
c1077f689d Merge pull request #1046 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-02 10:02:32 -04:00
Crowdin Bot
8b26a84564 New Crowdin translations by GitHub Action 2024-09-02 14:00:35 +00:00
Vitor Pamplona
2ca5186aef v0.90.6 2024-09-02 10:00:28 -04:00
Vitor Pamplona
064e211154 Merge pull request #1045 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-02 09:59:23 -04:00
Vitor Pamplona
2e8ab35f05 Merge pull request #1042 from greenart7c3/clickable_url
Long press to copy relay url
2024-09-02 09:59:14 -04:00
Crowdin Bot
df429b976f New Crowdin translations by GitHub Action 2024-09-02 13:59:11 +00:00
Vitor Pamplona
d40995c338 Merge pull request #1039 from davotoula/1038-Profile-Lightning-payment-is-100x-too-large
millisats were incorrectly multiplied by 1000
2024-09-02 09:57:45 -04:00
greenart7c3
bba7c257da long press to copy relay url in the relay icons and relay row 2024-09-02 09:52:39 -03:00
greenart7c3
a03e02bfeb Add a button to take a picture from camera 2024-09-02 08:42:46 -03:00
David Kaspar
a4a753e7b6 Rename amount to amountInMillisats to give visual clue to calling classes that millisats should be used 2024-09-02 12:26:26 +02:00
greenart7c3
53acbd8606 Long press to copy relay url 2024-09-02 07:15:59 -03:00
Vitor Pamplona
f89c1ff391 v0.90.5 2024-09-01 15:07:07 -04:00
Vitor Pamplona
471ee21749 Merge pull request #1041 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-09-01 12:38:26 -04:00
Crowdin Bot
24f7e043af New Crowdin translations by GitHub Action 2024-09-01 16:37:46 +00:00
Vitor Pamplona
34e82dab8b Merge pull request #1040 from davotoula/1037-tor-setting-reset-to-off-after-switching-away
1037 tor setting reset to off after switching away
2024-09-01 12:36:36 -04:00
David Kaspar
9fa1b8c541 Close "needsPassword.value" if statement earlier to make Orbot checkbox visible again 2024-09-01 17:09:03 +02:00
David Kaspar
1e61162f73 change the logic for saving of proxy state 2024-09-01 17:05:20 +02:00
David Kaspar
941266365e millisats were incorrectly multiplied by 1000 2024-08-31 19:24:12 +01:00
Vitor Pamplona
b1f1466e6b v0.90.4 2024-08-30 18:43:34 -04:00
Vitor Pamplona
55d53dbc4f Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-30 18:42:50 -04:00
Vitor Pamplona
aeaddf722b - Fixes caching bug of NIP-96 servers
- Allow NIP-96 setups with relative URLs
- Displays error messages if the server has sent in the body
- Adds a test case for both
2024-08-30 18:42:41 -04:00
Vitor Pamplona
b733fbce35 Merge pull request #1036 from Dawn11111111/patch-1
Typo Fix on Lnaddress TextField
2024-08-30 12:05:00 -04:00
Dawn
93e9e23730 Typo Fix on Lnaddress TextField
Removed 'i' from lightining :)
2024-08-30 09:45:13 -06:00
Vitor Pamplona
388ccdbe75 Speeds up the filter for NWC zap payments. 2024-08-29 12:32:34 -04:00
Vitor Pamplona
255f464046 Basic code for dynamic limits 2024-08-29 10:29:46 -04:00
Vitor Pamplona
8be74649ce Protects against empty nip04 content 2024-08-28 17:59:59 -04:00
Vitor Pamplona
b5709f9527 Adds basic support for NIP-46 events. 2024-08-28 17:39:25 -04:00
Vitor Pamplona
7d9efcd1d9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-28 16:07:47 -04:00
Vitor Pamplona
d44f5dad18 Merge pull request #1035 from greenart7c3/main
Show relay ping with the relay icon
2024-08-28 16:06:50 -04:00
greenart7c3
06f37ab81d Fix relay icons when using complete ui 2024-08-28 16:20:55 -03:00
Vitor Pamplona
cb4a73bb9c Enables decryption by nip04 and nip44 on NostrWalletConnect objects, NIP-51 lists and NIP-04 messages 2024-08-28 15:20:25 -04:00
greenart7c3
efde14a348 Show relay ping with the relay icon 2024-08-28 16:17:08 -03:00
Vitor Pamplona
0f86e3d8fb Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-28 09:20:48 -04:00
Vitor Pamplona
1407850e81 Fixes push registration with empty token ids 2024-08-28 09:20:40 -04:00
Vitor Pamplona
d9a87f6e7c Merge pull request #1034 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-28 08:33:37 -04:00
Crowdin Bot
5e0a49f225 New Crowdin translations by GitHub Action 2024-08-28 12:31:37 +00:00
Vitor Pamplona
3ebaf2bf1e Fixes ZapTheDevs card not disappearing after the donation. 2024-08-28 08:29:53 -04:00
Vitor Pamplona
9ef0ceed9d Merge pull request #1033 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-27 17:49:17 -04:00
Crowdin Bot
caa3760fd3 New Crowdin translations by GitHub Action 2024-08-27 21:46:25 +00:00
Vitor Pamplona
0b2a9e67c0 v0.90.3 2024-08-27 17:44:44 -04:00
Vitor Pamplona
931081c5e7 Exposes decryption crashing because it is used by Notification Services to check which account can decrypt a GiftWrap. 2024-08-27 17:43:12 -04:00
Vitor Pamplona
f7237b5666 Improves logs on new Notifications 2024-08-27 17:42:18 -04:00
Vitor Pamplona
d851637ea2 Fixes byte to string decoder for UnifiedPush 2024-08-27 17:41:56 -04:00
Vitor Pamplona
2d39b4edfe Only register for endpoints if it has changed from the one in memory 2024-08-27 17:41:37 -04:00
Vitor Pamplona
5001c5bef8 There is no need to extend onReceive 2024-08-27 17:40:53 -04:00
Vitor Pamplona
6ac1db9fc8 correctly passes the endpoint to remove it 2024-08-27 17:40:35 -04:00
Vitor Pamplona
52f33d79e0 Correctly registering the distributor at startup 2024-08-27 17:38:14 -04:00
Vitor Pamplona
1a9d91cdc0 Fixes correct saving of UnifiedPush endpoints 2024-08-27 17:37:53 -04:00
Vitor Pamplona
2f444e6ec5 Moves ChatMessage components to the chatroom package 2024-08-27 16:03:15 -04:00
Vitor Pamplona
4665c488d5 Adds a small border between the author and the message of a chat bubble 2024-08-27 15:57:56 -04:00
Vitor Pamplona
42ab826d02 v0.90.2 2024-08-27 15:01:50 -04:00
Vitor Pamplona
80a3f7424b fixes conversation tab 2024-08-27 14:52:22 -04:00
Vitor Pamplona
1bd3e347b3 Updates media 3 dependencies. 2024-08-27 14:51:30 -04:00
Vitor Pamplona
a332126e71 Quick fix for to not show error messages on Zap the Devs 2024-08-27 14:33:57 -04:00
Vitor Pamplona
d97abe6af9 v0.90.0 2024-08-27 13:48:36 -04:00
Vitor Pamplona
62d341bd8a Publishing new user's events 2024-08-27 11:49:43 -04:00
Vitor Pamplona
a95d2e903e Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeDrawer.kt
2024-08-27 11:04:36 -04:00
Vitor Pamplona
2b5e62aaf7 - Improves rendering of the QR Code elements
- Makes the QR bigger
- Moves QR Code to rounder corners
- Moves QR Screen to use the arrow icon instead of the close button
2024-08-27 11:04:13 -04:00
Vitor Pamplona
1c3e82913f Merge pull request #1031 from greenart7c3/main
Use white background for qrcodes
2024-08-27 11:02:33 -04:00
Vitor Pamplona
ceaa20fabf Removes galleries live data 2024-08-27 08:34:25 -04:00
Vitor Pamplona
1bbabb747e Deleting Bundled updates for Account. 2024-08-26 19:12:39 -04:00
Vitor Pamplona
914c0a2926 Making sure scopes are cancelled for accountViewModel notification dot updates. 2024-08-26 19:12:28 -04:00
Vitor Pamplona
4b249156f9 Makes sure the accountViewModel is deleted when the user switches the screen, logs off or kills the activity. 2024-08-26 18:22:34 -04:00
Vitor Pamplona
7b8c7a1a78 Simplifies flow collection for new events. 2024-08-26 18:22:12 -04:00
Vitor Pamplona
66ed26dcea Finishes the migration of the service manager to the Application class 2024-08-26 18:21:53 -04:00
Vitor Pamplona
356740479c Moves to hold the feed itself in a stateflow 2024-08-26 17:48:27 -04:00
Vitor Pamplona
c88dded01b Improves stability of composables 2024-08-26 16:45:07 -04:00
Vitor Pamplona
7c974bc8ef adds a directory for metrics as well 2024-08-26 15:27:04 -04:00
Vitor Pamplona
efbc3e3e78 removes unecessary logs 2024-08-26 14:57:16 -04:00
Vitor Pamplona
fac803e208 Merge pull request #1032 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-26 14:56:04 -04:00
Crowdin Bot
484a3d6b32 New Crowdin translations by GitHub Action 2024-08-26 18:44:35 +00:00
Vitor Pamplona
4e3b6d0299 - Adds a sync Signer to facilitate library
- Separates Account actions from Account state in two objects
- Changes Startup procedures to start with Account state and not the full account object
- Moves scope for flows in Account from an Application-wide scope to ViewModel scope
- Removes all LiveData objects from Account in favor of flows from the state object
- Migrates settings saving logic to flows
- Migrates PushNotification services to work without Account and only Account Settings.
- Migrates the spam filter from LiveData to Flows
- Adds Default lists for NIP-65 inbox and outbox relays
- Adds Default lists for Search relays
- Adds local backup for UserMetadata objects
- Adds local backup for Mute lists
- Adds local backup for NIP-65 relays
- Adds local backup for DM Relays
- Adds local backup for private home relays
- Rewrites state flows initializers to avoid inconsistent startups
2024-08-26 14:42:55 -04:00
Vitor Pamplona
f3161ada8d Solves crash when opening the Relay screen with empty urls as relays. 2024-08-26 11:13:10 -04:00
greenart7c3
5c9b84476b Use white background for qrcodes 2024-08-26 07:21:19 -03:00
Vitor Pamplona
24ea0cabda Updates benchmark, composeBOM and firebaseBOM to the latest versions 2024-08-23 08:35:44 -04:00
Vitor Pamplona
8b617fbd60 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-22 17:27:04 -04:00
Vitor Pamplona
34149e6ce2 Merge pull request #1030 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-22 17:13:16 -04:00
Vitor Pamplona
315a662c10 Improve horizontal padding of the chat messages 2024-08-22 17:12:46 -04:00
Crowdin Bot
c373843e0f New Crowdin translations by GitHub Action 2024-08-22 21:03:22 +00:00
Vitor Pamplona
448ea796ef Improves caching of encrypted DMs:
- Migrates caching of decrypted value outside of the Event class
- Removes encrypted parts of NIP-17 from the cache
- Removes old NIP-04 messages from the cache
- Avoids deleting new NIP-17 plain text chats from memory
2024-08-22 17:01:37 -04:00
Vitor Pamplona
71b111ce8b Fixes the download of 1000s of NIP-65 relay lists because some relays consider empty lists as null and return everything. 2024-08-22 12:24:45 -04:00
Vitor Pamplona
ae686671ba Minimizing the number of filters for group chat metadata 2024-08-22 09:55:28 -04:00
Vitor Pamplona
6c45bb1ca1 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-21 20:32:06 -04:00
Vitor Pamplona
a05ad6e686 Adds support for NIP-35 torrents and their comments 2024-08-21 20:31:59 -04:00
Vitor Pamplona
485f7becd4 Merge pull request #1029 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-21 19:18:29 -04:00
Vitor Pamplona
83f1e523ea Creates links to njump when events can't be found on Amethyst 2024-08-21 17:05:14 -04:00
Vitor Pamplona
a8a3c94115 clear flows before removing notes from the local cache 2024-08-21 16:32:11 -04:00
Vitor Pamplona
4d21857adf moves the processing of nip19 links to the default thread 2024-08-21 16:31:57 -04:00
Crowdin Bot
0e7f41efe8 New Crowdin translations by GitHub Action 2024-08-21 20:09:17 +00:00
Vitor Pamplona
8d9bf60dba Merge pull request #1028 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-21 16:08:51 -04:00
Vitor Pamplona
7c67924914 fixes imports 2024-08-21 16:07:42 -04:00
Crowdin Bot
7626839037 New Crowdin translations by GitHub Action 2024-08-21 19:59:14 +00:00
Vitor Pamplona
23825ceed5 Avoids bottom nav recompositions 2024-08-21 15:57:45 -04:00
Vitor Pamplona
8d8016e01b Simplifies nav bar selected calculations 2024-08-21 15:51:01 -04:00
Vitor Pamplona
6668e5a178 Moves accountViewModelMock to a more appropriate file 2024-08-21 15:14:28 -04:00
Vitor Pamplona
7d6bef9576 Moves heavy computations away from IO 2024-08-21 15:00:13 -04:00
Vitor Pamplona
1aea7fbd03 Avoids remembering edit lists that will never exist. 2024-08-21 14:58:01 -04:00
Vitor Pamplona
ef96d296f8 avoids double update to the viewmodel 2024-08-21 14:53:35 -04:00
Vitor Pamplona
d4da58e14a Improves logs when the UserMetadata cannot be parsed 2024-08-21 14:38:17 -04:00
Vitor Pamplona
f646866192 moves markasread from io to default 2024-08-21 09:20:43 -04:00
Vitor Pamplona
e0849b12e7 Merge pull request #1027 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-21 07:55:58 -04:00
Crowdin Bot
1271343582 New Crowdin translations by GitHub Action 2024-08-21 11:52:51 +00:00
Vitor Pamplona
d61d14ea75 Merge pull request #1026 from davotoula/chage-twitter-to-x
Change twitter proof to use x.com
2024-08-21 07:51:04 -04:00
David Kaspar
668d604aa9 use original twitter colour in x.xml because I can't get the theme switching working 2024-08-21 11:56:45 +01:00
Vitor Pamplona
6df2c3a0cf improving speed of chatlist rendering 2024-08-20 15:23:15 -04:00
Vitor Pamplona
b0c33808e6 Adds support for MOD reports 2024-08-20 15:07:28 -04:00
Vitor Pamplona
7bd3e0beab Improved padding and spacing for chat messages 2024-08-20 13:05:44 -04:00
Vitor Pamplona
3a70cfb9e2 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-20 09:14:56 -04:00
Vitor Pamplona
17c758f8f8 Displays PoW of commitment if present. 2024-08-20 09:14:47 -04:00
Vitor Pamplona
7cb03b939f Merge pull request #1022 from KotlinGeekDev/fix-relay-set
Fix relay set event.
2024-08-20 08:57:34 -04:00
KotlinGeekDev
481ef91cb1 Change relay set kind to be NIP-51 consistent. 2024-08-20 12:13:42 +01:00
Vitor Pamplona
1183aa8d11 Changes fdroid push registration to re-register even if the saved distributor was already selected. 2024-08-19 17:58:50 -04:00
Vitor Pamplona
4023261400 Merge pull request #1020 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-19 12:07:35 -04:00
Crowdin Bot
f1433504e9 New Crowdin translations by GitHub Action 2024-08-19 16:00:50 +00:00
Vitor Pamplona
989cbb35d3 Disabling landscape rotation on full screen due to portrait videos. 2024-08-19 11:58:50 -04:00
Vitor Pamplona
9262b90096 Merge pull request #1019 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-19 11:40:22 -04:00
Crowdin Bot
ab3b7685cf New Crowdin translations by GitHub Action 2024-08-19 15:40:02 +00:00
Vitor Pamplona
40865ac97d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-19 11:36:39 -04:00
Vitor Pamplona
ded67964b0 Merge pull request #1018 from KotlinGeekDev/fullscreen-video-behavior
Change tap to fullscreen video behavior for livestreams.
2024-08-19 11:34:47 -04:00
Vitor Pamplona
e991707ddf updates runner 2024-08-19 11:32:04 -04:00
KotlinGeekDev
1b429475aa Remove the manual testing/debugging code. Remove the LocalConfiguration API in favour of DeviceUtils.isLandscapeMetric. 2024-08-17 18:51:07 +01:00
KotlinGeekDev
9613afb7ea Fix ZoomableImageDialog. isFoldableOrLarge already takes into account the orientation. 2024-08-17 17:07:43 +01:00
KotlinGeekDev
26a12f2dbe Remove the external config method and use the built-in Compose API.
Add some documentation.
2024-08-17 17:03:07 +01:00
KotlinGeekDev
23da02f932 Merge remote-tracking branch 'origin/fullscreen-video-behavior' into fullscreen-video-behavior 2024-08-17 15:19:47 +01:00
KotlinGeekDev
59ff9007a7 Move DeviceUtils closer to call-site. Copy code from Material3-Adaptive library(to avoid experimental annotation propagation everywhere).
Implement windowIsLarge to take care of device type handling(sort-of). Add some funky debug methods to make sure windowIsLarge works.
2024-08-17 14:18:48 +00:00
KotlinGeekDev
0f817dbf00 Put device in landscape mode when fullscreen button is pressed. 2024-08-17 14:18:48 +00:00
KotlinGeekDev
7ab6322886 Introduce DeviceUtils, and add functions for changing orientation, and checking for Landscape mode. 2024-08-17 14:18:48 +00:00
KotlinGeekDev
223c0c1263 Initial implementation for auto-fullscreen on rotation to landscape mode. 2024-08-17 14:18:48 +00:00
KotlinGeekDev
d3a639a2ee Consistent naming and behavior for the video player controls. 2024-08-17 14:18:48 +00:00
KotlinGeekDev
2fb1cbe11f Move DeviceUtils closer to call-site. Copy code from Material3-Adaptive library(to avoid experimental annotation propagation everywhere).
Implement windowIsLarge to take care of device type handling(sort-of). Add some funky debug methods to make sure windowIsLarge works.
2024-08-17 15:14:24 +01:00
Vitor Pamplona
29c5b45764 Moves the default zap type to a state flow and avoids passing on to the screen when using the default value. 2024-08-16 17:45:53 -04:00
Vitor Pamplona
ec83ad9d20 Removing unecessary livedata objects for translation services 2024-08-16 17:10:02 -04:00
Vitor Pamplona
239f973e89 Moves lastread routes to mutableStateFlow 2024-08-16 16:42:00 -04:00
Vitor Pamplona
6c81fbbcf9 Fixes crash when the relay url of a user is duplicated. 2024-08-16 16:00:03 -04:00
Vitor Pamplona
75e45f37b2 Migrating livedata to flow for contact list updates of the user. 2024-08-16 15:59:43 -04:00
Vitor Pamplona
0bff3462b1 Migrates the pubkeyHex from keypair to the signer. 2024-08-16 15:58:23 -04:00
Vitor Pamplona
990db68a4d Fixes spacing of short quotes 2024-08-16 13:12:05 -04:00
Vitor Pamplona
4343c744db Displaying stacks in error messages if they are present 2024-08-16 11:14:34 -04:00
Vitor Pamplona
5cb6f97178 linting cleanup 2024-08-16 10:48:30 -04:00
KotlinGeekDev
a8efc9d348 Put device in landscape mode when fullscreen button is pressed. 2024-08-16 14:53:40 +01:00
Vitor Pamplona
e0b667fd2d Fixes order for the notification feed after refactoring 2024-08-16 09:41:38 -04:00
Vitor Pamplona
a8ad2437fe refactors the Watch feed to scroll to the top 2024-08-16 09:39:15 -04:00
KotlinGeekDev
83018e621e Introduce DeviceUtils, and add functions for changing orientation, and checking for Landscape mode. 2024-08-16 14:19:55 +01:00
Vitor Pamplona
68b15ba483 Refactors old user zap object. 2024-08-15 17:08:15 -04:00
Vitor Pamplona
7513036d47 Refactors sorting of Notifications 2024-08-15 16:16:36 -04:00
Vitor Pamplona
c82c594926 Fixes slow down when the last message on chat is a base64 image. 2024-08-15 16:02:28 -04:00
Vitor Pamplona
bd18d4b4b2 Adjusts Outbox filter as soon as a new NIP-65 relay list shows up 2024-08-15 15:17:41 -04:00
Vitor Pamplona
2f5b99a1c7 Merge pull request #1016 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-15 15:06:50 -04:00
Crowdin Bot
1be6e0ec0f New Crowdin translations by GitHub Action 2024-08-15 18:58:39 +00:00
Vitor Pamplona
f43c0e5cf5 Moves error messages from uploading procedures to the full error dialog instead of just a toast. 2024-08-15 14:55:25 -04:00
Vitor Pamplona
d6d8719c8d Fixes the centralization of the "and more" part of relay recommendations 2024-08-14 18:50:26 -04:00
Vitor Pamplona
7478899388 Fixes miscaching flows of the relay lists from follows. 2024-08-14 18:03:25 -04:00
Vitor Pamplona
6b193894bd Fixes miscache of hashtag following button 2024-08-14 17:16:02 -04:00
Vitor Pamplona
69f5371ab8 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-14 17:15:36 -04:00
Vitor Pamplona
526e3db3c1 Adds more information to when error messages are not available in the relay stats. 2024-08-14 15:50:05 -04:00
Vitor Pamplona
3c9e8854eb Merge pull request #1015 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-14 15:14:42 -04:00
Crowdin Bot
7ee0aa47a9 New Crowdin translations by GitHub Action 2024-08-14 19:07:04 +00:00
Vitor Pamplona
5fbf0462fa No need to check for the MainThread in every updateFeedWith 2024-08-14 15:04:51 -04:00
Vitor Pamplona
eda5bc50e6 Adds a destroy method to FollowList state for consistency 2024-08-14 10:45:57 -04:00
David Kaspar
d6d26d466b clean up code in MetadataEvent.kt
update x drawable
2024-08-14 08:11:28 +01:00
Vitor Pamplona
6acd3ca091 Moves follow list states to the AccountViewModel 2024-08-13 19:35:04 -04:00
Vitor Pamplona
c64d179f7f Migrates Notification Summary to the new state model 2024-08-13 18:56:53 -04:00
Vitor Pamplona
32bc8fe667 Fixes f-droid flavor after refactoring 2024-08-13 18:35:31 -04:00
Vitor Pamplona
0a8c792d53 Moves the notification screen to the new state model instead of viewModels 2024-08-13 18:32:20 -04:00
Vitor Pamplona
c32dc61ea1 Refactoring Moving feed status from ViewModel to State objects 2024-08-13 17:32:15 -04:00
Vitor Pamplona
3af2cb52cc adds a prune for giftwrapped messages 2024-08-13 15:11:15 -04:00
Vitor Pamplona
ddb1d7ff82 Adds context to highlight events 2024-08-13 15:08:32 -04:00
KotlinGeekDev
fd0523085d Initial implementation for auto-fullscreen on rotation to landscape mode. 2024-08-13 10:42:49 +01:00
Vitor Pamplona
1f9fbb0c86 Revers letter spacing in inline code on Markdow 2024-08-12 15:58:17 -04:00
Vitor Pamplona
5b95a1c177 Improved logs when clearing the cache for DMs 2024-08-12 15:55:25 -04:00
Vitor Pamplona
2e144ebdf8 Adds previews to test markdown rendering 2024-08-12 15:55:01 -04:00
Vitor Pamplona
ac5f550ff3 Fixes heading sizes on markdown 2024-08-12 15:54:20 -04:00
Vitor Pamplona
9af123b709 Improves the look of inlinde code in markdown 2024-08-12 15:53:45 -04:00
Vitor Pamplona
75efd2b372 Improves badge display 2024-08-12 11:18:22 -04:00
Vitor Pamplona
2cface9ac9 Inverts spam logs 2024-08-12 11:11:23 -04:00
Vitor Pamplona
58491a97ac Merge pull request #1012 from greenart7c3/main
show only 3 users in the recommended relays section
2024-08-12 09:07:21 -04:00
greenart7c3
7a86d78c50 show only 3 users in the recommended relays section 2024-08-12 09:05:08 -03:00
David Kaspar
97a00ff83c First pass updating Twitter verification to X 2024-08-12 11:07:48 +02:00
Vitor Pamplona
4c6842faf4 v0.89.10 2024-08-09 18:47:48 -04:00
Vitor Pamplona
fad8b9df0f Improved filter for notifications 2024-08-09 18:47:38 -04:00
Vitor Pamplona
cc52681635 Moves service manager to the Application class 2024-08-09 18:14:09 -04:00
Vitor Pamplona
939c8e95b0 Adds protections against filters with empty arrays because some relays consider that null as opposed to empty. 2024-08-09 18:13:19 -04:00
Vitor Pamplona
312501e527 AGP to 8.5.2 2024-08-09 15:25:52 -04:00
Vitor Pamplona
89638ff261 Deletes all events in maximum chunks of 200 elements to avoid the 65KB stringified JSON limit of many relays. 2024-08-09 14:35:00 -04:00
Vitor Pamplona
27d2e2309a Fixes the clickable regions to open additional relay info on the relay list dialog. 2024-08-09 13:44:44 -04:00
Vitor Pamplona
7eaa553ebe Allows users to select and copy the notice from the relay on the relay list dialog 2024-08-09 13:43:53 -04:00
KotlinGeekDev
8adddd0bdb Consistent naming and behavior for the video player controls. 2024-08-09 15:26:42 +01:00
Vitor Pamplona
d8f084f886 Merge pull request #1009 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-08 18:37:10 -04:00
Crowdin Bot
5c5a23dfbb New Crowdin translations by GitHub Action 2024-08-08 22:30:34 +00:00
Vitor Pamplona
282d4614c0 Adds zap amount cache for the memory space calculations 2024-08-08 18:19:48 -04:00
Vitor Pamplona
8ed78ea38f Marks username as deprecated 2024-08-08 18:18:54 -04:00
Vitor Pamplona
2e7b7c5c00 improves wording on the name of relays 2024-08-08 14:35:45 -04:00
Vitor Pamplona
68cad97819 Reducing the amount of CPU memory used for images to the default. 2024-08-08 11:51:35 -04:00
Vitor Pamplona
02f7b54661 Updates translate dependencies 2024-08-08 08:31:38 -04:00
Vitor Pamplona
e887232132 v0.89.9 2024-08-07 18:39:02 -04:00
Vitor Pamplona
7bcf4e3750 Removes logs from previous commit 2024-08-07 18:34:38 -04:00
Vitor Pamplona
ed0676a5f5 Fixes the order of bookmarks (keeps the order of the event, instead of the created at) 2024-08-07 18:24:35 -04:00
Vitor Pamplona
f731c654b0 Improves the async rendering of Base64 content 2024-08-07 18:24:14 -04:00
Vitor Pamplona
b026bffe4a Moves discovery and video lists to Outbox when Follows or relay lists are selected 2024-08-07 16:39:24 -04:00
Vitor Pamplona
a1aaec0b7d Adds support for selecting authors based on their Outbox relays when searching for notes authored by them 2024-08-07 15:46:27 -04:00
Vitor Pamplona
6f59097ac0 Aligns default note comparator to NIP-01's created at descending and then by id ascending 2024-08-07 15:44:35 -04:00
Vitor Pamplona
263df0c895 Renames PerRelayFilter to SincePerRelay
Creates PerRelay interface to allow other types of filters
2024-08-07 15:44:03 -04:00
Vitor Pamplona
1ca5713494 Merge pull request #1008 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-07 12:57:34 -04:00
Crowdin Bot
b706c4e871 New Crowdin translations by GitHub Action 2024-08-07 16:04:54 +00:00
Vitor Pamplona
e8574c10bb Keep them public to allow testing in these particular functions 2024-08-07 12:02:48 -04:00
Vitor Pamplona
7cb87ea9c5 Refactors to use native contains instead of custom lambdas on Ammolite's Filter 2024-08-07 11:58:00 -04:00
Vitor Pamplona
07e5132943 Refactors Ammolite Filters to be regular ones and creates a PerRelayFilter for the use on Amethyst. 2024-08-07 11:40:01 -04:00
Vitor Pamplona
2b1e3cfc93 Renames the MinimumRelayList to RecommendationProcessor 2024-08-07 11:39:14 -04:00
Vitor Pamplona
c17e1f0e1f removes logs from previous commit 2024-08-06 17:14:22 -04:00
Vitor Pamplona
e88d1d0d00 Adds haptic feedback to draft deletion swipe 2024-08-06 17:13:06 -04:00
Vitor Pamplona
5fdff97cf8 Moves the ContactList cache lists to AccountViewModel, where it can be disposed more efficiently. 2024-08-06 15:36:04 -04:00
Vitor Pamplona
e5328d7975 Improves the accuracy of the Event memory counter. 2024-08-06 15:35:14 -04:00
Vitor Pamplona
71b45b96fb Adds event factory performance test 2024-08-06 11:04:30 -04:00
Vitor Pamplona
dee28f3aa8 Merge pull request #1006 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-05 17:26:13 -04:00
Crowdin Bot
3a118bb939 New Crowdin translations by GitHub Action 2024-08-05 20:59:07 +00:00
Vitor Pamplona
971c92b27a Adds extension possibility to Quartz's event factory 2024-08-05 16:57:21 -04:00
Vitor Pamplona
4c70065843 Moves DataSource dispatcher from IO to Default 2024-08-05 16:02:35 -04:00
Vitor Pamplona
eab69337ff Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-08-05 16:02:19 -04:00
Vitor Pamplona
ab772eb65e Makes stringRes Stable for compose 2024-08-05 15:48:06 -04:00
Vitor Pamplona
7a6720a7ab Merge pull request #1005 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-05 15:31:47 -04:00
Crowdin Bot
0f3dc18f7a New Crowdin translations by GitHub Action 2024-08-05 19:16:01 +00:00
Vitor Pamplona
84df86f4b6 Removes mutiny NWC button :( 2024-08-05 15:13:35 -04:00
Vitor Pamplona
a0289a0cb4 Moves Relay viewModels to Default thread 2024-08-05 15:08:08 -04:00
Vitor Pamplona
3c034ca8af v0.89.8 2024-08-02 16:34:51 -04:00
Vitor Pamplona
0c8c3003aa Merge pull request #1004 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-08-02 13:37:05 -04:00
Crowdin Bot
919dabfefa New Crowdin translations by GitHub Action 2024-08-02 16:39:19 +00:00
Vitor Pamplona
fd06193e93 Notifies relay failures after marking them as closed. 2024-08-02 12:36:54 -04:00
Vitor Pamplona
b02fa43628 Print relay writes on benchmark as well. 2024-08-02 12:35:52 -04:00
Vitor Pamplona
7fdf38d17b Normalizes logs for the DataSources 2024-08-02 12:35:25 -04:00
Vitor Pamplona
63e036a9bb Minimizes relay updates when rotating status events for each user. 2024-08-02 12:32:44 -04:00
Vitor Pamplona
fc98442f8b Adds support for CashuB tokens using CBOR 2024-07-31 17:36:48 -04:00
Vitor Pamplona
3bbb780d2b Simplifies the drawing of gallery entires without e tags. 2024-07-31 16:02:18 -04:00
Vitor Pamplona
42ceb0c404 Fixes test case for ImageUploaders 2024-07-31 15:59:07 -04:00
Vitor Pamplona
046bd7a0e9 Merge pull request #1003 from greenart7c3/main
Add delete all drafts button
2024-07-31 14:58:26 -04:00
Vitor Pamplona
0af0f745bf Fixes the need for main thread note loading for galleries 2024-07-31 14:57:44 -04:00
Vitor Pamplona
b24999d78c Basic refactoring to minimize the need for user-space params in the VideoView 2024-07-31 14:43:10 -04:00
Vitor Pamplona
d1278a4477 Better rendering for live streams that are planned but already finished. 2024-07-31 14:16:44 -04:00
greenart7c3
609ccaa635 Add delete all drafts button 2024-07-31 13:51:02 -03:00
Vitor Pamplona
4bae04ad31 Merge pull request #1001 from believethehype/better_gallery_view
Gallery: use FileHeader Info for rendering / faster loading
2024-07-31 10:41:19 -04:00
Believethehype
8bca729edd Merge remote-tracking branch 'upstream/main' into better_gallery_view 2024-07-31 16:36:25 +02:00
Vitor Pamplona
2b2f04f724 Adds the callbackUri to the display uncited hashtags so that it can reuse the previously parsed post. 2024-07-31 10:21:13 -04:00
Vitor Pamplona
231af1d3d8 Fixes #993 2024-07-31 10:17:19 -04:00
Believethehype
d1b44c0ac0 remove print 2024-07-31 12:59:23 +02:00
Believethehype
f06c3f7376 massive speed up of rendering the gallery 2024-07-31 12:55:02 +02:00
Believethehype
b8e10b040d cleanup 2024-07-31 12:02:39 +02:00
Believethehype
1a4396dbe3 removes unneded variable 2024-07-31 11:51:44 +02:00
Believethehype
856eb0da05 create GalleryFeed earlier 2024-07-31 11:40:59 +02:00
Believethehype
ceaeb8e0b9 use GalleryContentView instead of 2024-07-31 11:24:05 +02:00
Vitor Pamplona
b29ccd9746 Refactors the lambda used for content resolvers. 2024-07-30 18:16:28 -04:00
Vitor Pamplona
f82364939f Adds icons in SVG to the docs 2024-07-30 18:16:06 -04:00
Vitor Pamplona
286583bba2 fixing the need for a new coroutine startup 2024-07-30 13:50:08 -04:00
Vitor Pamplona
bc8e5aa458 Merge pull request #997 from VASHvic/fix-clipboard-copy-edited-notes
Fix copying to clipboard edited notes
2024-07-30 13:47:49 -04:00
Vitor Pamplona
b393fb042f Merge pull request #999 from believethehype/nutrendering
Show mint in cashu token rendering
2024-07-30 13:47:14 -04:00
Vitor Pamplona
0258c5aac2 Releasing Flows if not subscribed anymore. 2024-07-30 13:22:48 -04:00
Vitor Pamplona
a03a11c4cc Updates depedencies 2024-07-30 13:17:53 -04:00
Vitor Pamplona
3896bbab34 Merge pull request #1000 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-30 09:17:19 -04:00
Crowdin Bot
5459f31000 New Crowdin translations by GitHub Action 2024-07-29 23:24:58 +00:00
Vitor Pamplona
143c3a1444 Testing that both 02 and 03 compressed keys are valid Nostr keys and can encrypt and decrypt to each other. 2024-07-29 19:23:22 -04:00
Believethehype
bfbcf11fa3 Update CashuRedeem.kt 2024-07-29 20:42:17 +02:00
VASH
25823b1f39 Fix copying to clipboard edited notes 2024-07-27 13:05:20 +02:00
Believethehype
cb02955ac6 fix order of Share ImageAction 2024-07-24 07:49:44 +02:00
Vitor Pamplona
b440661e0d Adds the highlight to the base URL so that when the user opens the link, it highlights on the page. 2024-07-23 12:56:53 -04:00
Vitor Pamplona
38ba456a22 Signs for just one auth event to register with the push notification service instead of the dozens of events, one per relay. 2024-07-23 10:15:36 -04:00
Vitor Pamplona
bca941e045 enables crossfading between images 2024-07-23 08:47:20 -04:00
Vitor Pamplona
dbaadb4595 v0.89.7 2024-07-22 18:10:15 -04:00
Vitor Pamplona
231ca44e1c Avoids recomposition of the entire line when changing status. 2024-07-22 18:07:44 -04:00
Vitor Pamplona
ca00c87a6e Prints memory use in descending order by size 2024-07-22 16:59:58 -04:00
Vitor Pamplona
2a28111583 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-07-22 14:27:23 -04:00
Vitor Pamplona
4def80c914 updates the EOSE of users even if no user is found in the relay. 2024-07-22 14:27:10 -04:00
Vitor Pamplona
916ece1edb Merge pull request #992 from greenart7c3/main
Remove focus from status text field when closing the drawer
2024-07-22 14:00:24 -04:00
Vitor Pamplona
3c1f57fac0 Sorts keys before making the filter. 2024-07-22 13:57:31 -04:00
greenart7c3
e166c36731 Remove focus from status text field when closing the drawer 2024-07-22 14:45:39 -03:00
Vitor Pamplona
a636f4a786 updates coil and jackson libs 2024-07-22 11:31:57 -04:00
Vitor Pamplona
bb127506c7 Merge pull request #985 from believethehype/main
Store additional File Metadata in Gallery Entries
2024-07-22 08:10:26 -04:00
Vitor Pamplona
92c704d227 Merge pull request #986 from greenart7c3/main
Add sendAndWaitForResponse to Client.kt
2024-07-22 08:10:11 -04:00
greenart7c3
e764ebbcb2 if the relay just connected send the event 2024-07-22 07:49:54 -03:00
greenart7c3
68fb34a8ad Merge branch 'main' into main 2024-07-22 07:36:43 -03:00
greenart7c3
02683ba829 Merge remote-tracking branch 'origin/main' 2024-07-22 07:34:36 -03:00
greenart7c3
754a1b3a87 change the log of onRelayStateChange 2024-07-22 07:33:21 -03:00
Vitor Pamplona
4b58da7be7 Merge pull request #991 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-21 15:22:31 -04:00
Crowdin Bot
1ac9c850e4 New Crowdin translations by GitHub Action 2024-07-21 18:32:48 +00:00
Vitor Pamplona
3cc4dc1414 Merge pull request #990 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-21 14:31:54 -04:00
Vitor Pamplona
62ae43e863 v0.89.6 2024-07-21 14:31:17 -04:00
Crowdin Bot
8149a93f2f New Crowdin translations by GitHub Action 2024-07-21 18:31:01 +00:00
Vitor Pamplona
5a60c3a595 Fixes stringResource cache 2024-07-21 14:29:05 -04:00
Vitor Pamplona
2ef2f0d1a5 adds the key to avoid applying to a separate navcontroller 2024-07-21 11:32:08 -04:00
Vitor Pamplona
52fef33662 run faster animations 2024-07-21 11:18:56 -04:00
Vitor Pamplona
a5c6f86b35 v0.89.5 2024-07-20 16:27:27 -04:00
Vitor Pamplona
b80008d695 Adds more memory stat dumping for production apps 2024-07-20 16:27:19 -04:00
Vitor Pamplona
fa06aefbf1 increases disk cache to 1GB and adds a memory cache policy. 2024-07-20 15:32:34 -04:00
Vitor Pamplona
ab9ede1e26 Merge pull request #987 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-20 15:08:00 -04:00
Crowdin Bot
479b1ab20a New Crowdin translations by GitHub Action 2024-07-20 19:04:48 +00:00
Vitor Pamplona
8ed3f84cb7 Fixes screen rendering below the 3-button navigation 2024-07-20 15:02:57 -04:00
Vitor Pamplona
c128e43fe5 v0.89.4 2024-07-19 17:10:22 -04:00
Vitor Pamplona
f9ddf76686 action bar fix 2024-07-19 17:09:56 -04:00
Vitor Pamplona
b6cb839fe7 v0.89.3 2024-07-19 16:44:30 -04:00
Vitor Pamplona
2b6a48f391 Fixes issue with super large follow lists. 2024-07-19 16:40:46 -04:00
Vitor Pamplona
d1da863d3c Making sure an empty relay list uses the default list from Amethyst 2024-07-19 16:14:14 -04:00
greenart7c3
99b7a8005a Merge branch 'main' into main 2024-07-19 14:13:36 -03:00
greenart7c3
3f3367258f return true if at least one relay replied with success 2024-07-19 13:29:09 -03:00
Vitor Pamplona
15ab955a7d Fixes alt of NIP-96 server lists 2024-07-19 11:55:46 -04:00
greenart7c3
5cf8b5aa12 change proguard to see if now the client.listener works 2024-07-19 12:49:04 -03:00
greenart7c3
eba6d03b53 check if listeners work now 2024-07-19 12:17:26 -03:00
greenart7c3
de85239046 check if listeners work now 2024-07-19 11:56:30 -03:00
Vitor Pamplona
9176151971 Fixes auth infinite loop with nostr.wine 2024-07-19 10:45:06 -04:00
greenart7c3
cbd8acbd01 check eose too 2024-07-19 11:14:31 -03:00
greenart7c3
19c9bfa819 Merge remote-tracking branch 'origin/main' 2024-07-19 11:14:18 -03:00
greenart7c3
80c369a6ae change default timeout to 15 seconds 2024-07-19 10:56:35 -03:00
greenart7c3
d0190c87a2 add more logs, add optional listener, fix await call 2024-07-19 10:56:35 -03:00
greenart7c3
62a2cba18b fix lint 2024-07-19 10:56:35 -03:00
greenart7c3
59b3ff1b94 Added sendAndWaitForResponse to Client.kt 2024-07-19 10:56:35 -03:00
greenart7c3
35423e1bb6 change default timeout to 15 seconds 2024-07-19 10:52:36 -03:00
greenart7c3
e125690000 add more logs, add optional listener, fix await call 2024-07-19 10:50:59 -03:00
Vitor Pamplona
eba5ecfd91 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2024-07-19 09:47:04 -04:00
Vitor Pamplona
3e43a75acd Fixes the keyboard overriding parts of the screen 2024-07-19 09:46:58 -04:00
greenart7c3
04bf9c07ac fix lint 2024-07-19 08:05:04 -03:00
greenart7c3
ac58f99560 Added sendAndWaitForResponse to Client.kt 2024-07-19 07:58:12 -03:00
Believethehype
df328ff139 Store additional File Metadata in Gallery Entries 2024-07-18 17:53:37 +02:00
Vitor Pamplona
60ba7786a0 Merge pull request #984 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2024-07-18 10:54:01 -04:00
Crowdin Bot
1773ad5a88 New Crowdin translations by GitHub Action 2024-07-18 14:30:23 +00:00
Vitor Pamplona
26faa2bc69 Migrating to edge to edge in preparation for Android 15 2024-07-18 10:28:19 -04:00
2819 changed files with 223875 additions and 77424 deletions

View File

@@ -8,7 +8,8 @@ trim_trailing_whitespace = true
max_line_length = 5000
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ktlint_function_naming_ignore_when_annotated_with=Composable, Test
ktlint_function_naming_ignore_when_annotated_with=Composable
ktlint_standard_no-unused-imports=enabled
[*.gradle]
indent_size = 4

View File

@@ -24,9 +24,13 @@ A clear and concise description of what you expected to happen.
If applicable, add a video and/or screenshots to help explain your problem.
**Device (please complete the following information):**
- Phone Brand/Model [e.g. Pixel 7 Pro]:
- Android Version [e.g. 33]:
- App Version [e.g. v0.20.3]:
- Phone Brand/Model [e.g. Pixel 7 Pro]:
- Android Version [e.g. 34]:
- App Version [e.g. v0.94.3]:
- App Flavour [e.g. Google Play or FDroid]:
- Amber Version (if using it to sign):
**Bounty (in Bitcoin sats) offered for a solution**
Incentivize developers to work on your issue. Describe clear milestones to claim payment.
The size of the bounty is proportional to how much this matters to you. If no bounty is offered,
not even a small one, this bug will not be worked on because it doesn't matter to you. We prioritize
bug fixing to issues that have bounties, even small ones.

View File

@@ -7,14 +7,10 @@ assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Bounty (in Bitcoin sats) offered for the implementation**
Incentivize developers to work on your feature. Describe clear milestones to claim payment.
**Additional context**
Add any other context, video, or screenshots about the feature request here.
The size of the bounty is proportional to how much this matters to you. If no bounty is offered,
not even a small one, this feature will not be coded because it doesn't actually matter to you.
We prioritize feature development by its bounty size compared to how much work is required to get it done.

View File

@@ -15,11 +15,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
java-version: 21
- name: Cache gradle
uses: actions/cache@v4
@@ -40,17 +40,44 @@ jobs:
if: ${{ always() }} # IMPORTANT: run Android Test Report regardless
- name: Build APK (gradle)
run: ./gradlew assembleDebug --no-daemon
run: ./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v2
- name: Upload Play APK
uses: actions/upload-artifact@v4
with:
name: Debug APK
path: amethyst/build/outputs/apk/debug/app-debug.apk
name: Play Debug APK
path: amethyst/build/outputs/apk/play/debug/amethyst-play-universal-debug.apk
- name: Upload FDroid APK
uses: actions/upload-artifact@v4
with:
name: FDroid Debug APK
path: amethyst/build/outputs/apk/fdroid/debug/amethyst-fdroid-universal-debug.apk
- name: Build APK (gradle)
run: ./gradlew assembleBenchmark
- name: Upload Play APK Benchmark
uses: actions/upload-artifact@v4
with:
name: Play Benchmark APK
path: amethyst/build/outputs/apk/play/benchmark/amethyst-play-universal-benchmark.apk
- name: Upload FDroid APK Benchmark
uses: actions/upload-artifact@v4
with:
name: FDroid Benchmark APK
path: amethyst/build/outputs/apk/fdroid/benchmark/amethyst-fdroid-universal-benchmark.apk
- name: Upload Compose Reports
uses: actions/upload-artifact@v4
with:
name: Compose Reports
path: amethyst/build/compose_compiler
- name: Upload Test Results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Build Reports
name: Test Reports
path: amethyst/build/reports

View File

@@ -12,11 +12,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
java-version: 21
- name: Cache gradle
uses: actions/cache@v4
@@ -39,7 +39,7 @@ jobs:
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Sign AAB (F-Droid)
uses: r0adkll/sign-android-release@v1
@@ -51,7 +51,7 @@ jobs:
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Build APK
run: ./gradlew assembleRelease --stacktrace
@@ -66,7 +66,7 @@ jobs:
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Sign APK (F-Droid)
uses: r0adkll/sign-android-release@v1
@@ -78,7 +78,7 @@ jobs:
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0"
BUILD_TOOLS_VERSION: "36.0.0"
- name: Create Release
id: create_release
@@ -228,3 +228,11 @@ jobs:
asset_path: amethyst/build/outputs/bundle/fdroidRelease/amethyst-fdroid-release.aab
asset_name: amethyst-fdroid-${{ github.ref_name }}.aab
asset_content_type: application/zip
- name: Publish Quartz Lib
run: ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}

View File

@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v4
- name: crowdin action
uses: crowdin/github-action@v1
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: true

6
.gitignore vendored
View File

@@ -13,6 +13,12 @@
/.idea/ktfmt.xml
/.idea/studiobot.xml
/.idea/other.xml
/.idea/runConfigurations.xml
/.idea/ChatHistory_schema_v2.xml
/.idea/artifacts/*
/.idea/kotlinNotebook.xml
/.idea/ChatHistory_schema_v3.xml
/.idea/markdown.xml
.DS_Store
/build
/captures

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

13
.idea/deviceManager.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

1
.idea/icon.svg generated Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 170.16 186.2"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}</style><linearGradient id="linear-gradient" x1="80.19" y1="57.17" x2="91.66" y2="57.17" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#652d80"/><stop offset="1" stop-color="#2598cf"/></linearGradient><linearGradient id="linear-gradient-2" x1="335.87" y1="297.64" x2="506.02" y2="297.64" xlink:href="#linear-gradient"/></defs><title>Amethyst_svg_monogram</title><ellipse class="cls-1" cx="85.93" cy="57.17" rx="5.74" ry="5.64"/><path class="cls-2" d="M506,347.08c-.2-.61-46.85-111.69-59.68-142.5-15.62,0-48.61,0-71.88,0-6.83,16.89-24.46,59.94-38.6,94.62,7.75,18.49,14.71,35.1,20.08,47.91l29.27,0a2,2,0,0,0,1.87-2.35c-.25-5.91-16.34-27.39-11.54-50.63,1.28-5,2.26-10.08,3.66-15.06a175.77,175.77,0,0,1,13.44-34.8c.89-1.69,1.3-2.45,3.35-1.17,4.37,2.72,9.25,2.56,14.11,2.21,4.19-.91,6.23-2.92,13.91-.88,1.52,0,3,0,4.55.14,3.69.29,7.15,1.17,9.37,4.51,2.42,3.65,2.81,7.78,2.42,12-.59,6.31-.17,12.19,5.17,16.64a57.52,57.52,0,0,0,6,4c2.65,1.7,5.85,2.44,8.12,4.8,1.34,1.39,2.13,2.87,1.55,4.85s-2.13,2.91-4.17,3.13c-5.16.56-10.2-.49-15.27-1.1-.66-.08-1.31-.13-2-.17a11.47,11.47,0,0,1-3.81.13l-1.19,0a26.7,26.7,0,0,0-5.9,1.41c-4.78,1.74-9.13,3.66-14.77,3.56a4.32,4.32,0,0,0-2.05.89c-4.42,3.93-7.08,8.89-4.87,16.14,6.06,16.93,21.61,57.77,28.29,75.4-.19-11.94-.24-33.32-.28-43.7C461.83,347.1,502.74,347.08,506,347.08Z" transform="translate(-335.87 -204.54)"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,6 +1,34 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
@@ -9,6 +37,10 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
@@ -33,6 +65,10 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />

6
.idea/kotlinNotebook.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinNotebookOptionsProvider">
<option name="shouldAddProjectLibrariesToClasspath" value="true" />
</component>
</project>

8
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JsCompilerArguments">
<option name="moduleKind" value="plain" />
</component>
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
<option name="version" value="2.1.0" />
</component>
</project>

6655
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,33 +4,39 @@
Effective as of Jun 12, 2023
The Amethyst app for Android does not collect or process any personal information from its users. The app is used to connect to third-party Nostr servers (also called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
The Amethyst app for Android does not collect or process any personal information from its users.
The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts are only stored locally on the device when it's required for the functionality and performance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
The app is used to browse third-party Nostr servers (called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
Amethyst offers a few options to upload pictures and videos in order to post them online. You can choose the server at your discretion. Similar to relays, such services are independent of the app and have their own privacy policy and terms of use.
The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts is only stored locally on the device when it's required for the functionality and performance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
### Privacy with Relays services
Amethyst offers several options for uploading pictures and videos to post online. You can choose the server at your discretion. Similar to relays, such services are independent of the app and have their own privacy policy and terms of use.
Your internet protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
### Privacy with Relay services
Your Internet Protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g., a VPN) from trackers online.
The relay can also see which public keys you are using and what information you are requesting from the network. Your public key is tied to your IP address and your relay filters.
Relays have all your data in raw text. They know your IP, your name, your location (guessed from IP), your pub key, all your contacts, and other relays, and can read every action you do (post, like, boost, quote, report, etc) with the exception of the content inside Private Zaps and Private DMs.
While the content of direct messages (DMs) is only visible to you, and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link on the DM text. Your uploaded pictures are available to anyone with that direct link.
While the content of direct messages (DMs) is only visible to you and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link into the DM text. Your uploaded pictures are available to anyone with that direct link.
### Visibility & Permanence of Your Content on Nostr Relays
#### Information Visibility
Content that you share can be shared with other relays by any user of the network.
Information that you share is publicly visible to anyone reading from relays that have your information. Your information may also be visible to Nostr users who do not share relays with you.
The information you share is publicly visible to anyone reading from relays that have access to your information. Your information may also be visible to Nostr users who do not share relays with you.
#### Information Permanence
Information shared on Nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted.
## Child safety standards
Amethyst does not knowingly collect information from children. The app has no age verification because it collects no personal information from anyone. The application is 17+. We rely on Google Play's age verification to make sure the user downloading the app is an adult. Since we do not control which relays the user connects to, there is no content moderation beyond the standard block post, block account, and report post and/or account that will hide the content from the user.
## Terms of Use
### For versions downloaded from Google's Play Store

139
README.md
View File

@@ -1,4 +1,4 @@
<div align="center">
<div align="center">
<a href="https://amethyst.social">
<img src="./docs/design/3rd%20Logo%20-%20Zitron/amethyst.svg" alt="Amethyst Logo" title="Amethyst logo" width="80"/>
@@ -17,17 +17,18 @@ Join the social network you control.
[![JitPack version](https://jitpack.io/v/vitorpamplona/amethyst.svg)](https://jitpack.io/#vitorpamplona/amethyst)
[![CI](https://img.shields.io/github/actions/workflow/status/vitorpamplona/amethyst/build.yml?labelColor=27303D)](https://github.com/vitorpamplona/amethyst/actions/workflows/build.yml)
[![License: Apache-2.0](https://img.shields.io/github/license/vitorpamplona/amethyst?labelColor=27303D&color=0877d2)](/LICENSE)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/vitorpamplona/amethyst)
## Download and Install
[<img src="./docs/design/zapstore.svg"
alt="Get it on Zap Store"
height="70">](https://github.com/zapstore/zapstore/releases)
[<img src="./docs/design/obtainium.png"
alt="Get it on Obtaininum"
height="70">](https://github.com/ImranR98/Obtainium)
[<img src="https://github.com/machiav3lli/oandbackupx/raw/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png" alt="Get it on GitHub"
height="70">](https://github.com/vitorpamplona/amethyst/releases)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="70">](https://f-droid.org/packages/com.vitorpamplona.amethyst/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="70">](https://play.google.com/store/apps/details?id=com.vitorpamplona.amethyst)
@@ -61,7 +62,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [x] Bech Encoding support (NIP-19)
- [x] Command Results (NIP-20)
- [x] URI Support (NIP-21)
- [x] Long-form Content (NIP-23)
- [x] Long-form Content (NIP-23) (view only)
- [x] User Profile Fields / Relay list (NIP-24)
- [x] Reactions (NIP-25)
- [ ] Delegated Event Signing (NIP-26, Will not implement)
@@ -105,7 +106,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [x] Video Events (NIP-71)
- [x] Moderated Communities (NIP-72)
- [ ] Zap Goals (NIP-75)
- [ ] Arbitrary Custom App Data (NIP-78)
- [x] Arbitrary Custom App Data (NIP-78)
- [x] Highlights (NIP-84)
- [x] Notify Request (NIP-88/Draft)
- [x] Recommended Application Handlers (NIP-89)
@@ -134,7 +135,8 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [ ] Signed Filters (NIP-xx/Draft)
- [ ] Key Migration (NIP-xx/Draft)
- [ ] Time-based Sync (NIP-xx/Draft)
- [ ] Image/Video Capture in the app
- [x] Image Capture in the app
- [x] Video Capture in the app
- [ ] Local Database
- [ ] Workspaces
- [ ] Infinity Scroll
@@ -173,7 +175,7 @@ Lastly, the user's account information (private key/pub key) is stored in the An
## Setup
Make sure to have the following pre-requisites installed:
1. Java 17+
1. Java 21+
2. Android Studio
3. Android 8.0+ Phone or Emulation setup
@@ -235,26 +237,125 @@ openssl base64 < <my-release-key.keystore> | tr -d '\n' | tee some_signing_key.j
## Using the Quartz library
Setup [JitPack.io](https://jitpack.io/#vitorpamplona/amethyst/v0.84.3) to your build file
### Installing
Add `maven { url 'https://jitpack.io' }` to settings.gradle at the end of repositories:
Add the following line to your `commonMain` dependencies:
```gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
implementation('com.vitorpamplona.quartz:quartz:<Amethyst Version>')
```
Variations to each platform are also available:
```gradle
implementation('com.vitorpamplona.quartz:quartz-android:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-jvm:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-iosarm64:<Amethyst Version>')
implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:<Amethyst Version>')
```
### How to use
Manage logged in users with the `KeyPair` class
```kt
val keyPair = KeyPair() // creates a random key
val keyPair = KeyPair("hex...".hexToByteArray())
val keyPair = KeyPair("nsec1...".bechToBytes())
val keyPair = KeyPair(Nip06().privateKeyFromMnemonic("<mnemonic>"))
val readOnly = KeyPair(pubKey = "hex...".hexToByteArray())
val readOnly = KeyPair(pubKey = "npub1...".bechToBytes())
```
Create signers that can be Internal, when you have the private key or a read-only public key,
or External, when it is controlled by Amber in NIP-55.
Use either the `NostrSignerInternal` or `NostrSignerExternal` class:
```kt
val signer = NostrSignerInternal(keyPair)
val amberSigner = NostrSignerExternal(
pubKey = keyPair.pubKey.toHexKey(),
packageName = signerPackageName, // Amber package name
contentResolver = appContext.contentResolver,
)
```
Create a single NostrClient for the entire application and control which relays it will access by
registering subscriptions and sending events. The pool will automatically changed based on filters +
outbox events.
You will need a coroutine scope to process events and if you are using OKHttp, we offer a basic
wrapper to create the socket connections themselves.
```kt
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
val rootClient = OkHttpClient.Builder().build()
val socketBuilder = BasicOkHttpWebSocket.Builder { url -> rootClient }
val client = NostrClient(socketBuilder, appScope)
```
If you want to auth, given a logged-in `signer`:
```kt
val authCoordinator = RelayAuthenticator(client, appScope) { challenge, relay ->
val authedEvent = RelayAuthEvent.create(relay.url, challenge, signer)
client.sendIfExists(authedEvent, relay.url)
}
```
Add the dependency
To make a request subscription simply do:
```gradle
implementation('com.github.vitorpamplona.amethyst:quartz:v0.85.1')
```kt
val metadataSub = client.req(
relays = listOf("wss://nos.lol", "wss://nostr.mom"),
filters = Filter(
kinds = listOf(MetadataEvent.KIND),
authors = listOf(signer.pubkey)
)
) { event ->
/* consume event */
}
```
The client will add the relay to the pool, connect to it and start receiving events. The
`metadataSub` will be active until you call `metadataSub.close()`. If the client disconnects
and reconnects, the sub will be active again.
To manage subscriptions that change over time, the simplest approach is to build mutable
subscriptions with a filters lambda that you can change at will.
```kt
val metadataSub = client.req(
filters = {
// Let's say you have a list of users that need to be rendered
val users = pubkeysSeeingInTheScreen()
// And a cache repository with their outbox relays
val outboxRelays = outboxRelays(users)
val filters = listOf(
Filter(
kinds = listOf(MetadataEvent.KIND),
authors = users
)
)
outboxRelays.associateWith { filters }
}
) { event ->
/* consume event */
}
```
In that way, you can simply call `metadataSub.updateFilter()` when you need to update
subscriptions to all relays. Or call `metadataSub.close()` to stop the sub
without deleting it.
When your app goes to the background, you can use NostrClient's `connect` and `disconnect`
methods to stop all communication to relays. Add the `connect` to your `onResume` and `disconnect`
to `onPause` methods.
## Contributing
Issues can be logged on: [https://gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst)

View File

@@ -5,67 +5,105 @@ plugins {
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.googleServices)
alias(libs.plugins.jetbrainsComposeCompiler)
alias(libs.plugins.serialization)
}
def getCurrentBranch() {
try {
def branch = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim()
return branch
} catch (Exception e) {
println "Could not determine git branch: ${e.message}"
return "unknown"
}
}
def generateVersionName(String baseVersion) {
def currentBranch = getCurrentBranch()
if (currentBranch == "main" || currentBranch == "master" || currentBranch == "unknown" || currentBranch == "HEAD") {
return baseVersion
} else {
// Clean branch name for version (replace special characters)
def cleanBranch = currentBranch.replaceAll(/[^a-zA-Z0-9\-_]/, "-")
// Limit branch name to maximum 20 characters
if (cleanBranch.length() > 20) {
cleanBranch = cleanBranch.substring(0, 20)
}
return "${baseVersion}-${cleanBranch}"
}
}
android {
namespace 'com.vitorpamplona.amethyst'
compileSdk libs.versions.android.compileSdk.get().toInteger()
namespace = 'com.vitorpamplona.amethyst'
compileSdk = libs.versions.android.compileSdk.get().toInteger()
defaultConfig {
applicationId "com.vitorpamplona.amethyst"
minSdk libs.versions.android.minSdk.get().toInteger()
targetSdk libs.versions.android.targetSdk.get().toInteger()
versionCode 388
versionName "0.89.2"
buildConfigField "String", "RELEASE_NOTES_ID", "\"1130badb252bdf62aaf93cb65bfa2bb09e7600c7d6764894f2954555b3b73018\""
applicationId = "com.vitorpamplona.amethyst"
minSdk = libs.versions.android.minSdk.get().toInteger()
targetSdk = libs.versions.android.targetSdk.get().toInteger()
versionCode = 429
versionName = generateVersionName("1.04.0")
buildConfigField "String", "RELEASE_NOTES_ID", "\"3a03c75d85aaf6b181d3b232d064c4d4feea5c73f0bea2bd91ed61b8da7cd6a6\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
useSupportLibrary = true
}
resourceConfigurations += [
'ar',
'ar-rSA',
'bn-rBD',
'cs',
'cs-rCZ',
'cy-rGB',
'da-rDK',
'de',
'de-rDE',
'el-rGR',
'en-rGB',
'eo',
'eo-rUY',
'es',
'es-rES',
'es-rMX',
'es-rUS',
'et-rEE',
'fa',
'fa-rIR',
'fi-rFI',
'fo-rFO',
'fr',
'fr-rCA',
'fr-rFR',
'gu-rIN',
'hi-rIN',
'hr-rHR',
'hu',
'hu-rHU',
'in',
'in-rID',
'it-rIT',
'iw-rIL',
'ja',
'ja-rJP',
'kk-rKZ',
'ko-rKR',
'ks-rIN',
'ku-rTR',
'lt-rLT',
'ne-rNP',
'night',
'nl',
'nl-rBE',
'nl-rNL',
'pcm-rNG',
'pl-rPL',
'pt-rBR',
'pt-rPT',
'ru',
'ru-rRU',
'ru-rUA',
'sa-rIN',
'sl-rSI',
@@ -76,9 +114,13 @@ android {
'sw-rKE',
'sw-rTZ',
'ta',
'ta-rIN',
'th',
'th-rTH',
'tr',
'tr-rTR',
'uk',
'uk-rUA',
'ur-rIN',
'uz-rUZ',
'vi-rVN',
@@ -93,8 +135,7 @@ android {
buildTypes {
release {
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), 'proguard-rules.pro'
minifyEnabled true
resValue "string", "app_name", "@string/app_name_release"
minifyEnabled = true
}
debug {
applicationIdSuffix '.debug'
@@ -103,8 +144,20 @@ android {
}
create("benchmark") {
initWith(getByName("release"))
profileable true
signingConfig signingConfigs.debug
applicationIdSuffix '.benchmark'
versionNameSuffix '-BENCHMARK'
resValue "string", "app_name", "@string/app_name_benchmark"
profileable = true
signingConfig = signingConfigs.debug
}
}
// TODO: remove this when lightcompressor uses one MP4 parser only
packaging {
resources {
resources.pickFirsts.add('builddef.lst')
resources.pickFirsts.add('META-INF/LICENSE.md')
resources.pickFirsts.add('META-INF/LICENSE-notice.md')
}
}
@@ -123,21 +176,21 @@ android {
splits {
abi {
enable true
enable = true
reset()
include "x86", "x86_64", "arm64-v8a", "armeabi-v7a"
universalApk true
universalApk = true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
buildFeatures {
compose true
buildConfig true
compose = true
buildConfig = true
}
packagingOptions {
@@ -155,14 +208,26 @@ android {
}
}
// TODO: until google merges and unifiedpush updates https://github.com/tink-crypto/tink-java-apps/pull/5
configurations.all {
def tink = "com.google.crypto.tink:tink-android:1.17.0"
resolutionStrategy {
force(tink)
dependencySubstitution {
substitute module('com.google.crypto.tink:tink') using module(tink)
}
}
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
jvmTarget = JvmTarget.JVM_21
}
}
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
dependencies {
@@ -184,9 +249,6 @@ dependencies {
// Navigation
implementation libs.androidx.navigation.compose
// Observe Live data as State
implementation libs.androidx.runtime.livedata
// Material 3 Design
implementation libs.androidx.material3
implementation libs.androidx.material.icons
@@ -199,7 +261,6 @@ dependencies {
implementation libs.androidx.lifecycle.runtime.ktx
implementation libs.androidx.lifecycle.runtime.compose
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.livedata.ktx
// Zoomable images
implementation libs.zoomable
@@ -209,9 +270,11 @@ dependencies {
// Websockets API
implementation libs.okhttp
implementation libs.okhttpCoroutines
// Encrypted Key Storage
implementation libs.androidx.security.crypto.ktx
implementation libs.androidx.datastore.preferences
// view videos
implementation libs.androidx.media3.exoplayer
@@ -228,9 +291,8 @@ dependencies {
implementation libs.coil.gif
// view svgs
implementation libs.coil.svg
// create blurhash
implementation libs.trbl.blurhash
// enables network for coil
implementation libs.coil.okhttp
// Permission to upload pictures:
implementation libs.accompanist.permissions
@@ -252,6 +314,9 @@ dependencies {
// Language picker and Theme chooser
implementation libs.androidx.appcompat
// Dynamically adjust between phone and tablet UI
implementation libs.androidx.window.core.android
// Local model for language identification
playImplementation libs.google.mlkit.language.id
@@ -278,20 +343,37 @@ dependencies {
implementation libs.audiowaveform
// Video compression lib
implementation libs.abedElazizShe.image.compressor
implementation libs.abedElazizShe.video.compressor.fork
// Image compression lib
implementation libs.zelory.video.compressor
implementation libs.zelory.image.compressor
// Cbor for cashuB format
implementation libs.kotlinx.serialization.cbor
// Kotlin serialization for the times where we need the Json tree and performance is not that important.
implementation(libs.kotlinx.serialization.json)
implementation libs.tor.android
implementation libs.jtorctl
testImplementation libs.junit
testImplementation libs.mockk
testImplementation libs.kotlinx.coroutines.test
androidTestImplementation platform(libs.androidx.compose.bom)
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.junit.ktx
androidTestImplementation libs.androidx.espresso.core
androidTestImplementation libs.androidx.ui.test.junit4
androidTestImplementation libs.mockk.android
debugImplementation platform(libs.androidx.compose.bom)
debugImplementation libs.androidx.ui.tooling
debugImplementation libs.androidx.ui.test.manifest
}
implementation libs.androidx.camera.core
implementation libs.androidx.camera.camera2
implementation libs.androidx.camera.lifecycle
implementation libs.androidx.camera.view
implementation libs.androidx.camera.extensions
}

View File

@@ -2,7 +2,7 @@
"project_info": {
"project_number": "768341258853",
"project_id": "amethyst-3057a",
"storage_bucket": "amethyst-3057a.appspot.com"
"storage_bucket": "amethyst-3057a.firebasestorage.app"
},
"client": [
{
@@ -12,12 +12,7 @@
"package_name": "com.vitorpamplona.amethyst"
}
},
"oauth_client": [
{
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
"client_type": 3
}
],
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE"
@@ -25,12 +20,26 @@
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
"client_type": 3
}
]
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:768341258853:android:3270514bee61de546b8c8c",
"android_client_info": {
"package_name": "com.vitorpamplona.amethyst.benchmark"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
@@ -41,12 +50,7 @@
"package_name": "com.vitorpamplona.amethyst.debug"
}
},
"oauth_client": [
{
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
"client_type": 3
}
],
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE"
@@ -54,12 +58,7 @@
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com",
"client_type": 3
}
]
"other_platform_oauth_client": []
}
}
}

View File

@@ -38,6 +38,8 @@
-keep class com.lambdaworks.crypto.** { *; }
-keep class com.lambdaworks.jni.** { *; }
-keep class info.guardianproject.** { *; }
# JNA also requires AWT, which Android does not have. So the classes are broken down to filter AWT out
-keep class com.sun.jna.ToNativeConverter { *; }
-keep class com.sun.jna.NativeMapped { *; }
@@ -58,10 +60,6 @@
}
# JSON parsing
-keep class com.vitorpamplona.quartz.crypto.** { *; }
-keep class com.vitorpamplona.quartz.encoders.** { *; }
-keep class com.vitorpamplona.quartz.events.** { *; }
-keep class com.vitorpamplona.amethyst.model.** { *; }
-keep class com.vitorpamplona.amethyst.service.** { *; }
-keep class com.vitorpamplona.ammolite.service.** { *; }
-keep class com.vitorpamplona.ammolite.relays.** { *; }
-keep class com.vitorpamplona.quartz.** { *; }
-keep class com.vitorpamplona.amethyst.** { *; }
-keep class com.vitorpamplona.ammolite.** { *; }

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.service.cashu.CashuParser
import com.vitorpamplona.amethyst.service.cashu.CashuToken
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CashuBTest {
val cashuTokenA = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9"
val cashuTokenB1 = "cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA"
val cashuTokenB2 = "cashuBpGFkb01pbmliaXRzIHJ1bGVzIWFteEpodHRwOi8vbGJ1dGxoNWxmZ2dxNXI3eHBpd2hyYWpkbDdzeHB1cGdhZ2F6eGw2NXc0YzVjZzcyd3RvZmFzYWQub25pb246MzMzOGF0gaJhaUgAm7I9OpEuTmFwg6NhYRhAYWNYIQPfWR0mG80XbGnj6DO8q1NIyjHSGGIEkoWTA6H16HTpx2FzeEA3YThkY2Y5YjNlOGEyNDdjZTMzOWU3MzY5ZTliNGExOWYzMWVhY2I2OWQ4YjBjNjVkYWFlYjcyZDFhY2I5YWQzo2FhGCBhY1ghA55SwCFBc46dwnjbkb87Mzo30T2EE9Ws_nemuFneDegGYXN4QDlkODFjMWEyNjE2ODUzYWQ4MDQ5Y2JjZDFjN2MyNDdhZGQ4M2IzNzM4Mjg2MjBiYWMyZmQ3ZjNlNWE1OGFjZWKjYWEEYWNYIQM-yAQQTR2t6pIAmfmGM8Wxy7ajKVLOaUg7TrV8o-EdVWFzeEBmNmViMTI4ZmJlMDM3MTEzZTkzZjM3NjllYTYwMTk1NmY1N2NkZWNhNTYwOGY0NWUzMDhhZDU0ZmQ4YTQxNWVhYXVjc2F0"
@Test()
fun parseCashuA() {
runBlocking {
val parsed = (CashuParser().parse(cashuTokenA) as GenericLoadable.Loaded<ImmutableList<CashuToken>>).loaded[0]
assertEquals(cashuTokenA, parsed.token)
assertEquals("https://8333.space:3338", parsed.mint)
assertEquals(10, parsed.totalAmount)
assertEquals(2, parsed.proofs[0].amount)
assertEquals("407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837", parsed.proofs[0].secret)
assertEquals("009a1f293253e41e", parsed.proofs[0].id)
assertEquals("02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea", parsed.proofs[0].C)
assertEquals(8, parsed.proofs[1].amount)
assertEquals("fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be", parsed.proofs[1].secret)
assertEquals("009a1f293253e41e", parsed.proofs[1].id)
assertEquals("029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059", parsed.proofs[1].C)
}
}
@Test()
fun parseCashuB() =
runBlocking {
val parsed = (CashuParser().parse(cashuTokenB1) as GenericLoadable.Loaded<ImmutableList<CashuToken>>).loaded
assertEquals(cashuTokenB1, parsed[0].token)
assertEquals("http://localhost:3338", parsed[0].mint)
assertEquals(1, parsed[0].totalAmount)
assertEquals(1, parsed[0].proofs[0].amount)
assertEquals("acc12435e7b8484c3cf1850149218af90f716a52bf4a5ed347e48ecc13f77388", parsed[0].proofs[0].secret)
assertEquals("00ffd48b8f5ecf80", parsed[0].proofs[0].id)
assertEquals("0244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cf", parsed[0].proofs[0].C)
assertEquals(3, parsed[1].totalAmount)
assertEquals(2, parsed[1].proofs[0].amount)
assertEquals("1323d3d4707a58ad2e23ada4e9f1f49f5a5b4ac7b708eb0d61f738f48307e8ee", parsed[1].proofs[0].secret)
assertEquals("00ad268c4d1f5826", parsed[1].proofs[0].id)
assertEquals("023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94d", parsed[1].proofs[0].C)
assertEquals(1, parsed[1].proofs[1].amount)
assertEquals("56bcbcbb7cc6406b3fa5d57d2174f4eff8b4402b176926d3a57d3c3dcbb59d57", parsed[1].proofs[1].secret)
assertEquals("00ad268c4d1f5826", parsed[1].proofs[1].id)
assertEquals("0273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63", parsed[1].proofs[1].C)
}
@Test()
fun parseCashuB2() =
runBlocking {
val parsed = (CashuParser().parse(cashuTokenB2) as GenericLoadable.Loaded<ImmutableList<CashuToken>>).loaded
assertEquals(cashuTokenB2, parsed[0].token)
assertEquals("http://lbutlh5lfggq5r7xpiwhrajdl7sxpupgagazxl65w4c5cg72wtofasad.onion:3338", parsed[0].mint)
assertEquals(100, parsed[0].totalAmount)
assertEquals(64, parsed[0].proofs[0].amount)
assertEquals("7a8dcf9b3e8a247ce339e7369e9b4a19f31eacb69d8b0c65daaeb72d1acb9ad3", parsed[0].proofs[0].secret)
assertEquals("009bb23d3a912e4e", parsed[0].proofs[0].id)
assertEquals("03df591d261bcd176c69e3e833bcab5348ca31d218620492859303a1f5e874e9c7", parsed[0].proofs[0].C)
assertEquals(32, parsed[0].proofs[1].amount)
assertEquals("9d81c1a2616853ad8049cbcd1c7c247add83b373828620bac2fd7f3e5a58aceb", parsed[0].proofs[1].secret)
assertEquals("009bb23d3a912e4e", parsed[0].proofs[1].id)
assertEquals("039e52c02141738e9dc278db91bf3b333a37d13d8413d5acfe77a6b859de0de806", parsed[0].proofs[1].C)
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.service.okhttp.EncryptedBlobInterceptor
import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.utils.ciphers.AESGCM
import okhttp3.OkHttpClient
import okhttp3.Request
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DMFileDecryptionTest {
// Key cache to download and decrypt encrypted files before caching them.
val keyCache = EncryptionKeyCache()
val url = "https://cdn.satellite.earth/812fd4cf9d4d4b59c141ecd6a6c08c7571b5872237ad6477916cb2d119b5cacd"
val cipher =
AESGCM(
"7b184b3849e161027bab1aac9f2d96eb22bfe02c8dc34cad5d2cc436a64f191d".hexToByteArray(),
"3936923383652e3f0d72bfd40568b435".hexToByteArray(),
)
val decryptedSize = 1277122
val expectedMimeType = "video/mp4"
@Test
fun runDownloadAndDecryptVideo() {
val client =
OkHttpClient
.Builder()
.addNetworkInterceptor(EncryptedBlobInterceptor(keyCache))
.build()
keyCache.add(url, cipher, "video/mp4")
val request =
Request
.Builder()
.url(url)
.get()
.build()
client.newCall(request).execute().use {
assertEquals(decryptedSize, it.body.bytes().size)
assertEquals(expectedMimeType, it.body.contentType().toString())
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -23,17 +23,32 @@ package com.vitorpamplona.amethyst
import android.graphics.Bitmap
import android.graphics.Color
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.Nip96MediaServers
import com.vitorpamplona.amethyst.service.Nip96Retriever
import com.vitorpamplona.amethyst.service.Nip96Uploader
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import com.vitorpamplona.quartz.crypto.KeyPair
import junit.framework.TestCase.assertEquals
import androidx.test.platform.app.InstrumentationRegistry
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState
import com.vitorpamplona.amethyst.service.okhttp.DefaultContentTypeInterceptor
import com.vitorpamplona.amethyst.service.uploads.FileHeader
import com.vitorpamplona.amethyst.service.uploads.ImageDownloader
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
import com.vitorpamplona.amethyst.service.uploads.nip96.ServerInfoRetriever
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent
import com.vitorpamplona.quartz.utils.sha256.sha256
import junit.framework.TestCase.fail
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,82 +57,144 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
class ImageUploadTesting {
private suspend fun testBase(server: Nip96MediaServers.ServerName) {
val serverInfo =
Nip96Retriever()
.loadInfo(
server.baseUrl,
)
companion object {
val accountSettings = AccountSettings(KeyPair())
val signer = NostrSignerInternal(accountSettings.keyPair)
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val cache = LocalCache
val blossomServerListState =
BlossomServerListState(
signer = signer,
cache = cache,
scope = scope,
settings = accountSettings,
)
}
val client =
OkHttpClient
.Builder()
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor(DefaultContentTypeInterceptor("Amethyst/${BuildConfig.VERSION_NAME}"))
.build()
private suspend fun getBitmap(): ByteArray {
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
for (x in 0 until bitmap.width) {
for (y in 0 until bitmap.height) {
bitmap.setPixel(x, y, Color.rgb(Random.nextInt(), Random.nextInt(), Random.nextInt()))
}
}
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos)
val bytes = baos.toByteArray()
val inputStream = bytes.inputStream()
return baos.toByteArray()
}
val account = Account(KeyPair())
private suspend fun testBase(server: ServerName) {
if (server.type == ServerType.NIP96) {
testNip96(server)
} else {
testBlossom(server)
}
}
private suspend fun testBlossom(server: ServerName) {
val paylod = getBitmap()
val initialHash = sha256(paylod).toHexKey()
val inputStream = paylod.inputStream()
val result =
Nip96Uploader(account)
.uploadImage(
inputStream,
bytes.size.toLong(),
"image/png",
BlossomUploader()
.upload(
inputStream = inputStream,
hash = initialHash,
length = paylod.size,
baseFileName = "filename.png",
contentType = "image/png",
alt = null,
sensitiveContent = null,
serverInfo,
onProgress = {},
serverBaseUrl = server.baseUrl,
okHttpClient = { client },
httpAuth = blossomServerListState::createBlossomUploadAuth,
context = InstrumentationRegistry.getInstrumentation().targetContext,
)
val url = result.tags!!.first { it[0] == "url" }.get(1)
val size =
result.tags!!
.firstOrNull { it[0] == "size" }
?.get(1)
?.ifBlank { null }
val dim =
result.tags!!
.firstOrNull { it[0] == "dim" }
?.get(1)
?.ifBlank { null }
val hash =
result.tags!!
.firstOrNull { it[0] == "x" }
?.get(1)
?.ifBlank { null }
val contentType = result.tags!!.first { it[0] == "m" }.get(1)
val ox = result.tags!!.first { it[0] == "ox" }.get(1)
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
assertEquals("image/png", result.type)
assertEquals(paylod.size.toLong(), result.size)
assertEquals(initialHash, result.sha256)
assertEquals("${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
val imageData: ByteArray =
ImageDownloader().waitAndGetImage(url)
ImageDownloader().waitAndGetImage(result.url!!, { client })?.bytes
?: run {
fail("${server.name}: Should not be null")
return
}
FileHeader.prepare(
imageData,
"image/png",
null,
onReady = {
val downloadedHash = sha256(imageData).toHexKey()
assertEquals(initialHash, downloadedHash)
}
private suspend fun testNip96(server: ServerName) {
val serverInfo =
ServerInfoRetriever()
.loadInfo(
server.baseUrl,
{ client },
)
val payload = getBitmap()
val inputStream = payload.inputStream()
val result =
Nip96Uploader()
.upload(
inputStream = inputStream,
length = payload.size.toLong(),
contentType = "image/png",
alt = null,
sensitiveContent = null,
server = serverInfo,
okHttpClient = { client },
onProgress = {},
httpAuth = { url, method, body ->
signer.sign(HTTPAuthorizationEvent.build(url, method, body))
},
context = InstrumentationRegistry.getInstrumentation().targetContext,
)
val url = result.url!!
val size = result.size?.toInt()
val dim = result.dimension
val hash = result.sha256
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
val imageData: ByteArray =
ImageDownloader().waitAndGetImage(url, { client })?.bytes
?: run {
fail("${server.name}: Should not be null")
return
}
val prepared =
FileHeader.prepare(
imageData,
"image/png",
null,
)
prepared.fold(
onSuccess = {
if (dim != null) {
// assertEquals("${server.name}: Invalid dimensions", it.dim, dim)
assertEquals("${server.name}: Invalid dimensions", it.dim.toString(), dim.toString())
}
if (size != null) {
// assertEquals("${server.name}: Invalid size", it.size.toString(), size)
}
if (hash != null) {
assertEquals("${server.name}: Invalid hash", it.hash, hash)
assertEquals("${server.name}: Invalid size", it.size, size)
}
},
onError = { fail("${server.name}: It should not fail") },
onFailure = { fail("${server.name}: It should not fail") },
)
// delay(1000)
@@ -128,60 +205,84 @@ class ImageUploadTesting {
@Test
fun runTestOnDefaultServers() =
runBlocking {
Nip96MediaServers.DEFAULT.forEach {
testBase(it)
DEFAULT_MEDIA_SERVERS.forEach {
// skip paid servers and primal server is buggy.
if (!it.name.contains("Paid") && !it.name.contains("Primal")) {
testBase(it)
}
}
}
@Test()
fun testNostrCheck() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostrcheck.me", "https://nostrcheck.me"))
testBase(ServerName("nostrcheck.me", "https://nostrcheck.me", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testNostrage() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostrage", "https://nostrage.com"))
testBase(ServerName("nostrage", "https://nostrage.com", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testSove() =
runBlocking {
testBase(Nip96MediaServers.ServerName("sove", "https://sove.rent"))
testBase(ServerName("sove", "https://sove.rent", ServerType.NIP96))
}
@Ignore("Not Working anymore")
@Test()
fun testNostrBuild() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostr.build", "https://nostr.build"))
testBase(ServerName("nostr.build", "https://nostr.build", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testSovbit() =
runBlocking {
testBase(Nip96MediaServers.ServerName("sovbit", "https://files.sovbit.host"))
}
@Test()
fun testVoidCat() =
runBlocking {
testBase(Nip96MediaServers.ServerName("void.cat", "https://void.cat"))
testBase(ServerName("sovbit", "https://files.sovbit.host", ServerType.NIP96))
}
@Test()
@Ignore("Returns invalid image size")
fun testNostrPic() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostpic.com", "https://nostpic.com"))
testBase(ServerName("nostpic.com", "https://nostpic.com", ServerType.NIP96))
}
@Test(expected = RuntimeException::class)
fun testSprovoostNl() =
runBlocking {
testBase(ServerName("sprovoost.nl", "https://img.sprovoost.nl/", ServerType.NIP96))
}
@Test()
@Ignore("Not Working anymore")
fun testNostrOnch() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostr.onch.services", "https://nostr.onch.services"))
testBase(ServerName("nostr.onch.services", "https://nostr.onch.services", ServerType.NIP96))
}
@Ignore("Changes sha256")
fun testPrimalBlossom() =
runBlocking {
testBase(ServerName("primal.net", "https://blossom.primal.net", ServerType.Blossom))
}
@Test()
@Ignore("Not Working anymore/ Timeout")
fun testNostrCheckBlossom() =
runBlocking {
testBase(ServerName("nostrcheck", "https://cdn.nostrcheck.me", ServerType.Blossom))
}
@Ignore("Requires Payment")
fun testSatelliteBlossom() =
runBlocking {
testBase(ServerName("satellite", "https://cdn.satellite.earth", ServerType.Blossom))
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -21,20 +21,17 @@
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.OtsEvent
import com.vitorpamplona.quartz.ots.OpenTimestamps
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import junit.framework.TestCase.assertEquals
import org.junit.Assert
import org.junit.Before
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpOtsResolverBuilder
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
class OkHttpOtsTest {
@@ -43,53 +40,53 @@ class OkHttpOtsTest {
val otsPendingEvent = "{\"id\":\"12fa15ad4b4cf9dc5940389325b69b93c5c1f59c049c701ee669b275299fdaf1\",\"pubkey\":\"dcaa6c8a2f47b6fef4a34b20e8843c59dbe7c5f07a402338c09fd147dd01d22b\",\"created_at\":1708877521,\"kind\":1040,\"tags\":[[\"e\",\"a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6\"],[\"alt\",\"Opentimestamps Attestation\"]],\"content\":\"AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIqGNPU2jhd4no+zg2ytDkuf5PIoivr8KHI8BL68aKGNbwELidvzr0usf55CkpKf6OABQI//AQK3sWd2tq+7KO8YNJIARJugjxBGXbZtPwCL0H4/7GL5+SAIPf4w0u+QyOLCtodHRwczovL2JvYi5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFtcHMub3Jn//AQPDZsJgN1TnJXoUzlsgo93wjwIIfBc7LUqkCbC1BLZRZ+6LXztK50UdH5xe7fn40bupkrCPEEZdtm0/AI0CADXN5ZIncAg9/jDS75DI4uLWh0dHBzOi8vYWxpY2UuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ/AQcELcSrE04cuGKlZQf2LeVwjwILUDSf9vK2GaefKTpn/LV2oUsQaA5WbqaP3C+1ZxQfRNCPEEZdtm0/AIbCtb+yRXFqUAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2FsbC5jb20=\",\"sig\":\"f6854c0228c15c08aeb70bbabe9ed87bbb7289fab31b13cabac15138bb71179553e06080b83f4a813fbdaf614f63293beea3fc73fe865da6551193fa4d38de04\"}"
val otsEvent2Digest = "a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6"
val otsCache = OtsBlockHeightCache()
@Before
fun setup() {
OtsEvent.otsInstance = OpenTimestamps(OkHttpBlockstreamExplorer(), OkHttpCalendarBuilder())
}
val resolver =
OkHttpOtsResolverBuilder(
otsCache,
) { OkHttpClient.Builder().build() }.build()
@Test
fun verifyNostrEvent() {
val ots = Event.fromJson(otsEvent) as OtsEvent
println(ots.info())
assertEquals(1707688818L, ots.verify())
}
@Test
fun verifyNostrEvent2() {
val ots = Event.fromJson(otsEvent2) as OtsEvent
println(ots.info())
assertEquals(1706322179L, ots.verify())
}
@Test
fun verifyNostrPendingEvent() {
val ots = Event.fromJson(otsPendingEvent) as OtsEvent
println(ots.info())
assertEquals(null, ots.verify())
}
@Test
fun createOTSEventAndVerify() {
val signer = NostrSignerInternal(KeyPair())
var ots: OtsEvent? = null
val countDownLatch = CountDownLatch(1)
val otsFile = OtsEvent.stamp(otsEvent2Digest)
OtsEvent.create(otsEvent2Digest, otsFile, signer) {
ots = it
countDownLatch.countDown()
fun verifyNostrEvent() =
runBlocking {
val ots = OptimizedJsonMapper.fromJson(otsEvent) as OtsEvent
println(resolver.info(ots.otsByteArray()))
assertEquals(1707688818L, ots.verify(resolver))
}
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
@Test
fun verifyNostrEvent2() =
runBlocking {
val ots = OptimizedJsonMapper.fromJson(otsEvent2) as OtsEvent
println(resolver.info(ots.otsByteArray()))
assertEquals(1706322179L, ots.verify(resolver))
}
println(ots!!.toJson())
println(ots!!.info())
@Test
fun verifyNostrPendingEvent() =
runBlocking {
val ots = OptimizedJsonMapper.fromJson(otsPendingEvent) as OtsEvent
println(resolver.info(ots.otsByteArray()))
assertEquals(null, ots.verify(resolver))
}
// Should not be valid because we need to wait for confirmations
assertEquals(null, ots!!.verify())
}
@Test
fun createOTSEventAndVerify() =
runBlocking {
val signer = NostrSignerInternal(KeyPair())
val otsFile = OtsEvent.stamp(otsEvent2Digest, resolver)
val ots =
runBlocking {
signer.sign(OtsEvent.build(otsEvent2Digest, otsFile))
}
println(ots.toJson())
println(resolver.info(ots.otsByteArray()))
// Should not be valid because we need to wait for confirmations
assertEquals(null, ots.verify(resolver))
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import com.vitorpamplona.amethyst.ui.navigation.findParameterValue
import org.junit.Assert.assertEquals
import org.junit.Test
import java.net.URI
class URIParserTest {
@Test
fun testNEventWithAccount() {
val test = "nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
val testUri = URI(test)
// assertEquals("nostr", testUri.scheme)
assertEquals(null, testUri.authority)
assertEquals(null, testUri.host)
assertEquals(-1, testUri.port)
assertEquals(null, testUri.userInfo)
assertEquals("nevent1qqsp9wg5r3vkuv5al0459h3h44yxh6r0y7chgvu3pkdxhcj99t3msrgpzdmhxue69uhhwmm59e6hg7r09ehkuef0qgsdhcrqt2w8x9et446j8ge8kgmd2h4ykc6wsrnc4yqnmdu3lr74ktqrqsqqqqqp578kku", testUri.path)
assertEquals(null, testUri.fragment)
assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
}
@Test
fun testNotificationsWithAccount() {
val test = "notifications?account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
val testUri = URI(test)
// assertEquals("nostr", testUri.scheme)
assertEquals(null, testUri.authority)
assertEquals(null, testUri.host)
assertEquals(-1, testUri.port)
assertEquals(null, testUri.userInfo)
assertEquals("notifications", testUri.path)
assertEquals(null, testUri.fragment)
assertEquals("account=npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.rawQuery)
assertEquals("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z", testUri.findParameterValue("account"))
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -27,9 +27,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
import com.vitorpamplona.quartz.encoders.decodePublicKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

View File

@@ -1,4 +1,5 @@
<resources>
<string name="app_name_release">Amethyst</string>
<string name="app_name_debug">Amethyst Debug</string>
<string name="app_name">Amethyst</string>
<string name="app_name_debug">Amy Debug</string>
<string name="app_name_benchmark">Amy Benchmark</string>
</resources>

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -27,6 +27,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CancellationException
@RunWith(AndroidJUnit4::class)
class TranslationsTest {
@@ -124,7 +125,10 @@ class TranslationsTest {
"Have you seen this: lnbc12u1p3lvjeupp5a5ecgp45k6pa8tu7rnkgzfuwdy3l5ylv3k5tdzrg4cr8rj2f364sdq5g9kxy7fqd9h8vmmfvdjscqzpgxqyz5vqsp5zuzyetf33aphetf0e80w7tztw6dfsjs4lmvya4cyk8umfsx00qts9qyyssqke9hphcr36zvcav8wr502g0mhfhxpy8m9tt36zttg8vldm2qxw039ulccr8nwy3hjg2sw5vk65e99lwuhrhw0nuya2u57qszltvx7egp74jydn I think I have to pay",
"pt",
)
}
@Test(expected = CancellationException::class)
fun testTranslationLnInvoice2() {
assertTranslateContains(
"lnbc10u1p3l0wg0pp5y5y3vxt3429m28uuq56uqhwxadftn67yaarq06h3y9nqapz72n6sdqqxqyjw5q9q7sqqqqqqqqqqqqqqqqqqqqqqqqq9qsqsp5y2tazp42xde3c0tdsz30zqcekrt0lzrneszdtagy2qn7vs0d3p5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glcll7jdvcln4lhw5qqqqlgqqqqqeqqjqdau9jzseecmvmh03h88xyf5f980xx45fmn0cej654v5jr79ye36pww90jwdda38damlmgt54v8rn6q9kywtw057rh4v3wwrmn8fajagqnssr7v",
"Test lnbc10u1p3l0wg0pp5y5y3vxt3429m28uuq56uqhwxadftn67yaarq06h3y9nqapz72n6sdqqxqyjw5q9q7sqqqqqqqqqqqqqqqqqqqqqqqqq9qsqsp5y2tazp42xde3c0tdsz30zqcekrt0lzrneszdtagy2qn7vs0d3p5qrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glcll7jdvcln4lhw5qqqqlgqqqqqeqqjqdau9jzseecmvmh03h88xyf5f980xx45fmn0cej654v5jr79ye36pww90jwdda38damlmgt54v8rn6q9kywtw057rh4v3wwrmn8fajagqnssr7v",
@@ -132,7 +136,7 @@ class TranslationsTest {
)
}
@Test
@Test(expected = CancellationException::class)
fun testNostrEvents() {
assertTranslateContains(
"nostr:nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhql22rcy",
@@ -162,4 +166,15 @@ class TranslationsTest {
"pt",
)
}
@Test
fun testHttp() {
val text = "https://m.primal.net/MdDd.png \nRunning... \uD83D\uDE01 nostr:npub126ntw5mnermmj0znhjhgdk8lh2af72sm8qfzq48umdlnhaj9kuns3le9ll nostr:npub1getal6ykt05fsz5nqu4uld09nfj3y3qxmv8crys4aeut53unfvlqr80nfm"
assertTranslateContains(
"https://m.primal.net/MdDd.png",
text,
"pt",
)
}
}

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".Amethyst">

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.service.notifications
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.quartz.utils.Log
import org.unifiedpush.android.connector.UnifiedPush
interface PushDistributorActions {
@@ -38,13 +38,11 @@ interface PushDistributorActions {
}
object PushDistributorHandler : PushDistributorActions {
private val appContext = Amethyst.instance.applicationContext
private val unifiedPush: UnifiedPush = UnifiedPush
private var endpointInternal = ""
val endpoint = endpointInternal
fun getSavedEndpoint() = endpoint
fun getSavedEndpoint() = endpointInternal
fun setEndpoint(newEndpoint: String) {
endpointInternal = newEndpoint
@@ -55,11 +53,13 @@ object PushDistributorHandler : PushDistributorActions {
endpointInternal = ""
}
override fun getSavedDistributor(): String = unifiedPush.getSavedDistributor(appContext) ?: ""
fun appContext(): Context = Amethyst.instance.appContext
override fun getSavedDistributor(): String = unifiedPush.getSavedDistributor(appContext()) ?: ""
fun savedDistributorExists(): Boolean = getSavedDistributor().isNotEmpty()
override fun getInstalledDistributors(): List<String> = unifiedPush.getDistributors(appContext)
override fun getInstalledDistributors(): List<String> = unifiedPush.getDistributors(appContext())
fun formattedDistributorNames(): List<String> {
val distributorsArray = getInstalledDistributors().toTypedArray()
@@ -69,16 +69,16 @@ object PushDistributorHandler : PushDistributorActions {
try {
val ai =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
appContext.packageManager.getApplicationInfo(
appContext().packageManager.getApplicationInfo(
it,
PackageManager.ApplicationInfoFlags.of(
PackageManager.GET_META_DATA.toLong(),
),
)
} else {
appContext.packageManager.getApplicationInfo(it, 0)
appContext().packageManager.getApplicationInfo(it, 0)
}
appContext.packageManager.getApplicationLabel(ai)
appContext().packageManager.getApplicationLabel(ai)
} catch (e: PackageManager.NameNotFoundException) {
it
}
@@ -88,15 +88,15 @@ object PushDistributorHandler : PushDistributorActions {
}
override fun saveDistributor(distributor: String) {
unifiedPush.saveDistributor(appContext, distributor)
unifiedPush.registerApp(appContext)
unifiedPush.saveDistributor(appContext(), distributor)
unifiedPush.register(appContext())
}
override fun removeSavedDistributor() {
unifiedPush.safeRemoveDistributor(appContext)
unifiedPush.removeDistributor(appContext())
}
fun forceRemoveDistributor(context: Context) {
unifiedPush.forceRemoveDistributor(context)
UnifiedPush.removeDistributor(context)
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -22,44 +22,43 @@ package com.vitorpamplona.amethyst.service.notifications
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.util.Log
import android.util.LruCache
import androidx.core.content.ContextCompat
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.unifiedpush.android.connector.FailedReason
import org.unifiedpush.android.connector.MessagingReceiver
import org.unifiedpush.android.connector.data.PushEndpoint
import org.unifiedpush.android.connector.data.PushMessage
class PushMessageReceiver : MessagingReceiver() {
companion object {
private val TAG = "Amethyst-OSSPushReceiver"
}
private val appContext = Amethyst.instance.applicationContext
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val appContext = Amethyst.instance.appContext
private val scope = Amethyst.instance.applicationIOScope
private val eventCache = LruCache<String, String>(100)
private val pushHandler = PushDistributorHandler
override fun onMessage(
context: Context,
message: ByteArray,
message: PushMessage,
instance: String,
) {
val messageStr = String(message)
Log.d(TAG, "New message ${message.decodeToString()} for Instance: $instance")
val messageStr = message.content.decodeToString()
Log.d(TAG, "New message $messageStr for Instance: $instance")
scope.launch {
try {
parseMessage(messageStr)?.let { receiveIfNew(it) }
parseMessage(messageStr)?.let {
receiveIfNew(it)
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.d(TAG, "Message could not be parsed: ${e.message}")
@@ -83,35 +82,31 @@ class PushMessageReceiver : MessagingReceiver() {
override fun onNewEndpoint(
context: Context,
endpoint: String,
endpoint: PushEndpoint,
instance: String,
) {
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance")
val sanitizedEndpoint = endpoint.dropLast(5)
pushHandler.setEndpoint(sanitizedEndpoint)
scope.launch(Dispatchers.IO) {
RegisterAccounts(LocalPreferences.allSavedAccounts()).go(sanitizedEndpoint)
notificationManager().getOrCreateZapChannel(appContext)
notificationManager().getOrCreateDMChannel(appContext)
val sanitizedEndpoint = if (endpoint.url.endsWith("?up=1")) endpoint.url.dropLast(5) else endpoint.url
if (sanitizedEndpoint != pushHandler.getSavedEndpoint()) {
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance ${pushHandler.getSavedEndpoint()} $sanitizedEndpoint")
pushHandler.setEndpoint(sanitizedEndpoint)
scope.launch(Dispatchers.IO) {
PushNotificationUtils.checkAndInit(sanitizedEndpoint, LocalPreferences.allSavedAccounts()) {
Amethyst.instance.okHttpClients.getHttpClient(Amethyst.instance.torManager.isSocksReady())
}
NotificationUtils.getOrCreateZapChannel(appContext)
NotificationUtils.getOrCreateDMChannel(appContext)
}
} else {
Log.d(TAG, "Same endpoint provided:- $endpoint for Instance: $instance $sanitizedEndpoint")
}
}
override fun onReceive(
context: Context,
intent: Intent,
) {
val intentData = intent.dataString
val intentAction = intent.action.toString()
Log.d(TAG, "Intent Data:- $intentData Intent Action: $intentAction")
super.onReceive(context, intent)
}
override fun onRegistrationFailed(
context: Context,
reason: FailedReason,
instance: String,
) {
Log.d(TAG, "Registration failed for Instance: $instance")
scope.cancel()
pushHandler.forceRemoveDistributor(context)
}
@@ -119,7 +114,7 @@ class PushMessageReceiver : MessagingReceiver() {
context: Context,
instance: String,
) {
val removedEndpoint = pushHandler.endpoint
val removedEndpoint = pushHandler.getSavedEndpoint()
Log.d(TAG, "Endpoint: $removedEndpoint removed for Instance: $instance")
Log.d(TAG, "App is unregistered. ")
pushHandler.forceRemoveDistributor(context)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,25 +20,53 @@
*/
package com.vitorpamplona.amethyst.service.notifications
import android.util.Log
import com.vitorpamplona.amethyst.AccountInfo
import kotlinx.coroutines.CancellationException
import com.vitorpamplona.amethyst.service.retryIfException
import kotlinx.coroutines.Dispatchers
import okhttp3.OkHttpClient
object PushNotificationUtils {
var hasInit: Boolean = false
var lastToken: String? = null
var hasInit: List<AccountInfo>? = null
private val pushHandler = PushDistributorHandler
suspend fun init(accounts: List<AccountInfo>) {
if (hasInit || pushHandler.savedDistributorExists()) {
return
suspend fun checkAndInit(
accounts: List<AccountInfo>,
okHttpClient: (String) -> OkHttpClient,
) = with(Dispatchers.IO) {
if (!pushHandler.savedDistributorExists()) return
val currentDistributor = PushDistributorHandler.getSavedDistributor()
PushDistributorHandler.saveDistributor(currentDistributor)
val token = pushHandler.getSavedEndpoint()
if (hasInit?.equals(accounts) == true && lastToken == token) {
return@with
}
try {
if (pushHandler.savedDistributorExists()) {
RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.")
registerToken(token, accounts, okHttpClient)
}
suspend fun checkAndInit(
token: String,
accounts: List<AccountInfo>,
okHttpClient: (String) -> OkHttpClient,
) = with(Dispatchers.IO) {
// initializes if the accounts are different or if the token has changed
if (hasInit?.equals(accounts) == true && lastToken == token) {
return@with
}
registerToken(token, accounts, okHttpClient)
}
private suspend fun registerToken(
token: String,
accounts: List<AccountInfo>,
okHttpClient: (String) -> OkHttpClient,
) = retryIfException("RegisterAccounts") {
RegisterAccounts(accounts, okHttpClient).go(token)
lastToken = token
hasInit = accounts.toList()
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,7 +20,8 @@
*/
package com.vitorpamplona.amethyst.ui.components
import android.util.Log
import android.Manifest
import android.os.Build
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -36,6 +37,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -44,33 +46,52 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import com.halilibo.richtext.commonmark.MarkdownParseOptions
import com.halilibo.richtext.markdown.BasicMarkdown
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material3.RichText
import com.halilibo.richtext.ui.resolveDefaults
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.UiSettingsFlow
import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckifItNeedsToRequestNotificationPermission
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsRow
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.utils.Log
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesViewModel) {
val notificationPermissionState =
CheckifItNeedsToRequestNotificationPermission(sharedPreferencesViewModel)
fun SelectNotificationProvider(sharedPrefs: UiSettingsFlow) {
val notificationPermissionGranted =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val notificationPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
if (notificationPermissionState.status.isGranted) {
if (!sharedPreferencesViewModel.sharedPrefs.dontShowPushNotificationSelector) {
if (!notificationPermissionState.status.isGranted) {
val dontAskForNotificationPermissions by sharedPrefs.dontAskForNotificationPermissions.collectAsStateWithLifecycle()
if (!dontAskForNotificationPermissions) {
sharedPrefs.dontAskForNotificationPermissions()
// This will pause the APP, including the connection with relays.
LaunchedEffect(notificationPermissionState) {
notificationPermissionState.launchPermissionRequest()
}
}
}
notificationPermissionState.status.isGranted
} else {
true
}
if (notificationPermissionGranted) {
val pushNotificationSelector by sharedPrefs.dontShowPushNotificationSelector.collectAsStateWithLifecycle()
if (!pushNotificationSelector) {
val context = LocalContext.current
var distributorPresent by remember {
mutableStateOf(PushDistributorHandler.savedDistributorExists())
@@ -84,8 +105,8 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView
onSelect = { index ->
if (list[index] == "None") {
PushDistributorHandler.forceRemoveDistributor(context)
sharedPreferencesViewModel.dontAskForNotificationPermissions()
sharedPreferencesViewModel.dontShowPushNotificationSelector()
sharedPrefs.dontAskForNotificationPermissions()
sharedPrefs.dontShowPushNotificationSelector()
} else {
val fullDistributorName = list[index]
PushDistributorHandler.saveDistributor(fullDistributorName)
@@ -107,7 +128,7 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView
val astNode =
remember {
CommonmarkAstNodeParser(MarkdownParseOptions.MarkdownWithLinks).parse(content)
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(content)
}
RichText(
@@ -125,7 +146,7 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView
TextButton(
onClick = {
distributorPresent = true
sharedPreferencesViewModel.dontShowPushNotificationSelector()
sharedPrefs.dontShowPushNotificationSelector()
},
) {
Text(stringRes(R.string.quick_action_dont_show_again_button))
@@ -150,9 +171,6 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView
)
}
}
} else {
val currentDistributor = PushDistributorHandler.getSavedDistributor()
PushDistributorHandler.saveDistributor(currentDistributor)
}
}
}
@@ -190,19 +208,19 @@ fun LoadDistributors(onInner: @Composable (String, ImmutableList<String>, Immuta
}
@Composable
fun PushNotificationSettingsRow(sharedPreferencesViewModel: SharedPreferencesViewModel) {
fun PushNotificationSettingsRow(sharedPrefs: UiSettingsFlow) {
val context = LocalContext.current
LoadDistributors { currentDistributor, list, readableListWithExplainer ->
SettingsRow(
R.string.push_server_title,
R.string.push_server_explainer,
selectedItens = readableListWithExplainer,
selectedItems = readableListWithExplainer,
selectedIndex = list.indexOf(currentDistributor),
) { index ->
if (list[index] == "None") {
sharedPreferencesViewModel.dontAskForNotificationPermissions()
sharedPreferencesViewModel.dontShowPushNotificationSelector()
sharedPrefs.dontAskForNotificationPermissions()
sharedPrefs.dontShowPushNotificationSelector()
PushDistributorHandler.forceRemoveDistributor(context)
} else {
PushDistributorHandler.saveDistributor(list[index])

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -22,10 +22,14 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.theme.MaxWidthPaddingTop5dp
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
@Composable
fun TranslatableRichTextViewer(
@@ -38,7 +42,7 @@ fun TranslatableRichTextViewer(
id: String,
callbackUri: String? = null,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
nav: INav,
) = ExpandableRichTextViewer(
content,
canPreview,
@@ -51,3 +55,12 @@ fun TranslatableRichTextViewer(
accountViewModel,
nav,
)
@Composable
fun TranslatableRichTextViewer(
content: String,
id: String,
translationMessageModifier: Modifier = MaxWidthPaddingTop5dp,
accountViewModel: AccountViewModel,
displayText: @Composable (String) -> Unit,
) = displayText(content)

View File

@@ -8,24 +8,28 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
<intent>
<action android:name="android.intent.action.TTS_SERVICE" />
</intent>
</queries>
<!-- Doesn't require a camera -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- To connect with relays -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- To Upload media (newer devices) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<!-- To Upload media (old devices) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- To read QRs that contain nostr:<NIP19> -->
<!-- To take pictures -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- To record audio messages -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- To read NFCs that contain nostr:<NIP19> -->
<uses-permission android:name="android.permission.NFC" />
@@ -69,9 +73,10 @@
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout"
android:taskAffinity=".service.playback.pip.PipVideoActivity"
android:theme="@style/Theme.Amethyst">
<intent-filter android:label="Amethyst">
@@ -84,6 +89,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostr" />
<data android:scheme="amethyst" />
</intent-filter>
<intent-filter android:label="Amethyst">
@@ -98,29 +104,108 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrwalletconnect" />
<data android:scheme="nostr+walletconnect" />
<data android:scheme="amethyst+walletconnect" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
<intent-filter android:label="New Post">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="New Post">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter android:label="New Post">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter android:label="njump.me">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="njump.me" />
</intent-filter>
<intent-filter android:label="Primal">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="primal.net" />
<data android:pathPrefix="/e/" />
<data android:pathPrefix="/p/" />
<data android:pathPrefix="/a/" />
</intent-filter>
<intent-filter android:label="Yakihonne">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="yakihonne.com" />
<data android:pathPrefix="/notes/" />
<data android:pathPrefix="/users/" />
<data android:pathPrefix="/videos/" />
<data android:pathPrefix="/article/" />
</intent-filter>
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
tools:replace="screenOrientation" />
tools:replace="screenOrientation"
tools:ignore="DiscouragedApi" />
<activity
android:name=".service.playback.pip.PipVideoActivity"
android:autoRemoveFromRecents="true"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|uiMode"
android:supportsPictureInPicture="true"
android:launchMode="singleInstance"
android:exported="false"
android:resizeableActivity="true"
android:theme="@style/noAnimTheme"
/>
<service
android:name=".service.playback.PlaybackService"
android:name=".service.playback.service.PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:stopWithTask="true"
android:exported="true">
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<receiver
android:name=".service.notifications.PokeyReceiver"
android:exported="true"
tools:ignore="ExportedReceiver"
>
<intent-filter>
<action android:name="com.shared.NOSTR" />
</intent-filter>
</receiver>
</application>
</manifest>
</manifest>

View File

@@ -1,122 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package androidx.compose.material3.pullrefresh
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.platform.inspectable
import androidx.compose.ui.unit.Velocity
/**
* A nested scroll modifier that provides scroll events to [state].
*
* Note that this modifier must be added above a scrolling container, such as a lazy column, in
* order to receive scroll events. For example:
*
* @param state The [PullRefreshState] associated with this pull-to-refresh component. The state
* will be updated by this modifier.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
* @sample androidx.compose.material.samples.PullRefreshSample
*/
fun Modifier.pullRefresh(
state: PullRefreshState,
enabled: Boolean = true,
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefresh"
properties["state"] = state
properties["enabled"] = enabled
},
) {
Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
}
/**
* A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom
* pull refresh components.
*
* Note that this modifier must be added above a scrolling container, such as a lazy column, in
* order to receive scroll events. For example:
*
* @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
* Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
* down despite being at the top of a scrollable component), whereas negative delta (swiping up)
* is dispatched first (in case it is needed to push the indicator back up), and then the
* unconsumed delta is passed on to the child. The callback returns how much delta was consumed.
* @param onRelease Callback for when drag is released, takes float flingVelocity as argument. The
* callback returns how much velocity was consumed - in most cases this should only consume
* velocity if pull refresh has been dragged already and the velocity is positive (the fling is
* downwards), as an upwards fling should typically still scroll a scrollable component beneath
* the pullRefresh. This is invoked before any remaining velocity is passed to the child.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
* [onPull] nor [onRelease] will be invoked.
* @sample androidx.compose.material.samples.CustomPullRefreshSample
*/
fun Modifier.pullRefresh(
onPull: (pullDelta: Float) -> Float,
onRelease: suspend (flingVelocity: Float) -> Float,
enabled: Boolean = true,
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefresh"
properties["onPull"] = onPull
properties["onRelease"] = onRelease
properties["enabled"] = enabled
},
) {
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
}
private class PullRefreshNestedScrollConnection(
private val onPull: (pullDelta: Float) -> Float,
private val onRelease: suspend (flingVelocity: Float) -> Float,
private val enabled: Boolean,
) : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset =
when {
!enabled -> Offset.Zero
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
else -> Offset.Zero
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset =
when {
!enabled -> Offset.Zero
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
else -> Offset.Zero
}
override suspend fun onPreFling(available: Velocity): Velocity = Velocity(0f, onRelease(available.y))
}

View File

@@ -1,231 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package androidx.compose.material3.pullrefresh
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
/**
* The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.
*
* @param refreshing A boolean representing whether a refresh is occurring.
* @param state The [PullRefreshState] which controls where and how the indicator will be drawn.
* @param modifier Modifiers for the indicator.
* @param backgroundColor The color of the indicator's background.
* @param contentColor The color of the indicator's arc and arrow.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
* @sample androidx.compose.material.samples.PullRefreshSample
*/
@Composable
fun PullRefreshIndicator(
refreshing: Boolean,
state: PullRefreshState,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(backgroundColor),
scale: Boolean = false,
) {
val showElevation by
remember(refreshing, state) { derivedStateOf { refreshing || state.position > 0.5f } }
Surface(
modifier = modifier.size(IndicatorSize).pullRefreshIndicatorTransform(state, scale),
shape = SpinnerShape,
color = backgroundColor,
shadowElevation = if (showElevation) Elevation else 0.dp,
) {
Crossfade(
targetState = refreshing,
animationSpec = tween(durationMillis = CROSSFADE_DURATION_MS),
) { refreshing ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
val spinnerSize = (ArcRadius + StrokeWidth).times(2)
if (refreshing) {
CircularProgressIndicator(
color = contentColor,
strokeWidth = StrokeWidth,
modifier = Modifier.size(spinnerSize),
)
} else {
CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize))
}
}
}
}
}
/** Modifier.size MUST be specified. */
@Composable
private fun CircularArrowIndicator(
state: PullRefreshState,
color: Color,
modifier: Modifier,
) {
val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
val targetAlpha by
remember(state) { derivedStateOf { if (state.progress >= 1f) MAX_ALPHA else MIN_ALPHA } }
val alphaState = animateFloatAsState(targetValue = targetAlpha, animationSpec = AlphaTween)
// Empty semantics for tests
Canvas(modifier.semantics {}) {
val values = ArrowValues(state.progress)
val alpha = alphaState.value
rotate(degrees = values.rotation) {
val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
val arcBounds =
Rect(
size.center.x - arcRadius,
size.center.y - arcRadius,
size.center.x + arcRadius,
size.center.y + arcRadius,
)
drawArc(
color = color,
alpha = alpha,
startAngle = values.startAngle,
sweepAngle = values.endAngle - values.startAngle,
useCenter = false,
topLeft = arcBounds.topLeft,
size = arcBounds.size,
style =
Stroke(
width = StrokeWidth.toPx(),
cap = StrokeCap.Square,
),
)
drawArrow(path, arcBounds, color, alpha, values)
}
}
}
@Immutable
private class ArrowValues(
val rotation: Float,
val startAngle: Float,
val endAngle: Float,
val scale: Float,
)
private fun ArrowValues(progress: Float): ArrowValues {
// Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%.
val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3
// How far beyond the threshold pull has gone, as a percentage of the threshold.
val overshootPercent = abs(progress) - 1.0f
// Limit the overshoot to 200%. Linear between 0 and 200.
val linearTension = overshootPercent.coerceIn(0f, 2f)
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
val tensionPercent = linearTension - linearTension.pow(2) / 4
// Calculations based on SwipeRefreshLayout specification.
val endTrim = adjustedPercent * MAX_PROGRESS_ARC
val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
val startAngle = rotation * 360
val endAngle = (rotation + endTrim) * 360
val scale = min(1f, adjustedPercent)
return ArrowValues(rotation, startAngle, endAngle, scale)
}
private fun DrawScope.drawArrow(
arrow: Path,
bounds: Rect,
color: Color,
alpha: Float,
values: ArrowValues,
) {
arrow.reset()
arrow.moveTo(0f, 0f) // Move to left corner
arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
// Line to tip of arrow
arrow.lineTo(
x = ArrowWidth.toPx() * values.scale / 2,
y = ArrowHeight.toPx() * values.scale,
)
val radius = min(bounds.width, bounds.height) / 2f
val inset = ArrowWidth.toPx() * values.scale / 2f
arrow.translate(
Offset(
x = radius + bounds.center.x - inset,
y = bounds.center.y + StrokeWidth.toPx() / 2f,
),
)
arrow.close()
rotate(degrees = values.endAngle) { drawPath(path = arrow, color = color, alpha = alpha) }
}
private const val CROSSFADE_DURATION_MS = 100
private const val MAX_PROGRESS_ARC = 0.8f
private val IndicatorSize = 40.dp
private val SpinnerShape = CircleShape
private val ArcRadius = 7.5.dp
private val StrokeWidth = 2.5.dp
private val ArrowWidth = 10.dp
private val ArrowHeight = 5.dp
private val Elevation = 6.dp
// Values taken from SwipeRefreshLayout
private const val MIN_ALPHA = 0.3f
private const val MAX_ALPHA = 1f
private val AlphaTween = tween<Float>(300, easing = LinearEasing)

View File

@@ -1,76 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package androidx.compose.material3.pullrefresh
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.platform.inspectable
/**
* A modifier for translating the position and scaling the size of a pull-to-refresh indicator based
* on the given [PullRefreshState].
*
* @param state The [PullRefreshState] which determines the position of the indicator.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
* @sample androidx.compose.material.samples.PullRefreshIndicatorTransformSample
*/
fun Modifier.pullRefreshIndicatorTransform(
state: PullRefreshState,
scale: Boolean = false,
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefreshIndicatorTransform"
properties["state"] = state
properties["scale"] = scale
},
) {
Modifier
// Essentially we only want to clip the at the top, so the indicator will not appear when
// the position is 0. It is preferable to clip the indicator as opposed to the layout that
// contains the indicator, as this would also end up clipping shadows drawn by items in a
// list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE
// for the other dimensions to allow for more room for elevation / arbitrary indicators - we
// only ever really want to clip at the top edge.
.drawWithContent {
clipRect(
top = 0f,
left = -Float.MAX_VALUE,
right = Float.MAX_VALUE,
bottom = Float.MAX_VALUE,
) {
this@drawWithContent.drawContent()
}
}.graphicsLayer {
translationY = state.position - size.height
if (scale && !state.refreshing) {
val scaleFraction =
LinearOutSlowInEasing.transform(state.position / state.threshold).coerceIn(0f, 1f)
scaleX = scaleFraction
scaleY = scaleFraction
}
}
}

View File

@@ -1,234 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package androidx.compose.material3.pullrefresh
import androidx.compose.animation.core.animate
import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.pow
/**
* Creates a [PullRefreshState] that is remembered across compositions.
*
* Changes to [refreshing] will result in [PullRefreshState] being updated.
*
* @param refreshing A boolean representing whether a refresh is currently occurring.
* @param onRefresh The function to be called to trigger a refresh.
* @param refreshThreshold The threshold below which, if a release occurs, [onRefresh] will be
* called.
* @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This
* offset corresponds to the position of the bottom of the indicator.
* @sample androidx.compose.material.samples.PullRefreshSample
*/
@Composable
fun rememberPullRefreshState(
refreshing: Boolean,
onRefresh: () -> Unit,
refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
): PullRefreshState {
require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
val scope = rememberCoroutineScope()
val onRefreshState = rememberUpdatedState(onRefresh)
val thresholdPx: Float
val refreshingOffsetPx: Float
with(LocalDensity.current) {
thresholdPx = refreshThreshold.toPx()
refreshingOffsetPx = refreshingOffset.toPx()
}
val state =
remember(scope) { PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) }
SideEffect {
state.setRefreshing(refreshing)
state.setThreshold(thresholdPx)
state.setRefreshingOffset(refreshingOffsetPx)
}
return state
}
/**
* A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh
* behaviour to a scroll component. Based on Android's SwipeRefreshLayout.
*
* Provides [progress], a float representing how far the user has pulled as a percentage of the
* refreshThreshold. Values of one or less indicate that the user has not yet pulled past the
* threshold. Values greater than one indicate how far past the threshold the user has pulled.
*
* Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like
* pull-to-refresh behaviour with a custom indicator.
*
* Should be created using [rememberPullRefreshState].
*/
class PullRefreshState
internal constructor(
private val animationScope: CoroutineScope,
private val onRefreshState: State<() -> Unit>,
refreshingOffset: Float,
threshold: Float,
) {
/**
* A float representing how far the user has pulled as a percentage of the refreshThreshold.
*
* If the component has not been pulled at all, progress is zero. If the pull has reached halfway
* to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has gone beyond
* the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to two times the
* refreshThreshold.
*/
val progress
get() = adjustedDistancePulled / threshold
val refreshing
get() = _refreshing
val position
get() = _position
val threshold
get() = _threshold
private val adjustedDistancePulled by derivedStateOf { distancePulled * DRAG_MULTIPLIER }
private var _refreshing by mutableStateOf(false)
private var _position by mutableStateOf(0f)
private var distancePulled by mutableStateOf(0f)
private var _threshold by mutableStateOf(threshold)
private var refreshingOffsetState by mutableStateOf(refreshingOffset)
internal fun onPull(pullDelta: Float): Float {
if (_refreshing) return 0f // Already refreshing, do nothing.
val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f)
val dragConsumed = newOffset - distancePulled
distancePulled = newOffset
_position = calculateIndicatorPosition()
return dragConsumed
}
internal fun onRelease(velocity: Float): Float {
if (refreshing) return 0f // Already refreshing, do nothing
if (adjustedDistancePulled > threshold) {
onRefreshState.value()
}
animateIndicatorTo(0f)
val consumed =
when {
// We are flinging without having dragged the pull refresh (for example a fling inside
// a list) - don't consume
distancePulled == 0f -> 0f
// If the velocity is negative, the fling is upwards, and we don't want to prevent the
// the list from scrolling
velocity < 0f -> 0f
// We are showing the indicator, and the fling is downwards - consume everything
else -> velocity
}
distancePulled = 0f
return consumed
}
internal fun setRefreshing(refreshing: Boolean) {
if (_refreshing != refreshing) {
_refreshing = refreshing
distancePulled = 0f
animateIndicatorTo(if (refreshing) refreshingOffsetState else 0f)
}
}
internal fun setThreshold(threshold: Float) {
_threshold = threshold
}
internal fun setRefreshingOffset(refreshingOffset: Float) {
if (refreshingOffsetState != refreshingOffset) {
refreshingOffsetState = refreshingOffset
if (refreshing) animateIndicatorTo(refreshingOffset)
}
}
// Make sure to cancel any existing animations when we launch a new one. We use this instead of
// Animatable as calling snapTo() on every drag delta has a one frame delay, and some extra
// overhead of running through the animation pipeline instead of directly mutating the state.
private val mutatorMutex = MutatorMutex()
private fun animateIndicatorTo(offset: Float) =
animationScope.launch {
mutatorMutex.mutate {
animate(initialValue = _position, targetValue = offset) { value, _ -> _position = value }
}
}
private fun calculateIndicatorPosition(): Float =
when {
// If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
adjustedDistancePulled <= threshold -> adjustedDistancePulled
else -> {
// How far beyond the threshold pull has gone, as a percentage of the threshold.
val overshootPercent = abs(progress) - 1.0f
// Limit the overshoot to 200%. Linear between 0 and 200.
val linearTension = overshootPercent.coerceIn(0f, 2f)
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
val tensionPercent = linearTension - linearTension.pow(2) / 4
// The additional offset beyond the threshold.
val extraOffset = threshold * tensionPercent
threshold + extraOffset
}
}
}
/** Default parameter values for [rememberPullRefreshState]. */
object PullRefreshDefaults {
/**
* If the indicator is below this threshold offset when it is released, a refresh will be
* triggered.
*/
val RefreshThreshold = 80.dp
/** The offset at which the indicator should be rendered whilst a refresh is occurring. */
val RefreshingOffset = 56.dp
}
/**
* The distance pulled is multiplied by this value to give us the adjusted distance pulled, which is
* used in calculating the indicator position (when the adjusted distance pulled is less than the
* refresh threshold, it is the indicator position, otherwise the indicator position is derived from
* the progress).
*/
private const val DRAG_MULTIPLIER = 0.5f

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -21,103 +21,45 @@
package com.vitorpamplona.amethyst
import android.app.Application
import android.content.Context
import android.os.Looper
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import coil.ImageLoader
import coil.disk.DiskCache
import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
import com.vitorpamplona.amethyst.service.playback.VideoCache
import com.vitorpamplona.quartz.events.OtsEvent
import com.vitorpamplona.quartz.ots.OpenTimestamps
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
import kotlin.time.measureTimedValue
import com.vitorpamplona.amethyst.service.logging.Logging
import com.vitorpamplona.quartz.utils.Log
class Amethyst : Application() {
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
override fun onTerminate() {
super.onTerminate()
applicationIOScope.cancel()
init {
Log.d("AmethystApp", "Creating App $this")
}
val videoCache: VideoCache by lazy {
val newCache = VideoCache()
runBlocking {
newCache.initFileCache(this@Amethyst)
}
newCache
}
val coilCache: DiskCache by lazy {
DiskCache
.Builder()
.directory(applicationContext.safeCacheDir.resolve("image_cache"))
.maxSizePercent(0.2)
.maximumMaxSizeBytes(500L * 1024 * 1024) // 250MB
.build()
companion object {
lateinit var instance: AppModules
private set
}
override fun onCreate() {
super.onCreate()
Log.d("AmethystApp", "onCreate $this")
instance = AppModules(this)
instance = this
OtsEvent.otsInstance = OpenTimestamps(OkHttpBlockstreamExplorer(), OkHttpCalendarBuilder())
if (BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "benchmark") {
StrictMode.setThreadPolicy(
ThreadPolicy
.Builder()
.detectAll()
.penaltyLog()
.build(),
)
StrictMode.setVmPolicy(
VmPolicy
.Builder()
.detectAll()
.penaltyLog()
.build(),
)
Looper.getMainLooper().setMessageLogging(LogMonitor())
ChoreographerHelper.start()
if (isDebug) {
Logging.setup()
}
GlobalScope.launch(Dispatchers.IO) {
val (value, elapsed) =
measureTimedValue {
// initializes the video cache in a thread
videoCache
}
Log.d("Rendering Metrics", "VideoCache initialized in $elapsed")
}
instance.initiate(this)
}
fun imageLoaderBuilder(): ImageLoader.Builder = ImageLoader.Builder(applicationContext).diskCache { coilCache }
override fun onTerminate() {
super.onTerminate()
Log.d("AmethystApp", "onTerminate $this")
instance.terminate(this)
}
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(instance, npub)
companion object {
lateinit var instance: Amethyst
private set
/**
* Release memory when the UI becomes hidden or when system resources become low.
*
* @param level the memory-related event that was raised.
*/
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Log.d("AmethystApp", "onTrimMemory $level")
instance.trim()
}
}
internal val Context.safeCacheDir: File
get() {
val cacheDir = checkNotNull(cacheDir) { "cacheDir == null" }
return cacheDir.apply { mkdirs() }
}

View File

@@ -0,0 +1,305 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import android.content.ContentResolver
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.model.nip03Timestamp.IncomingOtsEventVerifier
import com.vitorpamplona.amethyst.model.nip03Timestamp.TorAwareOkHttpOtsResolverBuilder
import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever
import com.vitorpamplona.amethyst.model.preferences.TorSharedPreferences
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
import com.vitorpamplona.amethyst.model.torState.AccountsTorStateConnector
import com.vitorpamplona.amethyst.model.torState.TorRelayState
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager
import com.vitorpamplona.amethyst.service.crashreports.CrashReportCache
import com.vitorpamplona.amethyst.service.crashreports.UnexpectedCrashSaver
import com.vitorpamplona.amethyst.service.eventCache.MemoryTrimmingService
import com.vitorpamplona.amethyst.service.images.ImageCacheFactory
import com.vitorpamplona.amethyst.service.images.ImageLoaderSetup
import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.notifications.PokeyReceiver
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager
import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory
import com.vitorpamplona.amethyst.service.relayClient.CacheClientConnector
import com.vitorpamplona.amethyst.service.relayClient.RelayProxyClientConnector
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.AuthCoordinator
import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model.NotifyCoordinator
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.service.relayClient.speedLogger.RelaySpeedLogger
import com.vitorpamplona.amethyst.service.uploads.nip95.Nip95CacheFactory
import com.vitorpamplona.amethyst.ui.screen.UiSettingsState
import com.vitorpamplona.amethyst.ui.tor.TorManager
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayLogger
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.stats.RelayReqStats
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.io.File
class AppModules(
val appContext: Context,
) {
val appAgent = "Amethyst/${BuildConfig.VERSION_NAME}"
// Exists to avoid exceptions stopping the coroutine
val exceptionHandler =
CoroutineExceptionHandler { _, throwable ->
Log.e("AmethystCoroutine", "Caught exception: ${throwable.message}", throwable)
}
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler)
val applicationDefaultScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler)
// Blocking load of UI Preferences to avoid theme/language blinking
val uiPrefs by lazy {
UiSharedPreferences(appContext, applicationIOScope)
}
// Blocking load of Tor Settings to avoid connection leaks
val torPrefs by lazy {
TorSharedPreferences(appContext, applicationIOScope)
}
// App services that should be run as soon as there are subscribers to their flows
val locationManager = LocationState(appContext, applicationIOScope)
val connManager = ConnectivityManager(appContext, applicationIOScope)
val uiState by lazy {
UiSettingsState(uiPrefs.value, connManager.isMobileOrFalse, applicationIOScope)
}
val torManager = TorManager(torPrefs, appContext, applicationIOScope)
// Service that will run at all times to receive events from Pokey
val pokeyReceiver = PokeyReceiver()
// Key cache service to download and decrypt encrypted files before caching them.
val keyCache = EncryptionKeyCache()
// manages all the other connections separately from relays.
val okHttpClients =
DualHttpClientManager(
userAgent = appAgent,
proxyPortProvider = torManager.activePortOrNull,
isMobileDataProvider = connManager.isMobileOrNull,
keyCache = keyCache,
scope = applicationIOScope,
)
// manages all relay connections
val okHttpClientForRelays =
DualHttpClientManager(
userAgent = appAgent,
proxyPortProvider = torManager.activePortOrNull,
isMobileDataProvider = connManager.isMobileOrNull,
keyCache = keyCache,
scope = applicationIOScope,
)
// Offers easy methods to know when connections are happening through Tor or not
val roleBasedHttpClientBuilder = RoleBasedHttpClientBuilder(okHttpClients, torPrefs.value)
// Application-wide block height request cache
val otsBlockHeightCache by lazy { OtsBlockHeightCache() }
val otsResolverBuilder: TorAwareOkHttpOtsResolverBuilder =
TorAwareOkHttpOtsResolverBuilder(
roleBasedHttpClientBuilder::okHttpClientForMoney,
roleBasedHttpClientBuilder::shouldUseTorForMoneyOperations,
otsBlockHeightCache,
)
// Application-wide ots verification cache
val otsVerifCache by lazy { VerificationStateCache(otsResolverBuilder) }
val torEvaluatorFlow =
TorRelayState(
okHttpClients,
torPrefs.value,
applicationDefaultScope,
)
// Connects the NostrClient class with okHttp
val websocketBuilder =
OkHttpWebSocket.Builder { url ->
val useTor = torEvaluatorFlow.flow.value.useTor(url)
okHttpClientForRelays.getHttpClient(useTor)
}
// Caches all events in Memory
val cache: LocalCache = LocalCache
// Provides a relay pool
val client: INostrClient = NostrClient(websocketBuilder, applicationDefaultScope)
// Watches for changes on Tor and Relay List Settings
val relayProxyClientConnector =
RelayProxyClientConnector(
torEvaluatorFlow.flow,
okHttpClientForRelays,
connManager,
torManager,
client,
applicationDefaultScope,
)
// Verifies and inserts in the cache from all relays, all subscriptions
val cacheClientConnector = CacheClientConnector(client, cache)
// Show messages from the Relay and controls their dismissal
val notifyCoordinator = NotifyCoordinator(client)
// Authenticates with relays.
val authCoordinator = AuthCoordinator(client, applicationDefaultScope)
// Tries to verify new OTS events when they arrive.
val otsEventVerifier = IncomingOtsEventVerifier(otsVerifCache, cache, applicationDefaultScope)
// Tracks if it is possible to connect to relays.
val failureTracker = RelayOfflineTracker(client)
// Captures statistics about relays
val relayStats = RelayStats(client)
// Logs debug messages when needed
val detailedLogger = if (isDebug) RelayLogger(client, true, false) else null
val relayReqStats = if (isDebug) RelayReqStats(client) else null
val logger = if (isDebug) RelaySpeedLogger(client) else null
// Coordinates all subscriptions for the Nostr Client
val sources: RelaySubscriptionsCoordinator =
RelaySubscriptionsCoordinator(
LocalCache,
client,
authCoordinator.receiver,
failureTracker,
applicationDefaultScope,
)
// keeps all accounts live
val accountsCache =
AccountCacheState(
geolocationFlow = locationManager.geohashStateFlow,
nwcFilterAssembler = sources.nwc,
contentResolverFn = ::contentResolverFn,
otsResolverBuilder = otsResolverBuilder,
cache = cache,
client = client,
)
// Organizes cache clearing
val trimmingService = MemoryTrimmingService(cache)
// as new accounts are loaded, updates the state of the TorRelaySettings, which produces new TorRelayEvaluator
// and reconnects relays if the configuration has been changed.
val accountsTorStateConnector = AccountsTorStateConnector(accountsCache, torEvaluatorFlow, applicationDefaultScope)
// saves the .content of NIP-95 blobs in disk to save memory
val nip95cache: File by lazy { Nip95CacheFactory.new(appContext) }
// local video cache with disk + memory
val videoCache: VideoCache by lazy { VideoCacheFactory.new(appContext) }
// image cache in disk for coil
val diskCache: DiskCache by lazy { ImageCacheFactory.newDisk(appContext) }
// image cache in memory for coil
val memoryCache: MemoryCache by lazy { ImageCacheFactory.newMemory(appContext) }
// crash report storage
val crashReportCache: CrashReportCache by lazy { CrashReportCache(appContext) }
// cache for NIP-11 documents
val nip11Cache: Nip11CachedRetriever by lazy {
Nip11CachedRetriever(torEvaluatorFlow::okHttpClientForRelay)
}
fun contentResolverFn(): ContentResolver = appContext.contentResolver
fun setImageLoader() {
ImageLoaderSetup.setup(appContext, { diskCache }, { memoryCache }) { url ->
okHttpClients.getHttpClient(roleBasedHttpClientBuilder.shouldUseTorForImageDownload(url))
}
}
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(appContext, npub)
fun initiate(appContext: Context) {
Thread.setDefaultUncaughtExceptionHandler(UnexpectedCrashSaver(crashReportCache, applicationIOScope))
applicationIOScope.launch {
// loads main account quickly.
LocalPreferences.loadAccountConfigFromEncryptedStorage()
}
// forces initialization of uiPrefs in the main thread to avoid blinking themes
uiPrefs
// initializes diskcache on an IO thread.
applicationIOScope.launch {
// preloads tor preferences
torPrefs
}
// initializes diskcache on an IO thread.
applicationIOScope.launch {
// Sets Coil - Tor - OkHttp link
setImageLoader()
}
// registers to receive events
pokeyReceiver.register(appContext)
}
fun terminate(appContext: Context) {
pokeyReceiver.unregister(appContext)
applicationIOScope.cancel("Application onTerminate $appContext")
applicationDefaultScope.cancel("Application onTerminate $appContext")
accountsCache.clear()
}
fun trim() {
applicationDefaultScope.launch {
val loggedIn = accountsCache.accounts.value.values
trimmingService.run(loggedIn, LocalPreferences.allSavedAccounts())
}
}
}

View File

@@ -0,0 +1,241 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import android.app.ActivityManager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Debug
import androidx.core.content.getSystemService
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizedUrls
import com.vitorpamplona.quartz.utils.Log
import kotlin.time.DurationUnit
import kotlin.time.measureTimedValue
@Suppress("SENSELESS_COMPARISON")
val isDebug = BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "benchmark"
private const val STATE_DUMP_TAG = "STATE DUMP"
fun debugState(context: Context) {
val totalMemoryMb = Runtime.getRuntime().totalMemory() / (1024 * 1024)
val freeMemoryMb = Runtime.getRuntime().freeMemory() / (1024 * 1024)
val maxMemoryMb = Runtime.getRuntime().maxMemory() / (1024 * 1024)
val jvmHeapAllocatedMb = totalMemoryMb - freeMemoryMb
Log.d(STATE_DUMP_TAG, "Total Heap Allocated: $jvmHeapAllocatedMb/$maxMemoryMb MB")
val nativeHeap = Debug.getNativeHeapAllocatedSize() / (1024 * 1024)
val maxNative = Debug.getNativeHeapSize() / (1024 * 1024)
Log.d(STATE_DUMP_TAG, "Total Native Heap Allocated: $nativeHeap/$maxNative MB")
val activityManager: ActivityManager? = context.getSystemService()
if (activityManager != null) {
val isLargeHeap = (context.applicationInfo.flags and ApplicationInfo.FLAG_LARGE_HEAP) != 0
val memClass = if (isLargeHeap) activityManager.largeMemoryClass else activityManager.memoryClass
Log.d(STATE_DUMP_TAG, "Memory Class $memClass MB (largeHeap $isLargeHeap)")
}
Log.d(
STATE_DUMP_TAG,
"Connected Relays: " +
Amethyst.instance.client
.relayStatusFlow()
.value.connected.size + "/" +
Amethyst.instance.client
.relayStatusFlow()
.value.available.size,
)
Log.d(
STATE_DUMP_TAG,
"Indexed Relays: " +
Amethyst.instance.cache.relayHints.relayDB
.size() + "/" + normalizedUrls.size(),
)
Log.d(
STATE_DUMP_TAG,
"Image Disk Cache ${(Amethyst.instance.diskCache.size) / (1024 * 1024)}/${(Amethyst.instance.diskCache.maxSize) / (1024 * 1024)} MB",
)
Log.d(
STATE_DUMP_TAG,
"Image Memory Cache ${(Amethyst.instance.memoryCache.size) / (1024 * 1024)}/${(Amethyst.instance.memoryCache.maxSize) / (1024 * 1024)} MB",
)
Log.d(
STATE_DUMP_TAG,
"Notes: " +
LocalCache.notes.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.notes.filter { _, it -> it.event != null }.size +
" / " +
LocalCache.notes.size(),
)
Log.d(
STATE_DUMP_TAG,
"Addressables: " +
LocalCache.addressables.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.addressables.filter { _, it -> it.event != null }.size +
" / " +
LocalCache.addressables.size(),
)
Log.d(
STATE_DUMP_TAG,
"Users: " +
LocalCache.users.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
" / " +
LocalCache.users.size(),
)
Log.d(
STATE_DUMP_TAG,
"Public Chat Channels: " +
LocalCache.publicChatChannels.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.publicChatChannels.size() +
" / " +
LocalCache.publicChatChannels.values().sumOf { it.notes.size() },
)
Log.d(
STATE_DUMP_TAG,
"Live Chat Channels: " +
LocalCache.liveChatChannels.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.liveChatChannels.size() +
" / " +
LocalCache.liveChatChannels.values().sumOf { it.notes.size() },
)
Log.d(
STATE_DUMP_TAG,
"Ephemeral Chat Channels: " +
LocalCache.ephemeralChannels.filter { _, it -> it.flowSet != null }.size +
" / " +
LocalCache.ephemeralChannels.size() +
" / " +
LocalCache.ephemeralChannels.values().sumOf { it.notes.size() },
)
LocalCache.chatroomList.forEach { key, room ->
Log.d(
STATE_DUMP_TAG,
"Private Chats $key: " +
room.rooms.size() +
" / " +
room.rooms.sumOf { key, value -> value.messages.size },
)
}
Log.d(
STATE_DUMP_TAG,
"Deletion Events: " +
LocalCache.deletionIndex.size(),
)
Log.d(
STATE_DUMP_TAG,
"Observable Events: " +
LocalCache.observablesByKindAndETag.size +
" / " +
LocalCache.observablesByKindAndAuthor.size,
)
Log.d(
STATE_DUMP_TAG,
"Spam: " +
LocalCache.antiSpam.spamMessages.size() + " / " + LocalCache.antiSpam.recentEventIds.size() + " / " + LocalCache.antiSpam.recentAddressables.size(),
)
Log.d(
STATE_DUMP_TAG,
"Memory used by Events: " +
LocalCache.notes.sumOf { _, note -> note.event?.countMemory() ?: 0 } / (1024 * 1024) +
" MB",
)
val qttNotes = LocalCache.notes.countByGroup { _, it -> it.event?.kind }
val qttAddressables = LocalCache.addressables.countByGroup { _, it -> it.event?.kind }
val bytesNotes =
LocalCache.notes
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory()?.toLong() ?: 0L })
val bytesAddressables =
LocalCache.addressables
.sumByGroup(groupMap = { _, it -> it.event?.kind }, sumOf = { _, it -> it.event?.countMemory()?.toLong() ?: 0L })
qttNotes.toList().sortedByDescending { bytesNotes[it.first] }.forEach { (kind, qtt) ->
Log.d(STATE_DUMP_TAG, "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesNotes[kind]?.div((1024 * 1024))}MB ")
}
qttAddressables.toList().sortedByDescending { bytesNotes[it.first] }.forEach { (kind, qtt) ->
Log.d(STATE_DUMP_TAG, "Kind ${kind.toString().padStart(5,' ')}:\t${qtt.toString().padStart(6,' ')} elements\t${bytesAddressables[kind]?.div((1024 * 1024))}MB ")
}
}
inline fun <T> logTime(
debugMessage: String,
minToReportMs: Int = 1,
block: () -> T,
): T =
if (isDebug) {
val (result, elapsed) = measureTimedValue(block)
if (elapsed.inWholeMilliseconds > minToReportMs) {
Log.d("DEBUG-TIME", "${elapsed.toString(DurationUnit.MILLISECONDS, 3).padStart(12)}: $debugMessage")
}
result
} else {
block()
}
inline fun <T> logTime(
debugMessage: (T) -> String,
minToReportMs: Int = 1,
block: () -> T,
): T =
if (isDebug) {
val (result, elapsed) = measureTimedValue(block)
if (elapsed.inWholeMilliseconds > minToReportMs) {
Log.d("DEBUG-TIME", "${elapsed.toString(DurationUnit.MILLISECONDS, 3).padStart(12)}: ${debugMessage(result)}")
}
result
} else {
block()
}
fun debug(
tag: String,
debugMessage: String,
) {
if (isDebug) {
Log.d(tag, debugMessage)
}
}
inline fun debug(
tag: String,
debugMessage: () -> String,
) {
if (isDebug) {
Log.d(tag, debugMessage())
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in

View File

@@ -1,252 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import android.os.Build
import android.util.Log
import androidx.compose.runtime.Stable
import coil.Coil
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import coil.size.Precision
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
import com.vitorpamplona.amethyst.service.NostrCommunityDataSource
import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
import com.vitorpamplona.ammolite.relays.Client
import com.vitorpamplona.ammolite.service.HttpClientManager
import com.vitorpamplona.quartz.encoders.bechToBytes
import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull
import com.vitorpamplona.quartz.encoders.toHexKey
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@Stable
class ServiceManager {
private var isStarted: Boolean =
false // to not open amber in a loop trying to use auth relays and registering for notifications
private var account: Account? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var collectorJob: Job? = null
private fun start(account: Account) {
this.account = account
start()
}
private fun start() {
Log.d("ServiceManager", "Pre Starting Relay Services $isStarted $account")
if (isStarted && account != null) {
return
}
Log.d("ServiceManager", "Starting Relay Services")
val myAccount = account
// Resets Proxy Use
HttpClientManager.setDefaultProxy(account?.proxy)
HttpClientManager.setDefaultUserAgent("Amethyst/${BuildConfig.VERSION_NAME}")
LocalCache.antiSpam.active = account?.filterSpamFromStrangers ?: true
Coil.setImageLoader {
Amethyst.instance
.imageLoaderBuilder()
.components {
if (Build.VERSION.SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
add(SvgDecoder.Factory())
} // .logger(DebugLogger())
.okHttpClient { HttpClientManager.getHttpClient() }
.precision(Precision.INEXACT)
.respectCacheHeaders(false)
.build()
}
if (myAccount != null) {
val relaySet = myAccount.connectToRelays.value
Log.d("Relay", "Service Manager Connect Connecting ${relaySet.size}")
Client.reconnect(relaySet)
collectorJob?.cancel()
collectorJob = null
collectorJob =
scope.launch {
myAccount.connectToRelaysFlow.collectLatest {
delay(500)
if (isStarted) {
Client.reconnect(it, onlyIfChanged = true)
}
}
}
// start services
NostrAccountDataSource.account = myAccount
NostrAccountDataSource.otherAccounts =
LocalPreferences.allSavedAccounts().mapNotNull {
try {
it.npub.bechToBytes().toHexKey()
} catch (e: Exception) {
if (e is CancellationException) throw e
null
}
}
NostrHomeDataSource.account = myAccount
NostrChatroomListDataSource.account = myAccount
NostrVideoDataSource.account = myAccount
NostrDiscoveryDataSource.account = myAccount
// Notification Elements
NostrHomeDataSource.start()
NostrAccountDataSource.start()
GlobalScope.launch(Dispatchers.IO) {
delay(3000)
NostrChatroomListDataSource.start()
NostrDiscoveryDataSource.start()
NostrVideoDataSource.start()
}
// More Info Data Sources
NostrSingleEventDataSource.start()
NostrSingleChannelDataSource.start()
NostrSingleUserDataSource.start()
isStarted = true
}
}
private fun pause() {
Log.d("ServiceManager", "Pausing Relay Services")
collectorJob?.cancel()
collectorJob = null
NostrAccountDataSource.stopSync()
NostrHomeDataSource.stopSync()
NostrChannelDataSource.stopSync()
NostrChatroomDataSource.stopSync()
NostrChatroomListDataSource.stopSync()
NostrDiscoveryDataSource.stopSync()
NostrCommunityDataSource.stopSync()
NostrHashtagDataSource.stopSync()
NostrGeohashDataSource.stopSync()
NostrSearchEventOrUserDataSource.stopSync()
NostrSingleChannelDataSource.stopSync()
NostrSingleEventDataSource.stopSync()
NostrSingleUserDataSource.stopSync()
NostrThreadDataSource.stopSync()
NostrUserProfileDataSource.stopSync()
NostrVideoDataSource.stopSync()
Client.reconnect(null)
isStarted = false
}
fun cleanObservers() {
LocalCache.cleanObservers()
}
fun trimMemory() {
LocalCache.cleanObservers()
val accounts =
LocalPreferences.allSavedAccounts().mapNotNull { decodePublicKeyAsHexOrNull(it.npub) }.toSet()
account?.let {
LocalCache.pruneOldAndHiddenMessages(it)
NostrChatroomDataSource.clearEOSEs(it)
LocalCache.pruneHiddenMessages(it)
LocalCache.pruneContactLists(accounts)
LocalCache.pruneRepliesAndReactions(accounts)
LocalCache.prunePastVersionsOfReplaceables()
LocalCache.pruneExpiredEvents()
}
}
// This method keeps the pause/start in a Syncronized block to
// avoid concurrent pauses and starts.
@Synchronized
fun forceRestart(
account: Account? = null,
start: Boolean = true,
pause: Boolean = true,
) {
if (pause) {
pause()
}
if (start) {
if (account != null) {
start(account)
} else {
start()
}
}
}
fun restartIfDifferentAccount(account: Account) {
if (this.account != account) {
forceRestart(account, true, true)
}
}
fun forceRestart() {
forceRestart(null, true, true)
}
fun justStart() {
forceRestart(null, true, false)
}
fun pauseForGood() {
forceRestart(null, false, true)
}
fun pauseForGoodAndClearAccount() {
account = null
forceRestart(null, false, true)
}
}

View File

@@ -0,0 +1,595 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.tags.ChannelTag
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayInfo
import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import java.util.Locale
val DefaultChannels =
listOf(
// Anigma's Nostr
ChannelTag("25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", Constants.nos),
// Amethyst's Group
ChannelTag("42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5", Constants.nos),
)
val DefaultNIP65RelaySet = setOf(Constants.mom, Constants.nos, Constants.bitcoiner)
val DefaultNIP65List =
listOf(
AdvertisedRelayInfo(Constants.mom, AdvertisedRelayType.BOTH),
AdvertisedRelayInfo(Constants.nos, AdvertisedRelayType.BOTH),
AdvertisedRelayInfo(Constants.bitcoiner, AdvertisedRelayType.BOTH),
)
val DefaultDMRelayList = listOf(Constants.auth, Constants.oxchat, Constants.nos)
val DefaultSearchRelayList = setOf(Constants.band, Constants.wine, Constants.where, Constants.nostoday)
val DefaultIndexerRelayList = setOf(Constants.purplepages, Constants.coracle, Constants.userkinds)
val DefaultSignerPermissions =
listOf(
Permission(CommandType.SIGN_EVENT, RelayAuthEvent.KIND),
Permission(CommandType.SIGN_EVENT, DraftWrapEvent.KIND),
Permission(CommandType.NIP04_ENCRYPT),
Permission(CommandType.NIP04_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.DECRYPT_ZAP_EVENT),
)
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
val GLOBAL_FOLLOWS = " Global "
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
val ALL_FOLLOWS = " All Follows "
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
val ALL_USER_FOLLOWS = " All User Follows "
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
val KIND3_FOLLOWS = " Main User Follows "
// This has spaces to avoid mixing with a potential NIP-51 list with the same name.
val AROUND_ME = " Around Me "
@Stable
class AccountSettings(
val keyPair: KeyPair,
val transientAccount: Boolean = false,
var externalSignerPackageName: String? = null,
var localRelayServers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
var defaultFileServer: ServerName = DEFAULT_MEDIA_SERVERS[0],
val defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(ALL_FOLLOWS),
val defaultStoriesFollowList: MutableStateFlow<String> = MutableStateFlow(GLOBAL_FOLLOWS),
val defaultNotificationFollowList: MutableStateFlow<String> = MutableStateFlow(GLOBAL_FOLLOWS),
val defaultDiscoveryFollowList: MutableStateFlow<String> = MutableStateFlow(GLOBAL_FOLLOWS),
var zapPaymentRequest: MutableStateFlow<Nip47WalletConnect.Nip47URINorm?> = MutableStateFlow(null),
var hideDeleteRequestDialog: Boolean = false,
var hideBlockAlertDialog: Boolean = false,
var hideNIP17WarningDialog: Boolean = false,
var backupUserMetadata: MetadataEvent? = null,
var backupContactList: ContactListEvent? = null,
var backupDMRelayList: ChatMessageRelayListEvent? = null,
var backupNIP65RelayList: AdvertisedRelayListEvent? = null,
var backupSearchRelayList: SearchRelayListEvent? = null,
var backupIndexRelayList: IndexerRelayListEvent? = null,
var backupBlockedRelayList: BlockedRelayListEvent? = null,
var backupTrustedRelayList: TrustedRelayListEvent? = null,
var backupMuteList: MuteListEvent? = null,
var backupPrivateHomeRelayList: PrivateOutboxRelayListEvent? = null,
var backupAppSpecificData: AppSpecificDataEvent? = null,
var backupChannelList: ChannelListEvent? = null,
var backupCommunityList: CommunityListEvent? = null,
var backupHashtagList: HashtagListEvent? = null,
var backupGeohashList: GeohashListEvent? = null,
var backupEphemeralChatList: EphemeralChatListEvent? = null,
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
var hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf<String>()),
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow<Map<HexKey, String>>(mapOf()),
) {
val saveable = MutableStateFlow(AccountSettingsUpdater(null))
val syncedSettings: AccountSyncedSettings = AccountSyncedSettings(AccountSyncedSettingsInternal())
class AccountSettingsUpdater(
val accountSettings: AccountSettings?,
)
fun saveAccountSettings() {
saveable.update { AccountSettingsUpdater(this) }
}
fun isWriteable(): Boolean = keyPair.privKey != null || externalSignerPackageName != null
// ---
// Zaps and Reactions
// ---
fun changeDefaultZapType(zapType: LnZapEvent.ZapType): Boolean {
if (syncedSettings.zaps.defaultZapType.value != zapType) {
syncedSettings.zaps.defaultZapType.tryEmit(zapType)
saveAccountSettings()
return true
}
return false
}
fun changeZapAmounts(newAmounts: List<Long>): Boolean {
if (syncedSettings.zaps.zapAmountChoices.value != newAmounts) {
syncedSettings.zaps.zapAmountChoices.tryEmit(newAmounts.toImmutableList())
saveAccountSettings()
return true
}
return false
}
fun changeReactionTypes(newTypes: List<String>): Boolean {
if (syncedSettings.reactions.reactionChoices.value != newTypes) {
syncedSettings.reactions.reactionChoices.tryEmit(newTypes.toImmutableList())
saveAccountSettings()
return true
}
return false
}
fun changeZapPaymentRequest(newServer: Nip47WalletConnect.Nip47URINorm?): Boolean {
if (zapPaymentRequest.value != newServer) {
zapPaymentRequest.tryEmit(newServer)
saveAccountSettings()
return true
}
return false
}
// ---
// file servers
// ---
fun changeDefaultFileServer(server: ServerName) {
if (defaultFileServer != server) {
defaultFileServer = server
saveAccountSettings()
}
}
// ---
// list names
// ---
fun changeDefaultHomeFollowList(name: FeedDefinition) {
changeDefaultHomeFollowList(name.code)
}
fun changeDefaultHomeFollowList(name: String) {
if (defaultHomeFollowList.value != name) {
defaultHomeFollowList.tryEmit(name)
saveAccountSettings()
}
}
fun changeDefaultStoriesFollowList(name: FeedDefinition) {
changeDefaultStoriesFollowList(name.code)
}
fun changeDefaultStoriesFollowList(name: String) {
if (defaultStoriesFollowList.value != name) {
defaultStoriesFollowList.tryEmit(name)
saveAccountSettings()
}
}
fun changeDefaultNotificationFollowList(name: FeedDefinition) {
changeDefaultNotificationFollowList(name.code)
}
fun changeDefaultNotificationFollowList(name: String) {
if (defaultNotificationFollowList.value != name) {
defaultNotificationFollowList.tryEmit(name)
saveAccountSettings()
}
}
fun changeDefaultDiscoveryFollowList(name: FeedDefinition) {
changeDefaultDiscoveryFollowList(name.code)
}
fun changeDefaultDiscoveryFollowList(name: String) {
if (defaultDiscoveryFollowList.value != name) {
defaultDiscoveryFollowList.tryEmit(name)
saveAccountSettings()
}
}
// ---
// language services
// ---
fun toggleDontTranslateFrom(languageCode: String) {
syncedSettings.languages.toggleDontTranslateFrom(languageCode)
saveAccountSettings()
}
fun translateToContains(languageCode: Locale) = syncedSettings.languages.translateTo.contains(languageCode.language)
fun updateTranslateTo(languageCode: Locale): Boolean {
if (syncedSettings.languages.updateTranslateTo(languageCode)) {
saveAccountSettings()
return true
}
return false
}
fun prefer(
source: String,
target: String,
preference: String,
) {
syncedSettings.languages.prefer(source, target, preference)
saveAccountSettings()
}
fun preferenceBetween(
source: String,
target: String,
): String? = syncedSettings.languages.preferenceBetween(source, target)
// ----
// Backup Lists
// ----
fun updateLocalRelayServers(servers: Set<String>) {
if (localRelayServers.value != servers) {
localRelayServers.update { servers }
saveAccountSettings()
}
}
fun updateUserMetadata(newMetadata: MetadataEvent?) {
if (newMetadata == null) return
// Events might be different objects, we have to compare their ids.
if (backupUserMetadata?.id != newMetadata.id) {
backupUserMetadata = newMetadata
saveAccountSettings()
}
}
fun updateContactListTo(newContactList: ContactListEvent?) {
if (newContactList == null || newContactList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupContactList?.id != newContactList.id) {
backupContactList = newContactList
saveAccountSettings()
}
}
fun updateDMRelayList(newDMRelayList: ChatMessageRelayListEvent?) {
if (newDMRelayList == null || newDMRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupDMRelayList?.id != newDMRelayList.id) {
backupDMRelayList = newDMRelayList
saveAccountSettings()
}
}
fun updateNIP65RelayList(newNIP65RelayList: AdvertisedRelayListEvent?) {
if (newNIP65RelayList == null || newNIP65RelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupNIP65RelayList?.id != newNIP65RelayList.id) {
backupNIP65RelayList = newNIP65RelayList
saveAccountSettings()
}
}
fun updateSearchRelayList(newSearchRelayList: SearchRelayListEvent?) {
if (newSearchRelayList == null || newSearchRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupSearchRelayList?.id != newSearchRelayList.id) {
backupSearchRelayList = newSearchRelayList
saveAccountSettings()
}
}
fun updateIndexRelayList(newIndexRelayList: IndexerRelayListEvent?) {
if (newIndexRelayList == null || newIndexRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupIndexRelayList?.id != newIndexRelayList.id) {
backupIndexRelayList = newIndexRelayList
saveAccountSettings()
}
}
fun updateBlockedRelayList(newBlockedRelayList: BlockedRelayListEvent?) {
if (newBlockedRelayList == null || newBlockedRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupBlockedRelayList?.id != newBlockedRelayList.id) {
backupBlockedRelayList = newBlockedRelayList
saveAccountSettings()
}
}
fun updateTrustedRelayList(newTrustedRelayList: TrustedRelayListEvent?) {
if (newTrustedRelayList == null || newTrustedRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupTrustedRelayList?.id != newTrustedRelayList.id) {
backupTrustedRelayList = newTrustedRelayList
saveAccountSettings()
}
}
fun updatePrivateHomeRelayList(newPrivateHomeRelayList: PrivateOutboxRelayListEvent?) {
if (newPrivateHomeRelayList == null || newPrivateHomeRelayList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupPrivateHomeRelayList?.id != newPrivateHomeRelayList.id) {
backupPrivateHomeRelayList = newPrivateHomeRelayList
saveAccountSettings()
}
}
fun updateChannelListTo(newChannelList: ChannelListEvent?) {
if (newChannelList == null || newChannelList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupChannelList?.id != newChannelList.id) {
backupChannelList = newChannelList
saveAccountSettings()
}
}
fun updateGeohashListTo(newGeohashList: GeohashListEvent?) {
if (newGeohashList == null || newGeohashList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupGeohashList?.id != newGeohashList.id) {
backupGeohashList = newGeohashList
saveAccountSettings()
}
}
fun updateHashtagListTo(newHashtagList: HashtagListEvent?) {
if (newHashtagList == null || newHashtagList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupHashtagList?.id != newHashtagList.id) {
backupHashtagList = newHashtagList
saveAccountSettings()
}
}
fun updateCommunityListTo(newCommunityList: CommunityListEvent?) {
if (newCommunityList == null || newCommunityList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupCommunityList?.id != newCommunityList.id) {
backupCommunityList = newCommunityList
saveAccountSettings()
}
}
fun updateEphemeralChatListTo(newEphemeralChatList: EphemeralChatListEvent?) {
if (newEphemeralChatList == null || newEphemeralChatList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupEphemeralChatList?.id != newEphemeralChatList.id) {
backupEphemeralChatList = newEphemeralChatList
saveAccountSettings()
}
}
fun updateMuteList(newMuteList: MuteListEvent?) {
if (newMuteList == null || newMuteList.tags.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupMuteList?.id != newMuteList.id) {
backupMuteList = newMuteList
saveAccountSettings()
}
}
fun updateAppSpecificData(
appSettings: AppSpecificDataEvent?,
newSyncedSettings: AccountSyncedSettingsInternal,
) {
if (appSettings == null || appSettings.content.isEmpty()) return
// Events might be different objects, we have to compare their ids.
if (backupAppSpecificData?.id != appSettings.id) {
backupAppSpecificData = appSettings
syncedSettings.updateFrom(newSyncedSettings)
saveAccountSettings()
}
}
// ----
// Warning dialogs
// ----
fun setHideDeleteRequestDialog() {
if (!hideDeleteRequestDialog) {
hideDeleteRequestDialog = true
saveAccountSettings()
}
}
fun setHideNIP17WarningDialog() {
if (!hideNIP17WarningDialog) {
hideNIP17WarningDialog = true
saveAccountSettings()
}
}
fun setHideBlockAlertDialog() {
if (!hideBlockAlertDialog) {
hideBlockAlertDialog = true
saveAccountSettings()
}
}
// ---
// donations
// ---
fun hasDonatedInVersion(versionName: String) = hasDonatedInVersion.value.contains(versionName)
fun observeDonatedInVersion(versionName: String) =
hasDonatedInVersion
.map {
it.contains(versionName)
}
fun markDonatedInThisVersion(versionName: String): Boolean {
if (!hasDonatedInVersion.value.contains(versionName)) {
hasDonatedInVersion.update {
it + versionName
}
saveAccountSettings()
return true
}
return false
}
// ----
// last read flows
// ----
fun getLastReadFlow(route: String): StateFlow<Long> = lastReadPerRoute.value[route] ?: addLastRead(route, 0)
private fun addLastRead(
route: String,
timestampInSecs: Long,
): MutableStateFlow<Long> =
MutableStateFlow<Long>(timestampInSecs).also { newFlow ->
lastReadPerRoute.update { it + Pair(route, newFlow) }
saveAccountSettings()
}
fun markAsRead(
route: String,
timestampInSecs: Long,
): Boolean {
val lastTime = lastReadPerRoute.value[route]
return if (lastTime == null) {
addLastRead(route, timestampInSecs)
true
} else if (timestampInSecs > lastTime.value) {
lastTime.tryEmit(timestampInSecs)
saveAccountSettings()
true
} else {
false
}
}
// ---
// attestations
// ---
fun addPendingAttestation(
id: HexKey,
stamp: String,
) {
val current = pendingAttestations.value.get(id)
if (current == null) {
pendingAttestations.update {
it + Pair(id, stamp)
}
saveAccountSettings()
} else {
if (current != stamp) {
pendingAttestations.update {
it + Pair(id, stamp)
}
saveAccountSettings()
}
}
}
// ---
// filters
// ---
fun updateShowSensitiveContent(show: Boolean?): Boolean {
if (syncedSettings.security.updateShowSensitiveContent(show)) {
saveAccountSettings()
return true
}
return false
}
fun updateWarnReports(warnReports: Boolean): Boolean =
if (syncedSettings.security.updateWarnReports(warnReports)) {
saveAccountSettings()
true
} else {
false
}
fun updateFilterSpam(filterSpam: Boolean): Boolean =
if (syncedSettings.security.updateFilterSpam(filterSpam)) {
saveAccountSettings()
true
} else {
false
}
}

View File

@@ -0,0 +1,211 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import java.util.Locale
@Stable
class AccountSyncedSettings(
internalSettings: AccountSyncedSettingsInternal,
) {
val reactions = AccountReactionPreferences(MutableStateFlow(internalSettings.reactions.reactionChoices.toImmutableList()))
val zaps =
AccountZapPreferences(
MutableStateFlow(internalSettings.zaps.zapAmountChoices.toImmutableList()),
MutableStateFlow(internalSettings.zaps.defaultZapType),
)
val languages =
AccountLanguagePreferences(
internalSettings.languages.dontTranslateFrom,
internalSettings.languages.languagePreferences,
internalSettings.languages.translateTo,
)
val security =
AccountSecurityPreferences(
MutableStateFlow(internalSettings.security.showSensitiveContent),
internalSettings.security.warnAboutPostsWithReports,
MutableStateFlow(internalSettings.security.filterSpamFromStrangers),
)
fun toInternal(): AccountSyncedSettingsInternal =
AccountSyncedSettingsInternal(
reactions = AccountReactionPreferencesInternal(reactions.reactionChoices.value),
zaps =
AccountZapPreferencesInternal(
zaps.zapAmountChoices.value,
zaps.defaultZapType.value,
),
languages =
AccountLanguagePreferencesInternal(
languages.dontTranslateFrom,
languages.languagePreferences,
languages.translateTo,
),
security =
AccountSecurityPreferencesInternal(
security.showSensitiveContent.value,
security.warnAboutPostsWithReports,
security.filterSpamFromStrangers.value,
),
)
fun updateFrom(syncedSettingsInternal: AccountSyncedSettingsInternal) {
val newReactionChoices = syncedSettingsInternal.reactions.reactionChoices.toImmutableList()
if (!equalImmutableLists(reactions.reactionChoices.value, newReactionChoices)) {
reactions.reactionChoices.tryEmit(newReactionChoices)
}
val newZapChoices = syncedSettingsInternal.zaps.zapAmountChoices.toImmutableList()
if (!equalImmutableLists(zaps.zapAmountChoices.value, newZapChoices)) {
zaps.zapAmountChoices.tryEmit(newZapChoices)
}
if (zaps.defaultZapType.value != syncedSettingsInternal.zaps.defaultZapType) {
zaps.defaultZapType.tryEmit(syncedSettingsInternal.zaps.defaultZapType)
}
if (languages.dontTranslateFrom != syncedSettingsInternal.languages.dontTranslateFrom) {
languages.dontTranslateFrom = syncedSettingsInternal.languages.dontTranslateFrom
}
if (languages.languagePreferences != syncedSettingsInternal.languages.languagePreferences) {
languages.languagePreferences = syncedSettingsInternal.languages.languagePreferences
}
if (languages.translateTo != syncedSettingsInternal.languages.translateTo) {
languages.translateTo = syncedSettingsInternal.languages.translateTo
}
if (security.showSensitiveContent.value != syncedSettingsInternal.security.showSensitiveContent) {
security.showSensitiveContent.tryEmit(syncedSettingsInternal.security.showSensitiveContent)
}
if (security.filterSpamFromStrangers.value != syncedSettingsInternal.security.filterSpamFromStrangers) {
security.filterSpamFromStrangers.tryEmit(syncedSettingsInternal.security.filterSpamFromStrangers)
}
if (security.warnAboutPostsWithReports != syncedSettingsInternal.security.warnAboutPostsWithReports) {
security.warnAboutPostsWithReports = syncedSettingsInternal.security.warnAboutPostsWithReports
}
}
fun dontTranslateFromFilteredBySpokenLanguages(): Set<String> = languages.dontTranslateFrom - getLanguagesSpokenByUser()
}
@Stable
class AccountReactionPreferences(
var reactionChoices: MutableStateFlow<ImmutableList<String>>,
)
@Stable
class AccountZapPreferences(
var zapAmountChoices: MutableStateFlow<ImmutableList<Long>>,
val defaultZapType: MutableStateFlow<LnZapEvent.ZapType>,
)
@Stable
class AccountLanguagePreferences(
var dontTranslateFrom: Set<String>,
var languagePreferences: Map<String, String>,
var translateTo: String,
) {
// ---
// language services
// ---
fun toggleDontTranslateFrom(languageCode: String) {
dontTranslateFrom =
if (!dontTranslateFrom.contains(languageCode)) {
dontTranslateFrom.plus(languageCode)
} else {
dontTranslateFrom.minus(languageCode)
}
}
fun translateToContains(languageCode: Locale) = translateTo.contains(languageCode.language)
fun updateTranslateTo(languageCode: Locale): Boolean {
if (translateTo != languageCode.language) {
translateTo = languageCode.language
return true
}
return false
}
fun prefer(
source: String,
target: String,
preference: String,
) {
val key = "$source,$target"
if (key !in languagePreferences) {
languagePreferences = languagePreferences + Pair(key, preference)
} else {
if (languagePreferences.get(key) == preference) {
languagePreferences = languagePreferences.minus(key)
} else {
languagePreferences = languagePreferences + Pair(key, preference)
}
}
}
fun preferenceBetween(
source: String,
target: String,
): String? = languagePreferences["$source,$target"]
}
@Stable
class AccountSecurityPreferences(
val showSensitiveContent: MutableStateFlow<Boolean?> = MutableStateFlow(null),
var warnAboutPostsWithReports: Boolean = true,
var filterSpamFromStrangers: MutableStateFlow<Boolean> = MutableStateFlow(true),
) {
fun updateShowSensitiveContent(show: Boolean?): Boolean {
if (showSensitiveContent.value != show) {
showSensitiveContent.update { show }
return true
}
return false
}
fun updateWarnReports(warnReports: Boolean): Boolean =
if (warnAboutPostsWithReports != warnReports) {
warnAboutPostsWithReports = warnReports
true
} else {
false
}
fun updateFilterSpam(filterSpam: Boolean): Boolean =
if (filterSpam != filterSpamFromStrangers.value) {
filterSpamFromStrangers.tryEmit(filterSpam)
true
} else {
false
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import android.content.res.Resources
import androidx.core.os.ConfigurationCompat
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.serialization.Serializable
import java.util.Locale
val DefaultReactions =
listOf(
"\uD83D\uDE80",
"\uD83E\uDEC2",
"\uD83D\uDC40",
"\uD83D\uDE02",
"\uD83C\uDF89",
"\uD83E\uDD14",
"\uD83D\uDE31",
)
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
fun getLanguagesSpokenByUser(): Set<String> {
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
val codedList = mutableSetOf<String>()
for (i in 0 until languageList.size()) {
languageList.get(i)?.let { codedList.add(it.language) }
}
return codedList
}
@Serializable
class AccountSyncedSettingsInternal(
val reactions: AccountReactionPreferencesInternal = AccountReactionPreferencesInternal(),
val zaps: AccountZapPreferencesInternal = AccountZapPreferencesInternal(),
val languages: AccountLanguagePreferencesInternal = AccountLanguagePreferencesInternal(),
val security: AccountSecurityPreferencesInternal = AccountSecurityPreferencesInternal(),
)
@Serializable
class AccountReactionPreferencesInternal(
var reactionChoices: List<String> = DefaultReactions,
)
@Serializable
class AccountZapPreferencesInternal(
var zapAmountChoices: List<Long> = DefaultZapAmounts,
val defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PUBLIC,
)
@Serializable
class AccountLanguagePreferencesInternal(
var dontTranslateFrom: Set<String> = getLanguagesSpokenByUser(),
var languagePreferences: Map<String, String> = mapOf(),
var translateTo: String = Locale.getDefault().language,
)
@Serializable
class AccountSecurityPreferencesInternal(
val showSensitiveContent: Boolean? = null,
var warnAboutPostsWithReports: Boolean = true,
var filterSpamFromStrangers: Boolean = true,
)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,78 +20,120 @@
*/
package com.vitorpamplona.amethyst.model
import android.util.Log
import android.util.LruCache
import androidx.compose.runtime.Stable
import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.RelayStats
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.Dispatchers
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.ui.note.njumpLink
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.flow.MutableStateFlow
data class Spammer(
val pubkeyHex: HexKey,
var duplicatedMessages: Set<HexKey>,
)
var duplicatedEventIds: Set<HexKey>,
var duplicatedEventAddresses: Set<Address>,
) {
fun shouldHide() = duplicatedEventIds.size >= 5 || duplicatedEventAddresses.size >= 5
}
class AntiSpamFilter {
val recentMessages = LruCache<Int, String>(1000)
val recentEventIds = LruCache<Int, String>(2000)
val recentAddressables = LruCache<Int, Address>(2000)
val spamMessages = LruCache<Int, Spammer>(1000)
var active: Boolean = true
fun spamHashCode(event: Event): Int = 31 * event.content.hashCode() + event.tags.contentDeepHashCode()
fun isSpam(
event: Event,
relay: Relay?,
relay: NormalizedRelayUrl?,
): Boolean {
checkNotInMainThread()
if (!active) return false
val idHex = event.id
// if short message, ok
// The idea here is to avoid considering repeated "GM" messages spam.
if (event.content.length < 50) return false
if (event.content.length < 60) return false
// if the message is actually short but because it cites a user/event, the nostr: string is
// really long, make it ok.
// The idea here is to avoid considering repeated "@Bot, command" messages spam, while still
// blocking repeated "lnbc..." invoices or fishing urls
if (event.content.length < 180 && Nip19Bech32.nip19regex.matcher(event.content).find()) return false
if (event.content.length < 180 && event.content.startsWith("nostr:") && Nip19Parser.hasAny(event.content)) return false
// double list strategy:
// if duplicated, it goes into spam. 1000 spam messages are saved into the spam list.
// Considers tags so that same replies to different people don't count.
val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode()
val hash = spamHashCode(event)
if (
(recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null
) {
Log.w(
"Potential SPAM Message",
"${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${relay?.url} ${event.content.replace("\n", " | ")}",
)
// ignores multiple versions of the same addressable.
if (event is AddressableEvent) {
val address = event.address()
// Log down offenders
logOffender(hash, event)
// normal event
if (
(recentAddressables[hash] != null && recentAddressables[hash] != address) ||
(spamMessages[hash] != null && !spamMessages[hash].duplicatedEventAddresses.contains(address))
) {
val existingAddress = recentAddressables[hash]
if (relay != null) {
RelayStats.newSpam(relay.url, "https://njump.me/${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url)}")
val link1 = njumpLink(NAddress.create(existingAddress.kind, existingAddress.pubKeyHex, existingAddress.dTag, relay))
val link2 = njumpLink(NAddress.create(event.kind, event.pubKey, event.dTag(), relay))
Log.w("Duplicated/SPAM", "${relay?.url} $link1 $link2")
// Log down offenders
val spammer = logOffender(hash, event)
if (spammer.shouldHide() && relay != null) {
Amethyst.instance.relayStats
.get(relay)
.newSpam("$link1 $link2")
}
flowSpam.tryEmit(AntiSpamState(this))
return true
}
liveSpam.invalidateData()
recentAddressables.put(hash, address)
} else {
// normal event
if (
(recentEventIds[hash] != null && recentEventIds[hash] != event.id) ||
(spamMessages[hash] != null && !spamMessages[hash].duplicatedEventIds.contains(event.id))
) {
val existingEvent = recentEventIds[hash]
return true
val link1 = njumpLink(NEvent.create(existingEvent, null, null, relay))
val link2 = njumpLink(NEvent.create(event.id, null, null, relay))
Log.w("Duplicated/SPAM", "${relay?.url} $link1 $link2")
// Log down offenders
val spammer = logOffender(hash, event)
if (spammer.shouldHide() && relay != null) {
Amethyst.instance.relayStats
.get(relay)
.newSpam("$link1 $link2")
}
flowSpam.tryEmit(AntiSpamState(this))
return true
}
recentEventIds.put(hash, event.id)
}
recentMessages.put(hash, idHex)
return false
}
@@ -99,36 +141,37 @@ class AntiSpamFilter {
private fun logOffender(
hashCode: Int,
event: Event,
) {
if (spamMessages.get(hashCode) == null) {
spamMessages.put(hashCode, Spammer(event.pubKey, setOf(recentMessages[hashCode], event.id)))
): Spammer {
val spammer = spamMessages.get(hashCode)
if (spammer == null) {
val newSpammer =
if (event is AddressableEvent) {
Spammer(
pubkeyHex = event.pubKey,
duplicatedEventIds = setOf(),
duplicatedEventAddresses = setOf(recentAddressables[hashCode], event.address()),
)
} else {
Spammer(
pubkeyHex = event.pubKey,
duplicatedEventIds = setOf(recentEventIds[hashCode], event.id),
duplicatedEventAddresses = setOf(),
)
}
spamMessages.put(hashCode, newSpammer)
return newSpammer
} else {
val spammer = spamMessages.get(hashCode)
spammer.duplicatedMessages += event.id
}
}
val liveSpam: AntiSpamLiveData = AntiSpamLiveData(this)
}
@Stable
class AntiSpamLiveData(
val cache: AntiSpamFilter,
) : LiveData<AntiSpamState>(AntiSpamState(cache)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(300, Dispatchers.IO)
fun invalidateData() {
checkNotInMainThread()
bundler.invalidate {
checkNotInMainThread()
if (hasActiveObservers()) {
postValue(AntiSpamState(cache))
if (event is AddressableEvent) {
spammer.duplicatedEventAddresses += event.address()
} else {
spammer.duplicatedEventIds += event.id
}
return spammer
}
}
val flowSpam = MutableStateFlow<AntiSpamState>(AntiSpamState(this))
}
class AntiSpamState(

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -21,141 +21,115 @@
package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toNote
import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.coroutines.Dispatchers
import com.vitorpamplona.amethyst.ui.dal.ListChange
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.utils.cache.LargeCache
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import java.lang.ref.WeakReference
@Stable
class PublicChatChannel(
idHex: String,
) : Channel(idHex) {
var info = ChannelCreateEvent.ChannelData(null, null, null)
fun updateChannelInfo(
creator: User,
channelInfo: ChannelCreateEvent.ChannelData,
updatedAt: Long,
) {
this.info = channelInfo
super.updateChannelInfo(creator, updatedAt)
}
override fun toBestDisplayName(): String = info.name ?: super.toBestDisplayName()
override fun summary(): String? = info.about
override fun profilePicture(): String? {
if (info.picture.isNullOrBlank()) return super.profilePicture()
return info.picture ?: super.profilePicture()
}
override fun anyNameStartsWith(prefix: String): Boolean = listOfNotNull(info.name, info.about).filter { it.contains(prefix, true) }.isNotEmpty()
}
@Stable
class LiveActivitiesChannel(
val address: ATag,
) : Channel(address.toTag()) {
var info: LiveActivitiesEvent? = null
override fun idNote() = address.toNAddr()
override fun idDisplayNote() = idNote().toShortenHex()
fun address() = address
fun updateChannelInfo(
creator: User,
channelInfo: LiveActivitiesEvent,
updatedAt: Long,
) {
this.info = channelInfo
super.updateChannelInfo(creator, updatedAt)
}
override fun toBestDisplayName(): String = info?.title() ?: super.toBestDisplayName()
override fun summary(): String? = info?.summary()
override fun profilePicture(): String? = info?.image()?.ifBlank { null }
override fun anyNameStartsWith(prefix: String): Boolean =
listOfNotNull(info?.title(), info?.summary())
.filter { it.contains(prefix, true) }
.isNotEmpty()
}
@Stable
abstract class Channel(
val idHex: String,
) {
var creator: User? = null
var updatedMetadataAt: Long = 0
abstract class Channel : NotesGatherer {
val notes = LargeCache<HexKey, Note>()
var lastNoteCreatedAt: Long = 0
var lastNote: Note? = null
open fun id() = Hex.decode(idHex)
private var relays = mapOf<NormalizedRelayUrl, Counter>()
open fun idNote() = id().toNote()
private var changesFlow: WeakReference<MutableSharedFlow<ListChange<Note>>> = WeakReference(null)
open fun idDisplayNote() = idNote().toShortenHex()
open fun toBestDisplayName(): String = idDisplayNote()
open fun summary(): String? = null
open fun creatorName(): String? = creator?.toBestDisplayName()
open fun profilePicture(): String? = creator?.info?.banner
open fun updateChannelInfo(
creator: User,
updatedAt: Long,
) {
this.creator = creator
this.updatedMetadataAt = updatedAt
live.invalidateData()
fun changesFlow(): MutableSharedFlow<ListChange<Note>> {
val current = changesFlow.get()
if (current != null) return current
val new = MutableSharedFlow<ListChange<Note>>(0, 10, BufferOverflow.DROP_OLDEST)
changesFlow = WeakReference(new)
return new
}
fun addNote(note: Note) {
notes.put(note.idHex, note)
open fun participatingAuthors(maxTimeLimit: Long) =
notes.mapNotNull { key, value ->
val createdAt = value.createdAt()
if (createdAt != null && createdAt > maxTimeLimit) {
value.author
} else {
null
}
}
if ((note.createdAt() ?: 0) > lastNoteCreatedAt) {
lastNoteCreatedAt = note.createdAt() ?: 0
abstract fun toBestDisplayName(): String
open fun relays(): Set<NormalizedRelayUrl> =
relays.keys
.toSortedSet { o1, o2 ->
val o1Count = relays[o1]?.number ?: 0
val o2Count = relays[o2]?.number ?: 0
o2Count.compareTo(o1Count) // descending
}
fun updateChannelInfo() {
flowSet?.metadata?.invalidateData()
}
@Synchronized
fun addRelaySync(briefInfo: NormalizedRelayUrl) {
if (briefInfo !in relays) {
relays = relays + Pair(briefInfo, Counter(1))
}
}
fun removeNote(note: Note) {
notes.remove(note.idHex)
fun addRelay(relay: NormalizedRelayUrl) {
val counter = relays[relay]
if (counter != null) {
counter.number++
} else {
addRelaySync(relay)
}
}
fun removeNote(noteHex: String) {
notes.remove(noteHex)
fun addNote(
note: Note,
relay: NormalizedRelayUrl? = null,
) {
if (!notes.containsKey(note.idHex)) {
notes.put(note.idHex, note)
note.addGatherer(this)
if ((note.createdAt() ?: 0L) > (lastNote?.createdAt() ?: 0L)) {
lastNote = note
}
if (relay != null) {
addRelay(relay)
}
changesFlow.get()?.tryEmit(ListChange.Addition(note))
flowSet?.notes?.invalidateData()
}
}
abstract fun anyNameStartsWith(prefix: String): Boolean
override fun removeNote(note: Note) {
if (notes.containsKey(note.idHex)) {
notes.remove(note.idHex)
note.removeGatherer(this)
// Observers line up here.
val live: ChannelLiveData = ChannelLiveData(this)
if (note == lastNote) {
lastNote = notes.values().sortedWith(DefaultFeedOrder).firstOrNull()
}
fun pruneOldAndHiddenMessages(account: Account): Set<Note> {
changesFlow.get()?.tryEmit(ListChange.Deletion(note))
flowSet?.notes?.invalidateData()
}
}
fun pruneOldMessages(): Set<Note> {
val important =
notes
.filter { key, it ->
it.author?.let { author -> account.isHidden(author) } == false
}.sortedWith(DefaultFeedOrder)
.values()
.sortedWith(DefaultFeedOrder)
.take(500)
.toSet()
@@ -163,38 +137,87 @@ abstract class Channel(
toBeRemoved.forEach { notes.remove(it.idHex) }
changesFlow.get()?.tryEmit(ListChange.SetDeletion(toBeRemoved.toSet()))
flowSet?.notes?.invalidateData()
return toBeRemoved.toSet()
}
}
class ChannelLiveData(
val channel: Channel,
) : LiveData<ChannelState>(ChannelState(channel)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(300, Dispatchers.IO)
fun pruneHiddenMessages(account: Account): Set<Note> {
val hidden =
notes
.filter { key, it ->
it.author?.let { author -> account.isHidden(author) } == true
}.toSet()
fun invalidateData() {
checkNotInMainThread()
hidden.forEach { notes.remove(it.idHex) }
bundler.invalidate {
checkNotInMainThread()
if (hasActiveObservers()) {
postValue(ChannelState(channel))
changesFlow.get()?.tryEmit(ListChange.SetDeletion(hidden))
flowSet?.notes?.invalidateData()
return hidden.toSet()
}
var flowSet: ChannelFlowSet? = null
@Synchronized
fun createOrDestroyFlowSync(create: Boolean) {
if (create) {
if (flowSet == null) {
flowSet = ChannelFlowSet(this)
}
} else {
if (flowSet != null && flowSet?.isInUse() == false) {
flowSet = null
}
}
}
override fun onActive() {
super.onActive()
NostrSingleChannelDataSource.add(channel)
fun flow(): ChannelFlowSet {
if (flowSet == null) {
createOrDestroyFlowSync(true)
}
return flowSet!!
}
override fun onInactive() {
super.onInactive()
NostrSingleChannelDataSource.remove(channel)
fun clearFlow() {
if (flowSet != null && flowSet?.isInUse() == false) {
createOrDestroyFlowSync(false)
}
}
}
data class Counter(
var number: Int = 0,
)
@Stable
class ChannelFlowSet(
u: Channel,
) {
// Observers line up here.
val metadata = ChannelFlow(u)
val notes = ChannelFlow(u)
fun isInUse(): Boolean =
metadata.hasObservers() ||
notes.hasObservers()
}
class ChannelFlow(
val channel: Channel,
) {
val stateFlow = MutableStateFlow(ChannelState(channel))
fun invalidateData() {
stateFlow.tryEmit(ChannelState(channel))
}
fun hasObservers() = stateFlow.subscriptionCount.value > 0
}
class ChannelState(
val channel: Channel,
)

View File

@@ -1,93 +0,0 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.utils.TimeUtils
@Stable
class Chatroom {
var authors: Set<User> = setOf()
var roomMessages: Set<Note> = setOf()
var subject: String? = null
var subjectCreatedAt: Long? = null
@Synchronized
fun addMessageSync(msg: Note) {
checkNotInMainThread()
if (msg !in roomMessages) {
roomMessages = roomMessages + msg
msg.author?.let { author ->
if (author !in authors) {
authors += author
}
}
val newSubject = msg.event?.subject()
if (newSubject != null && (msg.createdAt() ?: 0) > (subjectCreatedAt ?: 0)) {
subject = newSubject
subjectCreatedAt = msg.createdAt()
}
}
}
@Synchronized
fun removeMessageSync(msg: Note) {
checkNotInMainThread()
if (msg in roomMessages) {
roomMessages = roomMessages - msg
roomMessages
.filter { it.event?.subject() != null }
.sortedBy { it.createdAt() }
.lastOrNull()
?.let {
subject = it.event?.subject()
subjectCreatedAt = it.createdAt()
}
}
}
fun senderIntersects(keySet: Set<HexKey>): Boolean = authors.any { it.pubkeyHex in keySet }
fun pruneMessagesToTheLatestOnly(): Set<Note> {
val sorted = roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
val toKeep =
if ((sorted.firstOrNull()?.createdAt() ?: 0) > TimeUtils.oneWeekAgo()) {
// Recent messages, keep last 100
sorted.take(100).toSet()
} else {
// Old messages, keep the last one.
sorted.take(1).toSet()
} + sorted.filter { it.liveSet?.isInUse() ?: false }
val toRemove = roomMessages.minus(toKeep)
roomMessages = toKeep
return toRemove
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
object Constants {
val nos = RelayUrlNormalizer.normalize("wss://nos.lol")
val mom = RelayUrlNormalizer.normalize("wss://nostr.mom")
val primal = RelayUrlNormalizer.normalize("wss://relay.primal.net")
val damus = RelayUrlNormalizer.normalize("wss://relay.damus.io")
val wine = RelayUrlNormalizer.normalize("wss://nostr.wine")
val band = RelayUrlNormalizer.normalize("wss://relay.nostr.band")
val where = RelayUrlNormalizer.normalize("wss://relay.noswhere.com")
val elites = RelayUrlNormalizer.normalize("wss://nostrelites.org")
val bitcoiner = RelayUrlNormalizer.normalize("wss://nostr.bitcoiner.social")
val oxtr = RelayUrlNormalizer.normalize("wss://nostr.oxtr.dev")
val nostoday = RelayUrlNormalizer.normalize("wss://search.nos.today")
val auth = RelayUrlNormalizer.normalize("wss://auth.nostr1.com")
val oxchat = RelayUrlNormalizer.normalize("wss://relay.0xchat.com")
val purplepages = RelayUrlNormalizer.normalize("wss://purplepag.es")
val coracle = RelayUrlNormalizer.normalize("wss://indexer.coracle.social")
val userkinds = RelayUrlNormalizer.normalize("wss://user.kindpag.es")
val bootstrapInbox = setOf(band, damus, primal, mom, nos, bitcoiner, oxtr)
val eventFinderRelays = setOf(band, wine, damus, primal, mom, nos, bitcoiner, oxtr)
val defaultSearchRelaySet = setOf(band, wine, where)
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -33,7 +33,9 @@ import com.vitorpamplona.amethyst.commons.hashtags.Btc
import com.vitorpamplona.amethyst.commons.hashtags.Cashu
import com.vitorpamplona.amethyst.commons.hashtags.Coffee
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.commons.hashtags.Flowerstr
import com.vitorpamplona.amethyst.commons.hashtags.Footstr
import com.vitorpamplona.amethyst.commons.hashtags.Gamestr
import com.vitorpamplona.amethyst.commons.hashtags.Grownostr
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
import com.vitorpamplona.amethyst.commons.hashtags.Mate
@@ -47,22 +49,24 @@ import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
import com.vitorpamplona.amethyst.ui.components.HashTag
import com.vitorpamplona.amethyst.ui.components.RenderRegular
import com.vitorpamplona.amethyst.ui.components.RenderTextParagraph
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
@Preview
@Composable
fun RenderHashTagIcons() {
val nav: (String) -> Unit = {}
fun RenderHashTagIconsPreview() {
ThemeComparisonColumn {
RenderRegular(
"Testing rendering of hashtags: #Bitcoin, #nostr, #lightning, #zap, #amethyst, #cashu, #plebs, #coffee, #skullofsatoshi, #grownostr, #footstr, #tunestr, #weed, #mate",
"Testing rendering of hashtags: #flowerstr #Bitcoin, #nostr, #lightning, #zap, #amethyst, #cashu, #plebs, #coffee, #skullofsatoshi, #grownostr, #footstr, #tunestr, #weed, #mate, #gamestr, #gamechain",
EmptyTagList,
) { word, state ->
when (word) {
is HashTagSegment -> HashTag(word, nav)
is RegularTextSegment -> Text(word.segmentText)
) { paragraph, state, spaceWidth, modifier ->
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
when (word) {
is HashTagSegment -> HashTag(word, EmptyNav())
is RegularTextSegment -> Text(word.segmentText)
}
}
}
}
@@ -81,9 +85,11 @@ fun checkForHashtagWithIcon(tag: String): HashtagIcon? =
"skullofsatoshi" -> skull
"grownostr", "gardening", "garden" -> growstr
"footstr" -> footstr
"flowerstr" -> flowerstr
"tunestr", "music", "nowplaying" -> tunestr
"mate", "matechain", "matestr" -> matestr
"weed", "weedstr", "420", "cannabis", "marijuana" -> weed
"gamestr", "gaming", "gamechain" -> gamestr
else -> null
}
@@ -98,9 +104,11 @@ val coffee = HashtagIcon(CustomHashTagIcons.Coffee, "Coffee", Modifier.padding(s
val skull = HashtagIcon(CustomHashTagIcons.Skull, "SkullofSatoshi", Modifier.padding(start = 1.dp, bottom = 1.dp, top = 1.dp))
val growstr = HashtagIcon(CustomHashTagIcons.Grownostr, "GrowNostr", Modifier.padding(start = 1.dp, bottom = 1.dp, top = 1.dp))
val footstr = HashtagIcon(CustomHashTagIcons.Footstr, "Footstr", Modifier.padding(start = 2.dp, bottom = 1.dp, top = 1.dp))
val flowerstr = HashtagIcon(CustomHashTagIcons.Flowerstr, "Flowerstr", Modifier.padding(start = 2.dp, bottom = 1.dp, top = 1.dp))
val tunestr = HashtagIcon(CustomHashTagIcons.Tunestr, "Tunestr", Modifier.padding(start = 1.dp, bottom = 1.dp, top = 1.dp))
val weed = HashtagIcon(CustomHashTagIcons.Weed, "Weed", Modifier.padding(start = 1.dp, bottom = 0.dp, top = 0.dp))
val matestr = HashtagIcon(CustomHashTagIcons.Mate, "Mate", Modifier.padding(start = 1.dp, bottom = 0.dp, top = 0.dp))
val gamestr = HashtagIcon(CustomHashTagIcons.Gamestr, "GameStr", Modifier.padding(start = 1.dp, bottom = 0.dp, top = 0.dp))
@Immutable
class HashtagIcon(

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.quartz.utils.cache.CacheOperations
import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentSkipListMap
import java.util.function.BiConsumer
class LargeSoftCache<K, V> : CacheOperations<K, V> {
protected val cache = ConcurrentSkipListMap<K, WeakReference<V>>()
fun keys() = cache.keys
fun get(key: K): V? {
val softRef = cache.get(key)
if (softRef == null) return null
val value = softRef.get()
return if (value != null) {
value
} else {
cache.remove(key, softRef)
null
}
}
fun remove(key: K) = cache.remove(key)
fun removeIf(
key: K,
value: WeakReference<V>,
) = cache.remove(key, value)
override fun size() = cache.size
fun isEmpty() = cache.isEmpty()
fun clear() = cache.clear()
fun containsKey(key: K) = cache.containsKey(key)
/**
* Puts an object into the cache with a specified key.
* The object is stored as a SoftReference.
*
* @param key The key to associate with the object.
* @param value The object to cache.
*/
fun put(
key: K,
value: V,
) {
cache.put(key, WeakReference(value))
}
/**
* Retrieves an object from the cache using its key.
* Returns the object if it's still available (not garbage collected),
* otherwise returns null. If the object has been garbage collected,
* its entry is also removed from the cache.
*
* @param key The key of the object to retrieve.
* @return The cached object, or null if it's no longer available.
*/
fun getOrCreate(
key: K,
builder: (key: K) -> V,
): V {
val softRef = cache[key]
return if (softRef == null) {
val newObject = builder(key)
cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
} else {
val value = softRef.get()
if (value != null) {
value
} else {
// removes first to make sure the putIfAbsent works.
// another thread may put in between
cache.remove(key, softRef)
val newObject = builder(key)
return cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
}
}
}
/**
* Proactively cleans up the cache by removing entries whose weakly referenced
* objects have been garbage collected. While `get` handles cleanup on access,
* this method can be called periodically or when memory pressure is high.
*/
fun cleanUp() {
val keysToRemove = mutableMapOf<K, WeakReference<V>>()
cache.forEach { key, softRef ->
if (softRef.get() == null) {
keysToRemove.put(key, softRef)
}
}
keysToRemove.forEach { key, value ->
cache.remove(key, value)
}
}
override fun forEach(consumer: BiConsumer<K, V>) {
cache.forEach(BiConsumerWrapper(this, consumer))
}
override fun forEach(
from: K,
to: K,
consumer: BiConsumer<K, V>,
) {
cache
.subMap(from, true, to, true)
.forEach(BiConsumerWrapper(this, consumer))
}
class BiConsumerWrapper<K, V>(
val cache: LargeSoftCache<K, V>,
val inner: BiConsumer<K, V>,
) : BiConsumer<K, WeakReference<V>> {
override fun accept(
k: K,
ref: WeakReference<V>,
) {
val value = ref.get()
if (value == null) {
cache.removeIf(k, ref)
} else {
inner.accept(k, value)
}
}
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.utils.cache.CacheCollectors
const val START_KEY = "0000000000000000000000000000000000000000000000000000000000000000"
const val END_KEY = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
const val START_D_TAG = ""
const val END_D_TAG = "\uFFFF\uFFFF\uFFFF\uFFFF"
fun kindStart(
kind: Int,
pubKey: HexKey,
) = Address(kind, pubKey, "")
fun kindEnd(
kind: Int,
pubKey: HexKey,
) = Address(kind, pubKey, END_D_TAG)
fun kindStart(kind: Int) = kindStart(kind, START_KEY)
fun kindEnd(kind: Int) = kindEnd(kind, END_KEY)
val ACCEPT_ALL_FILTER = CacheCollectors.BiFilter<Address, AddressableNote> { key, note -> true }
fun LargeSoftCache<Address, AddressableNote>.filter(
kind: Int,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): List<AddressableNote> = filter(kindStart(kind), kindEnd(kind), consumer)
fun LargeSoftCache<Address, AddressableNote>.filter(
kinds: List<Int>,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): List<AddressableNote> {
val set = mutableSetOf<AddressableNote>()
kinds.forEach {
set.addAll(filter(kindStart(it), kindEnd(it), consumer))
}
return set.toList()
}
fun LargeSoftCache<Address, AddressableNote>.filter(
kind: Int,
pubKey: HexKey,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): List<AddressableNote> = filter(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer)
fun LargeSoftCache<Address, AddressableNote>.filterIntoSet(
kind: Int,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): Set<AddressableNote> = filterIntoSet(kindStart(kind), kindEnd(kind), consumer)
fun LargeSoftCache<Address, AddressableNote>.filterIntoSet(
kinds: List<Int>,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): Set<AddressableNote> {
val set = mutableSetOf<AddressableNote>()
kinds.forEach {
set.addAll(filterIntoSet(kindStart(it), kindEnd(it), consumer))
}
return set
}
fun LargeSoftCache<Address, AddressableNote>.filterIntoSet(
kind: Int,
pubKey: HexKey,
consumer: CacheCollectors.BiFilter<Address, AddressableNote> = ACCEPT_ALL_FILTER,
): Set<AddressableNote> = filterIntoSet(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer)
fun <R> LargeSoftCache<Address, AddressableNote>.mapNotNullIntoSet(
kind: Int,
consumer: CacheCollectors.BiMapper<Address, AddressableNote, R?>,
): Set<R> = mapNotNullIntoSet(kindStart(kind), kindEnd(kind), consumer)
fun <R> LargeSoftCache<Address, AddressableNote>.mapNotNullIntoSet(
kind: Int,
pubKey: HexKey,
consumer: CacheCollectors.BiMapper<Address, AddressableNote, R?>,
): Set<R> = mapNotNullIntoSet(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer)

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import android.util.LruCache
interface MutableMediaAspectRatioCache {
fun get(url: String): Float?
fun add(
url: String,
width: Int,
height: Int,
)
}
object MediaAspectRatioCache : MutableMediaAspectRatioCache {
val mediaAspectRatioCacheByUrl = LruCache<String, Float>(1000)
override fun get(url: String): Float? = mediaAspectRatioCacheByUrl.get(url)
override fun add(
url: String,
width: Int,
height: Int,
) {
if (height > 1) {
mediaAspectRatioCacheByUrl.put(url, width.toFloat() / height.toFloat())
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,11 +20,13 @@
*/
package com.vitorpamplona.amethyst.model
import android.util.Log
import androidx.compose.runtime.Stable
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.utils.Log
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
@@ -40,11 +42,13 @@ import kotlinx.collections.immutable.toImmutableMap
JsonSubTypes.Type(value = Bundle::class, name = "Bundle"),
JsonSubTypes.Type(value = VisionPrescription::class, name = "VisionPrescription"),
)
@Stable
open class Resource(
var resourceType: String? = null,
var id: String = "",
)
@Stable
class Practitioner(
resourceType: String? = null,
id: String = "",
@@ -53,6 +57,7 @@ class Practitioner(
var gender: String? = null,
) : Resource(resourceType, id)
@Stable
class Patient(
resourceType: String? = null,
id: String = "",
@@ -61,6 +66,7 @@ class Patient(
var gender: String? = null,
) : Resource(resourceType, id)
@Stable
class HumanName(
var use: String? = null,
var family: String? = null,
@@ -69,6 +75,7 @@ class HumanName(
fun assembleName(): String = given.joinToString(" ") + " " + family
}
@Stable
class Bundle(
resourceType: String? = null,
id: String = "",
@@ -77,6 +84,7 @@ class Bundle(
var entry: List<Resource> = arrayListOf(),
) : Resource(resourceType, id)
@Stable
class VisionPrescription(
resourceType: String? = null,
id: String = "",
@@ -87,15 +95,31 @@ class VisionPrescription(
var dateWritten: String? = null,
var prescriber: Reference? = Reference(),
var lensSpecification: List<LensSpecification> = arrayListOf(),
) : Resource(resourceType, id)
) : Resource(resourceType, id) {
fun glasses() = lensSpecification.filter { it.product == "lens" }
fun contacts() = lensSpecification.filter { it.product == "contacts" }
fun glassesRightEyes() = lensSpecification.filter { it.product == "lens" && it.eye == "right" }
fun glassesLeftEyes() = lensSpecification.filter { it.product == "lens" && it.eye == "left" }
fun contactsRightEyes() = lensSpecification.filter { it.product == "contacts" && it.eye == "right" }
fun contactsLeftEyes() = lensSpecification.filter { it.product == "contacts" && it.eye == "left" }
}
@Stable
class LensSpecification(
var product: String? = null,
var eye: String? = null,
var sphere: Double? = null,
var cylinder: Double? = null,
var axis: Double? = null,
var pd: Double? = null,
var interAdd: Double? = null,
var add: Double? = null,
var prism: List<Prism> = arrayListOf(),
var prism: Prism? = null,
// contact lenses
var power: Double? = null,
var backCurve: Double? = null,
@@ -105,6 +129,7 @@ class LensSpecification(
var note: String? = null,
)
@Stable
class Prism(
var amount: Double? = null,
var base: String? = null,
@@ -136,7 +161,7 @@ fun parseResourceBundleOrNull(json: String): FhirElementDatabase? {
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
return try {
val resource = mapper.readValue(json, Resource::class.java)
val resource = mapper.readValue<Resource>(json)
val db =
when (resource) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.nip01Core.core.HexKey
class ParticipantListBuilder {
private fun addFollowsThatDirectlyParticipateOnToSet(
@@ -96,7 +96,7 @@ class ParticipantListBuilder {
it.replyTo?.forEach { addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet) }
}
LocalCache.getChannelIfExists(baseNote.idHex)?.notes?.forEach { key, it ->
LocalCache.getPublicChatChannelIfExists(baseNote.idHex)?.notes?.forEach { key, it ->
addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet)
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,12 +20,14 @@
*/
package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent
import com.vitorpamplona.quartz.events.RepostEvent
import kotlin.time.measureTimedValue
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet
class ThreadAssembler {
private fun searchRoot(
@@ -34,13 +36,15 @@ class ThreadAssembler {
): Note? {
if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note
if (note.event is RepostEvent || note.event is GenericRepostEvent) return note
val noteEvent = note.event
if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) return note
testedNotes.add(note)
val markedAsRoot =
note.event
?.tags()
noteEvent
?.tags
?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }
?.getOrNull(1)
if (markedAsRoot != null) {
@@ -50,19 +54,21 @@ class ThreadAssembler {
}
}
val hasNoReplyTo = note.replyTo?.reversed()?.firstOrNull { it.replyTo?.isEmpty() == true }
val hasNoReplyTo =
note.replyTo?.lastOrNull {
it.replyTo?.isEmpty() == true
}
if (hasNoReplyTo != null) return hasNoReplyTo
// recursive
val roots =
note.replyTo
?.map {
if (it !in testedNotes) {
searchRoot(it, testedNotes)
} else {
null
}
}?.filterNotNull()
note.replyTo?.mapNotNull {
if (it !in testedNotes) {
searchRoot(it, testedNotes)
} else {
null
}
}
if (roots != null && roots.isNotEmpty()) {
return roots[0]
@@ -71,32 +77,62 @@ class ThreadAssembler {
return null
}
fun findThreadFor(noteId: String): Set<Note> {
@Stable
class ThreadInfo(
val root: Note,
val allNotes: ImmutableSet<Note>,
)
fun findRoot(noteId: String): Note? {
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
return if (note.event != null) {
val thread = OnlyLatestVersionSet()
searchRoot(note, thread) ?: note
} else {
note
}
}
fun findThreadFor(noteId: String): ThreadInfo? {
checkNotInMainThread()
val (result, elapsed) =
measureTimedValue {
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return emptySet<Note>()
val note = LocalCache.checkGetOrCreateNote(noteId) ?: return null
if (note.event != null) {
val thread = OnlyLatestVersionSet()
return if (note.event != null) {
val thread = OnlyLatestVersionSet()
val threadRoot = searchRoot(note, thread) ?: note
val threadRoot = searchRoot(note, thread) ?: note
loadDown(threadRoot, thread)
// adds the replies of the note in case the search for Root
// did not added them.
note.replies.forEach { loadDown(it, thread) }
loadUp(note, thread)
thread
} else {
setOf(note)
}
}
loadDown(threadRoot, thread)
// adds the replies of the note in case the search for Root
// did not added them.
note.replies.forEach { loadDown(it, thread) }
println("Model Refresh: Thread loaded in $elapsed")
ThreadInfo(
root = note,
allNotes = thread.toImmutableSet(),
)
} else {
ThreadInfo(
root = note,
allNotes = setOf(note).toImmutableSet(),
)
}
}
return result
fun loadUp(
note: Note,
thread: MutableSet<Note>,
) {
if (note !in thread) {
thread.add(note)
note.replyTo?.forEach { loadUp(it, thread) }
}
}
fun loadDown(
@@ -112,7 +148,7 @@ class ThreadAssembler {
}
class OnlyLatestVersionSet : MutableSet<Note> {
val map = hashMapOf<ATag, Long>()
val map = hashMapOf<Address, Long>()
val set = hashSetOf<Note>()
override fun add(element: Note): Boolean {
@@ -129,7 +165,7 @@ class OnlyLatestVersionSet : MutableSet<Note> {
}
private fun innerAdd(
address: ATag,
address: Address,
element: Note,
loadedCreatedAt: Long,
): Boolean {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -20,9 +20,10 @@
*/
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.GenericRepostEvent
import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import java.lang.Long.min
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@@ -54,13 +55,23 @@ object ThreadLevelCalculator {
now: Long,
): LevelSignature {
val replyTo = note.replyTo
// estimates the min date by replies if it doesn't exist.
val createdAt =
note.createdAt() ?: min(
note.replies.minOfOrNull { it.createdAt() ?: now } ?: now,
note.reactions.values.minOfOrNull { it.minOfOrNull { it.createdAt() ?: now } ?: now } ?: now,
)
val noteAuthor = note.author
if (
note.event is RepostEvent || note.event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()
) {
return LevelSignature(
signature = "/" + formattedDateTime(note.createdAt() ?: 0) + note.idHex.substring(0, 8) + ";",
createdAt = note.createdAt(),
author = note.author,
signature = "/" + formattedDateTime(createdAt) + note.idHex.substring(0, 8) + ";",
createdAt = createdAt,
author = noteAuthor,
)
}
@@ -86,23 +97,21 @@ object ThreadLevelCalculator {
val parentSignature = parent?.signature?.removeSuffix(";") ?: ""
val threadOrder =
if (parent?.author == note.author && note.createdAt() != null) {
if (noteAuthor != null && parent?.author == noteAuthor) {
// author of the thread first, in **ascending** order
"9" +
formattedDateTime((parent?.createdAt ?: 0) + (now - (note.createdAt() ?: 0))) +
note.idHex.substring(0, 8)
} else if (note.author?.pubkeyHex == account.pubkeyHex) {
"8" + formattedDateTime(note.createdAt() ?: 0) + note.idHex.substring(0, 8) // my replies
} else if (note.author?.pubkeyHex in accountFollowingSet) {
"7" + formattedDateTime(note.createdAt() ?: 0) + note.idHex.substring(0, 8) // my follows replies.
"9" + formattedDateTime((parent.createdAt ?: 0) + (now - createdAt)) + note.idHex.substring(0, 8)
} else if (noteAuthor != null && noteAuthor.pubkeyHex == account.pubkeyHex) {
"8" + formattedDateTime(createdAt) + note.idHex.substring(0, 8) // my replies
} else if (noteAuthor != null && noteAuthor.pubkeyHex in accountFollowingSet) {
"7" + formattedDateTime(createdAt) + note.idHex.substring(0, 8) // my follows replies.
} else {
"0" + formattedDateTime(note.createdAt() ?: 0) + note.idHex.substring(0, 8) // everyone else.
"0" + formattedDateTime(createdAt) + note.idHex.substring(0, 8) // everyone else.
}
val mySignature =
LevelSignature(
signature = parentSignature + "/" + threadOrder + ";",
createdAt = note.createdAt(),
signature = "$parentSignature/$threadOrder;",
createdAt = createdAt,
author = note.author,
)
@@ -118,11 +127,17 @@ object ThreadLevelCalculator {
if (
note.event is RepostEvent || note.event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()
) {
cachedLevels[note] = 0
return 0
}
return replyTo.maxOf {
cachedLevels[it] ?: replyLevel(it, cachedLevels).apply { cachedLevels.put(it, this) }
} + 1
val thisLevel =
replyTo.maxOf {
cachedLevels[it] ?: replyLevel(it, cachedLevels)
} + 1
cachedLevels[note] = thisLevel
return thisLevel
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -22,9 +22,11 @@ package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.R
import kotlinx.serialization.Serializable
@Stable
data class Settings(
@Serializable
data class UiSettings(
val theme: ThemeType = ThemeType.SYSTEM,
val preferredLanguage: String? = null,
val automaticallyShowImages: ConnectivityType = ConnectivityType.ALWAYS,
@@ -35,6 +37,7 @@ data class Settings(
val dontShowPushNotificationSelector: Boolean = false,
val dontAskForNotificationPermissions: Boolean = false,
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
val gallerySet: ProfileGalleryType = ProfileGalleryType.CLASSIC,
)
enum class ThemeType(
@@ -75,6 +78,14 @@ enum class FeatureSetType(
PERFORMANCE(2, R.string.ui_feature_set_type_performance),
}
enum class ProfileGalleryType(
val screenCode: Int,
val resourceId: Int,
) {
CLASSIC(0, R.string.gallery_type_classic),
MODERN(1, R.string.gallery_type_modern),
}
fun parseConnectivityType(code: Boolean?): ConnectivityType =
when (code) {
ConnectivityType.ALWAYS.prefCode -> ConnectivityType.ALWAYS
@@ -105,6 +116,15 @@ fun parseFeatureSetType(screenCode: Int): FeatureSetType =
}
}
fun parseGalleryType(screenCode: Int): ProfileGalleryType =
when (screenCode) {
ProfileGalleryType.CLASSIC.screenCode -> ProfileGalleryType.CLASSIC
ProfileGalleryType.MODERN.screenCode -> ProfileGalleryType.MODERN
else -> {
ProfileGalleryType.CLASSIC
}
}
enum class BooleanType(
val prefCode: Boolean?,
val screenCode: Int,
@@ -131,3 +151,33 @@ fun parseBooleanType(screenCode: Int): BooleanType =
BooleanType.ALWAYS
}
}
enum class WarningType(
val prefCode: Boolean?,
val screenCode: Int,
val resourceId: Int,
) {
WARN(null, 0, R.string.content_warning_see_warnings_option),
SHOW(true, 1, R.string.content_warning_show_all_sensitive_content_option),
HIDE(false, 2, R.string.content_warning_hide_all_sensitive_content_option),
}
fun parseWarningType(screenCode: Int): WarningType =
when (screenCode) {
WarningType.WARN.screenCode -> WarningType.WARN
WarningType.SHOW.screenCode -> WarningType.SHOW
WarningType.HIDE.screenCode -> WarningType.HIDE
else -> {
WarningType.WARN
}
}
fun parseWarningType(code: Boolean?): WarningType =
when (code) {
WarningType.WARN.prefCode -> WarningType.WARN
WarningType.HIDE.prefCode -> WarningType.HIDE
WarningType.SHOW.prefCode -> WarningType.SHOW
else -> {
WarningType.WARN
}
}

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
class UiSettingsFlow(
val theme: MutableStateFlow<ThemeType> = MutableStateFlow(ThemeType.SYSTEM),
val preferredLanguage: MutableStateFlow<String?> = MutableStateFlow(null),
val automaticallyShowImages: MutableStateFlow<ConnectivityType> = MutableStateFlow(ConnectivityType.ALWAYS),
val automaticallyStartPlayback: MutableStateFlow<ConnectivityType> = MutableStateFlow(ConnectivityType.ALWAYS),
val automaticallyShowUrlPreview: MutableStateFlow<ConnectivityType> = MutableStateFlow(ConnectivityType.ALWAYS),
val automaticallyHideNavigationBars: MutableStateFlow<BooleanType> = MutableStateFlow(BooleanType.ALWAYS),
val automaticallyShowProfilePictures: MutableStateFlow<ConnectivityType> = MutableStateFlow(ConnectivityType.ALWAYS),
val dontShowPushNotificationSelector: MutableStateFlow<Boolean> = MutableStateFlow(false),
val dontAskForNotificationPermissions: MutableStateFlow<Boolean> = MutableStateFlow(false),
val featureSet: MutableStateFlow<FeatureSetType> = MutableStateFlow(FeatureSetType.SIMPLIFIED),
val gallerySet: MutableStateFlow<ProfileGalleryType> = MutableStateFlow(ProfileGalleryType.CLASSIC),
) {
// emits at every change in any of the propertyes.
val propertyWatchFlow =
combine(
listOf(
theme,
preferredLanguage,
automaticallyShowImages,
automaticallyStartPlayback,
automaticallyShowUrlPreview,
automaticallyHideNavigationBars,
automaticallyShowProfilePictures,
dontShowPushNotificationSelector,
dontAskForNotificationPermissions,
featureSet,
gallerySet,
),
) { flows ->
UiSettings(
flows[0] as ThemeType,
flows[1] as String?,
flows[2] as ConnectivityType,
flows[3] as ConnectivityType,
flows[4] as ConnectivityType,
flows[5] as BooleanType,
flows[6] as ConnectivityType,
flows[7] as Boolean,
flows[8] as Boolean,
flows[9] as FeatureSetType,
flows[10] as ProfileGalleryType,
)
}
fun toSettings(): UiSettings =
UiSettings(
theme.value,
preferredLanguage.value,
automaticallyShowImages.value,
automaticallyStartPlayback.value,
automaticallyShowUrlPreview.value,
automaticallyHideNavigationBars.value,
automaticallyShowProfilePictures.value,
dontShowPushNotificationSelector.value,
dontAskForNotificationPermissions.value,
featureSet.value,
gallerySet.value,
)
fun update(torSettings: UiSettings): Boolean {
var any = false
if (theme.value != torSettings.theme) {
theme.tryEmit(torSettings.theme)
any = true
}
if (preferredLanguage.value != torSettings.preferredLanguage) {
preferredLanguage.tryEmit(torSettings.preferredLanguage)
any = true
}
if (automaticallyShowImages.value != torSettings.automaticallyShowImages) {
automaticallyShowImages.tryEmit(torSettings.automaticallyShowImages)
any = true
}
if (automaticallyStartPlayback.value != torSettings.automaticallyStartPlayback) {
automaticallyStartPlayback.tryEmit(torSettings.automaticallyStartPlayback)
any = true
}
if (automaticallyShowUrlPreview.value != torSettings.automaticallyShowUrlPreview) {
automaticallyShowUrlPreview.tryEmit(torSettings.automaticallyShowUrlPreview)
any = true
}
if (automaticallyHideNavigationBars.value != torSettings.automaticallyHideNavigationBars) {
automaticallyHideNavigationBars.tryEmit(torSettings.automaticallyHideNavigationBars)
any = true
}
if (automaticallyShowProfilePictures.value != torSettings.automaticallyShowProfilePictures) {
automaticallyShowProfilePictures.tryEmit(torSettings.automaticallyShowProfilePictures)
any = true
}
if (dontShowPushNotificationSelector.value != torSettings.dontShowPushNotificationSelector) {
dontShowPushNotificationSelector.tryEmit(torSettings.dontShowPushNotificationSelector)
any = true
}
if (dontAskForNotificationPermissions.value != torSettings.dontAskForNotificationPermissions) {
dontAskForNotificationPermissions.tryEmit(torSettings.dontAskForNotificationPermissions)
any = true
}
if (featureSet.value != torSettings.featureSet) {
featureSet.tryEmit(torSettings.featureSet)
any = true
}
if (gallerySet.value != torSettings.gallerySet) {
gallerySet.tryEmit(torSettings.gallerySet)
any = true
}
return any
}
fun dontShowPushNotificationSelector() {
if (!dontShowPushNotificationSelector.value) {
dontShowPushNotificationSelector.tryEmit(true)
}
}
fun dontAskForNotificationPermissions() {
if (!dontAskForNotificationPermissions.value) {
dontAskForNotificationPermissions.tryEmit(true)
}
}
companion object {
fun build(torSettings: UiSettings): UiSettingsFlow =
UiSettingsFlow(
MutableStateFlow(torSettings.theme),
MutableStateFlow(torSettings.preferredLanguage),
MutableStateFlow(torSettings.automaticallyShowImages),
MutableStateFlow(torSettings.automaticallyStartPlayback),
MutableStateFlow(torSettings.automaticallyShowUrlPreview),
MutableStateFlow(torSettings.automaticallyHideNavigationBars),
MutableStateFlow(torSettings.automaticallyShowProfilePictures),
MutableStateFlow(torSettings.dontShowPushNotificationSelector),
MutableStateFlow(torSettings.dontAskForNotificationPermissions),
MutableStateFlow(torSettings.featureSet),
MutableStateFlow(torSettings.gallerySet),
)
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -22,10 +22,9 @@ package com.vitorpamplona.amethyst.model
import android.util.LruCache
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.service.previews.BahaUrlPreview
import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback
import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
import com.vitorpamplona.amethyst.service.previews.UrlPreview
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import okhttp3.OkHttpClient
@Stable
object UrlCachedPreviewer {
@@ -34,6 +33,7 @@ object UrlCachedPreviewer {
suspend fun previewInfo(
url: String,
okHttpClient: (String) -> OkHttpClient,
onReady: suspend (UrlPreviewState) -> Unit,
) {
cache[url]?.let {
@@ -41,39 +41,37 @@ object UrlCachedPreviewer {
return
}
BahaUrlPreview(
UrlPreview().fetch(
url,
object : IUrlPreviewCallback {
override suspend fun onComplete(urlInfo: UrlInfoItem) {
cache[url]?.let {
if (it is UrlPreviewState.Loaded || it is UrlPreviewState.Empty) {
onReady(it)
return
}
}
val state =
if (urlInfo.fetchComplete() && urlInfo.url == url) {
UrlPreviewState.Loaded(urlInfo)
} else {
UrlPreviewState.Empty
}
cache.put(url, state)
onReady(state)
}
override suspend fun onFailed(throwable: Throwable) {
cache[url]?.let {
okHttpClient,
onComplete = { urlInfo ->
cache[url]?.let {
if (it is UrlPreviewState.Loaded || it is UrlPreviewState.Empty) {
onReady(it)
return
return@fetch
}
}
val state =
if (urlInfo.fetchComplete() && urlInfo.url == url) {
UrlPreviewState.Loaded(urlInfo)
} else {
UrlPreviewState.Empty
}
val state = UrlPreviewState.Error(throwable.message ?: "Error Loading url preview")
cache.put(url, state)
onReady(state)
}
cache.put(url, state)
onReady(state)
},
).fetchUrlPreview()
onFailed = { throwable ->
cache[url]?.let {
onReady(it)
return@fetch
}
val state = UrlPreviewState.Error(throwable.message ?: "Error Loading url preview")
cache.put(url, state)
onReady(state)
},
)
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2024 Vitor Pamplona
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -22,78 +22,78 @@ package com.vitorpamplona.amethyst.model
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.distinctUntilChanged
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.ammolite.relays.EOSETime
import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Lud06
import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.events.BookmarkListEvent
import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.Dispatchers
import com.vitorpamplona.amethyst.ui.note.toShortDisplay
import com.vitorpamplona.quartz.lightning.Lud06
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.DualCase
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.containsAny
import kotlinx.coroutines.flow.MutableStateFlow
import java.math.BigDecimal
@Stable
class User(
val pubkeyHex: String,
val nip65RelayListNote: Note,
val dmRelayListNote: Note,
) {
var info: UserMetadata? = null
var latestMetadata: MetadataEvent? = null
var latestMetadataRelay: String? = null
var latestMetadataRelay: NormalizedRelayUrl? = null
var latestContactList: ContactListEvent? = null
var latestBookmarkList: BookmarkListEvent? = null
var reports = mapOf<User, Set<Note>>()
private set
var latestEOSEs: Map<String, EOSETime> = emptyMap()
var zaps = mapOf<Note, Note?>()
private set
var relaysBeingUsed = mapOf<String, RelayInfo>()
private set
var privateChatrooms = mapOf<ChatroomKey, Chatroom>()
var relaysBeingUsed = mapOf<NormalizedRelayUrl, RelayInfo>()
private set
fun pubkey() = Hex.decode(pubkeyHex)
fun pubkeyNpub() = pubkey().toNpub()
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
fun pubkeyDisplayHex() = pubkeyNpub().toShortDisplay()
fun toNProfile(): String {
val relayList = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)?.writeRelays()
fun dmInboxRelayList() = dmRelayListNote.event as? ChatMessageRelayListEvent
return Nip19Bech32.createNProfile(
pubkeyHex,
relayList?.take(3) ?: listOfNotNull(latestMetadataRelay),
)
}
fun authorRelayList() = nip65RelayListNote.event as? AdvertisedRelayListEvent
fun toNProfile() = NProfile.create(pubkeyHex, relayHints())
fun outboxRelays() = authorRelayList()?.writeRelaysNorm()
fun relayHints() = authorRelayList()?.writeRelaysNorm()?.take(3) ?: listOfNotNull(latestMetadataRelay)
fun inboxRelays() = authorRelayList()?.readRelaysNorm()
fun dmInboxRelays() = dmInboxRelayList()?.relays()?.ifEmpty { null } ?: inboxRelays()
fun bestRelayHint() = authorRelayList()?.writeRelaysNorm()?.firstOrNull() ?: latestMetadataRelay
fun toPTag() = PTag(pubkeyHex, bestRelayHint())
fun toNostrUri() = "nostr:${toNProfile()}"
override fun toString(): String = pubkeyHex
fun toBestShortFirstName(): String {
val fullName = toBestDisplayName()
@@ -116,17 +116,6 @@ class User(
fun profilePicture(): String? = info?.picture
fun updateBookmark(event: BookmarkListEvent) {
if (event.id == latestBookmarkList?.id) return
latestBookmarkList = event
liveSet?.innerBookmarks?.invalidateData()
}
fun clearEOSE() {
latestEOSEs = emptyMap()
}
fun updateContactList(event: ContactListEvent) {
if (event.id == latestContactList?.id) return
@@ -134,7 +123,6 @@ class User(
latestContactList = event
// Update following of the current user
liveSet?.innerFollows?.invalidateData()
flowSet?.follows?.invalidateData()
// Update Followers of the past user list
@@ -142,20 +130,17 @@ class User(
(oldContactListEvent)?.unverifiedFollowKeySet()?.forEach {
LocalCache
.getUserIfExists(it)
?.liveSet
?.innerFollowers
?.flowSet
?.followers
?.invalidateData()
}
(latestContactList)?.unverifiedFollowKeySet()?.forEach {
LocalCache
.getUserIfExists(it)
?.liveSet
?.innerFollowers
?.flowSet
?.followers
?.invalidateData()
}
liveSet?.innerRelays?.invalidateData()
flowSet?.relays?.invalidateData()
}
fun addReport(note: Note) {
@@ -164,10 +149,10 @@ class User(
val reportsBy = reports[author]
if (reportsBy == null) {
reports = reports + Pair(author, setOf(note))
liveSet?.innerReports?.invalidateData()
flowSet?.reports?.invalidateData()
} else if (!reportsBy.contains(note)) {
reports = reports + Pair(author, reportsBy + note)
liveSet?.innerReports?.invalidateData()
flowSet?.reports?.invalidateData()
}
}
@@ -177,7 +162,7 @@ class User(
if (reports[author]?.contains(deleteNote) == true) {
reports[author]?.let {
reports = reports + Pair(author, it.minus(deleteNote))
liveSet?.innerReports?.invalidateData()
flowSet?.reports?.invalidateData()
}
}
}
@@ -188,17 +173,17 @@ class User(
) {
if (zaps[zapRequest] == null) {
zaps = zaps + Pair(zapRequest, zap)
liveSet?.innerZaps?.invalidateData()
flowSet?.zaps?.invalidateData()
}
}
fun removeZap(zapRequestOrZapEvent: Note) {
if (zaps.containsKey(zapRequestOrZapEvent)) {
zaps = zaps.minus(zapRequestOrZapEvent)
liveSet?.innerZaps?.invalidateData()
flowSet?.zaps?.invalidateData()
} else if (zaps.containsValue(zapRequestOrZapEvent)) {
zaps = zaps.filter { it.value != zapRequestOrZapEvent }
liveSet?.innerZaps?.invalidateData()
flowSet?.zaps?.invalidateData()
}
}
@@ -228,83 +213,13 @@ class User(
}
}.flatten()
@Synchronized
private fun getOrCreatePrivateChatroomSync(key: ChatroomKey): Chatroom {
checkNotInMainThread()
return privateChatrooms[key]
?: run {
val privateChatroom = Chatroom()
privateChatrooms = privateChatrooms + Pair(key, privateChatroom)
privateChatroom
}
}
private fun getOrCreatePrivateChatroom(user: User): Chatroom {
val key = ChatroomKey(persistentSetOf(user.pubkeyHex))
return getOrCreatePrivateChatroom(key)
}
private fun getOrCreatePrivateChatroom(key: ChatroomKey): Chatroom = privateChatrooms[key] ?: getOrCreatePrivateChatroomSync(key)
fun addMessage(
room: ChatroomKey,
msg: Note,
) {
val privateChatroom = getOrCreatePrivateChatroom(room)
if (msg !in privateChatroom.roomMessages) {
privateChatroom.addMessageSync(msg)
liveSet?.innerMessages?.invalidateData()
}
}
fun addMessage(
user: User,
msg: Note,
) {
val privateChatroom = getOrCreatePrivateChatroom(user)
if (msg !in privateChatroom.roomMessages) {
privateChatroom.addMessageSync(msg)
liveSet?.innerMessages?.invalidateData()
}
}
fun createChatroom(withKey: ChatroomKey) {
getOrCreatePrivateChatroom(withKey)
}
fun removeMessage(
user: User,
msg: Note,
) {
checkNotInMainThread()
val privateChatroom = getOrCreatePrivateChatroom(user)
if (msg in privateChatroom.roomMessages) {
privateChatroom.removeMessageSync(msg)
liveSet?.innerMessages?.invalidateData()
}
}
fun removeMessage(
room: ChatroomKey,
msg: Note,
) {
checkNotInMainThread()
val privateChatroom = getOrCreatePrivateChatroom(room)
if (msg in privateChatroom.roomMessages) {
privateChatroom.removeMessageSync(msg)
liveSet?.innerMessages?.invalidateData()
}
}
fun addRelayBeingUsed(
relay: Relay,
relay: NormalizedRelayUrl,
eventTime: Long,
) {
val here = relaysBeingUsed[relay.url]
val here = relaysBeingUsed[relay]
if (here == null) {
relaysBeingUsed = relaysBeingUsed + Pair(relay.url, RelayInfo(relay.url, eventTime, 1))
relaysBeingUsed = relaysBeingUsed + Pair(relay, RelayInfo(relay, eventTime, 1))
} else {
if (eventTime > here.lastEvent) {
here.lastEvent = eventTime
@@ -312,7 +227,7 @@ class User(
here.counter++
}
liveSet?.innerRelayInfo?.invalidateData()
flowSet?.usedRelays?.invalidateData()
}
fun updateUserInfo(
@@ -331,104 +246,65 @@ class User(
}
}
liveSet?.innerMetadata?.invalidateData()
flowSet?.metadata?.invalidateData()
}
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
fun isFollowingHashtag(tag: String): Boolean = latestContactList?.isTaggedHash(tag) ?: false
fun isFollowingHashtag(tag: String) = latestContactList?.isTaggedHash(tag) ?: false
fun isFollowingHashtagCached(tag: String): Boolean {
return latestContactList?.verifiedFollowTagSet?.let {
return tag.lowercase() in it
}
?: false
}
fun isFollowingGeohashCached(geoTag: String): Boolean {
return latestContactList?.verifiedFollowGeohashSet?.let {
return geoTag.lowercase() in it
}
?: false
}
fun isFollowingCached(user: User): Boolean {
return latestContactList?.verifiedFollowKeySet?.let {
return user.pubkeyHex in it
}
?: false
}
fun isFollowingCached(userHex: String): Boolean {
return latestContactList?.verifiedFollowKeySet?.let {
return userHex in it
}
?: false
}
fun isFollowingGeohash(geoTag: String) = latestContactList?.isTaggedGeoHash(geoTag) ?: false
fun transientFollowCount(): Int? = latestContactList?.unverifiedFollowKeySet()?.size
suspend fun transientFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
fun cachedFollowingKeySet(): Set<HexKey> = latestContactList?.verifiedFollowKeySet ?: emptySet()
fun cachedFollowingTagSet(): Set<String> = latestContactList?.verifiedFollowTagSet ?: emptySet()
fun cachedFollowingGeohashSet(): Set<HexKey> = latestContactList?.verifiedFollowGeohashSet ?: emptySet()
fun cachedFollowingCommunitiesSet(): Set<HexKey> = latestContactList?.verifiedFollowCommunitySet ?: emptySet()
fun cachedFollowCount(): Int? = latestContactList?.verifiedFollowKeySet?.size
suspend fun cachedFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
fun hasSentMessagesTo(key: ChatroomKey?): Boolean {
val messagesToUser = privateChatrooms[key] ?: return false
return messagesToUser.authors.any { this == it }
}
fun hasReport(
loggedIn: User,
type: ReportEvent.ReportType,
type: ReportType,
): Boolean =
reports[loggedIn]?.firstOrNull {
it.event is ReportEvent &&
(it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
(it.event as? ReportEvent)?.reportedAuthor()?.any { it.type == type } ?: false
} != null
fun containsAny(hiddenWordsCase: List<DualCase>): Boolean {
if (hiddenWordsCase.isEmpty()) return false
if (toBestDisplayName().containsAny(hiddenWordsCase)) {
return true
}
if (profilePicture()?.containsAny(hiddenWordsCase) == true) {
return true
}
if (info?.banner?.containsAny(hiddenWordsCase) == true) {
return true
}
if (info?.about?.containsAny(hiddenWordsCase) == true) {
return true
}
if (info?.lud06?.containsAny(hiddenWordsCase) == true) {
return true
}
if (info?.lud16?.containsAny(hiddenWordsCase) == true) {
return true
}
if (info?.nip05?.containsAny(hiddenWordsCase) == true) {
return true
}
return false
}
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
var liveSet: UserLiveSet? = null
var flowSet: UserFlowSet? = null
fun live(): UserLiveSet {
if (liveSet == null) {
createOrDestroyLiveSync(true)
}
return liveSet!!
}
fun clearLive() {
if (liveSet != null && liveSet?.isInUse() == false) {
createOrDestroyLiveSync(false)
}
}
@Synchronized
fun createOrDestroyLiveSync(create: Boolean) {
if (create) {
if (liveSet == null) {
liveSet = UserLiveSet(this)
}
} else {
if (liveSet != null && liveSet?.isInUse() == false) {
liveSet?.destroy()
liveSet = null
}
}
}
@Synchronized
fun createOrDestroyFlowSync(create: Boolean) {
if (create) {
@@ -437,7 +313,6 @@ class User(
}
} else {
if (flowSet != null && flowSet?.isInUse() == false) {
flowSet?.destroy()
flowSet = null
}
}
@@ -462,158 +337,45 @@ class UserFlowSet(
u: User,
) {
// Observers line up here.
val metadata = UserBundledRefresherFlow(u)
val follows = UserBundledRefresherFlow(u)
val relays = UserBundledRefresherFlow(u)
fun isInUse(): Boolean = relays.stateFlow.subscriptionCount.value > 0 || follows.stateFlow.subscriptionCount.value > 0
fun destroy() {
relays.destroy()
follows.destroy()
}
}
@Stable
class UserLiveSet(
u: User,
) {
val innerMetadata = UserBundledRefresherLiveData(u)
// UI Observers line up here.
val innerFollows = UserBundledRefresherLiveData(u)
val innerFollowers = UserBundledRefresherLiveData(u)
val innerReports = UserBundledRefresherLiveData(u)
val innerMessages = UserBundledRefresherLiveData(u)
val innerRelays = UserBundledRefresherLiveData(u)
val innerRelayInfo = UserBundledRefresherLiveData(u)
val innerZaps = UserBundledRefresherLiveData(u)
val innerBookmarks = UserBundledRefresherLiveData(u)
val innerGallery = UserBundledRefresherLiveData(u)
val innerStatuses = UserBundledRefresherLiveData(u)
// UI Observers line up here.
val metadata = innerMetadata.map { it }
val follows = innerFollows.map { it }
val followers = innerFollowers.map { it }
val reports = innerReports.map { it }
val messages = innerMessages.map { it }
val relays = innerRelays.map { it }
val relayInfo = innerRelayInfo.map { it }
val zaps = innerZaps.map { it }
val bookmarks = innerBookmarks.map { it }
val gallery = innerGallery.map { it }
val statuses = innerStatuses.map { it }
val profilePictureChanges = innerMetadata.map { it.user.profilePicture() }.distinctUntilChanged()
val nip05Changes = innerMetadata.map { it.user.nip05() }.distinctUntilChanged()
val userMetadataInfo = innerMetadata.map { it.user.info }.distinctUntilChanged()
val followers = UserBundledRefresherFlow(u)
val reports = UserBundledRefresherFlow(u)
val usedRelays = UserBundledRefresherFlow(u)
val zaps = UserBundledRefresherFlow(u)
val statuses = UserBundledRefresherFlow(u)
fun isInUse(): Boolean =
metadata.hasObservers() ||
follows.hasObservers() ||
followers.hasObservers() ||
reports.hasObservers() ||
messages.hasObservers() ||
relays.hasObservers() ||
relayInfo.hasObservers() ||
usedRelays.hasObservers() ||
zaps.hasObservers() ||
bookmarks.hasObservers() ||
gallery.hasObservers() ||
statuses.hasObservers() ||
profilePictureChanges.hasObservers() ||
nip05Changes.hasObservers() ||
userMetadataInfo.hasObservers()
fun destroy() {
innerMetadata.destroy()
innerFollows.destroy()
innerFollowers.destroy()
innerReports.destroy()
innerMessages.destroy()
innerRelays.destroy()
innerRelayInfo.destroy()
innerZaps.destroy()
innerBookmarks.destroy()
innerGallery.destroy()
innerStatuses.destroy()
}
statuses.hasObservers()
}
@Immutable
data class RelayInfo(
val url: String,
val url: NormalizedRelayUrl,
var lastEvent: Long,
var counter: Long,
)
class UserBundledRefresherLiveData(
val user: User,
) : LiveData<UserState>(UserState(user)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(500, Dispatchers.IO)
fun destroy() {
bundler.cancel()
}
fun invalidateData() {
checkNotInMainThread()
bundler.invalidate {
checkNotInMainThread()
postValue(UserState(user))
}
}
fun <Y> map(transform: (UserState) -> Y): UserLoadingLiveData<Y> {
val initialValue = this.value?.let { transform(it) }
val result = UserLoadingLiveData(user, initialValue)
result.addSource(this) { x -> result.value = transform(x) }
return result
}
}
@Stable
class UserBundledRefresherFlow(
val user: User,
) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(500, Dispatchers.IO)
val stateFlow = MutableStateFlow(UserState(user))
fun destroy() {
bundler.cancel()
}
fun invalidateData() {
checkNotInMainThread()
bundler.invalidate {
checkNotInMainThread()
stateFlow.emit(UserState(user))
}
stateFlow.tryEmit(UserState(user))
}
fun hasObservers() = stateFlow.subscriptionCount.value > 0
}
class UserLoadingLiveData<Y>(
val user: User,
initialValue: Y?,
) : MediatorLiveData<Y>(initialValue) {
override fun onActive() {
super.onActive()
NostrSingleUserDataSource.add(user)
}
override fun onInactive() {
super.onInactive()
NostrSingleUserDataSource.remove(user)
}
}
@Immutable class UserState(
@Immutable
class UserState(
val user: User,
)

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.accountsCache
import android.content.ContentResolver
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
class AccountCacheState(
val geolocationFlow: StateFlow<LocationState.LocationResult>,
val nwcFilterAssembler: NWCPaymentFilterAssembler,
val contentResolverFn: () -> ContentResolver,
val otsResolverBuilder: OtsResolverBuilder,
val cache: LocalCache,
val client: INostrClient,
) {
val accounts = MutableStateFlow<Map<HexKey, Account>>(emptyMap())
fun removeAccount(pubkey: HexKey) {
accounts.update { existingAccounts ->
val oldValue = existingAccounts[pubkey]
oldValue?.scope?.cancel()
existingAccounts.minus(pubkey)
}
}
fun loadAccount(accountSettings: AccountSettings): Account =
loadAccount(
signer =
if (accountSettings.keyPair.privKey != null) {
NostrSignerInternal(accountSettings.keyPair)
} else {
when (val packageName = accountSettings.externalSignerPackageName) {
null -> NostrSignerInternal(accountSettings.keyPair)
else ->
NostrSignerExternal(
pubKey = accountSettings.keyPair.pubKey.toHexKey(),
packageName = packageName,
contentResolver = contentResolverFn(),
)
}
},
accountSettings = accountSettings,
)
fun loadAccount(
signer: NostrSigner,
accountSettings: AccountSettings,
): Account {
val cached = accounts.value[signer.pubKey]
if (cached != null) return cached
return Account(
settings = accountSettings,
signer = signer,
geolocationFlow = geolocationFlow,
nwcFilterAssembler = nwcFilterAssembler,
otsResolverBuilder = otsResolverBuilder,
cache = cache,
client = client,
scope =
CoroutineScope(
Dispatchers.IO +
SupervisorJob() +
CoroutineExceptionHandler { _, throwable ->
Log.e("AccountCacheState", "Account ${signer.pubKey} caught exception: ${throwable.message}", throwable)
},
),
).also { newAccount ->
accounts.update { existingAccounts ->
existingAccounts.plus(Pair(signer.pubKey, newAccount))
}
}
}
fun clear() {
accounts.update { existingAccounts ->
existingAccounts.forEach {
it.value.scope.cancel()
}
emptyMap()
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.edits
import com.vitorpamplona.amethyst.model.nip51Lists.relayLists.GenericRelayListCache
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
class PrivateStorageRelayListDecryptionCache(
signer: NostrSigner,
) : GenericRelayListCache<PrivateOutboxRelayListEvent>(signer)

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.edits
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class PrivateStorageRelayListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: PrivateStorageRelayListDecryptionCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val privateOutboxListNote = cache.getOrCreateAddressableNote(getPrivateOutboxRelayListAddress())
fun getPrivateOutboxRelayListAddress() = PrivateOutboxRelayListEvent.createAddress(signer.pubKey)
fun getPrivateOutboxRelayListFlow(): StateFlow<NoteState> = privateOutboxListNote.flow().metadata.stateFlow
fun getPrivateOutboxRelayList(): PrivateOutboxRelayListEvent? = privateOutboxListNote.event as? PrivateOutboxRelayListEvent
suspend fun normalizePrivateOutboxRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> {
val event = note.event as? PrivateOutboxRelayListEvent ?: settings.backupPrivateHomeRelayList
return event?.let { decryptionCache.relays(it) } ?: emptySet()
}
val flow =
getPrivateOutboxRelayListFlow()
.map { normalizePrivateOutboxRelayListWithBackup(it.note) }
.onStart {
emit(normalizePrivateOutboxRelayListWithBackup(privateOutboxListNote))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun saveRelayList(relays: List<NormalizedRelayUrl>): PrivateOutboxRelayListEvent {
val relayListForPrivateOutbox = getPrivateOutboxRelayList()
return if (relayListForPrivateOutbox != null) {
PrivateOutboxRelayListEvent.updateRelayList(
earlierVersion = relayListForPrivateOutbox,
relays = relays,
signer = signer,
)
} else {
PrivateOutboxRelayListEvent.create(
relays = relays,
signer = signer,
)
}
}
init {
settings.backupPrivateHomeRelayList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved private home relay list ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
LocalCache.justConsumeMyOwnEvent(event)
}
}
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Private Home Relay List Collector Start")
getPrivateOutboxRelayListFlow().collect { noteState ->
Log.d("AccountRegisterObservers", "Updating Private Home Relay List for ${signer.pubKey}")
(noteState.note.event as? PrivateOutboxRelayListEvent)?.let {
settings.updatePrivateHomeRelayList(it)
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.emphChat
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
@Stable
class EphemeralChatChannel(
val roomId: RoomId,
) : Channel() {
override fun relays() = setOf(roomId.relayUrl)
override fun toBestDisplayName() = roomId.toDisplayKey()
fun anyNameStartsWith(prefix: String): Boolean = roomId.id.contains(prefix, true)
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.emphChat
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
import com.vitorpamplona.quartz.experimental.ephemChat.list.roomSet
import com.vitorpamplona.quartz.experimental.ephemChat.list.rooms
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEventCache
class EphemeralChatListDecryptionCache(
val signer: NostrSigner,
) {
val cachedPrivateLists = PrivateTagArrayEventCache<EphemeralChatListEvent>(signer)
fun cachedRoomSet(event: EphemeralChatListEvent) = cachedPrivateLists.mergeTagListPrecached(event).roomSet()
fun cachedRooms(event: EphemeralChatListEvent) = cachedPrivateLists.mergeTagListPrecached(event).rooms()
suspend fun roomSet(event: EphemeralChatListEvent) = cachedPrivateLists.mergeTagList(event).roomSet()
suspend fun rooms(event: EphemeralChatListEvent) = cachedPrivateLists.mergeTagList(event).rooms()
}

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.emphChat
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
class EphemeralChatListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: EphemeralChatListDecryptionCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val ephemeralChatListNote = cache.getOrCreateAddressableNote(getEphemeralChatListAddress())
fun getEphemeralChatListAddress() = EphemeralChatListEvent.createAddress(signer.pubKey)
fun getEphemeralChatListFlow(): StateFlow<NoteState> = ephemeralChatListNote.flow().metadata.stateFlow
fun getEphemeralChatList(): EphemeralChatListEvent? = ephemeralChatListNote.event as? EphemeralChatListEvent
suspend fun ephemeralChatListWithBackup(note: Note): Set<RoomId> {
val event = note.event as? EphemeralChatListEvent ?: settings.backupEphemeralChatList
return event?.let { decryptionCache.roomSet(it) } ?: emptySet()
}
@OptIn(ExperimentalCoroutinesApi::class)
val liveEphemeralChatList: StateFlow<Set<RoomId>> =
getEphemeralChatListFlow()
.transformLatest { noteState ->
emit(ephemeralChatListWithBackup(noteState.note))
}.onStart {
emit(ephemeralChatListWithBackup(ephemeralChatListNote))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun follow(channel: EphemeralChatChannel): EphemeralChatListEvent {
val ephemeralChatList = getEphemeralChatList()
return if (ephemeralChatList == null) {
EphemeralChatListEvent.create(
room = channel.roomId,
isPrivate = true,
signer = signer,
)
} else {
EphemeralChatListEvent.add(
earlierVersion = ephemeralChatList,
room = channel.roomId,
isPrivate = true,
signer = signer,
)
}
}
suspend fun unfollow(channel: EphemeralChatChannel): EphemeralChatListEvent? {
val ephemeralChatList = getEphemeralChatList()
return if (ephemeralChatList != null) {
EphemeralChatListEvent.remove(
earlierVersion = ephemeralChatList,
room = channel.roomId,
signer = signer,
)
} else {
null
}
}
init {
settings.backupEphemeralChatList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved ephemeral chat list")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
LocalCache.justConsumeMyOwnEvent(event)
}
}
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "EphemeralChatList Collector Start")
getEphemeralChatListFlow().collect { noteState ->
Log.d("AccountRegisterObservers", "EphemeralChatList List for ${signer.pubKey}")
(noteState.note.event as? EphemeralChatListEvent)?.let {
settings.updateEphemeralChatListTo(it)
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.localRelays
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class LocalRelayListState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
fun normalizeLocalRelayListWithBackup(relayList: Set<String>): Set<NormalizedRelayUrl> = relayList.mapNotNull { RelayUrlNormalizer.normalizeOrNull(it) }.toSet()
val flow =
settings.localRelayServers
.map { normalizeLocalRelayListWithBackup(it) }
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
normalizeLocalRelayListWithBackup(settings.localRelayServers.value),
)
fun saveRelayList(
relays: List<NormalizedRelayUrl>,
onDone: () -> Unit,
) {
settings.updateLocalRelayServers(relays.map { it.url }.toSet())
onDone()
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip01UserMetadata
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
class AccountHomeRelayState(
nip65: Nip65RelayListState,
privateStorage: PrivateStorageRelayListState,
local: LocalRelayListState,
scope: CoroutineScope,
) {
val flow =
combine(
nip65.outboxFlow,
privateStorage.flow,
local.flow,
) { nip65Outbox, privateOutBox, localRelays ->
nip65Outbox + privateOutBox + localRelays
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
nip65.outboxFlow.value +
privateStorage.flow.value +
local.flow.value,
)
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip01UserMetadata
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.broadcastRelays.BroadcastRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
class AccountOutboxRelayState(
nip65: Nip65RelayListState,
privateStorage: PrivateStorageRelayListState,
local: LocalRelayListState,
broadcast: BroadcastRelayListState,
scope: CoroutineScope,
) {
val flow =
combine(
nip65.outboxFlow,
privateStorage.flow,
local.flow,
broadcast.flow,
) { nip65Outbox, privateOutBox, localRelays, broadcastRelays ->
nip65Outbox + privateOutBox + localRelays + broadcastRelays
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
nip65.outboxFlow.value +
privateStorage.flow.value +
local.flow.value +
broadcast.flow.value,
)
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip01UserMetadata
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
class NotificationInboxRelayState(
nip65RelayList: Nip65RelayListState,
localRelayList: LocalRelayListState,
scope: CoroutineScope,
) {
val flow =
combine(
nip65RelayList.inboxFlow,
localRelayList.flow,
) { nip65Inbox, localRelays ->
nip65Inbox + localRelays
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
nip65RelayList.inboxFlow.value + localRelayList.flow.value,
)
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip01UserMetadata
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class UserMetadataState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val user = cache.getOrCreateUser(signer.pubKey)
// fun getEphemeralChatListAddress() = cache.getOrCreateUser(signer.pubKey)
fun getUserMetadataFlow(): StateFlow<UserState> = user.flow().metadata.stateFlow
fun getUserMetadataEvent(): MetadataEvent? = user.latestMetadata
suspend fun sendNewUserMetadata(
name: String? = null,
picture: String? = null,
banner: String? = null,
website: String? = null,
pronouns: String? = null,
about: String? = null,
nip05: String? = null,
lnAddress: String? = null,
lnURL: String? = null,
twitter: String? = null,
mastodon: String? = null,
github: String? = null,
): MetadataEvent {
val latest = getUserMetadataEvent()
val template =
if (latest != null) {
MetadataEvent.updateFromPast(
latest = latest,
name = name,
displayName = name,
picture = picture,
banner = banner,
website = website,
pronouns = pronouns,
about = about,
nip05 = nip05,
lnAddress = lnAddress,
lnURL = lnURL,
twitter = twitter,
mastodon = mastodon,
github = github,
)
} else {
MetadataEvent.createNew(
name = name,
displayName = name,
picture = picture,
banner = banner,
website = website,
pronouns = pronouns,
about = about,
nip05 = nip05,
lnAddress = lnAddress,
lnURL = lnURL,
twitter = twitter,
mastodon = mastodon,
github = github,
)
}
return signer.sign(template)
}
init {
settings.backupUserMetadata?.let {
Log.d("AccountRegisterObservers", "Loading saved user metadata ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsumeMyOwnEvent(it) }
}
// saves contact list for the next time.
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Kind 0 Collector Start")
getUserMetadataFlow().collect {
Log.d("AccountRegisterObservers", "Updating Kind 0 ${it.user.toBestDisplayName()}")
settings.updateUserMetadata(it.user.latestMetadata)
}
}
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip02FollowLists
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class DeclaredFollowsPerOutboxRelay(
kind3Follows: Kind3FollowListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
val calculator = OutboxRelayLoader(true)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
kind3Follows.flow
.transformLatest {
emitAll(
calculator.toAuthorsPerRelayFlow(it.authors, cache) { it },
)
}.onStart {
emit(
calculator.authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it },
)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip02FollowLists
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class FollowListOutboxOrProxyRelays(
kind3Follows: Kind3FollowListState,
blockedRelayList: BlockedRelayListState,
proxyRelayList: ProxyRelayListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
@OptIn(ExperimentalCoroutinesApi::class)
val outboxRelayFlow: StateFlow<Set<NormalizedRelayUrl>> =
kind3Follows.flow
.transformLatest { follows ->
emitAll(
OutboxRelayLoader(true).toAuthorsPerRelayFlow(follows.authors, cache) { it.keys },
)
}.onStart {
emit(
OutboxRelayLoader(true).authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it.keys },
)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
val outboxRelayMinusBlockedFlow: StateFlow<Set<NormalizedRelayUrl>> =
combine(outboxRelayFlow, blockedRelayList.flow) { followList, blockedRelays ->
followList.minus(blockedRelays)
}.onStart {
emit(outboxRelayFlow.value.minus(blockedRelayList.flow.value.toSet()))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<NormalizedRelayUrl>> =
proxyRelayList.flow
.flatMapLatest { proxyRelays ->
if (proxyRelays.isEmpty()) {
outboxRelayMinusBlockedFlow
} else {
MutableStateFlow(proxyRelays)
}
}.onStart {
emit(
proxyRelayList.flow.value.ifEmpty {
outboxRelayMinusBlockedFlow.value
},
)
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flowSet: StateFlow<Set<String>> =
flow
.map { relayList ->
relayList.mapTo(mutableSetOf()) { it.url }
}.onStart {
emit(flow.value.mapTo(mutableSetOf()) { it.url })
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
}

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip02FollowLists
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class FollowListReusedOutboxOrProxyRelays(
kind3Follows: Kind3FollowListState,
blockedRelayList: BlockedRelayListState,
proxyRelayList: ProxyRelayListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
@OptIn(ExperimentalCoroutinesApi::class)
val outboxRelayFlow: StateFlow<Set<NormalizedRelayUrl>> =
kind3Follows.flow
.transformLatest { follows ->
emitAll(
OutboxRelayLoader(true).toAuthorsPerRelayFlow(follows.authors, cache) { authorsPerRelay ->
authorsPerRelay
.mapNotNull {
if (it.value.size > 1) {
it.key
} else {
null
}
}.toSet()
},
)
}.onStart {
emit(
OutboxRelayLoader(true).authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { authorsPerRelay ->
authorsPerRelay
.mapNotNull {
if (it.value.size > 1) {
it.key
} else {
null
}
}.toSet()
},
)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
val outboxRelayMinusBlockedFlow: StateFlow<Set<NormalizedRelayUrl>> =
combine(outboxRelayFlow, blockedRelayList.flow) { followList, blockedRelays ->
followList.minus(blockedRelays)
}.onStart {
emit(outboxRelayFlow.value.minus(blockedRelayList.flow.value.toSet()))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<NormalizedRelayUrl>> =
proxyRelayList.flow
.flatMapLatest { proxyRelays ->
if (proxyRelays.isEmpty()) {
outboxRelayMinusBlockedFlow
} else {
MutableStateFlow(proxyRelays)
}
}.onStart {
emit(
proxyRelayList.flow.value.ifEmpty {
outboxRelayMinusBlockedFlow.value
},
)
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flowSet: StateFlow<Set<String>> =
flow
.map { relayList ->
relayList.mapTo(mutableSetOf()) { it.url }
}.onStart {
emit(flow.value.mapTo(mutableSetOf()) { it.url })
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip02FollowLists
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxRelayLoader
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class FollowsPerOutboxRelay(
kind3Follows: Kind3FollowListState,
blockedRelayList: BlockedRelayListState,
proxyRelayList: ProxyRelayListState,
val cache: LocalCache,
scope: CoroutineScope,
) {
@OptIn(ExperimentalCoroutinesApi::class)
val outboxPerRelayFlow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
kind3Follows.flow
.transformLatest {
emitAll(
OutboxRelayLoader().toAuthorsPerRelayFlow(it.authors, cache) { it },
)
}.onStart {
emit(
OutboxRelayLoader().authorsPerRelaySnapshot(kind3Follows.flow.value.authors, cache) { it },
)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
val outboxPerRelayMinusBlockedFlow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
combine(outboxPerRelayFlow, blockedRelayList.flow) { followList, blockedRelays ->
followList.minus(blockedRelays)
}.onStart {
emit(outboxPerRelayFlow.value.minus(blockedRelayList.flow.value.toSet()))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Map<NormalizedRelayUrl, Set<HexKey>>> =
proxyRelayList.flow
.flatMapLatest { proxyRelays ->
if (proxyRelays.isEmpty()) {
outboxPerRelayMinusBlockedFlow
} else {
kind3Follows.flow.map { follows ->
proxyRelays.associateWith { follows.authors }
}
}
}.onStart {
if (proxyRelayList.flow.value.isEmpty()) {
emit(outboxPerRelayMinusBlockedFlow.value)
} else {
emit(proxyRelayList.flow.value.associateWith { kind3Follows.flow.value.authors })
}
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyMap(),
)
}

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip02FollowLists
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
class Kind3FollowListState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val user = cache.getOrCreateUser(signer.pubKey)
fun getFollowListFlow(): StateFlow<UserState> = user.flow().follows.stateFlow
fun getFollowListEvent(): ContactListEvent? = user.latestContactList
@OptIn(ExperimentalCoroutinesApi::class)
private val innerFlow: Flow<Kind3Follows> =
getFollowListFlow().transformLatest {
emit(buildKind3Follows(it.user.latestContactList ?: settings.backupContactList))
}
val flow =
innerFlow
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
// this has priority.
buildKind3Follows(getFollowListEvent() ?: settings.backupContactList),
)
// Creates a long-term reference for all follows of a user
val userList =
flow
.map { kind3Follows ->
kind3Follows.authors.mapNotNull {
cache.checkGetOrCreateUser(it)
}
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
// this has priority.
flow.value.authors.mapNotNull {
cache.checkGetOrCreateUser(it)
},
)
/**
This contains a big OR of everything the user wants to see in the a single feed.
*/
@Immutable
class Kind3Follows(
val authors: Set<HexKey> = emptySet(),
val authorsPlusMe: Set<HexKey>,
)
fun buildKind3Follows(latestContactList: ContactListEvent?): Kind3Follows {
// makes sure the output include only valid p tags
val verifiedFollowingUsers = latestContactList?.verifiedFollowKeySet() ?: emptySet()
return Kind3Follows(
authors = verifiedFollowingUsers,
authorsPlusMe = verifiedFollowingUsers + signer.pubKey,
)
}
suspend fun follow(user: User): ContactListEvent {
val contactList = getFollowListEvent()
return if (contactList != null) {
ContactListEvent.followUser(contactList, user.pubkeyHex, signer)
} else {
ContactListEvent.createFromScratch(
followUsers = listOf(ContactTag(user.pubkeyHex, user.bestRelayHint(), null)),
relayUse = emptyMap(),
signer = signer,
)
}
}
suspend fun unfollow(user: User): ContactListEvent? {
val contactList = getFollowListEvent()
return if (contactList != null && contactList.tags.isNotEmpty()) {
ContactListEvent.unfollowUser(
contactList,
user.pubkeyHex,
signer,
)
} else {
null
}
}
init {
settings.backupContactList?.let {
Log.d("AccountRegisterObservers", "Loading saved ${it.tags.size} contacts")
@OptIn(DelicateCoroutinesApi::class)
scope.launch(Dispatchers.IO) { LocalCache.justConsumeMyOwnEvent(it) }
}
// saves contact list for the next time.
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Kind 3 Collector Start")
getFollowListFlow().collect {
Log.d("AccountRegisterObservers", "Updating Kind 3 ${signer.pubKey}")
settings.updateContactListTo(it.user.latestContactList)
}
}
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip03Timestamp
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
class IncomingOtsEventVerifier(
private val otsVerifCache: VerificationStateCache,
private val cache: LocalCache,
private val scope: CoroutineScope,
) {
val verifying =
cache.live.newEventBundles
.onEach { newNotes ->
newNotes.forEach {
consume(it)
}
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
null,
)
suspend fun consume(note: Note) {
note.event?.let { event ->
if (event is OtsEvent) {
otsVerifCache.cacheVerify(event)
}
}
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip03Timestamp
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.Base64
class OtsState(
val signer: NostrSigner,
val cache: LocalCache,
val otsResolver: OtsResolverBuilder,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
var lastTimeItTriedToUpdateAttestations: Long = 0
fun upgradeAttestationsIfNeeded(onReady: (List<OtsEvent>) -> Unit) {
// only tries to upgrade every hour
val now = TimeUtils.now()
if (now - lastTimeItTriedToUpdateAttestations > TimeUtils.ONE_HOUR) {
lastTimeItTriedToUpdateAttestations = now
scope.launch {
onReady(updateAttestations())
}
}
}
suspend fun updateAttestations(): List<OtsEvent> {
Log.d("Pending Attestations", "Updating ${settings.pendingAttestations.value.size} pending attestations")
return settings.pendingAttestations.value.toList().mapNotNull { (key, value) ->
val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver.build())
if (otsState != null) {
val hint = cache.getNoteIfExists(key)?.toEventHint<Event>()
val template =
if (hint != null) {
OtsEvent.build(hint, otsState)
} else {
OtsEvent.build(key, otsState)
}
val otsEvent = signer.sign(template)
settings.pendingAttestations.update { it - key }
otsEvent
} else {
null
}
}
}
fun hasPendingAttestations(note: Note): Boolean {
val id = note.event?.id ?: note.idHex
return settings.pendingAttestations.value[id] != null
}
suspend fun timestamp(note: Note) {
if (note.isDraft()) return
val id = note.event?.id ?: note.idHex
settings.addPendingAttestation(
id = id,
stamp =
Base64.getEncoder().encodeToString(
OtsEvent.stamp(
id,
otsResolver.build(),
),
),
)
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip03Timestamp
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer
import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpCalendar
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
import okhttp3.OkHttpClient
class TorAwareOkHttpOtsResolverBuilder(
val okHttpClient: (url: String) -> OkHttpClient,
val isTorActive: (url: String) -> Boolean,
val cache: OtsBlockHeightCache,
) : OtsResolverBuilder {
fun getAPI(usingTor: Boolean) =
if (usingTor) {
OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL
} else {
OkHttpBitcoinExplorer.Companion.BLOCKSTREAM_API_URL
}
override fun build(): OtsResolver =
OtsResolver(
explorer =
OkHttpBitcoinExplorer(
baseUrl = {
getAPI(usingTor = isTorActive(OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL))
},
client = okHttpClient(OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL),
cache = cache,
),
calendar = OkHttpCalendar(okHttpClient),
)
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip11RelayInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
import com.vitorpamplona.quartz.utils.Log
@Composable
fun loadRelayInfo(relay: NormalizedRelayUrl): State<Nip11RelayInformation> = loadRelayInfo(relay, Amethyst.instance.nip11Cache)
@Composable
fun loadRelayInfo(
relay: NormalizedRelayUrl,
nip11Cache: Nip11CachedRetriever,
): State<Nip11RelayInformation> =
produceState(
nip11Cache.getFromCache(relay),
relay,
) {
nip11Cache.loadRelayInfo(
relay = relay,
onInfo = {
value = it
},
onError = { url, errorCode, exceptionMessage ->
Log.e("RelayInfo", "Error loading relay info for ${relay.url}: $errorCode - $exceptionMessage")
},
)
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip11RelayInfo
import android.util.LruCache
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.toHttp
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
import okhttp3.OkHttpClient
class Nip11CachedRetriever(
val okHttpClient: (NormalizedRelayUrl) -> OkHttpClient,
) {
private val relayInformationEmptyCache = LruCache<NormalizedRelayUrl, Nip11RelayInformation>(1000)
private val relayInformationDocumentCache = LruCache<NormalizedRelayUrl, RetrieveResult?>(1000)
private val retriever = Nip11Retriever(okHttpClient)
fun getEmpty(relay: NormalizedRelayUrl): Nip11RelayInformation {
relayInformationEmptyCache.get(relay)?.let { return it }
val info =
Nip11RelayInformation(
name = relay.displayUrl(),
icon = relay.toHttp() + "favicon.ico",
)
relayInformationEmptyCache.put(relay, info)
return info
}
fun getFromCache(relay: NormalizedRelayUrl): Nip11RelayInformation {
val result = relayInformationDocumentCache.get(relay)
if (result == null) {
// resets the clock
val empty = getEmpty(relay)
relayInformationDocumentCache.put(relay, RetrieveResult.Empty(empty))
return empty
}
return when (result) {
is RetrieveResult.Success -> result.data
is RetrieveResult.Error -> result.data
is RetrieveResult.Empty -> result.data
is RetrieveResult.Loading -> result.data
}
}
suspend fun loadRelayInfo(
relay: NormalizedRelayUrl,
onInfo: (Nip11RelayInformation) -> Unit,
onError: (NormalizedRelayUrl, Nip11Retriever.ErrorCode, String?) -> Unit,
) {
val doc = relayInformationDocumentCache.get(relay)
if (doc != null) {
when (doc) {
is RetrieveResult.Success -> onInfo(doc.data)
is RetrieveResult.Loading -> {
if (doc.isValid()) {
// just wait.
} else {
retrieve(relay, onInfo, onError)
}
}
is RetrieveResult.Error -> {
if (doc.isValid()) {
onError(relay, doc.error, null)
} else {
retrieve(relay, onInfo, onError)
}
}
is RetrieveResult.Empty -> retrieve(relay, onInfo, onError)
}
} else {
retrieve(relay, onInfo, onError)
}
}
private suspend fun retrieve(
relay: NormalizedRelayUrl,
onInfo: (Nip11RelayInformation) -> Unit,
onError: (NormalizedRelayUrl, Nip11Retriever.ErrorCode, String?) -> Unit,
) {
relayInformationDocumentCache.put(relay, RetrieveResult.Loading(getEmpty(relay)))
retriever.loadRelayInfo(
relay = relay,
onInfo = {
relayInformationDocumentCache.put(relay, RetrieveResult.Success(it))
relayInformationEmptyCache.remove(relay)
onInfo(it)
},
onError = { relay, code, errorMsg ->
relayInformationDocumentCache.put(relay, RetrieveResult.Error(getEmpty(relay), code, errorMsg))
relayInformationEmptyCache.remove(relay)
onError(relay, code, errorMsg)
},
)
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip11RelayInfo
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.toHttp
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.coroutines.executeAsync
class Nip11Retriever(
val okHttpClient: (NormalizedRelayUrl) -> OkHttpClient,
) {
enum class ErrorCode {
FAIL_TO_ASSEMBLE_URL,
FAIL_TO_REACH_SERVER,
FAIL_TO_PARSE_RESULT,
FAIL_WITH_HTTP_STATUS,
}
suspend fun loadRelayInfo(
relay: NormalizedRelayUrl,
onInfo: (Nip11RelayInformation) -> Unit,
onError: (NormalizedRelayUrl, ErrorCode, String?) -> Unit,
) {
val url = relay.toHttp()
try {
val request: Request =
Request
.Builder()
.header("Accept", "application/nostr+json")
.url(url)
.build()
val client = okHttpClient(relay)
client.newCall(request).executeAsync().use { response ->
withContext(Dispatchers.IO) {
val body = response.body.string()
try {
if (response.isSuccessful) {
if (body.startsWith("{")) {
onInfo(Nip11RelayInformation.fromJson(body))
} else {
onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, body)
}
} else {
onError(relay, ErrorCode.FAIL_WITH_HTTP_STATUS, response.code.toString())
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e(
"RelayInfoFail",
"Resulting Message from Relay ${relay.url} in not parseable: $body",
e,
)
onError(relay, ErrorCode.FAIL_TO_PARSE_RESULT, e.message)
}
}
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("RelayInfoFail", "Invalid URL ${relay.url}", e)
onError(relay, ErrorCode.FAIL_TO_ASSEMBLE_URL, e.message)
}
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip11RelayInfo
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
import com.vitorpamplona.quartz.utils.TimeUtils
sealed class RetrieveResult(
val data: Nip11RelayInformation,
val time: Long,
) {
class Error(
data: Nip11RelayInformation,
val error: Nip11Retriever.ErrorCode,
val msg: String? = null,
) : RetrieveResult(data, TimeUtils.now())
class Success(
data: Nip11RelayInformation,
) : RetrieveResult(data, TimeUtils.now())
class Loading(
data: Nip11RelayInformation,
) : RetrieveResult(data, TimeUtils.now())
class Empty(
data: Nip11RelayInformation,
) : RetrieveResult(data, TimeUtils.now())
fun isValid() = time > TimeUtils.oneHourAgo()
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip17Dms
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
class DmInboxRelayState(
// main relay
dmRelayList: DmRelayListState,
// backup relays
nip65RelayList: Nip65RelayListState,
privateOutbox: PrivateStorageRelayListState,
localRelayList: LocalRelayListState,
scope: CoroutineScope,
) {
val flow =
combine(
nip65RelayList.inboxFlow,
dmRelayList.flow,
privateOutbox.flow,
localRelayList.flow,
) { nip65Inbox, dmRelayList, privateOutBox, localRelays ->
nip65Inbox + dmRelayList + privateOutBox + localRelays
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
nip65RelayList.inboxFlow.value +
dmRelayList.flow.value +
privateOutbox.flow.value +
localRelayList.flow.value,
)
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip17Dms
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class DmRelayListState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val dmListNote = cache.getOrCreateAddressableNote(getDMRelayListAddress())
fun getDMRelayListAddress() = ChatMessageRelayListEvent.createAddress(signer.pubKey)
fun getDMRelayListFlow(): StateFlow<NoteState> = dmListNote.flow().metadata.stateFlow
fun getDMRelayList(): ChatMessageRelayListEvent? = dmListNote.event as? ChatMessageRelayListEvent
fun normalizeDMRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> {
val event = note.event as? ChatMessageRelayListEvent ?: settings.backupDMRelayList
return event?.relays()?.toSet() ?: emptySet()
}
val flow =
getDMRelayListFlow()
.map { normalizeDMRelayListWithBackup(it.note) }
.onStart { emit(normalizeDMRelayListWithBackup(dmListNote)) }
.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun saveRelayList(dmRelays: List<NormalizedRelayUrl>): ChatMessageRelayListEvent {
val relayListForDMs = getDMRelayList()
return if (relayListForDMs != null && relayListForDMs.tags.isNotEmpty()) {
ChatMessageRelayListEvent.updateRelayList(
earlierVersion = relayListForDMs,
relays = dmRelays,
signer = signer,
)
} else {
ChatMessageRelayListEvent.create(
relays = dmRelays,
signer = signer,
)
}
}
init {
settings.backupDMRelayList?.let {
Log.d("AccountRegisterObservers", "Loading saved DM Relay List ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
LocalCache.justConsumeMyOwnEvent(it)
}
}
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "NIP-17 Relay List Collector Start")
getDMRelayListFlow().collect {
Log.d("AccountRegisterObservers", "Updating DM Relay List for ${signer.pubKey}")
(it.note.event as? ChatMessageRelayListEvent)?.let {
settings.updateDMRelayList(it)
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip18Reposts
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
class RepostAction {
companion object {
suspend fun repost(
note: Note,
signer: NostrSigner,
): Event? {
val noteEvent = note.event ?: return null
if (note.hasBoostedInTheLast5Minutes(signer.pubKey)) {
// has already bosted in the past 5mins
return null
}
val noteHint = note.relayHintUrl()
val authorHint = note.author?.bestRelayHint()
val template =
if (noteEvent.kind == 1) {
RepostEvent.build(noteEvent, noteHint, authorHint)
} else {
GenericRepostEvent.build(noteEvent, noteHint, authorHint)
}
return signer.sign(template)
}
}
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip25Reactions
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
class ReactionAction {
companion object {
suspend fun reactTo(
note: Note,
reaction: String,
by: User,
signer: NostrSigner,
onPublic: (ReactionEvent) -> Unit,
onPrivate: suspend (NIP17Factory.Result) -> Unit,
) {
if (!signer.isWriteable()) return
if (note.hasReacted(by, reaction)) {
// has already liked this note
return
}
val noteEvent = note.event
if (noteEvent is NIP17Group) {
val users = noteEvent.groupMembers().toList()
if (reaction.startsWith(":")) {
val emojiUrl = EmojiUrlTag.decode(reaction)
if (emojiUrl != null) {
note.toEventHint<Event>()?.let {
onPrivate(
NIP17Factory().createReactionWithinGroup(
emojiUrl = emojiUrl,
originalNote = it,
to = users,
signer = signer,
),
)
}
return
}
}
note.toEventHint<Event>()?.let {
onPrivate(
NIP17Factory().createReactionWithinGroup(
content = reaction,
originalNote = it,
to = users,
signer = signer,
),
)
}
return
} else {
if (reaction.startsWith(":")) {
val emojiUrl = EmojiUrlTag.decode(reaction)
if (emojiUrl != null) {
note.event?.let {
val template = ReactionEvent.build(emojiUrl, EventHintBundle(it, note.relayHintUrl()))
onPublic(signer.sign(template))
}
return
}
}
note.toEventHint<Event>()?.let {
onPublic(signer.sign(ReactionEvent.build(reaction, it)))
}
}
}
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip28PublicChats
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.note.toShortDisplay
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHintOptional
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelDataNorm
@Stable
class PublicChatChannel(
val idHex: String,
) : Channel() {
var creator: User? = null
var event: ChannelCreateEvent? = null
// Important to keep this long-term reference because LocalCache uses WeakReferences.
var creationEventNote: Note? = null
var updateEventNote: Note? = null
var info = ChannelDataNorm(null, null, null, null)
var infoTags = EmptyTagList
var updatedMetadataAt: Long = 0
override fun relays() = info.relays?.toSet() ?: super.relays()
fun relayHintUrls() = relays().take(3)
fun relayHintUrl() = relays().firstOrNull()
fun toNEvent() = NEvent.create(idHex, event?.pubKey, ChannelCreateEvent.KIND, relayHintUrls())
fun toNostrUri() = "nostr:${toNEvent()}"
fun toEventHint() = event?.let { EventHintBundle(it, relayHintUrl(), null) }
fun toEventId() = EventIdHintOptional(idHex, relayHintUrl())
fun updateChannelInfo(
creator: User,
event: ChannelCreateEvent,
eventNote: Note? = null,
) {
this.creator = creator
this.event = event
this.info = event.channelInfo()
this.infoTags = event.tags.toImmutableListOfLists()
this.updatedMetadataAt = event.createdAt
this.creationEventNote = eventNote
updateChannelInfo()
}
fun updateChannelInfo(
creator: User,
event: ChannelMetadataEvent,
eventNote: Note? = null,
) {
this.creator = creator
this.info = event.channelInfo()
this.infoTags = event.tags.toImmutableListOfLists()
this.updatedMetadataAt = event.createdAt
this.updateEventNote = eventNote
super.updateChannelInfo()
}
override fun toBestDisplayName(): String = info.name ?: toNEvent().toShortDisplay()
fun summary(): String? = info.about
fun profilePicture(): String? {
if (info.picture.isNullOrBlank()) return creator?.info?.banner
return info.picture ?: creator?.info?.banner
}
fun anyNameStartsWith(prefix: String): Boolean =
idHex.startsWith(prefix) ||
info.name?.contains(prefix, true) == true ||
info.about?.contains(prefix, true) == true
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip28PublicChats
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.channelSet
import com.vitorpamplona.quartz.nip28PublicChat.list.channels
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEventCache
class PublicChatListDecryptionCache(
val signer: NostrSigner,
) {
val cachedPrivateLists = PrivateTagArrayEventCache<ChannelListEvent>(signer)
fun cachedChannelSet(event: ChannelListEvent) = cachedPrivateLists.mergeTagListPrecached(event).channelSet()
fun cachedChannels(event: ChannelListEvent) = cachedPrivateLists.mergeTagListPrecached(event).channels()
suspend fun channelSet(event: ChannelListEvent) = cachedPrivateLists.mergeTagList(event).channelSet()
suspend fun channels(event: ChannelListEvent) = cachedPrivateLists.mergeTagList(event).channels()
}

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip28PublicChats
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.tags.ChannelTag
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
class PublicChatListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: PublicChatListDecryptionCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val publicChatListNote = cache.getOrCreateAddressableNote(getChannelListAddress())
fun getChannelListAddress() = ChannelListEvent.createAddress(signer.pubKey)
fun getChannelListFlow(): StateFlow<NoteState> = publicChatListNote.flow().metadata.stateFlow
fun getChannelList(): ChannelListEvent? = publicChatListNote.event as? ChannelListEvent
suspend fun publicChatListWithBackup(note: Note): Set<ChannelTag> {
val event = note.event as? ChannelListEvent ?: settings.backupChannelList
return event?.let { decryptionCache.channelSet(it) } ?: emptySet()
}
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<ChannelTag>> =
getChannelListFlow()
.transformLatest { noteState ->
emit(publicChatListWithBackup(noteState.note))
}.onStart {
emit(publicChatListWithBackup(publicChatListNote))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
@OptIn(ExperimentalCoroutinesApi::class)
val flowSet: StateFlow<Set<HexKey>> =
flow
.map {
it.mapTo(mutableSetOf()) { it.eventId }
}.onStart {
emit(flow.value.mapTo(mutableSetOf()) { it.eventId })
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun follow(channel: PublicChatChannel): ChannelListEvent {
val publicChatList = getChannelList()
return if (publicChatList == null) {
ChannelListEvent.create(ChannelTag(channel.idHex, channel.relayHintUrl()), true, signer)
} else {
ChannelListEvent.add(publicChatList, ChannelTag(channel.idHex, channel.relayHintUrl()), true, signer)
}
}
suspend fun follow(channels: List<PublicChatChannel>): ChannelListEvent {
val publicChatList = getChannelList()
val channelTags = channels.map { ChannelTag(it.idHex, it.relayHintUrl()) }
return if (publicChatList == null) {
ChannelListEvent.create(channelTags, true, signer)
} else {
ChannelListEvent.add(publicChatList, channelTags, true, signer)
}
}
suspend fun unfollow(channel: PublicChatChannel): ChannelListEvent? {
val publicChatList = getChannelList()
return if (publicChatList != null) {
ChannelListEvent.remove(publicChatList, ChannelTag(channel.idHex, channel.relayHintUrl()), signer)
} else {
null
}
}
init {
settings.backupChannelList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved channel list ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
LocalCache.justConsumeMyOwnEvent(event)
}
}
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Channel List Collector Start")
getChannelListFlow().collect {
Log.d("AccountRegisterObservers", "Channel List for ${signer.pubKey}")
(it.note.event as? ChannelListEvent)?.let {
settings.updateChannelListTo(it)
}
}
}
}
}

View File

@@ -0,0 +1,155 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.model.nip30CustomEmojis
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.aTag.taggedAddresses
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.taggedEmojis
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
class EmojiPackState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
) {
class EmojiMedia(
val code: String,
val link: MediaUrlImage,
)
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val emojiPackListNote = cache.getOrCreateAddressableNote(getEmojiPackSelectionAddress())
fun getEmojiPackSelectionAddress() = EmojiPackSelectionEvent.createAddress(signer.pubKey)
fun getEmojiPackSelection(): EmojiPackSelectionEvent? = emojiPackListNote.event as? EmojiPackSelectionEvent
fun getEmojiPackSelectionFlow(): StateFlow<NoteState> = emojiPackListNote.flow().metadata.stateFlow
fun convertEmojiSelectionPack(selection: EmojiPackSelectionEvent?): List<StateFlow<NoteState>>? =
selection?.taggedAddresses()?.map {
cache
.getOrCreateAddressableNote(it)
.flow()
.metadata.stateFlow
}
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<List<StateFlow<NoteState>>?> =
getEmojiPackSelectionFlow()
.transformLatest {
emit(convertEmojiSelectionPack(it.note.event as? EmojiPackSelectionEvent))
}.onStart {
emit(convertEmojiSelectionPack(getEmojiPackSelection()))
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyList(),
)
fun convertEmojiPack(pack: EmojiPackEvent): List<EmojiMedia> =
pack.taggedEmojis().map {
EmojiMedia(it.code, MediaUrlImage(it.url))
}
fun mergePack(list: Array<NoteState>): List<EmojiMedia> =
list
.mapNotNull {
val ev = it.note.event as? EmojiPackEvent
if (ev != null) {
convertEmojiPack(ev)
} else {
null
}
}.flatten()
.distinctBy { it.link }
@OptIn(ExperimentalCoroutinesApi::class)
val myEmojis =
flow
.transformLatest { emojiList ->
if (emojiList != null) {
emitAll(
combineTransform(emojiList) {
emit(mergePack(it))
},
)
} else {
emit(emptyList())
}
}.onStart {
emit(
mergePack(
convertEmojiSelectionPack(
getEmojiPackSelection(),
)?.map { it.value }?.toTypedArray() ?: emptyArray(),
),
)
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyList(),
)
suspend fun addEmojiPack(emojiPack: Note): EmojiPackSelectionEvent {
val emojiPackEvent = emojiPack.event
if (emojiPackEvent !is EmojiPackEvent) throw IllegalArgumentException("Cannot add an emoji pack to this kind of event.")
val eventHint = emojiPack.toEventHint<EmojiPackEvent>() ?: throw IllegalArgumentException("Cannot add an emoji pack to this kind of event.")
val usersEmojiList = getEmojiPackSelection()
return if (usersEmojiList == null) {
val template = EmojiPackSelectionEvent.build(listOf(eventHint))
signer.sign(template)
} else {
val template = EmojiPackSelectionEvent.add(usersEmojiList, eventHint)
signer.sign(template)
}
}
suspend fun removeEmojiPack(emojiPack: Note): EmojiPackSelectionEvent? {
val usersEmojiList = getEmojiPackSelection() ?: throw IllegalArgumentException("Cannot remove an emoji pack to this kind of event.")
val emojiPackEvent = emojiPack.event
if (emojiPackEvent !is EmojiPackEvent) return null
val template = EmojiPackSelectionEvent.remove(usersEmojiList, emojiPackEvent)
return signer.sign(template)
}
}

Some files were not shown because too many files have changed in this diff Show More