Commit Graph

11570 Commits

Author SHA1 Message Date
Claude 8444b943f7 fix: show emoji-list action (not bookmark) for kind 30030 notes
Three-dots menu on an EmojiPackEvent now offers Add/Remove from my emoji
list (kind 10030) and navigates to EmojiPackSelection. The regular
Manage bookmarks row is hidden for emoji packs so they can't end up in
the bookmark list by mistake. Closes #2426.
2026-04-20 17:58:33 +00:00
Claude 2c6b8596ec feat: add emoji pack management screens
Add screens under ui/screen/loggedIn/emojipacks/ mirroring the bookmarkgroups
layout:
 - list/ListOfEmojiPacksScreen plus EmojiPackItem for the pack feed, with a
   "My Emoji List" row at the top that surfaces kind 10030 selection count.
 - list/metadata/EmojiPackMetadataScreen(+ViewModel) for create/edit form.
 - display/EmojiPackScreen(+ViewModel) rendering the emoji grid, FAB to add,
   long-press to delete. AddEmojiDialog validates the shortcode live against
   EmojiUrlTag.isValidShortcode.
 - membershipManagement/EmojiPackSelectionScreen providing a single toggle
   for the user's kind 10030 selection.

New routes EmojiPacks, EmojiPackView, EmojiPackMetadataEdit, and
EmojiPackSelection wired through AppNavigation. A Manage Emoji Packs row is
added to the drawer. English-only strings added to strings.xml.
2026-04-20 17:58:19 +00:00
Claude 49568b9ca3 feat: add OwnedEmojiPacksState for managing the user's kind 30030 packs
Mirrors the LabeledBookmarkListsState structure: observes all authored
EmojiPackEvents in the local cache, exposes a sorted StateFlow<List<OwnedEmojiPack>>
for the UI, and provides suspend helpers for create/update/addEmoji/removeEmoji/
deletePack. Pack deletion publishes a kind 5 deletion event. Account gains
pass-through methods so AccountViewModel.launchSigner can invoke them.

Add a unit test for the OwnedEmojiPack data class.
2026-04-20 17:57:58 +00:00
Claude 5d74b4175b NIP-30: fix EmojiPackEvent.build typing and add metadata helpers
The build DSL was typed as TagArrayBuilder<GitRepositoryEvent>, which made
signer.sign(template) return the wrong event subclass. Retype it to
EmojiPackEvent and add local TagArrayBuilder extensions for title,
description, and image tags. Also add title()/description()/image()
accessors on EmojiPackEvent to match the LabeledBookmarkListEvent pattern.
2026-04-20 17:57:42 +00:00
Vitor Pamplona f76e4f4c49 Merge pull request #2458 from vitorpamplona/claude/investigate-github-issue-k17Fx
Add emoji set support and shortcode validation to NIP-30
2026-04-20 12:36:18 -04:00
Claude 254f5bdcc2 NIP-30: use Address type for emoji-set reference instead of raw string
Makes the API safer: callers can't accidentally pass an unparsed or
malformed identifier. EmojiUrlTag.emojiSet is now Address?, serialized
via toValue() and parsed via Address.parse() (which rejects anything
that isn't a well-formed kind:pubkey:dTag).
2026-04-20 16:35:29 +00:00
Vitor Pamplona 99ce01af4e Merge pull request #2457 from vitorpamplona/claude/fix-members-tag-update-OKFvB
Refactor Marmot group members to use reactive state flow
2026-04-20 12:33:34 -04:00
Vitor Pamplona e0094f163b ⏺ fix: derive nostrGroupId from MLS GroupContext in Welcome processing
The "h" tag is optional in MIP-02 Welcome events — senders like
  whitenoise-rs omit it. Instead of failing when the h-tag is absent,
  derive nostrGroupId from the NostrGroupData extension embedded in the
  Welcome's GroupContext (the authoritative MLS source). An h-tag hint,
  if present, is still validated against the MLS-derived value.
2026-04-20 12:32:42 -04:00
Claude b6e31b21c8 refactor: make Marmot group members list reactive
Expose the member list as MutableStateFlow<List<GroupMemberInfo>> on
MarmotGroupChatroom, populated by MarmotManager.syncMetadataTo alongside
the existing memberCount. MarmotGroupInfoScreen and RemoveMemberScreen
now observe it via collectAsStateWithLifecycle, so both remote commits
(already routed through syncMetadataTo in DecryptAndIndexProcessor) and
local mutations refresh the UI without manual re-queries.

Account.addMarmotGroupMember and removeMarmotGroupMember now call
syncMetadataTo right after the MLS state is mutated, mirroring the
pattern already used by updateMarmotGroupMetadata, so the local change
is visible immediately without waiting for the relay round-trip.
2026-04-20 16:27:31 +00:00
Vitor Pamplona 9766cc5901 Fixes the group id parsing 2026-04-20 12:23:46 -04:00
Claude 93c2041f0f NIP-30: add optional emoji-set address and shortcode validation
Per NIP-30 the emoji tag is ["emoji", <shortcode>, <url>, <emoji-set-address>]
with the fourth field optional. EmojiUrlTag only exposed the first three.
Adds emojiSet as an optional field, preserves existing positional callers
via default null, and introduces isValidShortcode() so callers can validate
user input against the spec (alphanumeric, hyphens, underscores).
2026-04-20 16:21:30 +00:00
Vitor Pamplona 4ccabb01e6 Adding capabilities to Amethyst's groups 2026-04-20 12:14:35 -04:00
Vitor Pamplona 4f2f8e180e Generates keypackages with 64 chars 2026-04-20 12:00:31 -04:00
Claude 7e45042c27 fix: refresh Marmot group members list when returning to info screen
Replace LaunchedEffect with LifecycleResumeEffect so the members list
(and chatroom.memberCount) is re-queried every time MarmotGroupInfoScreen
resumes, not just when nostrGroupId changes. Previously, after adding a
member via the AddMemberScreen and popping back, the members count
displayed in the header and chat top bar stayed stale until the screen
was fully recreated.
2026-04-20 15:58:00 +00:00
Vitor Pamplona f71d8eebb8 fixes the script for keypackages not showing up in wn 2026-04-20 11:42:29 -04:00
Vitor Pamplona e41ad84096 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-20 11:22:50 -04:00
Vitor Pamplona 1c797107bc fixes script on macos 2026-04-20 11:20:38 -04:00
Vitor Pamplona ad624031bf Improves the FetchFirst to not fail in the first eose from the first relay. 2026-04-20 11:15:09 -04:00
Vitor Pamplona d7cc99b196 update dependencies 2026-04-20 10:51:40 -04:00
Vitor Pamplona 37af8b0c45 fixes deprecation on vico charts 2026-04-20 10:51:18 -04:00
Vitor Pamplona 036323fa53 This was added by the secp rewrite, but we moved them out to another repo, so now it can be removed. 2026-04-20 10:43:54 -04:00
Vitor Pamplona 57af16302e Merge pull request #2455 from vitorpamplona/claude/fix-scaffold-scrolling-NHdRa
Refactor DisappearingScaffold to use unified bar state management
2026-04-20 10:42:01 -04:00
Vitor Pamplona 3e488623d4 Merge pull request #2453 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-20 10:24:23 -04:00
Crowdin Bot e808d40834 New Crowdin translations by GitHub Action 2026-04-20 14:19:21 +00:00
Claude 7637596835 Merge remote-tracking branch 'origin/main' into claude/fix-scaffold-scrolling-NHdRa 2026-04-20 14:19:18 +00:00
Vitor Pamplona 5551f2f7d2 Merge pull request #2456 from vitorpamplona/claude/nip51-interest-sets-p0f6r
Add Interest Sets feature for organizing hashtags
2026-04-20 10:17:31 -04:00
Claude 4c82011a25 fix(interest-sets): consume kind 30015 locally + empty-state polish
- LocalCache.consume dispatcher now handles InterestSetEvent via
  consumeBaseReplaceable, so the event created by the user is stored
  and flows through newEventBundles → interestSets.newNotes → listFeedFlow
  refresh. Without this, the just-signed event sat in the sendMyPublicAndPrivateOutbox
  call but the UI never saw the update.
- List screen: icon + centered text for the empty state and dividers
  between rows.
2026-04-20 14:11:06 +00:00
Vitor Pamplona 282b3a436b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-20 09:53:18 -04:00
Vitor Pamplona 0e077c02d0 linting 2026-04-20 09:41:58 -04:00
Vitor Pamplona 83837ad090 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-20 09:38:12 -04:00
davotoula 5eb91145df style: strip trailing whitespace and fix indent flagged by spotless
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:37:56 +02:00
Vitor Pamplona 2f3d035d3c Fixes failing tests due to dispatcher misconfiguration 2026-04-20 09:34:48 -04:00
Claude 198e950f37 feat(interest-sets): NIP-51 Interest Sets (kind 30015) UI + TopNav chips
Add Android UI for managing NIP-51 Interest Sets and surface them as chips
in the TopNav feed filter alongside followed hashtags. InterestSetEvent was
already implemented in Quartz; this wires it into the app.

- InterestSetsState mirrors LabeledBookmarkListsState: listFeedFlow with
  versioned refresh, decrypted hashtag cache keyed by dTag for the feed
  filter pipeline, and suspend helpers for create/rename/delete/clone/add
  hashtag/remove hashtag/move (public<->private).
- New screens under ui/screen/loggedIn/interestSets/: list, metadata edit
  (create/rename, title-only since InterestSetEvent has no image field),
  and display screen with add/remove + public/private toggle per hashtag.
- TopFilter.InterestSet(address) feeds into a new MultiHashtagFeedFlow
  that reuses HashtagTopNavFilter with Set<String>.
- TopNavFilterState.mergeInterests emits interest-set chips; FeedFilterSpinner
  groups them under a new "Interest Sets" category.
- Drawer entry + HomeScreen FAB branch.
2026-04-20 13:28:51 +00:00
Vitor Pamplona 516115cc1c Fixes test cases for the CallManagerTest 2026-04-20 09:21:01 -04:00
Vitor Pamplona fab435509b Merge pull request #2454 from vitorpamplona/claude/add-thumbhash-support-ZfNS1
Add ThumbHash support for image placeholders
2026-04-20 08:51:36 -04:00
Claude 63da850e3b perf(thumbhash): cache cosine tables and flatten hot loop in decoder
The reference port recomputed the inverse DCT cosine tables once per output
pixel — for a 32x24 decode that's ~43k redundant cos() calls. This change
lifts the tables out of the inner loop and caches them across decodes
keyed by (size, componentCount) so subsequent placeholders at the same
target size skip cosine evaluation entirely.

Additional wins:
- AC coefficients unpack into pre-sized DoubleArrays (no ArrayList<Double>
  boxing, no post-hoc copy)
- truncated hashes are rejected up-front via a single length check instead
  of mid-stream
- LPQA -> sRGB uses an inline branch clamp instead of a
  min/max/round/coerceIn chain

Tests: 12 unit tests cover determinism across repeated decodes, cache
clearing, alpha preservation, aspect-ratio preservation both directions,
average-color drift bounds, truncated input rejection, and round-tripping
base64 vs. raw bytes. All green.

Bench: new ThumbHashBenchmark exercises opaque decode, alpha decode,
warm- and cold-cache decode, aspect-ratio probe, and the full
decodeKeepAspectRatio pipeline so the perf delta is visible on device.
2026-04-20 00:33:26 +00:00
Claude b2f297b3bb feat: add ThumbHash support alongside BlurHash across events, uploads, and UI
Adds a parallel ThumbHash placeholder everywhere BlurHash is already used.
Remote events now carry both hashes; renderers prefer thumbhash when
available and fall back to blurhash. The MIP-04 `thumbhash` imeta field
that was previously reserved-but-unused is now wired end-to-end.

- New ThumbHash encoder/decoder in commons/commonMain (no new Gradle dep)
- New ThumbhashTag and thumbhash accessors/builders across NIP-17, 68,
  71, 94, 95, 99, the experimental profile gallery, and MIP-04
- RichTextParser + MediaContentModels carry a thumbhash field alongside
  blurhash so every downstream composable can pick its preferred placeholder
- New ThumbHashFetcher (Android + Desktop) registered with Coil, plus a
  small placeholderModel(thumbhash, blurhash) helper centralising the
  "prefer thumbhash, fall back to blurhash" rule
- Rename BlurhashMetadataCalculator -> PreviewMetadataCalculator; the
  calculator decodes the bitmap / video thumbnail once and runs both
  encoders on the same pixels to keep upload cost flat
- DesktopMediaMetadata, MediaUploadResult, and FileHeader now surface
  both hashes through the NIP-96, Blossom, NIP-95, MIP-04, NIP-17 DM,
  Classifieds, picture, video, and long-form upload paths
- Round-trip unit test for the ThumbHash port
2026-04-20 00:12:28 +00:00
Claude 5b8bbbaa42 merge: origin/main (BadgesScreen + banner features)
Resolves conflicts between our CompositionLocal-based scaffold padding
and main's new features:

Conflicts resolved:
- HomeScreen: main added `HomeAlgoFeedStatusBanner` floated on top of
  the feed with a Box wrapper. Kept main's Box + banner, but dropped
  the `Modifier.padding(paddingValues)` + `HorizontalPager
  (contentPadding = PaddingValues(0.dp))` — inner LazyColumns now pull
  scaffold padding via LocalDisappearingScaffoldPadding, and the banner
  aligns TopCenter with `padding(top = paddingValues.calculateTopPadding())`
  so it sits below the top bar even when the feed scrolls behind it.
- PinnedNotesScreen / BookmarkListScreen / OldBookmarkListScreen: main
  added `DeletedItemsBanner` that was stacked above the feed inside a
  padded Column. Same pattern as Home — feed fills the Box, banner
  overlays at TopCenter with the scaffold's top padding so items scroll
  under the bar while the banner stays pinned.
- `DeletedItemsBanner` gained a `modifier: Modifier = Modifier` param
  so callers can position it.

New-on-main screens audited and migrated:
- BadgesScreen: dropped `Column(Modifier.padding(paddingValues))`
  wrapper; LazyColumn now extends behind the bars via the
  CompositionLocal.
- BookmarkGroupScreen: SecondaryTabRow `containerColor =
  Color.Transparent` changed to `MaterialTheme.colorScheme.background`
  to match the other opaque tab rows (items scrolling underneath
  would otherwise bleed through).
- AwardBadgeScreen, NewBadgeScreen, ProfileBadgesScreen,
  AllSettingsScreen, FavoriteAlgoFeedsListScreen: none use
  DisappearingScaffold (stock `Scaffold`) — no migration needed.

Unit tests still green (10/10). Full amethyst module compiles.

https://claude.ai/code/session_01M3Bj24jLc9aVhMuvn55jXa
2026-04-19 23:46:54 +00:00
Vitor Pamplona 3bfeac885a Merge pull request #2452 from vitorpamplona/claude/badge-system-amethyst-J07UM
Add comprehensive badge system with creation, awarding, and profile management
2026-04-19 17:41:10 -04:00
Vitor Pamplona f43b334d93 Merge branch 'main' into claude/badge-system-amethyst-J07UM 2026-04-19 17:41:02 -04:00
Claude 35cab28543 feat(badges): add accept controls to notification card
The notification card BadgeCompose rendered the awarded badge via
BadgeDisplay (definition-only) and stopped there, so a recipient
viewing a fresh badge in their inbox had no way to add it to their
profile without navigating to the award's thread first.

Expose AcceptBadgeControls (was private inside Badge.kt) and call it
from BadgeCompose under the BadgeDisplay row when the underlying note
is a BadgeAwardEvent.

Other surfaces are unchanged:
- RenderBadgeAward (Badge feed / threads) keeps its own call to the
  same composable; behaviour is identical because the controls were
  already package-private.
- BadgeCompose has only one caller (CardFeedView, the notifications
  feed), so the new row only appears there.
2026-04-19 21:35:04 +00:00
Claude 8eeeae9601 refactor(badges/new): show form first with an upload placeholder
The FAB now opens the new-badge dialog directly. The dialog renders a
big bordered "Upload an image" placeholder where the picture will go;
tapping it opens the gallery. Once the user picks an image, the
placeholder is replaced by the existing ShowImageUploadGallery preview
and tapping the preview lets them pick a different image.

Lets the user see the whole form (name, description, server, quality,
strip-metadata) immediately instead of being thrown into the picker
the moment they hit the FAB.
2026-04-19 21:20:38 +00:00
Claude 29518dbf19 feat(badges/new): image-first creation flow with upload + auto UUID
Replace the form-first create screen with a picker-first media pipeline
that matches the other upload screens in the app.

- NewBadgeButton (FAB, AddPhotoAlternate icon) opens GallerySelect
  directly.
- After the image is picked, NewBadgeDialog mirrors the NewMediaView /
  ImageVideoPost pattern: a thumbnail strip, name + description fields,
  server picker, compression-quality slider, and strip-metadata switch.
- NewBadgeModel drives the upload through the shared MultiOrchestrator.
  Only on a successful upload does it reach into Account.sendBadgeDefinition
  with an auto-generated UUID d-tag, the uploaded URL + dimensions, and
  the uploaded URL reused as the NIP-58 thumb (a separate thumbnail
  upload can land as a follow-up).

The Cancel / Post buttons use the CreatingTopBar so "Create" reads
right on the primary action. Submit is disabled until the name is
non-empty, an image is staged, and a server is selected.

Drops the old route-based NewBadgeScreen / NewBadgeViewModel and the
Route.NewBadge entry.
2026-04-19 20:53:24 +00:00
Claude a346ae86de refactor(badges): default Badges feed filter to Mine 2026-04-19 20:20:55 +00:00
Vitor Pamplona 2d5097e8b0 Merge pull request #2451 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-19 16:13:40 -04:00
Crowdin Bot af27ad4673 New Crowdin translations by GitHub Action 2026-04-19 19:37:16 +00:00
davotoula f15e280862 updated cz,sv,pt,de 2026-04-19 21:31:56 +02:00
Vitor Pamplona fd49ee27ad Merge pull request #2443 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-19 15:04:49 -04:00
Vitor Pamplona bb1817c67c Merge pull request #2449 from vitorpamplona/claude/add-favorite-dvm-filter-DfveW
Add NIP-90 DVM favorite list support with content discovery
2026-04-19 15:04:42 -04:00
Claude f99fc4c333 refactor: rename FavoriteDvm* -> FavoriteAlgoFeed* throughout amethyst
The wire-level class was renamed to FavoriteAlgoFeedsListEvent in a
prior commit. Finish the rename through the rest of the feature so
the internal vocabulary (packages, classes, properties, routes, DSL
helpers, TopFilter variants, composables) matches what users see.

Packages:
- model.nip51Lists.favoriteDvmLists -> favoriteAlgoFeedsLists
- model.dvms                         -> algoFeeds
- model.topNavFeeds.favoriteDvm      -> favoriteAlgoFeeds
- home.datasource.nip90Dvms          -> nip90AlgoFeeds

Classes/composables renamed (selected):
- FavoriteDvmListState/DecryptionCache -> FavoriteAlgoFeedsListState/DecryptionCache
- FavoriteDvmOrchestrator / FavoriteDvmSnapshot -> FavoriteAlgoFeedsOrchestrator / FavoriteAlgoFeedsSnapshot
- FavoriteDvmTopNavFilter{,PerRelayFilter{,Set}}, FavoriteDvmFeedFlow ->
  FavoriteAlgoFeedTopNavFilter{,PerRelayFilter{,Set}}, FavoriteAlgoFeedFlow
- AllFavoriteDvmsTopNavFilter / AllFavoriteDvmsFeedFlow / AllFavoriteDvmsBanner ->
  AllFavoriteAlgoFeedsTopNavFilter / AllFavoriteAlgoFeedsFlow / AllFavoriteAlgoFeedsBanner
- FavoriteDvmName -> FavoriteAlgoFeedName
- FavoriteDvmToggle -> FavoriteAlgoFeedToggle
- FavoriteDvmListScreen -> FavoriteAlgoFeedsListScreen
- HomeDvmStatusBanner / SingleDvmBanner / DvmStatusBanner ->
  HomeAlgoFeedStatusBanner / SingleAlgoFeedBanner / AlgoFeedStatusBanner
- filterHomePostsByDvmIds -> filterHomePostsByAlgoFeedIds
- TopFilter.FavoriteDvm / TopFilter.AllFavoriteDvms ->
  TopFilter.FavoriteAlgoFeed / TopFilter.AllFavoriteAlgoFeeds
- Route.EditFavoriteDvms -> Route.EditFavoriteAlgoFeeds

Account / AccountSettings / AccountViewModel properties & mutators
renamed consistently: favoriteDvmList -> favoriteAlgoFeedsList,
backupFavoriteDvmList -> backupFavoriteAlgoFeedsList, follow/unfollow/
isFavoriteDvm -> follow/unfollow/isFavoriteAlgoFeed, refreshFavoriteDvm
-> refreshFavoriteAlgoFeed, dvmAddress kwargs -> feedAddress,
dvmNote locals -> feedNote, etc.

Persisted TopFilter.FavoriteAlgoFeed/AllFavoriteAlgoFeeds `code` strings
changed (old "FavoriteDvm/…" / " All Favourite DVMs " values don't
round-trip anymore) — kotlinx-serialization polymorphism keys the
sealed class by class name, so cold-start of pre-existing installs
will fall back to AllFollows for this filter. Acceptable since this
is a new feature only on this branch.

Kept unchanged: ui/screen/loggedIn/dvms/ folder (hosts generic
DvmContentDiscoveryScreen / DvmTopBar / DvmPaymentActions), quartz
nip90Dvms package (NIP-90 wire-protocol naming). XML string resource
keys (favorite_dvms_title, dvm_home_* etc) kept stable; only their
values were updated previously.

Build + tests green on both modules.
2026-04-19 17:56:16 +00:00