refactor: rename packages, helpers and app code after the NIP event renames

Follow-up to the event class renames: the names around those classes now
match too.

Quartz (published API, every old name kept as a @Deprecated alias/forwarder):
- nip51Lists packages moved: followList -> starterPack, hashtagList -> interestList,
  peopleList -> followSet, labeledBookmarkList -> bookmarkSet. The old packages
  keep a Deprecated.kt with typealiases and forwarding extension functions
  (ReplaceWith points at the new package).
- LnZapPrivateEvent -> PrivateZapEvent, LnZapReceiptValidator -> ZapReceiptValidator,
  ChannelListDiff -> PublicChatListDiff, HashtagListDiff -> InterestListDiff.
- Kind 30063 now indexes NIP-82 release notes for search (never NIP-51 content,
  which can hold encrypted private items). searchable-events docs updated.

App code (amethyst, commons, desktopApp, cli; no aliases needed):
- model packages nip51Lists.{hashtagLists,peopleList,labeledBookmarkLists,relayFeeds}
  -> {interestLists,followSets,bookmarkSets,favoriteRelays}.
- State/cache/UI names derived from the old event names: HashtagListState ->
  InterestListState, PeopleListsState -> FollowSetsState, FollowListsState ->
  StarterPacksState, LabeledBookmarkList -> BookmarkSet, RelayFeedListState ->
  FavoriteRelayListState, ContactCardsState -> UserAssertionsState, NIP90* view
  models/renderers -> Dvm*, LnZap* handlers -> Zap*, SealedRumor* -> Seal*, and
  Account.peopleLists/followLists/hashtagList -> followSets/starterPacks/interestList.
- String literals were left untouched, so preference keys and @SerialName values
  stored on disk are unchanged.
- The generic PeopleList UI model (shared by follow sets and starter packs) and the
  EmojiPackSelection route (which shows a kind 30030 pack) keep their names.

Docs: plans, brainstorms, changelogs, skills and READMEs use the new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015VvJ8XpeYqBe8bYT3bJ7UD
This commit is contained in:
Claude
2026-09-26 20:56:07 +00:00
parent 6d2c37c346
commit f72388a559
252 changed files with 1262 additions and 945 deletions
+2 -2
View File
@@ -43,10 +43,10 @@ Relay frame ──► LocalCache.insertOrUpdateNote() ──► LocalCacheFlow e
- `kind3FollowList = Kind3FollowListState(...)` ← NIP-02 ContactList (kind 3)
- `nip65RelayList = Nip65RelayListState(...)` ← NIP-65 RelayList (kind 10002), plus siblings `dmRelayList`, `searchRelayList`, `blockedRelayList`, `trustedRelayList`, `proxyRelayList`, `broadcastRelayList`, `indexerRelayList`, …
- `muteList = MuteListState(...)` ← NIP-51 MuteList (kind 10000)
- `bookmarkState = BookmarkListState(...)` ← NIP-51 Bookmarks (kind 10003), plus `labeledBookmarkLists`, `pinState`, `interestSets`, `peopleLists`, `followLists`, `hashtagList`, `geohashList`, `communityList`, `emoji`, `blossomServers`, …
- `bookmarkState = BookmarkListState(...)` ← NIP-51 Bookmarks (kind 10003), plus `bookmarkSets`, `pinState`, `interestSets`, `peopleLists`, `followLists`, `hashtagList`, `geohashList`, `communityList`, `emoji`, `blossomServers`, …
- Derived/merged views: `hiddenUsers`, `allFollows`, `homeRelays`, `outboxRelays`, `dmRelays`, `notificationRelays`, `trustedRelays`, and the `live*FollowListsPerRelay` outbox loaders.
- **The pattern:** each `XState` class pins its addressable note via `cache.getOrCreateAddressableNote(address)` (a long-term reference so GC/eviction can't drop it), exposes `val flow: StateFlow<…>` derived from the note's metadata flow (decrypted through a per-feature `DecryptionCache`, with backup fallback from `AccountSettings`, `stateIn(scope, Eagerly, …)`), and offers suspend mutation helpers (e.g. `MuteListState.hideUser(pubkey)`) that build the updated signed event. Consumers read `account.muteList.flow`, never a raw `MutableStateFlow` on `Account`.
- Encrypted lists pair the state object with a `DecryptionCache` sibling (`muteListDecryptionCache`, `peopleListDecryptionCache`, …) so NIP-44 decryption results are cached per event.
- Encrypted lists pair the state object with a `DecryptionCache` sibling (`muteListDecryptionCache`, `followSetDecryptionCache`, …) so NIP-44 decryption results are cached per event.
- UI reads via `collectAsStateWithLifecycle` on Android and `collectAsState` on Desktop.
- Sibling files per feature live alongside: `AccountSettings.kt`, `AccountSyncedSettings.kt`, plus per-NIP state classes under `model/nip02FollowLists/`, `model/nip51Lists/`, `model/nip65RelayList/`, etc.
@@ -16,8 +16,8 @@ been renamed, grep `Account.kt` for the class name.)
| `userMetadata` | `UserMetadataState` | 0 | `amethyst/.../model/nip01UserMetadata/` |
| `kind3FollowList` | `Kind3FollowListState` | 3 | `model/nip02FollowLists/` |
| `muteList` (+ `muteListDecryptionCache`) | `MuteListState` | 10000 | `model/nip51Lists/muteList/` |
| `blockPeopleList`, `peopleLists` | `BlockPeopleListState`, `PeopleListsState` | NIP-51 people sets | `model/nip51Lists/peopleList/` |
| `followLists` | `FollowListsState` | NIP-51 follow sets | `model/nip51Lists/peopleList/` |
| `blockPeopleList`, `peopleLists` | `BlockPeopleListState`, `FollowSetsState` | NIP-51 people sets | `model/nip51Lists/followSets/` |
| `followLists` | `StarterPacksState` | NIP-51 follow sets | `model/nip51Lists/followSets/` |
| `hiddenUsers` | `HiddenUsersState` — derived from `muteList.flow` + `blockPeopleList.flow` | — | `model/nip51Lists/` |
| `allFollows` | `MergedFollowListsState` — merges kind3 + people/follow/hashtag/geohash/community lists | — | `model/serverList/` |
@@ -31,7 +31,7 @@ been renamed, grep `Account.kt` for the class name.)
| `blockedRelayList` | `BlockedRelayListState` | 10006 | `model/nip51Lists/blockedRelays/` |
| `localRelayList` | `LocalRelayListState` | local | `model/localRelays/` |
| `privateStorageRelayList` | `PrivateStorageRelayListState` | private storage | `model/edits/` |
| `keyPackageRelayList`, `trustedRelayList`, `proxyRelayList`, `broadcastRelayList`, `indexerRelayList`, `relayFeedsList` | per-feature `…RelayListState` classes, each with a `DecryptionCache` sibling | custom relay sets | `model/nip51Lists/…` |
| `keyPackageRelayList`, `trustedRelayList`, `proxyRelayList`, `broadcastRelayList`, `indexerRelayList`, `favoriteRelayList` | per-feature `…RelayListState` classes, each with a `DecryptionCache` sibling | custom relay sets | `model/nip51Lists/…` |
Derived relay views (merge several of the above): `homeRelays`
(`AccountHomeRelayState`), `outboxRelays`, `dmRelays`, `notificationRelays`,
@@ -43,10 +43,10 @@ Derived relay views (merge several of the above): `homeRelays`
| Account property | State class | Kind | Package |
|------------------|-------------|------|---------|
| `bookmarkState` (and legacy `oldBookmarkState`) | `BookmarkListState` | 10003 | `model/nip51Lists/` |
| `labeledBookmarkLists` | `LabeledBookmarkListsState` | NIP-51 bookmark sets | `model/nip51Lists/labeledBookmarkLists/` |
| `bookmarkSets` | `BookmarkSetsState` | NIP-51 bookmark sets | `model/nip51Lists/bookmarkSets/` |
| `pinState` | `PinListState` | NIP-51 | `model/nip51Lists/` |
| `interestSets` | `InterestSetsState` | NIP-51 interest sets | `model/nip51Lists/interestSets/` |
| `hashtagList` / `geohashList` | `HashtagListState` / `GeohashListState` | NIP-51 | `model/nip51Lists/hashtagLists/`, `…/geohashLists/` |
| `hashtagList` / `geohashList` | `InterestListState` / `GeohashListState` | NIP-51 | `model/nip51Lists/interestLists/`, `…/geohashLists/` |
| `communityList` | `CommunityListState` | NIP-72 communities | `model/nip72Communities/` |
| `favoriteAlgoFeedsList` | `FavoriteAlgoFeedsListState` | NIP-51 | `model/nip51Lists/` |
| `emoji`, `ownedEmojiPacks` | `EmojiPackState`, `OwnedEmojiPacksState` | 10030 | `commons/.../commons/model/nip30CustomEmojis/` |
@@ -92,8 +92,8 @@ The same kind serves two roles, distinguished **by author**:
2. **The account's own contact cards (nicknames, NIP-81-style)** — signed by the account,
one per target user. The petname, summary, and their NIP-30 emoji mappings **always live in
the NIP-44 encrypted content, never in public tags** (`UserAssertionEvent.build`/
`updatePetNameAndSummary` strip stray public copies; asserted by `ContactCardPetNameTest`).
`commons/.../ContactCardsState.kt` keys everything on `author == account` and ignores
`updatePetNameAndSummary` strip stray public copies; asserted by `UserAssertionPetNameTest`).
`commons/.../UserAssertionsState.kt` keys everything on `author == account` and ignores
provider cards.
## Tag vocabulary and value semantics
@@ -225,8 +225,8 @@ card's `a`-tag (`30382:<provider>:<target>`).
- **Publisher**: `quartz/.../experimental/graperank/GrapeRankPublisher.kt` (canonical 30382
writer), `cli/.../graperank/` (`amy graperank register|unregister|providers|publish`).
- **Client model**: `commons/.../model/nip85TrustedAssertions/` (`ContactCardsState`,
`UserCardsCache`, `ContactCardDecryptionCache`, `TrustProviderListDecryptionCache`),
- **Client model**: `commons/.../model/nip85TrustedAssertions/` (`UserAssertionsState`,
`UserCardsCache`, `UserAssertionDecryptionCache`, `TrustProviderListDecryptionCache`),
`amethyst/.../model/trustedAssertions/TrustProviderListState.kt`.
- **Relay plumbing**: `commons/.../relayClient/assemblers/ContactCardFilters.kt`,
`amethyst/.../reqCommand/user/watchers/UserCardsSubAssembler.kt`.
+3 -4
View File
@@ -45,10 +45,9 @@ Notables that surprise people:
(`zapRequest?.content.orEmpty()`) — receipts are searchable by the zapper's comment.
- **Kind 0 / 31990** index many profile fields space-joined (name, about, nip05, lud16,
website, picture URL, …).
- **Kind 30063 is claimed twice** (`ReleaseArtifactSetEvent` in nip51Lists and the experimental
`SoftwareReleaseEvent`); `EventFactory` resolves 30063 to `ReleaseArtifactSetEvent`, so
`title()\ndescription()` is what actually gets indexed — `SoftwareReleaseEvent.indexableContent()`
is dead on the store path.
- **Kind 30063** (`ReleaseArtifactSetEvent`) serves both NIP-51 release artifact sets and NIP-82
software releases: it indexes `title()\ndescription()`, plus the release notes in `content` only
for NIP-82 releases (NIP-51 `content` can be encrypted private items).
- Poll kinds (1068, 6969) append each option label on its own line.
## MANDATORY maintenance when you touch this surface
@@ -5,7 +5,7 @@ expression. **Update this file in the same PR as any change to the searchable se
`indexableContent()` body** (see SKILL.md). Verified against the code 2026-09-17.
Counts: 137 concrete classes covering 140 kind values (`GitStatusEvent` spans 4 kinds;
kind 30063 has a collision — see the footnote). File paths are under
kind 30063 is shared by two NIPs — see the footnote). File paths are under
`quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/`.
Separator legend: **NL** = `joinToString("\n")`, **SP** = `joinToString(" ")`.
@@ -74,10 +74,10 @@ Separator legend: **NL** = `joinToString("\n")`, **SP** = `joinToString(" ")`.
| 12473 | BirdexEvent | experimental/birdstar | `(listOfNotNull(summary()) + speciesNames())` NL |
| 15128 | RootSiteEvent | nip5aStaticWebsites | `listOfNotNull(title(), description())` NL |
| 15129 | RootNappletEvent | nip5dNapplets | `listOfNotNull(title(), description())` NL |
| 30000 | FollowSetEvent | nip51Lists/peopleList | `listOfNotNull(titleOrName(), description())` NL |
| 30000 | FollowSetEvent | nip51Lists/followSet | `listOfNotNull(titleOrName(), description())` NL |
| 30001 | OldBookmarkListEvent | nip51Lists/bookmarkList | `listOfNotNull(title())` NL |
| 30002 | RelaySetEvent | nip51Lists/relaySets | `listOfNotNull(title(), description())` NL |
| 30003 | BookmarkSetEvent | nip51Lists/labeledBookmarkList | `listOfNotNull(titleOrName(), description())` NL |
| 30003 | BookmarkSetEvent | nip51Lists/bookmarkSet | `listOfNotNull(titleOrName(), description())` NL |
| 30004 | ArticleCurationSetEvent | nip51Lists/articleCurationSet | `listOfNotNull(title(), description())` NL |
| 30005 | VideoCurationSetEvent | nip51Lists/videoCurationSet | `listOfNotNull(title(), description())` NL |
| 30006 | PictureCurationSetEvent | nip51Lists/pictureCurationSet | `listOfNotNull(title(), description())` NL |
@@ -91,7 +91,7 @@ Separator legend: **NL** = `joinToString("\n")`, **SP** = `joinToString(" ")`.
| 30030 | EmojiPackEvent | nip30CustomEmoji/pack | `listOfNotNull(titleOrName(), description(), content)` NL |
| 30054 | Podcasting20EpisodeEvent | nipXXPodcasting20/episode | `(listOfNotNull(title(), description(), content) + topics())` NL |
| 30055 | Podcasting20TrailerEvent | nipXXPodcasting20/trailer | `listOfNotNull(title(), content)` NL |
| 30063 | ReleaseArtifactSetEvent † | nip51Lists/releaseArtifactSet | `listOfNotNull(title(), description())` NL |
| 30063 | ReleaseArtifactSetEvent † | nip51Lists/releaseArtifactSet | `listOfNotNull(title(), description(), content if NIP-82)` NL |
| 30175 | PersonaEvent | buzz/apPersonas | `personaOrNull()?.let { listOfNotNull(it.displayName, it.systemPrompt).joinToString("\n") } ?: ""` |
| 30176 | TeamEvent | buzz/teams | `teamOrNull()?.let { listOfNotNull(it.name, it.description, it.instructions).joinToString("\n") } ?: ""` |
| 30177 | ManagedAgentEvent | buzz/managedAgents | `agentOrNull()?.let { listOfNotNull(it.name, it.systemPrompt).joinToString("\n") } ?: ""` |
@@ -138,7 +138,7 @@ Separator legend: **NL** = `joinToString("\n")`, **SP** = `joinToString(" ")`.
| 38192 | Ps1SaveEvent | experimental/ps1saves | `listOfNotNull(summary(), saveTitle(), region(), filename())` NL |
| 38383 | P2POrderEvent | nip69P2pOrderEvents | `(listOfNotNull(makerName(), currency()) + paymentMethods().orEmpty()).joinToString(" ")` (SP) |
| 39000 | GroupMetadataEvent | nip29RelayGroups/metadata | `listOfNotNull(name(), about())` NL |
| 39089 | StarterPackEvent | nip51Lists/followList | `listOfNotNull(title(), description())` NL |
| 39089 | StarterPackEvent | nip51Lists/starterPack | `listOfNotNull(title(), description())` NL |
| 39092 | MediaStarterPackEvent | nip51Lists/mediaStarterPack | `listOfNotNull(title(), description())` NL |
| 39307 | TextTrackEvent | nip71Video/textTrack | `content` (the WebVTT cue text) |
| 39701 | WebBookmarkEvent | nipB0WebBookmarks | `listOfNotNull(title(), description())` NL |
@@ -150,10 +150,10 @@ Separator legend: **NL** = `joinToString("\n")`, **SP** = `joinToString(" ")`.
| 45003 | ForumCommentEvent | buzz/forum | `content` |
| 48106 | HuddleGuidelinesEvent | buzz/huddles | `content` |
† **Kind 30063 collision:** `experimental/nip82SoftwareApps/release/SoftwareReleaseEvent` also
declares `KIND = 30063` and implements `SearchableEvent` (`content`), but `EventFactory` maps
30063 to `ReleaseArtifactSetEvent`, so on every store path kind 30063 indexes
`title()\ndescription()`. If the factory mapping ever changes, this table changes with it.
† **Kind 30063 is shared** by NIP-51 release artifact sets and NIP-82 software releases, and
`ReleaseArtifactSetEvent` parses both. It indexes `title()` and `description()`, plus `content`
(the release notes) only when the event carries the NIP-82 `i` + `version` tags — a NIP-51 set
may hold encrypted private items in `content`, which must never be indexed.
## Abstract bases (no kind of their own)
@@ -68,7 +68,7 @@ The NIP-42 plumbing and a first-cut permission gate are already in place:
| Settings screen (global policy + per-relay list) | `amethyst/.../ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt` |
| Global policy setting, persisted local-only | `AccountSettings.defaultRelayAuthPolicy`, `LocalPreferences` key `DEFAULT_RELAY_AUTH_POLICY` |
| Blocked-relay list (kind 10006) | `amethyst/.../model/nip51Lists/blockedRelays/BlockedRelayListState.kt` (`.flow`) |
| Follow checks | `Account.isFollowing(...)`, `Account.allFollows.flow.value.authors`, `FollowListsState.isUserInFollowSets(...)` |
| Follow checks | `Account.isFollowing(...)`, `Account.allFollows.flow.value.authors`, `StarterPacksState.isUserInFollowSets(...)` |
| DM / NIP-65 relay lookups | `DmRelayListState`, `Nip65RelayListState` (+ per-user via `LocalCache`) |
## The gap
@@ -180,7 +180,7 @@ class RelayAuthContext(val relayUrl: String, val purposes: List<AuthPurpose>)
- `IF_IN_MY_LIST` → `ALLOW` if relay ∈ my relay lists, else fall through
- `TRUSTED_FOLLOWS` *(new — see idea A below)* → `ALLOW` if relay ∈ my lists
**or** any counterparty in `ctx.purposes` is followed (`Account.allFollows`
/ `FollowListsState.isUserInFollowSets`) and the purpose permits it; else
/ `StarterPacksState.isUserInFollowSets`) and the purpose permits it; else
fall through.
4. **Fall-through**: `ASK` if the purpose is attributable (we can show a reason);
otherwise `DENY` silently (don't prompt for anonymous stranger challenges).
@@ -392,11 +392,11 @@ can be a group event if it carries `h` and the relay accepts the kind."
has:
- `metadata/` — `GroupMetadataEvent` (kind **39000**), `GroupAdminsEvent`
(39001), `GroupMembersEvent` (39002), `SupportedRolesEvent` (39003).
- `moderation/` — `CreateGroupEvent` (9007), `EditMetadataEvent` (9002),
`PutUserEvent` (9000), `RemoveUserEvent` (9001), `DeleteEventEvent` (9005),
`DeleteGroupEvent` (9008), `CreateInviteEvent` (9009), plus tag helpers.
- `request/` — `JoinRequestEvent` (9021), `LeaveRequestEvent` (9022).
(39001), `GroupMembersEvent` (39002), `GroupRolesEvent` (39003).
- `moderation/` — `CreateGroupEvent` (9007), `GroupEditMetadataEvent` (9002),
`GroupPutUserEvent` (9000), `GroupRemoveUserEvent` (9001), `GroupDeleteEventEvent` (9005),
`DeleteGroupEvent` (9008), `GroupCreateInviteEvent` (9009), plus tag helpers.
- `request/` — `GroupJoinRequestEvent` (9021), `GroupLeaveRequestEvent` (9022).
- `tags/` — `GroupIdTag` (the `h` tag), `CodeTag`, `GroupAdminTag`, `RoleTag`,
and even a `PreviousTag`.
@@ -427,7 +427,7 @@ NIP-04/17 DMs, NIP-28 public channels, and NIP-C7 ephemeral chats — all under
### 5.2 The main protocol gap
There is **no kind-9 group chat message event** in `nip29RelayGroups/`
(`CreateInviteEvent` at 9009 is the highest kind present; nothing for kind 9).
(`GroupCreateInviteEvent` at 9009 is the highest kind present; nothing for kind 9).
The 9xxx moderation, 39xxx metadata, and 9021/9022 request events exist, but the
actual message carrier does not. This is the first thing to build:
@@ -71,7 +71,7 @@ Feed-level:
marker update (only the newest visible timestamp matters; the marker is
monotonic). One coroutine per scroll session instead of one per row.
6. **Jumbo without a coroutine (#4)**: only launch the decrypt effect for
encrypted kinds (`PrivateDmEvent`, sealed rumors not yet in the decrypt
encrypted kinds (`EncryptedDmEvent`, sealed rumors not yet in the decrypt
cache). Plaintext kinds (public chats, NIP-17 rumors already unwrapped —
the vast majority) take the synchronous path only.
7. **Retire settled delivery ticks (#6)**: once a message is fully accepted (or
@@ -55,7 +55,7 @@ Bitchat's Nostr side has two chat features. Amethyst is a pure-Nostr client
1. **Encrypted DMs (Phase 2).** Port the `bitchat1:` binary packet
(`BitchatPacket` TLV + `NoisePayloadType`) into quartz, wrap/unwrap it in the
existing NIP-17 stack (`GiftWrapEvent`/`SealedRumorEvent`/`ChatMessageEvent`),
existing NIP-17 stack (`GiftWrapEvent`/`SealEvent`/`ChatMessageEvent`),
handle geohash DMs (to a per-geohash pubkey) and delivery/read receipts.
Add `amy geochat dm` for interop testing.
2. **Desktop UI.** The shared pieces (quartz events, `GeoRelayDirectory`) are
@@ -178,10 +178,10 @@ This keeps the *policy* (who gets notified) exactly where it is and isolates the
| Kind(s) | Category | Style | Accent / icon | Title → Body | Actions |
| --- | --- | --- | --- | --- | --- |
| NIP-17 `ChatMessageEvent` (14), file (15), NIP-04 `PrivateDmEvent` (4), Marmot group | DIRECT_MESSAGE | `MessagingStyle` + Conversation shortcut + Bubble | blue-green / `chat` | sender → message | Reply, Mark-read |
| NIP-17 `ChatMessageEvent` (14), file (15), NIP-04 `EncryptedDmEvent` (4), Marmot group | DIRECT_MESSAGE | `MessagingStyle` + Conversation shortcut + Bubble | blue-green / `chat` | sender → message | Reply, Mark-read |
| `TextNoteEvent`(1)/`CommentEvent`(1111)/`ChannelMessageEvent`(42) **reply to me** | REPLY | `MessagingStyle` (parent as prior message) | purple / `reply` | "X replied" → excerpt (+ quoted parent) | Reply, View thread, Mute thread |
| Same kinds, **mention/quote only** | MENTION | plain (BigText) | purple / `alternate_email` | "X mentioned you" → excerpt | View, Mute thread |
| `LnZapEvent`(9735) | ZAP | **colorized** amount card (BigText) | gold / `bolt` | "⚡ 2,100 sats from X" → zap comment + zapped-note excerpt | Zap back, View |
| `ZapReceiptEvent`(9735) | ZAP | **colorized** amount card (BigText) | gold / `bolt` | "⚡ 2,100 sats from X" → zap comment + zapped-note excerpt | Zap back, View |
| `NutzapEvent`(9321) *(new)* | ZAP | colorized amount card | gold / `bolt` (cashu tint) | "🥜 X nutzapped you 2,100 sats" → … | Zap back, View |
| `OnchainZapEvent`(8333) *(new)* | ZAP | colorized amount card | gold / `bolt` | "⛓ X sent an onchain zap" → … | View |
| `ReactionEvent`(7) | REACTION | aggregated `InboxStyle` | heart-red / `favorite` | "🤙 X & N others liked your post" → note excerpt | Like back, View |
@@ -189,7 +189,7 @@ This keeps the *policy* (who gets notified) exactly where it is and isolates the
| `PictureEvent`(20)/`VideoNormal`/`Short`/`Vertical`/`Horizontal` | MEDIA | `BigPictureStyle` | purple / `image` | "X shared a photo/video" → caption, image inline | View |
| `PollEvent`/`ZapPollEvent` | MENTION | plain | purple / `ballot` | "X asked a question" → poll title | Vote, View |
| `HighlightEvent`(9802) | ARTICLE | plain | purple / `format_quote` | "X highlighted your article" → highlighted text | View |
| `LongTextNoteEvent`(30023)/`WikiNoteEvent`(30818) | ARTICLE | plain | purple / `article` | "X mentioned you in an article" → title | View |
| `LongFormContentEvent`(30023)/`WikiArticleEvent`(30818) | ARTICLE | plain | purple / `article` | "X mentioned you in an article" → title | View |
| `GitIssueEvent`/`GitPatchEvent`/`GitPullRequestEvent`/`…Update` | CODE | plain | slate / `merge` | "X opened an issue/PR" → subject | View |
| `BadgeAwardEvent`(8) *(new)* | BADGE | `BigPictureStyle` (badge art) | gold / `award_star` | "You earned a badge" → badge name, image | View |
| `LiveChessGameAcceptEvent`/`LiveChessMoveEvent` | CHESS | plain | brown / `chess` | "X accepted your challenge" / "your turn" | Open board |
+1 -1
View File
@@ -59,7 +59,7 @@ and dispatches the decrypted `Response`, so a new method needs no changes there:
decrypt-on-arrival.
- `NwcPaymentTracker` (`commons/…/service/nwc/`) — request↔response match with the
author-spoof gate.
- `LocalCache.consume(LnZapPaymentResponseEvent)` — routes 23195 back to the callback.
- `LocalCache.consume(NwcResponseEvent)` — routes 23195 back to the callback.
- Wallet storage: `AccountSettings.nwcWallets` + `defaultPaymentSourceId`;
`PaymentSourceResolver`.
- Pay rail entry: `ZapPaymentHandler.zap()` → `payViaNWC()` (the `PaymentSource.Nwc`
@@ -41,9 +41,9 @@ storm.
|---|---|
| The chip row + "Add" chip | `ui/note/creators/notify/Notifying.kt` (`Notifying`, `NotifyUserChip`, `AddUserChip`) |
| Audience state | `ShortNotePostViewModel.pTags: List<User>?`, `mutedNotifies: Set<HexKey>`, `activeNotifies()`, `addToReplyList(user)` |
| My NIP-51 people lists (kind 30000, public **and** decrypted private members) | `account.peopleLists.uiListFlow: StateFlow<List<PeopleList>>` (`model/nip51Lists/peopleList/PeopleListsState.kt`) |
| My follow packs (kind 39089, public members) | `account.followLists.uiListFlow` (`model/nip51Lists/peopleList/FollowListsState.kt`) |
| `PeopleList` UI model (`title`, `image`, `publicMembers`, `privateMembers` as `Set<User>`) | `model/nip51Lists/peopleList/PeopleList.kt` |
| My NIP-51 people lists (kind 30000, public **and** decrypted private members) | `account.peopleLists.uiListFlow: StateFlow<List<PeopleList>>` (`model/nip51Lists/followSets/FollowSetsState.kt`) |
| My follow packs (kind 39089, public members) | `account.followLists.uiListFlow` (`model/nip51Lists/followSets/StarterPacksState.kt`) |
| `PeopleList` UI model (`title`, `image`, `publicMembers`, `privateMembers` as `Set<User>`) | `model/nip51Lists/followSets/PeopleList.kt` |
| Two-column list catalog rendering | `ui/screen/loggedIn/lists/memberEdit/FollowListAndPackAndUserView.kt` — same "Follow sets" + "Discover follows" sectioning |
| Multi-select member review (count header, select-all checkbox, per-user row, confirm button) | `ui/screen/loggedIn/newUser/ImportFollowListPickFollowsScreen.kt` (`PreviewList` / `FollowEntryRow`) |
| Bottom-sheet picker shell w/ search field | `ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupParentPicker.kt` |
@@ -281,7 +281,7 @@ In `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt`
```kotlin
import com.vitorpamplona.quartz.nip90Dvms.dvmHeartbeat.DvmHeartbeatEvent
```
2. Add a dispatch branch inside the `when (kind)` near the other NIP-90 kinds (next to line 736 `NIP90StatusEvent.KIND -> ...`):
2. Add a dispatch branch inside the `when (kind)` near the other NIP-90 kinds (next to line 736 `DvmStatusEvent.KIND -> ...`):
```kotlin
DvmHeartbeatEvent.KIND -> DvmHeartbeatEvent(id, pubKey, createdAt, tags, content, sig)
```
@@ -304,7 +304,7 @@ private val knownDTagReaders =
- [ ] **Step 6: Update the design doc §2**
In `amethyst/plans/2026-09-10-dvm-heartbeat-liveness.md`, replace the §2 bullet that says the class "extends `BaseReplaceableEvent`" / "Overrides `dTag()`" with the as-built wording: extends `BaseAddressableEvent` (the codebase convention for 10xxx events with real `d` tags, e.g. `FollowListEvent`), so `dTag()`/`address()`/`addressTag()` come from the base; note `MAX_AGE_SECONDS`, `isFreshAt`, and the `knownDTagReaders` entry live in quartz too (commons imports them).
In `amethyst/plans/2026-09-10-dvm-heartbeat-liveness.md`, replace the §2 bullet that says the class "extends `BaseReplaceableEvent`" / "Overrides `dTag()`" with the as-built wording: extends `BaseAddressableEvent` (the codebase convention for 10xxx events with real `d` tags, e.g. `StarterPackEvent`), so `dTag()`/`address()`/`addressTag()` come from the base; note `MAX_AGE_SECONDS`, `isFreshAt`, and the `knownDTagReaders` entry live in quartz too (commons imports them).
- [ ] **Step 7: Run the tests to verify they pass**
@@ -45,7 +45,7 @@ feeds and the detail surface show an offline state instead.
New `quartz/.../nip90Dvms/dvmHeartbeat/DvmHeartbeatEvent.kt`:
- `class DvmHeartbeatEvent(...) : BaseAddressableEvent(...)`, `KIND = 11998` — the codebase
convention for 10xxx events with real `d` tags (e.g. `FollowListEvent`), so `dTag()` /
convention for 10xxx events with real `d` tags (e.g. `StarterPackEvent`), so `dTag()` /
`address()` / `addressTag()` come from the base. The cache address is
`Address(11998, dvmPubkey, dTag)`, the exact mirror of the announcement's
`Address(31990, dvmPubkey, dTag)`.
@@ -83,7 +83,7 @@ The listing event is mostly existing tags wearing a new kind number:
| `r` | `nip51Lists/tags/RelayTag` (`"r"`) |
| `a` | `nip01Core/tags/aTag/ATag` (curation list + found log) |
| `title`, `description` (37517) | `nip51Lists/tags/TitleTag`, `.../DescriptionTag` |
| embedded 7517 | `Event.fromJson(...)` (same trick `LnZapEvent.zapRequest()` uses) |
| embedded 7517 | `Event.fromJson(...)` (same trick `ZapReceiptEvent.zapRequest()` uses) |
| signature check | `Event.verifySignature()` in `nip01Core/crypto/EventExt.kt` |
| kind 1111 logs | `nip22Comments/CommentEvent` **as-is** — it already models `A/K/P` + `a/k/p` addressable root+parent (`rootAddress()`, `replyAddress()`, `hasRootAddress()`). Only the `t` log-type vocabulary is new. |
| required-tag gate | `containsAllTagNamesWithValues(REQUIRED_FIELDS)`, as `ClassifiedsEvent.isWellFormed()` does |
@@ -94,7 +94,7 @@ Section labels: "Bitcoin" and "Lightning".
## Subscription edits — extend existing kind lists
No new assemblers. Add `OnchainZapEvent.KIND` (8333) to the existing
`LnZapEvent.KIND` (9735) sites:
`ZapReceiptEvent.KIND` (9735) sites:
| File | Edit |
|---|---|
@@ -109,7 +109,7 @@ No new assemblers. Add `OnchainZapEvent.KIND` (8333) to the existing
## Display path — fold into `Note.zapsAmount`
Today: `LocalCache.consume(LnZapEvent)` → `Note.addZap()` → `Note.updateZapTotal()`
Today: `LocalCache.consume(ZapReceiptEvent)` → `Note.addZap()` → `Note.updateZapTotal()`
sums lightning amounts into `Note.zapsAmount`, which `ReactionsRow` /
`ObserveZapAmountText` / `SlidingAnimationAmount` render. We add onchain zap
sats to the same `Note.zapsAmount` — no UI changes required.
@@ -118,7 +118,7 @@ sats to the same `Note.zapsAmount` — no UI changes required.
- Add `var onchainZaps = mapOf<...>()` (separate map from `zaps`).
- Extend `updateZapTotal()` to add **verified** onchain sats. Unverified or
pending tx amounts are NOT counted.
- `amethyst/.../model/LocalCache.kt` (after the `consume(LnZapEvent)` block ~line 1667)
- `amethyst/.../model/LocalCache.kt` (after the `consume(ZapReceiptEvent)` block ~line 1667)
- New `consume(event: OnchainZapEvent)` handler.
- Reject self-zap.
- Enqueue verification against the configured `OnchainBackend`.
@@ -34,7 +34,7 @@ and what remains as future work.
2. **Private mute/block leak via `identity.getMutes`/`getBlocked`.** These read
`muteList.flow` / `blockPeopleList.flow`, which contain **decrypted private** entries.
Now they read the events' **public** tags only (`MuteListEvent.publicMutes()`,
`PeopleListEvent.publicUsersIdSet()`).
`FollowSetEvent.publicUsersIdSet()`).
3. **Silent "allow-always" actions + UI-redress.** Added persistent **trusted chrome**
(a sandbox bar the applet can't draw over: shield + name + tap-to-see "what it can
@@ -62,7 +62,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
@@ -599,7 +599,7 @@ object LocalPreferences {
putOrRemove(PrefKeys.LATEST_NIP65_RELAY_LIST, settings.backupNIP65RelayList)
putOrRemove(PrefKeys.LATEST_SEARCH_RELAY_LIST, settings.backupSearchRelayList)
putOrRemove(PrefKeys.LATEST_INDEX_RELAY_LIST, settings.backupIndexRelayList)
putOrRemove(PrefKeys.LATEST_RELAY_FEEDS_LIST, settings.backupRelayFeedsList)
putOrRemove(PrefKeys.LATEST_RELAY_FEEDS_LIST, settings.backupFavoriteRelayList)
putOrRemove(PrefKeys.LATEST_BLOCKED_RELAY_LIST, settings.backupBlockedRelayList)
putOrRemove(PrefKeys.LATEST_TRUSTED_RELAY_LIST, settings.backupTrustedRelayList)
@@ -613,9 +613,9 @@ object LocalPreferences {
putOrRemove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST, settings.backupPrivateHomeRelayList)
putOrRemove(PrefKeys.LATEST_APP_SPECIFIC_DATA, settings.backupAppSpecificData)
putOrRemove(PrefKeys.LATEST_CHANNEL_LIST, settings.backupChannelList)
putOrRemove(PrefKeys.LATEST_CHANNEL_LIST, settings.backupPublicChatList)
putOrRemove(PrefKeys.LATEST_COMMUNITY_LIST, settings.backupCommunityList)
putOrRemove(PrefKeys.LATEST_HASHTAG_LIST, settings.backupHashtagList)
putOrRemove(PrefKeys.LATEST_HASHTAG_LIST, settings.backupInterestList)
putOrRemove(PrefKeys.LATEST_GEOHASH_LIST, settings.backupGeohashList)
putOrRemove(PrefKeys.LATEST_EPHEMERAL_LIST, settings.backupEphemeralChatList)
putOrRemove(PrefKeys.LATEST_RELAY_GROUP_LIST, settings.backupRelayGroupList)
@@ -827,15 +827,15 @@ object LocalPreferences {
val latestNip65RelayListStr = getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)
val latestSearchRelayListStr = getString(PrefKeys.LATEST_SEARCH_RELAY_LIST, null)
val latestIndexRelayListStr = getString(PrefKeys.LATEST_INDEX_RELAY_LIST, null)
val latestRelayFeedsListStr = getString(PrefKeys.LATEST_RELAY_FEEDS_LIST, null)
val latestFavoriteRelayListStr = getString(PrefKeys.LATEST_RELAY_FEEDS_LIST, null)
val latestBlockedRelayListStr = getString(PrefKeys.LATEST_BLOCKED_RELAY_LIST, null)
val latestTrustedRelayListStr = getString(PrefKeys.LATEST_TRUSTED_RELAY_LIST, null)
val latestMuteListStr = getString(PrefKeys.LATEST_MUTE_LIST, null)
val latestPrivateHomeRelayListStr = getString(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST, null)
val latestAppSpecificDataStr = getString(PrefKeys.LATEST_APP_SPECIFIC_DATA, null)
val latestChannelListStr = getString(PrefKeys.LATEST_CHANNEL_LIST, null)
val latestPublicChatListStr = getString(PrefKeys.LATEST_CHANNEL_LIST, null)
val latestCommunityListStr = getString(PrefKeys.LATEST_COMMUNITY_LIST, null)
val latestHashtagListStr = getString(PrefKeys.LATEST_HASHTAG_LIST, null)
val latestInterestListStr = getString(PrefKeys.LATEST_HASHTAG_LIST, null)
val latestGeohashListStr = getString(PrefKeys.LATEST_GEOHASH_LIST, null)
val latestEphemeralListStr = getString(PrefKeys.LATEST_EPHEMERAL_LIST, null)
val latestRelayGroupListStr = getString(PrefKeys.LATEST_RELAY_GROUP_LIST, null)
@@ -891,15 +891,15 @@ object LocalPreferences {
val latestNip65RelayList = async { parseEventOrNull<AdvertisedRelayListEvent>(latestNip65RelayListStr) }
val latestSearchRelayList = async { parseEventOrNull<SearchRelayListEvent>(latestSearchRelayListStr) }
val latestIndexRelayList = async { parseEventOrNull<IndexerRelayListEvent>(latestIndexRelayListStr) }
val latestRelayFeedsList = async { parseEventOrNull<FavoriteRelayListEvent>(latestRelayFeedsListStr) }
val latestFavoriteRelayList = async { parseEventOrNull<FavoriteRelayListEvent>(latestFavoriteRelayListStr) }
val latestBlockedRelayList = async { parseEventOrNull<BlockedRelayListEvent>(latestBlockedRelayListStr) }
val latestTrustedRelayList = async { parseEventOrNull<TrustedRelayListEvent>(latestTrustedRelayListStr) }
val latestMuteList = async { parseEventOrNull<MuteListEvent>(latestMuteListStr) }
val latestPrivateHomeRelayList = async { parseEventOrNull<PrivateOutboxRelayListEvent>(latestPrivateHomeRelayListStr) }
val latestAppSpecificData = async { parseEventOrNull<AppSpecificDataEvent>(latestAppSpecificDataStr) }
val latestChannelList = async { parseEventOrNull<PublicChatListEvent>(latestChannelListStr) }
val latestPublicChatList = async { parseEventOrNull<PublicChatListEvent>(latestPublicChatListStr) }
val latestCommunityList = async { parseEventOrNull<CommunityListEvent>(latestCommunityListStr) }
val latestHashtagList = async { parseEventOrNull<InterestListEvent>(latestHashtagListStr) }
val latestInterestList = async { parseEventOrNull<InterestListEvent>(latestInterestListStr) }
val latestGeohashList = async { parseEventOrNull<GeohashListEvent>(latestGeohashListStr) }
val latestEphemeralList = async { parseEventOrNull<EphemeralChatListEvent>(latestEphemeralListStr) }
val latestRelayGroupList = async { parseEventOrNull<SimpleGroupListEvent>(latestRelayGroupListStr) }
@@ -947,15 +947,15 @@ object LocalPreferences {
val latestNip65RelayListResolved = latestNip65RelayList.await()
val latestSearchRelayListResolved = latestSearchRelayList.await()
val latestIndexRelayListResolved = latestIndexRelayList.await()
val latestRelayFeedsListResolved = latestRelayFeedsList.await()
val latestFavoriteRelayListResolved = latestFavoriteRelayList.await()
val latestBlockedRelayListResolved = latestBlockedRelayList.await()
val latestTrustedRelayListResolved = latestTrustedRelayList.await()
val latestMuteListResolved = latestMuteList.await()
val latestPrivateHomeRelayListResolved = latestPrivateHomeRelayList.await()
val latestAppSpecificDataResolved = latestAppSpecificData.await()
val latestChannelListResolved = latestChannelList.await()
val latestPublicChatListResolved = latestPublicChatList.await()
val latestCommunityListResolved = latestCommunityList.await()
val latestHashtagListResolved = latestHashtagList.await()
val latestInterestListResolved = latestInterestList.await()
val latestGeohashListResolved = latestGeohashList.await()
val latestEphemeralListResolved = latestEphemeralList.await()
val latestRelayGroupListResolved = latestRelayGroupList.await()
@@ -1049,15 +1049,15 @@ object LocalPreferences {
backupDMRelayList = latestDmRelayListResolved,
backupSearchRelayList = latestSearchRelayListResolved,
backupIndexRelayList = latestIndexRelayListResolved,
backupRelayFeedsList = latestRelayFeedsListResolved,
backupFavoriteRelayList = latestFavoriteRelayListResolved,
backupBlockedRelayList = latestBlockedRelayListResolved,
backupTrustedRelayList = latestTrustedRelayListResolved,
backupPrivateHomeRelayList = latestPrivateHomeRelayListResolved,
backupMuteList = latestMuteListResolved,
backupAppSpecificData = latestAppSpecificDataResolved,
backupChannelList = latestChannelListResolved,
backupPublicChatList = latestPublicChatListResolved,
backupCommunityList = latestCommunityListResolved,
backupHashtagList = latestHashtagListResolved,
backupInterestList = latestInterestListResolved,
backupGeohashList = latestGeohashListResolved,
backupEphemeralChatList = latestEphemeralListResolved,
backupRelayGroupList = latestRelayGroupListResolved,
@@ -75,21 +75,21 @@ import com.vitorpamplona.amethyst.commons.model.nip51Lists.blockPeopleList.Block
import com.vitorpamplona.amethyst.commons.model.nip51Lists.blockedRelays.BlockedRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.broadcastRelays.BroadcastRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.favoriteAlgoFeedsLists.FavoriteAlgoFeedsListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.favoriteRelays.FavoriteRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.FollowSetDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.geohashLists.GeohashListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.hashtagLists.HashtagListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.indexerRelays.IndexerRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.interestLists.InterestListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.muteList.MuteListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.proxyRelays.ProxyRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.relayFeeds.RelayFeedsListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.searchRelays.SearchRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.trustedRelays.TrustedRelayListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip56Reports.ReportAction
import com.vitorpamplona.amethyst.commons.model.nip62Vanish.VanishRequestsState
import com.vitorpamplona.amethyst.commons.model.nip72Communities.CommunityListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip85TrustedAssertions.ContactCardDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip85TrustedAssertions.ContactCardsState
import com.vitorpamplona.amethyst.commons.model.nip85TrustedAssertions.TrustProviderListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip85TrustedAssertions.UserAssertionDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip85TrustedAssertions.UserAssertionsState
import com.vitorpamplona.amethyst.commons.model.nipBCOnchainZaps.OnchainWalletState
import com.vitorpamplona.amethyst.commons.model.privateChatLastReadRoute
import com.vitorpamplona.amethyst.commons.model.privateChats.hasEncryptedContent
@@ -145,18 +145,18 @@ import com.vitorpamplona.amethyst.model.nip46Signer.Nip46SignerState
import com.vitorpamplona.amethyst.model.nip47WalletConnect.NwcSignerState
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.bookmarkSets.BookmarkSetsState
import com.vitorpamplona.amethyst.model.nip51Lists.broadcastRelays.BroadcastRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.favoriteAlgoFeedsLists.FavoriteAlgoFeedsListState
import com.vitorpamplona.amethyst.model.nip51Lists.favoriteRelays.FavoriteRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSetsState
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.StarterPacksState
import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState
import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListState
import com.vitorpamplona.amethyst.model.nip51Lists.indexerRelays.IndexerRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.interestLists.InterestListState
import com.vitorpamplona.amethyst.model.nip51Lists.interestSets.InterestSetsState
import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkListsState
import com.vitorpamplona.amethyst.model.nip51Lists.muteList.MuteListState
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.FollowListsState
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleListsState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.relayFeeds.RelayFeedListState
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListState
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
@@ -281,8 +281,8 @@ import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.AddressBookmark
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapCache
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEvent
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
import com.vitorpamplona.quartz.nip57Zaps.ZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
@@ -639,8 +639,8 @@ class Account(
val indexerRelayListDecryptionCache = IndexerRelayListDecryptionCache(signer)
val indexerRelayList = IndexerRelayListState(signer, cache, indexerRelayListDecryptionCache, scope, settings)
val relayFeedsListDecryptionCache = RelayFeedsListDecryptionCache(signer)
val relayFeedsList = RelayFeedListState(signer, cache, relayFeedsListDecryptionCache, scope, settings)
val favoriteRelayListDecryptionCache = FavoriteRelayListDecryptionCache(signer)
val favoriteRelayList = FavoriteRelayListState(signer, cache, favoriteRelayListDecryptionCache, scope, settings)
val blockedRelayListDecryptionCache = BlockedRelayListDecryptionCache(signer)
val blockedRelayList = BlockedRelayListState(signer, cache, blockedRelayListDecryptionCache, scope, settings)
@@ -758,8 +758,8 @@ class Account(
val communityListDecryptionCache = CommunityListDecryptionCache(signer)
val communityList = CommunityListState(signer, cache, communityListDecryptionCache, scope, settings)
val hashtagListDecryptionCache = HashtagListDecryptionCache(signer)
val hashtagList = HashtagListState(signer, cache, hashtagListDecryptionCache, scope, settings)
val interestListDecryptionCache = InterestListDecryptionCache(signer)
val interestList = InterestListState(signer, cache, interestListDecryptionCache, scope, settings)
val favoriteAlgoFeedsListDecryptionCache = FavoriteAlgoFeedsListDecryptionCache(signer)
val favoriteAlgoFeedsList = FavoriteAlgoFeedsListState(signer, cache, favoriteAlgoFeedsListDecryptionCache, scope, settings)
@@ -777,14 +777,14 @@ class Account(
val trustProviderListDecryptionCache = TrustProviderListDecryptionCache(signer)
val trustProviderList = TrustProviderListState(signer, cache, trustProviderListDecryptionCache, scope, settings)
val peopleListDecryptionCache = PeopleListDecryptionCache(signer)
val blockPeopleList = BlockPeopleListState(signer, cache, peopleListDecryptionCache, scope)
val peopleLists = PeopleListsState(signer, cache, peopleListDecryptionCache, scope)
val followLists = FollowListsState(signer, cache, scope)
val followSetDecryptionCache = FollowSetDecryptionCache(signer)
val blockPeopleList = BlockPeopleListState(signer, cache, followSetDecryptionCache, scope)
val followSets = FollowSetsState(signer, cache, followSetDecryptionCache, scope)
val starterPacks = StarterPacksState(signer, cache, scope)
val hiddenUsers = HiddenUsersState(muteList.flow, blockPeopleList.flow, scope, settings)
val labeledBookmarkLists = LabeledBookmarkListsState(signer, cache, scope)
val bookmarkSets = BookmarkSetsState(signer, cache, scope)
val interestSets = InterestSetsState(signer, cache, scope)
val appRecommendations = AppRecommendationsState(signer, cache, scope)
val oldBookmarkState = OldBookmarkListState(signer, cache, scope)
@@ -795,8 +795,8 @@ class Account(
val ownedEmojiPacks = OwnedEmojiPacksState(signer, cache, scope)
// needs `emoji` above: nickname edits resolve :shortcodes: against the account's packs
val contactCardDecryptionCache = ContactCardDecryptionCache(signer)
val contactCards = ContactCardsState(signer, cache, contactCardDecryptionCache, emoji)
val userAssertionDecryptionCache = UserAssertionDecryptionCache(signer)
val userAssertions = UserAssertionsState(signer, cache, userAssertionDecryptionCache, emoji)
val vanish = VanishRequestsState(signer, cache, client, scope)
@@ -893,7 +893,7 @@ class Account(
val followsPerRelay = FollowsPerOutboxRelay(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope).flow
// Merges all follow lists to create a single All Follows feed.
val allFollows = MergedFollowListsState(kind3FollowList, peopleLists, followLists, hashtagList, geohashList, communityList, scope)
val allFollows = MergedFollowListsState(kind3FollowList, followSets, starterPacks, interestList, geohashList, communityList, scope)
val privateDMDecryptionCache = PrivateDMCache(signer)
override val privateZapsDecryptionCache = PrivateZapCache(signer)
@@ -1030,10 +1030,10 @@ class Account(
val feedDecryptionCaches =
FeedDecryptionCaches(
peopleListCache = peopleListDecryptionCache,
peopleListCache = followSetDecryptionCache,
muteListCache = muteListDecryptionCache,
communityListCache = communityListDecryptionCache,
hashtagCache = hashtagListDecryptionCache,
hashtagCache = interestListDecryptionCache,
geohashCache = geohashListDecryptionCache,
)
@@ -1047,7 +1047,7 @@ class Account(
blockedRelays = blockedRelayList.flow,
proxyRelays = proxyRelayList.flow,
mineRelays = mineRelays.flow,
relayFeeds = relayFeedsList.flow,
relayFeeds = favoriteRelayList.flow,
caches = feedDecryptionCaches,
signer = signer,
scope = scope,
@@ -2126,9 +2126,9 @@ class Account(
suspend fun unfollow(community: AddressableNote) = sendMyPublicAndPrivateOutbox(communityList.unfollow(community))
suspend fun followHashtag(tag: String) = sendMyPublicAndPrivateOutbox(hashtagList.follow(tag))
suspend fun followHashtag(tag: String) = sendMyPublicAndPrivateOutbox(interestList.follow(tag))
suspend fun unfollowHashtag(tag: String) = sendMyPublicAndPrivateOutbox(hashtagList.unfollow(tag))
suspend fun unfollowHashtag(tag: String) = sendMyPublicAndPrivateOutbox(interestList.unfollow(tag))
suspend fun followFavoriteAlgoFeed(dvm: AddressBookmark) = sendMyPublicAndPrivateOutbox(favoriteAlgoFeedsList.follow(dvm))
@@ -3440,11 +3440,11 @@ class Account(
* encrypted in the content. `null` clears a field. Goes out through the
* account's extended outbox relays.
*/
suspend fun updateContactCardPetName(
suspend fun updateUserAssertionPetName(
pubkeyHex: HexKey,
petName: String?,
summary: String?,
) = sendMyPublicAndPrivateOutbox(contactCards.updatePetNameAndSummary(pubkeyHex, petName, summary))
) = sendMyPublicAndPrivateOutbox(userAssertions.updatePetNameAndSummary(pubkeyHex, petName, summary))
suspend fun showUser(pubkeyHex: HexKey) {
sendMyPublicAndPrivateOutbox(blockPeopleList.showUser(pubkeyHex))
@@ -3550,7 +3550,7 @@ class Account(
}
}
suspend fun decryptZapOrNull(event: ZapRequestEvent): LnZapPrivateEvent? = if (event.isPrivateZap() && isWriteable()) privateZapsDecryptionCache.decryptPrivateZap(event) else null
suspend fun decryptZapOrNull(event: ZapRequestEvent): PrivateZapEvent? = if (event.isPrivateZap() && isWriteable()) privateZapsDecryptionCache.decryptPrivateZap(event) else null
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
@@ -3747,11 +3747,11 @@ class Account(
suspend fun saveTrustedRelayList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(trustedRelayList.saveRelayList(trustedRelays))
suspend fun saveRelayFeedsList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(relayFeedsList.saveRelayList(trustedRelays))
suspend fun saveFavoriteRelayList(trustedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(favoriteRelayList.saveRelayList(trustedRelays))
suspend fun followRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(relayFeedsList.addRelay(url))
suspend fun followRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(favoriteRelayList.addRelay(url))
suspend fun unfollowRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(relayFeedsList.removeRelay(url))
suspend fun unfollowRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(favoriteRelayList.removeRelay(url))
suspend fun saveBlockedRelayList(blockedRelays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(blockedRelayList.saveRelayList(blockedRelays))
@@ -3783,7 +3783,7 @@ class Account(
proxyRelayList.getProxyRelayList(),
broadcastRelayList.getBroadcastRelayList(),
indexerRelayList.getIndexerRelayList(),
relayFeedsList.getRelayFeedsList(),
favoriteRelayList.getFavoriteRelayList(),
blockedRelayList.getBlockedRelayList(),
muteList.getMuteList(),
bookmarkState.getBookmarkList(),
@@ -4056,9 +4056,9 @@ class Account(
logTime("Account ${userProfile().toBestDisplayName()} newEventBundle Update with ${newNotes.size} new notes") {
upgradeAttestations()
newNotesPreProcessor.runNew(newNotes)
peopleLists.newNotes(newNotes)
followLists.newNotes(newNotes)
labeledBookmarkLists.newNotes(newNotes)
followSets.newNotes(newNotes)
starterPacks.newNotes(newNotes)
bookmarkSets.newNotes(newNotes)
interestSets.newNotes(newNotes)
ownedEmojiPacks.newNotes(newNotes)
}
@@ -4069,9 +4069,9 @@ class Account(
cache.live.deletedEventBundles.collect { deletedNotes ->
logTime("Account ${userProfile().toBestDisplayName()} deletedEventBundle Update with ${deletedNotes.size} new notes") {
newNotesPreProcessor.runDeleted(deletedNotes)
peopleLists.deletedNotes(deletedNotes)
followLists.deletedNotes(deletedNotes)
labeledBookmarkLists.deletedNotes(deletedNotes)
followSets.deletedNotes(deletedNotes)
starterPacks.deletedNotes(deletedNotes)
bookmarkSets.deletedNotes(deletedNotes)
interestSets.deletedNotes(deletedNotes)
ownedEmojiPacks.deletedNotes(deletedNotes)
}
@@ -68,7 +68,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
@@ -208,15 +208,15 @@ class AccountSettings(
var backupNIP65RelayList: AdvertisedRelayListEvent? = null,
var backupSearchRelayList: SearchRelayListEvent? = null,
var backupIndexRelayList: IndexerRelayListEvent? = null,
var backupRelayFeedsList: FavoriteRelayListEvent? = null,
var backupFavoriteRelayList: FavoriteRelayListEvent? = null,
var backupBlockedRelayList: BlockedRelayListEvent? = null,
var backupTrustedRelayList: TrustedRelayListEvent? = null,
var backupMuteList: MuteListEvent? = null,
var backupPrivateHomeRelayList: PrivateOutboxRelayListEvent? = null,
var backupAppSpecificData: AppSpecificDataEvent? = null,
var backupChannelList: PublicChatListEvent? = null,
var backupPublicChatList: PublicChatListEvent? = null,
var backupCommunityList: CommunityListEvent? = null,
var backupHashtagList: InterestListEvent? = null,
var backupInterestList: InterestListEvent? = null,
var backupFavoriteAlgoFeedsList: FavoriteAlgoFeedsListEvent? = null,
var backupGeohashList: GeohashListEvent? = null,
var backupEphemeralChatList: EphemeralChatListEvent? = null,
@@ -1084,13 +1084,13 @@ class AccountSettings(
is Bolt12OfferListEvent -> updateBolt12Offers(incoming)
is SearchRelayListEvent -> updateSearchRelayList(incoming)
is IndexerRelayListEvent -> updateIndexRelayList(incoming)
is FavoriteRelayListEvent -> updateRelayFeedList(incoming)
is FavoriteRelayListEvent -> updateFavoriteRelayList(incoming)
is BlockedRelayListEvent -> updateBlockedRelayList(incoming)
is TrustedRelayListEvent -> updateTrustedRelayList(incoming)
is PrivateOutboxRelayListEvent -> updatePrivateHomeRelayList(incoming)
is PublicChatListEvent -> updateChannelListTo(incoming)
is PublicChatListEvent -> updatePublicChatListTo(incoming)
is GeohashListEvent -> updateGeohashListTo(incoming)
is InterestListEvent -> updateHashtagListTo(incoming)
is InterestListEvent -> updateInterestListTo(incoming)
is FavoriteAlgoFeedsListEvent -> updateFavoriteAlgoFeedsListTo(incoming)
is CommunityListEvent -> updateCommunityListTo(incoming)
is EphemeralChatListEvent -> updateEphemeralChatListTo(incoming)
@@ -1284,11 +1284,11 @@ class AccountSettings(
}
}
fun updateRelayFeedList(newRelayFeedList: FavoriteRelayListEvent?) {
if (newRelayFeedList == null) return
fun updateFavoriteRelayList(newFavoriteRelayList: FavoriteRelayListEvent?) {
if (newFavoriteRelayList == null) return
if (backupGuard.accept(backupRelayFeedsList, newRelayFeedList, isEmpty = newRelayFeedList.tags.isEmpty()) { updateRelayFeedList(newRelayFeedList) }) {
backupRelayFeedsList = newRelayFeedList
if (backupGuard.accept(backupFavoriteRelayList, newFavoriteRelayList, isEmpty = newFavoriteRelayList.tags.isEmpty()) { updateFavoriteRelayList(newFavoriteRelayList) }) {
backupFavoriteRelayList = newFavoriteRelayList
saveAccountSettings()
}
}
@@ -1320,13 +1320,13 @@ class AccountSettings(
}
}
override fun channelList() = backupChannelList
override fun publicChatList() = backupPublicChatList
override fun updateChannelListTo(newChannelList: PublicChatListEvent?) {
if (newChannelList == null) return
override fun updatePublicChatListTo(newPublicChatList: PublicChatListEvent?) {
if (newPublicChatList == null) return
if (backupGuard.accept(backupChannelList, newChannelList, isEmpty = newChannelList.tags.isEmpty()) { updateChannelListTo(newChannelList) }) {
backupChannelList = newChannelList
if (backupGuard.accept(backupPublicChatList, newPublicChatList, isEmpty = newPublicChatList.tags.isEmpty()) { updatePublicChatListTo(newPublicChatList) }) {
backupPublicChatList = newPublicChatList
saveAccountSettings()
}
}
@@ -1340,11 +1340,11 @@ class AccountSettings(
}
}
fun updateHashtagListTo(newHashtagList: InterestListEvent?) {
if (newHashtagList == null) return
fun updateInterestListTo(newInterestList: InterestListEvent?) {
if (newInterestList == null) return
if (backupGuard.accept(backupHashtagList, newHashtagList, isEmpty = newHashtagList.tags.isEmpty()) { updateHashtagListTo(newHashtagList) }) {
backupHashtagList = newHashtagList
if (backupGuard.accept(backupInterestList, newInterestList, isEmpty = newInterestList.tags.isEmpty()) { updateInterestListTo(newInterestList) }) {
backupInterestList = newInterestList
saveAccountSettings()
}
}
@@ -45,7 +45,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.isGroupScoped
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.BookmarkSetEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
@@ -18,7 +18,7 @@
* 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.nip51Lists.labeledBookmarkLists
package com.vitorpamplona.amethyst.model.nip51Lists.bookmarkSets
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.cache.filter
import com.vitorpamplona.amethyst.commons.model.eventIdSet
import com.vitorpamplona.amethyst.commons.model.events
import com.vitorpamplona.amethyst.commons.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.bookmarkSets.BookmarkSet
import com.vitorpamplona.amethyst.commons.model.updateFlow
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.quartz.nip01Core.core.Address
@@ -37,10 +37,10 @@ import com.vitorpamplona.quartz.nip09Deletions.DeletionRequestEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.AddressBookmark
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.BookmarkIdTag
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.EventBookmark
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.description
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.image
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.title
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.description
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.image
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.title
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,41 +55,41 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update
class LabeledBookmarkListsState(
class BookmarkSetsState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
) {
val user = cache.getOrCreateUser(signer.pubKey)
fun existingLabeledBookmarkNotes() = cache.addressables.filter(BookmarkSetEvent.KIND, user.pubkeyHex)
fun existingBookmarkSetNotes() = cache.addressables.filter(BookmarkSetEvent.KIND, user.pubkeyHex)
val labeledBookmarkListVersions = MutableStateFlow(0)
val bookmarkSetVersions = MutableStateFlow(0)
val labeledBookmarkListNotes =
labeledBookmarkListVersions
.map { existingLabeledBookmarkNotes() }
.onStart { emit(existingLabeledBookmarkNotes()) }
val bookmarkSetNotes =
bookmarkSetVersions
.map { existingBookmarkSetNotes() }
.onStart { emit(existingBookmarkSetNotes()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptyList())
val labeledBookmarkListEventIds =
labeledBookmarkListNotes
val bookmarkSetEventIds =
bookmarkSetNotes
.map { it.eventIdSet() }
.onStart { emit(labeledBookmarkListNotes.value.eventIdSet()) }
.onStart { emit(bookmarkSetNotes.value.eventIdSet()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptySet())
@OptIn(ExperimentalCoroutinesApi::class)
val latestBookmarkLists: StateFlow<List<BookmarkSetEvent>> =
labeledBookmarkListNotes
bookmarkSetNotes
.transformLatest { emitAll(it.updateFlow<BookmarkSetEvent>()) }
.onStart { emit(labeledBookmarkListNotes.value.events()) }
.onStart { emit(bookmarkSetNotes.value.events()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptyList())
suspend fun BookmarkSetEvent.toLabeledBookmarkList() =
LabeledBookmarkList(
suspend fun BookmarkSetEvent.toBookmarkSet() =
BookmarkSet(
identifier = dTag(),
title = titleOrName() ?: dTag(),
description = description(),
@@ -98,23 +98,23 @@ class LabeledBookmarkListsState(
publicBookmarks = publicBookmarks().toSet(),
)
suspend fun List<BookmarkSetEvent>.toLabeledBookmarkListsFeed() = map { it.toLabeledBookmarkList() }.sortedBy { it.title }
suspend fun List<BookmarkSetEvent>.toBookmarkSetsFeed() = map { it.toBookmarkSet() }.sortedBy { it.title }
val listFeedFlow =
latestBookmarkLists
.map { it.toLabeledBookmarkListsFeed() }
.onStart { emit(latestBookmarkLists.value.toLabeledBookmarkListsFeed()) }
.map { it.toBookmarkSetsFeed() }
.onStart { emit(latestBookmarkLists.value.toBookmarkSetsFeed()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptyList())
fun List<LabeledBookmarkList>.getList(bookmarkListId: String) =
fun List<BookmarkSet>.getList(bookmarkListId: String) =
this.firstOrNull {
it.identifier == bookmarkListId
}
fun getBookmarkList(dTag: String) = listFeedFlow.value.getList(bookmarkListId = dTag)
fun DeletionRequestEvent.hasAnyDeletedBookmarkLists() = deleteAddressesWithKind(BookmarkSetEvent.KIND) || deletesAnyEventIn(labeledBookmarkListEventIds.value)
fun DeletionRequestEvent.hasAnyDeletedBookmarkLists() = deleteAddressesWithKind(BookmarkSetEvent.KIND) || deletesAnyEventIn(bookmarkSetEventIds.value)
fun hasItemInNoteList(notes: Set<Note>): Boolean =
notes.anyNotNullEvent { event ->
@@ -138,14 +138,14 @@ class LabeledBookmarkListsState(
}
fun forceRefresh() {
labeledBookmarkListVersions.update { it + 1 }
bookmarkSetVersions.update { it + 1 }
}
fun getLabeledBookmarkListNote(bookmarkIdentifier: String): AddressableNote? = existingLabeledBookmarkNotes().find { it.dTag() == bookmarkIdentifier }
fun getBookmarkSetNote(bookmarkIdentifier: String): AddressableNote? = existingBookmarkSetNotes().find { it.dTag() == bookmarkIdentifier }
fun getLabeledBookmarkListEvent(bookmarkIdentifier: String): BookmarkSetEvent = getLabeledBookmarkListNote(bookmarkIdentifier)?.event as BookmarkSetEvent
fun getBookmarkSetEvent(bookmarkIdentifier: String): BookmarkSetEvent = getBookmarkSetNote(bookmarkIdentifier)?.event as BookmarkSetEvent
fun getLabeledBookmarkListFlow(bookmarkIdentifier: String) =
fun getBookmarkSetFlow(bookmarkIdentifier: String) =
listFeedFlow
.map { it.getList(bookmarkIdentifier) }
.onStart {
@@ -154,7 +154,7 @@ class LabeledBookmarkListsState(
)
}.flowOn(Dispatchers.IO)
suspend fun addLabeledBookmarkList(
suspend fun addBookmarkSet(
listName: String,
listDescription: String? = null,
listImage: String? = null,
@@ -178,10 +178,10 @@ class LabeledBookmarkListsState(
listName: String?,
listDescription: String?,
listImage: String?,
bookmarkList: LabeledBookmarkList,
bookmarkList: BookmarkSet,
account: Account,
) {
val listEvent = getLabeledBookmarkListEvent(bookmarkList.identifier)
val listEvent = getBookmarkSetEvent(bookmarkList.identifier)
val template =
listEvent.update {
@@ -197,10 +197,10 @@ class LabeledBookmarkListsState(
suspend fun renameBookmarkList(
newName: String,
bookmarkList: LabeledBookmarkList,
bookmarkList: BookmarkSet,
account: Account,
) {
val listEvent = getLabeledBookmarkListEvent(bookmarkList.identifier)
val listEvent = getBookmarkSetEvent(bookmarkList.identifier)
val renamedList =
BookmarkSetEvent.modifyName(
earlierVersion = listEvent,
@@ -212,10 +212,10 @@ class LabeledBookmarkListsState(
suspend fun modifyListDescription(
newDescription: String?,
bookmarkList: LabeledBookmarkList,
bookmarkList: BookmarkSet,
account: Account,
) {
val listEvent = getLabeledBookmarkListEvent(bookmarkList.identifier)
val listEvent = getBookmarkSetEvent(bookmarkList.identifier)
val modifiedList =
BookmarkSetEvent.modifyDescription(
earlierVersion = listEvent,
@@ -226,7 +226,7 @@ class LabeledBookmarkListsState(
}
suspend fun cloneBookmarkList(
currentBookmarkList: LabeledBookmarkList,
currentBookmarkList: BookmarkSet,
customCloneName: String?,
customCloneDescription: String?,
account: Account,
@@ -246,7 +246,7 @@ class LabeledBookmarkListsState(
bookmarkListIdentifier: String,
account: Account,
) {
val listEvent = getLabeledBookmarkListEvent(bookmarkListIdentifier)
val listEvent = getBookmarkSetEvent(bookmarkListIdentifier)
val deletionEventTemplate = DeletionRequestEvent.build(listOf(listEvent))
val deletionEvent = account.signer.sign(deletionEventTemplate)
account.sendMyPublicAndPrivateOutbox(deletionEvent)
@@ -258,7 +258,7 @@ class LabeledBookmarkListsState(
isBookmarkPrivate: Boolean,
account: Account,
) {
val currentBookmarkList = getLabeledBookmarkListEvent(bookmarkListIdentifier)
val currentBookmarkList = getBookmarkSetEvent(bookmarkListIdentifier)
val updatedList =
BookmarkSetEvent.addBookmark(
earlierVersion = currentBookmarkList,
@@ -275,7 +275,7 @@ class LabeledBookmarkListsState(
isBookmarkCurrentlyPrivate: Boolean,
account: Account,
) {
val bookmarkList = getLabeledBookmarkListEvent(bookmarkListIdentifier)
val bookmarkList = getBookmarkSetEvent(bookmarkListIdentifier)
val updatedList =
BookmarkSetEvent.moveBookmark(
earlierVersion = bookmarkList,
@@ -292,7 +292,7 @@ class LabeledBookmarkListsState(
isBookmarkPrivate: Boolean,
account: Account,
) {
val currentBookmarkList = getLabeledBookmarkListEvent(bookmarkListIdentifier)
val currentBookmarkList = getBookmarkSetEvent(bookmarkListIdentifier)
val updatedList =
BookmarkSetEvent.removeBookmark(
earlierVersion = currentBookmarkList,
@@ -311,7 +311,7 @@ class LabeledBookmarkListsState(
) {
if (deletedEventIds.isEmpty() && deletedAddresses.isEmpty()) return
val currentList = getLabeledBookmarkListNote(bookmarkListIdentifier)?.event as? BookmarkSetEvent ?: return
val currentList = getBookmarkSetNote(bookmarkListIdentifier)?.event as? BookmarkSetEvent ?: return
val newPublicTags =
currentList.tags
@@ -18,12 +18,12 @@
* 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.nip51Lists.relayFeeds
package com.vitorpamplona.amethyst.model.nip51Lists.favoriteRelays
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.NoteState
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.relayFeeds.RelayFeedsListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.favoriteRelays.FavoriteRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
@@ -40,32 +40,32 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class RelayFeedListState(
class FavoriteRelayListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: RelayFeedsListDecryptionCache,
val decryptionCache: FavoriteRelayListDecryptionCache,
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 relayFeedListNote = cache.getOrCreateAddressableNote(getRelayFeedsListAddress())
val favoriteRelayListNote = cache.getOrCreateAddressableNote(getFavoriteRelayListAddress())
fun getRelayFeedsListAddress() = FavoriteRelayListEvent.createAddress(signer.pubKey)
fun getFavoriteRelayListAddress() = FavoriteRelayListEvent.createAddress(signer.pubKey)
fun getRelayFeedsListFlow(): StateFlow<NoteState> = relayFeedListNote.flow().metadata.stateFlow
fun getFavoriteRelayListFlow(): StateFlow<NoteState> = favoriteRelayListNote.flow().metadata.stateFlow
fun getRelayFeedsList(): FavoriteRelayListEvent? = relayFeedListNote.event as? FavoriteRelayListEvent
fun getFavoriteRelayList(): FavoriteRelayListEvent? = favoriteRelayListNote.event as? FavoriteRelayListEvent
fun relayFeedsListEvent(note: Note) = note.event as? FavoriteRelayListEvent ?: settings.backupRelayFeedsList
fun favoriteRelayListEvent(note: Note) = note.event as? FavoriteRelayListEvent ?: settings.backupFavoriteRelayList
suspend fun normalizeRelayFeedsListWithBackup(note: Note): Set<NormalizedRelayUrl> = relayFeedsListEvent(note)?.let { decryptionCache.relays(it) }?.ifEmpty { null } ?: emptySet()
suspend fun normalizeFavoriteRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> = favoriteRelayListEvent(note)?.let { decryptionCache.relays(it) }?.ifEmpty { null } ?: emptySet()
suspend fun normalizeRelayFeedsListWithBackupNoDefaults(note: Note): Set<NormalizedRelayUrl> = relayFeedsListEvent(note)?.let { decryptionCache.relays(it) } ?: emptySet()
suspend fun normalizeFavoriteRelayListWithBackupNoDefaults(note: Note): Set<NormalizedRelayUrl> = favoriteRelayListEvent(note)?.let { decryptionCache.relays(it) } ?: emptySet()
val flow =
getRelayFeedsListFlow()
.map { normalizeRelayFeedsListWithBackup(it.note) }
.onStart { emit(normalizeRelayFeedsListWithBackup(relayFeedListNote)) }
getFavoriteRelayListFlow()
.map { normalizeFavoriteRelayListWithBackup(it.note) }
.onStart { emit(normalizeFavoriteRelayListWithBackup(favoriteRelayListNote)) }
.flowOn(Dispatchers.IO)
.stateIn(
scope,
@@ -74,9 +74,9 @@ class RelayFeedListState(
)
val flowNoDefaults =
getRelayFeedsListFlow()
.map { normalizeRelayFeedsListWithBackupNoDefaults(it.note) }
.onStart { emit(normalizeRelayFeedsListWithBackupNoDefaults(relayFeedListNote)) }
getFavoriteRelayListFlow()
.map { normalizeFavoriteRelayListWithBackupNoDefaults(it.note) }
.onStart { emit(normalizeFavoriteRelayListWithBackupNoDefaults(favoriteRelayListNote)) }
.flowOn(Dispatchers.IO)
.stateIn(
scope,
@@ -85,24 +85,24 @@ class RelayFeedListState(
)
suspend fun addRelay(relay: NormalizedRelayUrl): FavoriteRelayListEvent {
val current = normalizeRelayFeedsListWithBackupNoDefaults(relayFeedListNote).toMutableList()
val current = normalizeFavoriteRelayListWithBackupNoDefaults(favoriteRelayListNote).toMutableList()
if (relay !in current) current.add(relay)
return saveRelayList(current)
}
suspend fun removeRelay(relay: NormalizedRelayUrl): FavoriteRelayListEvent? {
val current = normalizeRelayFeedsListWithBackupNoDefaults(relayFeedListNote).toMutableList()
val current = normalizeFavoriteRelayListWithBackupNoDefaults(favoriteRelayListNote).toMutableList()
if (relay !in current) return null
current.remove(relay)
return saveRelayList(current)
}
suspend fun saveRelayList(relayFeeds: List<NormalizedRelayUrl>): FavoriteRelayListEvent {
val relayFeedsList = getRelayFeedsList()
val favoriteRelayList = getFavoriteRelayList()
return if (relayFeedsList != null && relayFeedsList.tags.isNotEmpty()) {
return if (favoriteRelayList != null && favoriteRelayList.tags.isNotEmpty()) {
FavoriteRelayListEvent.updateRelayList(
earlierVersion = relayFeedsList,
earlierVersion = favoriteRelayList,
relays = relayFeeds,
signer = signer,
)
@@ -115,7 +115,7 @@ class RelayFeedListState(
}
init {
settings.backupRelayFeedsList?.let {
settings.backupFavoriteRelayList?.let {
Log.d("AccountRegisterObservers") { "Loading saved relay feeds list ${it.toJson()}" }
@OptIn(DelicateCoroutinesApi::class)
scope.launch(Dispatchers.IO) { LocalCache.justConsumeMyOwnEvent(it) }
@@ -123,10 +123,10 @@ class RelayFeedListState(
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Relay feeds list Collector Start")
getRelayFeedsListFlow().collect {
getFavoriteRelayListFlow().collect {
Log.d("AccountRegisterObservers") { "Updating Relay feeds list for ${signer.pubKey}" }
(it.note.event as? FavoriteRelayListEvent)?.let {
settings.updateRelayFeedList(it)
settings.updateFavoriteRelayList(it)
}
}
}
@@ -18,7 +18,7 @@
* 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.nip51Lists.peopleList
package com.vitorpamplona.amethyst.model.nip51Lists.followSets
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
@@ -28,19 +28,19 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.cache.filter
import com.vitorpamplona.amethyst.commons.model.eventIdSet
import com.vitorpamplona.amethyst.commons.model.events
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.FollowSetDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.commons.model.updateFlow
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.update
import com.vitorpamplona.quartz.nip09Deletions.DeletionRequestEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.description
import com.vitorpamplona.quartz.nip51Lists.followSet.image
import com.vitorpamplona.quartz.nip51Lists.followSet.title
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.UserTag
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.description
import com.vitorpamplona.quartz.nip51Lists.peopleList.image
import com.vitorpamplona.quartz.nip51Lists.peopleList.title
import com.vitorpamplona.quartz.utils.flattenToSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -62,10 +62,10 @@ import java.util.UUID
*
* This class must receive updates from Large Cache as it receives new events.
*/
class PeopleListsState(
class FollowSetsState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: PeopleListDecryptionCache,
val decryptionCache: FollowSetDecryptionCache,
val scope: CoroutineScope,
) {
val user = cache.getOrCreateUser(signer.pubKey)
@@ -18,7 +18,7 @@
* 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.nip51Lists.peopleList
package com.vitorpamplona.amethyst.model.nip51Lists.followSets
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.cache.filter
import com.vitorpamplona.amethyst.commons.model.eventIdSet
import com.vitorpamplona.amethyst.commons.model.events
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.commons.model.updateFlow
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -36,14 +36,14 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.update
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.nip09Deletions.DeletionRequestEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.followList.description
import com.vitorpamplona.quartz.nip51Lists.followList.image
import com.vitorpamplona.quartz.nip51Lists.followList.person
import com.vitorpamplona.quartz.nip51Lists.followList.personFirst
import com.vitorpamplona.quartz.nip51Lists.followList.removePerson
import com.vitorpamplona.quartz.nip51Lists.followList.title
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.UserTag
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.description
import com.vitorpamplona.quartz.nip51Lists.starterPack.image
import com.vitorpamplona.quartz.nip51Lists.starterPack.person
import com.vitorpamplona.quartz.nip51Lists.starterPack.personFirst
import com.vitorpamplona.quartz.nip51Lists.starterPack.removePerson
import com.vitorpamplona.quartz.nip51Lists.starterPack.title
import com.vitorpamplona.quartz.utils.flattenToSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -65,7 +65,7 @@ import java.util.UUID
*
* This class must receive updates from Large Cache as it receives new events.
*/
class FollowListsState(
class StarterPacksState(
val signer: NostrSigner,
val cache: LocalCache,
val scope: CoroutineScope,
@@ -84,25 +84,25 @@ class FollowListsState(
val followListVersions = MutableStateFlow(0)
val followListNotes =
val starterPackNotes =
followListVersions
.map { existingPeopleListNotes() }
.onStart { emit(existingPeopleListNotes()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptyList())
val followListsEventIds =
followListNotes
val starterPacksEventIds =
starterPackNotes
.map { it.eventIdSet() }
.onStart { emit(followListNotes.value.eventIdSet()) }
.onStart { emit(starterPackNotes.value.eventIdSet()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptySet())
@OptIn(ExperimentalCoroutinesApi::class)
val latestLists: StateFlow<List<StarterPackEvent>> =
followListNotes
starterPackNotes
.transformLatest { emitAll(it.updateFlow<StarterPackEvent>()) }
.onStart { emit(followListNotes.value.events()) }
.onStart { emit(starterPackNotes.value.events()) }
.flowOn(Dispatchers.IO)
.stateIn(scope, SharingStarted.Eagerly, emptyList())
@@ -148,12 +148,12 @@ class FollowListsState(
fun isUserInFollowSets(user: User): Boolean = allPeopleListProfiles.value.contains(user.pubkeyHex)
fun DeletionRequestEvent.hasDeletedAnyFollowList() = deleteAddressesWithKind(StarterPackEvent.KIND) || deletesAnyEventIn(followListsEventIds.value)
fun DeletionRequestEvent.hasDeletedAnyStarterPack() = deleteAddressesWithKind(StarterPackEvent.KIND) || deletesAnyEventIn(starterPacksEventIds.value)
fun hasItemInNoteList(notes: Set<Note>): Boolean =
notes.anyNotNullEvent { event ->
if (event.pubKey == signer.pubKey) {
event is StarterPackEvent || (event is DeletionRequestEvent && event.hasDeletedAnyFollowList())
event is StarterPackEvent || (event is DeletionRequestEvent && event.hasDeletedAnyStarterPack())
} else {
false
}
@@ -18,15 +18,15 @@
* 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.nip51Lists.hashtagLists
package com.vitorpamplona.amethyst.model.nip51Lists.interestLists
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.NoteState
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.hashtagLists.HashtagListDecryptionCache
import com.vitorpamplona.amethyst.commons.model.nip51Lists.interestLists.InterestListDecryptionCache
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -40,30 +40,30 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
class HashtagListState(
class InterestListState(
val signer: NostrSigner,
val cache: LocalCache,
val decryptionCache: HashtagListDecryptionCache,
val decryptionCache: InterestListDecryptionCache,
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 hashtagListNote = cache.getOrCreateAddressableNote(getHashtagListAddress())
val hashtagListNote = cache.getOrCreateAddressableNote(getInterestListAddress())
fun getHashtagListAddress() = InterestListEvent.createAddress(signer.pubKey)
fun getInterestListAddress() = InterestListEvent.createAddress(signer.pubKey)
fun getHashtagListFlow(): StateFlow<NoteState> = hashtagListNote.flow().metadata.stateFlow
fun getInterestListFlow(): StateFlow<NoteState> = hashtagListNote.flow().metadata.stateFlow
fun getHashtagList(): InterestListEvent? = hashtagListNote.event as? InterestListEvent
fun getInterestList(): InterestListEvent? = hashtagListNote.event as? InterestListEvent
suspend fun hashtagListWithBackup(note: Note): Set<String> {
val event = note.event as? InterestListEvent ?: settings.backupHashtagList
val event = note.event as? InterestListEvent ?: settings.backupInterestList
return event?.let { decryptionCache.hashtags(it).mapTo(mutableSetOf()) { it.lowercase() } } ?: emptySet()
}
@OptIn(ExperimentalCoroutinesApi::class)
val flow: StateFlow<Set<String>> =
getHashtagListFlow()
getInterestListFlow()
.transformLatest { noteState ->
emit(hashtagListWithBackup(noteState.note))
}.onStart {
@@ -76,7 +76,7 @@ class HashtagListState(
)
suspend fun follow(hashtags: List<String>): InterestListEvent {
val hashtagList = getHashtagList()
val hashtagList = getInterestList()
return if (hashtagList == null) {
InterestListEvent.create(hashtags, true, signer)
@@ -86,7 +86,7 @@ class HashtagListState(
}
suspend fun follow(hashtag: String): InterestListEvent {
val hashtagList = getHashtagList()
val hashtagList = getInterestList()
return if (hashtagList == null) {
InterestListEvent.create(hashtag.lowercase(), true, signer)
@@ -96,7 +96,7 @@ class HashtagListState(
}
suspend fun unfollow(hashtag: String): InterestListEvent? {
val hashtagList = getHashtagList()
val hashtagList = getInterestList()
return if (hashtagList != null) {
InterestListEvent.remove(hashtagList, hashtag, signer)
@@ -106,7 +106,7 @@ class HashtagListState(
}
init {
settings.backupHashtagList?.let { event ->
settings.backupInterestList?.let { event ->
Log.d("AccountRegisterObservers") { "Loading saved Hashtag list ${event.toJson()}" }
@OptIn(DelicateCoroutinesApi::class)
scope.launch(Dispatchers.IO) {
@@ -116,10 +116,10 @@ class HashtagListState(
scope.launch(Dispatchers.IO) {
Log.d("AccountRegisterObservers", "Hashtag List Collector Start")
getHashtagListFlow().collect {
getInterestListFlow().collect {
Log.d("AccountRegisterObservers") { "Hashtag List for ${signer.pubKey}" }
(it.note.event as? InterestListEvent)?.let {
settings.updateHashtagListTo(it)
settings.updateInterestListTo(it)
}
}
}
@@ -49,9 +49,9 @@ class AccountPreferenceStores(
val latestMuteList = stringPreferencesKey("latestMuteList")
val latestPrivateHomeRelayList = stringPreferencesKey("latestPrivateHomeRelayList")
val latestAppSpecificData = stringPreferencesKey("latestAppSpecificData")
val latestChannelList = stringPreferencesKey("latestChannelList")
val latestPublicChatList = stringPreferencesKey("latestChannelList")
val latestCommunityList = stringPreferencesKey("latestCommunityList")
val latestHashtagList = stringPreferencesKey("latestHashtagList")
val latestInterestList = stringPreferencesKey("latestHashtagList")
val latestGeohashList = stringPreferencesKey("latestGeohashList")
val latestEphemeralChatList = stringPreferencesKey("latestEphemeralChatList")
@@ -22,10 +22,10 @@ package com.vitorpamplona.amethyst.model.serverList
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSetsState
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.StarterPacksState
import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState
import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListState
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.FollowListsState
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleListsState
import com.vitorpamplona.amethyst.model.nip51Lists.interestLists.InterestListState
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip72ModCommunities.follow.tags.CommunityTag
@@ -44,9 +44,9 @@ import kotlinx.coroutines.flow.stateIn
@Suppress("UNCHECKED_CAST")
class MergedFollowListsState(
val kind3List: Kind3FollowListState,
val peopleList: PeopleListsState,
val followList: FollowListsState,
val hashtagList: HashtagListState,
val followSets: FollowSetsState,
val starterPacks: StarterPacksState,
val interestList: InterestListState,
val geohashList: GeohashListState,
val communityList: CommunityListState,
val scope: CoroutineScope,
@@ -85,9 +85,9 @@ class MergedFollowListsState(
combine(
listOf(
kind3List.flow,
peopleList.allGoodPeopleListProfiles,
followList.allPeopleListProfiles,
hashtagList.flow,
followSets.allGoodPeopleListProfiles,
starterPacks.allPeopleListProfiles,
interestList.flow,
geohashList.flow,
communityList.flow,
),
@@ -104,9 +104,9 @@ class MergedFollowListsState(
emit(
mergeLists(
kind3List.flow.value,
peopleList.allGoodPeopleListProfiles.value,
followList.allPeopleListProfiles.value,
hashtagList.flow.value,
followSets.allGoodPeopleListProfiles.value,
starterPacks.allPeopleListProfiles.value,
interestList.flow.value,
geohashList.flow.value,
communityList.flow.value,
),
@@ -118,9 +118,9 @@ class MergedFollowListsState(
SharingStarted.Eagerly,
mergeLists(
kind3List.flow.value,
peopleList.allGoodPeopleListProfiles.value,
followList.allPeopleListProfiles.value,
hashtagList.flow.value,
followSets.allGoodPeopleListProfiles.value,
starterPacks.allPeopleListProfiles.value,
interestList.flow.value,
geohashList.flow.value,
communityList.flow.value,
),
@@ -101,7 +101,7 @@ class NotificationDispatcher(
private const val TAG = "NotificationDispatcher"
// The dispatcher observes the *final* notification payload kinds.
// GiftWrap/EphemeralGiftWrap (1059/21059) and SealedRumor (13) are NOT
// GiftWrap/EphemeralGiftWrap (1059/21059) and Seal (13) are NOT
// listed here — by the time we care, Account.newNotesPreProcessor has
// already unwrapped them and inserted the inner payload into LocalCache,
// which fires the observer a second time on the inner event.
@@ -235,8 +235,8 @@ class NotificationDispatcher(
if (event is WakeUpEvent) return@predicate true
// getNoteIfExists(event) — not (event.id) — so
// AddressableEvent kinds (LongTextNote, WikiNote,
// LiveChess*, VideoHorizontal/Vertical) resolve to
// AddressableEvent kinds (LongFormContent, WikiArticle,
// LiveChess*, AddressableNormalVideo/AddressableShortVideo) resolve to
// their address-keyed replaceable note. The id-keyed
// version note has its replyTo moved away during
// insertion (LocalCache.consumeBaseReplaceable), so
@@ -36,7 +36,7 @@ import kotlinx.coroutines.CancellationException
*
* Once decrypted, the inner event is fed into [LocalCache]. From there the
* normal `Account.newNotesPreProcessor` → `GiftWrapEventHandler` →
* `SealedRumorEventHandler` chain unwraps any remaining layers, and
* `SealEventHandler` chain unwraps any remaining layers, and
* [NotificationDispatcher] picks up the final payload and notifies.
*/
object PushWrapDecryptor {
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.filterContactCardsByAuthorInTheRelay
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.filterUserAssertionsByAuthorInTheRelay
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState.Companion.APP_SPECIFIC_DATA_D_TAG
@@ -37,7 +37,7 @@ import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayList
import com.vitorpamplona.quartz.nip38UserStatus.UserStatusEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
@@ -141,7 +141,7 @@ fun filterAccountInfoAndListsFromKey(
),
// The account's own kind:30382 contact cards (nicknames, NIP-44 encrypted).
// Addressable — one card per target user — hence its own larger-limit filter.
filterContactCardsByAuthorInTheRelay(
filterUserAssertionsByAuthorInTheRelay(
relay = relay,
authors = pubkeys,
since = since,
@@ -39,7 +39,7 @@ import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -64,11 +64,11 @@ fun observeUserName(
// Subscribe in the relay for changes in the metadata of this user.
UserFinderFilterAssemblerSubscription(user, accountViewModel)
val contactCards = accountViewModel.account.contactCards
val flow = remember(user) { contactCards.displayNameFlow(user) }
val userAssertions = accountViewModel.account.userAssertions
val flow = remember(user) { userAssertions.displayNameFlow(user) }
// Subscribe in the LocalCache for changes that arrive in the device
return flow.collectAsStateWithLifecycle(remember(user) { contactCards.cachedDisplayName(user) })
return flow.collectAsStateWithLifecycle(remember(user) { userAssertions.cachedDisplayName(user) })
}
/**
@@ -83,10 +83,10 @@ fun observeUserNickname(
user: User,
accountViewModel: AccountViewModel,
): State<Nickname?> {
val contactCards = accountViewModel.account.contactCards
val flow = remember(user) { contactCards.nicknameFlow(user) }
val userAssertions = accountViewModel.account.userAssertions
val flow = remember(user) { userAssertions.nicknameFlow(user) }
return flow.collectAsStateWithLifecycle(remember(user) { contactCards.cachedNickname(user) })
return flow.collectAsStateWithLifecycle(remember(user) { userAssertions.cachedNickname(user) })
}
@Composable
@@ -132,9 +132,9 @@ fun observeUserTagFollowCount(
.metadata.stateFlow
.sample(1000)
.mapLatest { noteState ->
(noteState.note.event as? InterestListEvent)?.let { accountViewModel.account.hashtagListDecryptionCache.hashtags(it) }?.size ?: 0
(noteState.note.event as? InterestListEvent)?.let { accountViewModel.account.interestListDecryptionCache.hashtags(it) }?.size ?: 0
}.onStart {
emit((accountViewModel.hashtagFollows(user).event as? InterestListEvent)?.let { accountViewModel.account.hashtagListDecryptionCache.hashtags(it) }?.size ?: 0)
emit((accountViewModel.hashtagFollows(user).event as? InterestListEvent)?.let { accountViewModel.account.interestListDecryptionCache.hashtags(it) }?.size ?: 0)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}
@@ -160,9 +160,9 @@ fun observeUserTagFollows(
.metadata.stateFlow
.sample(200)
.mapLatest { noteState ->
(noteState.note.event as? InterestListEvent)?.let { accountViewModel.account.hashtagListDecryptionCache.hashtags(it) }?.sorted() ?: emptyList()
(noteState.note.event as? InterestListEvent)?.let { accountViewModel.account.interestListDecryptionCache.hashtags(it) }?.sorted() ?: emptyList()
}.onStart {
emit((accountViewModel.hashtagFollows(user).event as? InterestListEvent)?.let { accountViewModel.account.hashtagListDecryptionCache.hashtags(it) }?.sorted() ?: emptyList())
emit((accountViewModel.hashtagFollows(user).event as? InterestListEvent)?.let { accountViewModel.account.interestListDecryptionCache.hashtags(it) }?.sorted() ?: emptyList())
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}
@@ -304,16 +304,16 @@ fun observeUserIsFollowingHashtag(
// Subscribe in the LocalCache for changes that arrive in the device
val flow =
remember(accountViewModel) {
accountViewModel.account.hashtagList.flow
accountViewModel.account.interestList.flow
.mapLatest { hashtags ->
hashtag in hashtags
}.onStart {
emit(hashtag in accountViewModel.account.hashtagList.flow.value)
emit(hashtag in accountViewModel.account.interestList.flow.value)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}
return flow.collectAsStateWithLifecycle(hashtag in accountViewModel.account.hashtagList.flow.value)
return flow.collectAsStateWithLifecycle(hashtag in accountViewModel.account.interestList.flow.value)
}
@SuppressLint("StateFlowValueCalledInComposition")
@@ -373,16 +373,16 @@ fun observeUserIsFollowingRelay(
): State<Boolean> {
val flow =
remember(accountViewModel) {
accountViewModel.account.relayFeedsList.flowNoDefaults
accountViewModel.account.favoriteRelayList.flowNoDefaults
.mapLatest { relays ->
relayUrl in relays
}.onStart {
emit(relayUrl in accountViewModel.account.relayFeedsList.flowNoDefaults.value)
emit(relayUrl in accountViewModel.account.favoriteRelayList.flowNoDefaults.value)
}.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}
return flow.collectAsStateWithLifecycle(relayUrl in accountViewModel.account.relayFeedsList.flowNoDefaults.value)
return flow.collectAsStateWithLifecycle(relayUrl in accountViewModel.account.favoriteRelayList.flowNoDefaults.value)
}
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
@@ -439,7 +439,7 @@ fun observeUserIsFollowingChannel(
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
@Composable
fun observeUserContactCardsScore(
fun observeUserAssertionsScore(
user: User,
accountViewModel: AccountViewModel,
subscribe: Boolean = true,
@@ -459,7 +459,7 @@ fun observeUserContactCardsScore(
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
@Composable
fun observeUserContactCardsFollowerCount(
fun observeUserAssertionsFollowerCount(
user: User,
accountViewModel: AccountViewModel,
): State<String> {
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip46RemoteSigner.signer.NostrSignerRemote
import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEvent
import com.vitorpamplona.quartz.nip57Zaps.ZapRequestEvent
import com.vitorpamplona.quartz.nip89AppHandlers.clientTag.NostrSignerWithClientTag
@@ -89,7 +89,7 @@ class MeteringNostrSigner(
fromPublicKey: HexKey,
): String = metered(UsageKeys.DECRYPT_COUNT, UsageKeys.DECRYPT_US) { inner.nip44Decrypt(ciphertext, fromPublicKey) }
override suspend fun decryptZapEvent(event: ZapRequestEvent): LnZapPrivateEvent = metered(UsageKeys.DECRYPT_COUNT, UsageKeys.DECRYPT_US) { inner.decryptZapEvent(event) }
override suspend fun decryptZapEvent(event: ZapRequestEvent): PrivateZapEvent = metered(UsageKeys.DECRYPT_COUNT, UsageKeys.DECRYPT_US) { inner.decryptZapEvent(event) }
override suspend fun deriveKey(nonce: HexKey): HexKey = inner.deriveKey(nonce)
@@ -32,8 +32,8 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasMoreHashtagsThan
import com.vitorpamplona.quartz.nip10Notes.threadRootIdOrSelf
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class FilterByListParams(
@@ -303,7 +303,7 @@ private class WarmTargets {
* `m` mime type (a hashed URL may carry no extension). The decode listener
* records their dims like any other image.
*/
fun addFileHeaderMedia(event: Event) {
fun addFileMetadataMedia(event: Event) {
val urls = event.tags.mapNotNull(UrlTag::parse)
if (urls.isEmpty()) return
val imageMime = event.tags.firstNotNullOfOrNull(MimeTypeTag::parse)?.startsWith("image/") == true
@@ -335,7 +335,7 @@ private fun Note.collectWarmTargets(): WarmTargets? {
// NIP-94 file-header convention: the blob is a top-level `url` tag with sibling
// `m`/`dim`, not imeta (FileMetadataEvent and the gallery/file kinds). The generic
// imetas() above misses these entirely.
targets.addFileHeaderMedia(ev)
targets.addFileMetadataMedia(ev)
// NIP-73 external-content scope. Only kind 1111 carries one, so the type check
// keeps every other kind out of the tag walk. Reuses scope() — the same accessor
@@ -146,7 +146,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.profileContentHeaderModifier
import com.vitorpamplona.amethyst.isDebug
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsFollowerCount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAssertionsFollowerCount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
import com.vitorpamplona.amethyst.ui.layouts.PermanentDrawerWidth
@@ -587,7 +587,7 @@ fun DisplayFollowerCount(
baseAccountUser: Account,
accountViewModel: AccountViewModel,
) {
val followerCount by observeUserContactCardsFollowerCount(baseAccountUser.userProfile(), accountViewModel)
val followerCount by observeUserAssertionsFollowerCount(baseAccountUser.userProfile(), accountViewModel)
Text(
text = followerCount,
@@ -57,7 +57,7 @@ import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestEvent
import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestUpdateEvent
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
@@ -106,7 +106,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor
import com.vitorpamplona.amethyst.commons.ui.theme.overPictureBackground
import com.vitorpamplona.amethyst.commons.ui.theme.profile35dpModifier
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsScore
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAssertionsScore
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
import com.vitorpamplona.amethyst.ui.components.ExpandableRichTextViewer
@@ -841,7 +841,7 @@ fun ObserveAndRenderBoxedUserCards(
accountViewModel: AccountViewModel,
subscribe: Boolean = true,
) {
val score by observeUserContactCardsScore(user, accountViewModel, subscribe)
val score by observeUserAssertionsScore(user, accountViewModel, subscribe)
score?.let {
Box(modifier = Size35Modifier, contentAlignment = Alignment.BottomCenter) {
@@ -140,18 +140,18 @@ import com.vitorpamplona.amethyst.ui.note.types.DisplayBlockedRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayBroadcastRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayContactList
import com.vitorpamplona.amethyst.ui.note.types.DisplayDMRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayFollowList
import com.vitorpamplona.amethyst.ui.note.types.DisplayFavoriteRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayIndexerRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayProxyRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelayFeedsList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
import com.vitorpamplona.amethyst.ui.note.types.DisplaySearchRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayStarterPack
import com.vitorpamplona.amethyst.ui.note.types.DisplayTrustedRelayList
import com.vitorpamplona.amethyst.ui.note.types.EditState
import com.vitorpamplona.amethyst.ui.note.types.EmptyState
import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay
import com.vitorpamplona.amethyst.ui.note.types.FileMetadataDisplay
import com.vitorpamplona.amethyst.ui.note.types.FileStorageHeaderDisplay
import com.vitorpamplona.amethyst.ui.note.types.PictureDisplay
import com.vitorpamplona.amethyst.ui.note.types.RenderAppDefinition
@@ -180,6 +180,8 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderCitation
import com.vitorpamplona.amethyst.ui.note.types.RenderClassifieds
import com.vitorpamplona.amethyst.ui.note.types.RenderCommunity
import com.vitorpamplona.amethyst.ui.note.types.RenderCyberspaceBag
import com.vitorpamplona.amethyst.ui.note.types.RenderDvmContentDiscoveryResponse
import com.vitorpamplona.amethyst.ui.note.types.RenderDvmStatus
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
import com.vitorpamplona.amethyst.ui.note.types.RenderEntityRating
import com.vitorpamplona.amethyst.ui.note.types.RenderExternalReaction
@@ -200,14 +202,11 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessChallenge
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd
import com.vitorpamplona.amethyst.ui.note.types.RenderLnZap
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderMusicPlaylist
import com.vitorpamplona.amethyst.ui.note.types.RenderMusicTrack
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedNappletEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedSiteEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderNipContent
@@ -257,21 +256,22 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderWikiMergeAcceptance
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiMergeRequest
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiRedirect
import com.vitorpamplona.amethyst.ui.note.types.RenderZapPoll
import com.vitorpamplona.amethyst.ui.note.types.RenderZapReceipt
import com.vitorpamplona.amethyst.ui.note.types.ReplyRenderType
import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderAppCurationSet
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderArticleCurationSet
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderBookmarkList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderBookmarkSet
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderFavoriteAlgoFeedsList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderGeohashList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderGitAuthorList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderGitRepositoryList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderGoodWikiAuthorList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderGoodWikiRelayList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderHashtagList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderInterestList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderInterestSet
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderKindMuteSet
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderLabeledBookmarkList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderMediaFollowList
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderMediaStarterPack
import com.vitorpamplona.amethyst.ui.note.types.lists.RenderMuteList
@@ -367,21 +367,20 @@ import com.vitorpamplona.quartz.nip51Lists.appCurationSet.AppCurationSetEvent
import com.vitorpamplona.quartz.nip51Lists.articleCurationSet.ArticleCurationSetEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.gitAuthorList.GitAuthorListEvent
import com.vitorpamplona.quartz.nip51Lists.gitRepositoryList.GitRepositoryListEvent
import com.vitorpamplona.quartz.nip51Lists.goodWikiAuthorList.GoodWikiAuthorListEvent
import com.vitorpamplona.quartz.nip51Lists.goodWikiRelayList.GoodWikiRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
import com.vitorpamplona.quartz.nip51Lists.kindMuteSet.KindMuteSetEvent
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.mediaFollowList.MediaFollowListEvent
import com.vitorpamplona.quartz.nip51Lists.mediaStarterPack.MediaStarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.pictureCurationSet.PictureCurationSetEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BroadcastRelayListEvent
@@ -392,6 +391,7 @@ import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent
import com.vitorpamplona.quartz.nip51Lists.releaseArtifactSet.ReleaseArtifactSetEvent
import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.SimpleGroupListEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.videoCurationSet.VideoCurationSetEvent
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent
@@ -1192,7 +1192,7 @@ private fun RenderNoteRow(
}
is ZapReceiptEvent -> {
RenderLnZap(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
RenderZapReceipt(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is NutzapEvent -> {
@@ -1220,7 +1220,7 @@ private fun RenderNoteRow(
}
is StarterPackEvent -> {
DisplayFollowList(baseNote, true, accountViewModel, nav)
DisplayStarterPack(baseNote, true, accountViewModel, nav)
}
is ContactListEvent -> {
@@ -1252,7 +1252,7 @@ private fun RenderNoteRow(
}
is FavoriteRelayListEvent -> {
DisplayRelayFeedsList(baseNote, backgroundColor, accountViewModel, nav)
DisplayFavoriteRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is IndexerRelayListEvent -> {
@@ -1551,7 +1551,7 @@ private fun RenderNoteRow(
}
is DvmContentDiscoveryResponseEvent -> {
RenderNIP90ContentDiscoveryResponse(
RenderDvmContentDiscoveryResponse(
baseNote,
makeItShort,
canPreview,
@@ -1563,7 +1563,7 @@ private fun RenderNoteRow(
}
is DvmStatusEvent -> {
RenderNIP90Status(
RenderDvmStatus(
baseNote,
accountViewModel,
nav,
@@ -1641,7 +1641,7 @@ private fun RenderNoteRow(
}
is FileMetadataEvent -> {
FileHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
FileMetadataDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
}
// Covers every NIP-71 video kind (21, 22, 34235, 34236) via the shared interface,
@@ -1880,7 +1880,7 @@ private fun RenderNoteRow(
}
is BookmarkSetEvent -> {
RenderLabeledBookmarkList(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
RenderBookmarkSet(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is ArticleCurationSetEvent -> {
@@ -1924,7 +1924,7 @@ private fun RenderNoteRow(
}
is InterestListEvent -> {
RenderHashtagList(baseNote, backgroundColor, accountViewModel, nav)
RenderInterestList(baseNote, backgroundColor, accountViewModel, nav)
}
is InterestSetEvent -> {
@@ -116,8 +116,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.report.ReportNoteDialog
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
@@ -358,7 +358,7 @@ private fun EmojiSelector(
onClick: ((EmojiUrlTag) -> Unit)? = null,
) {
LoadAddressableNote(
accountViewModel.account.emoji.getEmojiPackSelectionAddress(),
accountViewModel.account.emoji.getEmojiListAddress(),
) { emptyNote ->
emptyNote?.let { usersEmojiList ->
val collections by observeNoteEventAndMapNotNull(usersEmojiList, accountViewModel) { event: EmojiListEvent ->
@@ -52,7 +52,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.ScoreTag
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsScore
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAssertionsScore
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -565,7 +565,7 @@ fun ObserveAndRenderUserCards(
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
) {
val score by observeUserContactCardsScore(user, accountViewModel)
val score by observeUserAssertionsScore(user, accountViewModel)
score?.let {
ScoreTag(it, size, modifier)
@@ -67,7 +67,7 @@ import com.vitorpamplona.amethyst.commons.model.composer.AudienceList
import com.vitorpamplona.amethyst.commons.model.composer.AudienceListKind
import com.vitorpamplona.amethyst.commons.model.composer.AudienceMember
import com.vitorpamplona.amethyst.commons.model.composer.AudienceSelection
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.audience_add_anyway
import com.vitorpamplona.amethyst.commons.resources.audience_add_people
@@ -545,9 +545,9 @@ private fun MemberBadge(
*/
@Composable
fun rememberAudienceLists(accountViewModel: AccountViewModel): List<AudienceList> {
val peopleLists by accountViewModel.account.peopleLists.uiListFlow
val peopleLists by accountViewModel.account.followSets.uiListFlow
.collectAsStateWithLifecycle()
val followPacks by accountViewModel.account.followLists.uiListFlow
val followPacks by accountViewModel.account.starterPacks.uiListFlow
.collectAsStateWithLifecycle()
return remember(peopleLists, followPacks) {
@@ -281,7 +281,7 @@ fun observeBookmarksFollowsAndAccount(
accountViewModel.account.bookmarkState.bookmarks,
accountViewModel.account.pinState.pinnedEventIdSet,
accountViewModel.showSensitiveContent(),
accountViewModel.account.emoji.getEmojiPackSelectionFlow(),
accountViewModel.account.emoji.getEmojiListFlow(),
) { follows, bookmarks, pinnedIds, showSensitiveContent, emojiSelectionState ->
val isEmojiPackInMyList =
if (noteIdForEmoji != null) {
@@ -312,7 +312,7 @@ fun observeBookmarksFollowsAndAccount(
isEmojiPackInMyList =
noteIdForEmoji?.let {
accountViewModel.account.emoji
.getEmojiPackSelection()
.getEmojiList()
?.isTaggedAddressableNote(it) == true
} ?: false,
),
@@ -103,10 +103,10 @@ private object ActivityCardPreviewData {
}
}
fun lnZapNote(): Note {
fun zapReceiptNote(): Note {
val id = "d".repeat(64)
// Receipts are signed by the lightning provider; the preview skips the
// embedded request and feeds the decrypted card to RenderLnZapCard directly.
// embedded request and feeds the decrypted card to RenderZapReceiptCard directly.
return Note(id).apply {
event = null
author = null
@@ -171,12 +171,12 @@ fun EmojiReactionActivityCardPreview() {
@Preview
@Composable
fun LnZapActivityCardPreview() {
fun ZapReceiptActivityCardPreview() {
val accountViewModel = mockAccountViewModel()
ThemeComparisonColumn {
RenderLnZapCard(
note = ActivityCardPreviewData.lnZapNote(),
RenderZapReceiptCard(
note = ActivityCardPreviewData.zapReceiptNote(),
card =
ZapAmountCommentNotification(
user = LocalCache.getOrCreateUser(ActivityCardPreviewData.senderHex),
@@ -181,7 +181,7 @@ private fun EmojiListOptions(
emojiPackNote: Note,
) {
LoadAddressableNote(
accountViewModel.account.emoji.getEmojiPackSelectionAddress(),
accountViewModel.account.emoji.getEmojiListAddress(),
) {
it?.let { usersEmojiList ->
val hasAddedThis by observeNoteAndMap(usersEmojiList, accountViewModel) {
@@ -38,7 +38,7 @@ import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip94FileMetadata.FileMetadataEvent
@Composable
fun FileHeaderDisplay(
fun FileMetadataDisplay(
note: Note,
roundedCorner: Boolean,
contentScale: ContentScale,
@@ -53,7 +53,7 @@ fun FileHeaderDisplay(
// which viewer happens to render it, so an NSFW-tagged archive stays behind the same gate.
SensitivityWarning(note = note, accountViewModel = accountViewModel) {
if (content == null) {
FileHeaderAttachmentCard(event, fullUrl, mimeType)
FileMetadataAttachmentCard(event, fullUrl, mimeType)
} else {
ZoomableContentView(
content = content,
@@ -70,7 +70,7 @@ fun FileHeaderDisplay(
*
* Kind 1063 is a *generic* file container — its `m` tag can name any type, so unlike a NIP-71
* video event the kind itself asserts nothing about how to render the payload. A null here means
* the file belongs in [FileHeaderAttachmentCard] rather than being pushed into the video player.
* the file belongs in [FileMetadataAttachmentCard] rather than being pushed into the video player.
*/
internal fun FileMetadataEvent.toMediaContent(
note: Note,
@@ -128,7 +128,7 @@ internal fun FileMetadataEvent.toMediaContent(
/** The link card a kind-1063 header falls back to when [toMediaContent] returns null. */
@Composable
internal fun FileHeaderAttachmentCard(
internal fun FileMetadataAttachmentCard(
event: FileMetadataEvent,
url: String,
mimeType: String?,
@@ -38,7 +38,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip90Dvms.contentDiscoveryResponse.DvmContentDiscoveryResponseEvent
@Composable
fun RenderNIP90ContentDiscoveryResponse(
fun RenderDvmContentDiscoveryResponse(
note: Note,
makeItShort: Boolean,
canPreview: Boolean,
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip90Dvms.status.DvmStatusEvent
@Composable
fun RenderNIP90Status(
fun RenderDvmStatus(
note: Note,
accountViewModel: AccountViewModel,
nav: INav,
@@ -56,7 +56,7 @@ import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -76,7 +76,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor
import com.vitorpamplona.amethyst.commons.ui.theme.innerPostModifier
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsFollowerCount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAssertionsFollowerCount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
@@ -288,7 +288,7 @@ private fun ProfileCardChips(
// A self-follow in your own kind:3 is common; "Follows you" on your own card is not a fact.
val followsMe by observeUserIsFollowing(author, accountViewModel.account.userProfile(), accountViewModel)
val followsYou = followsMe && !accountViewModel.isLoggedUser(author)
val followerCount by observeUserContactCardsFollowerCount(author, accountViewModel)
val followerCount by observeUserAssertionsFollowerCount(author, accountViewModel)
// "--" is the placeholder the contact-card observer emits while no trusted
// assertion for this user has arrived. Nothing to brag about yet.
val followers = followerCount.takeIf { it != "--" }
@@ -241,14 +241,14 @@ fun DisplayTrustedRelayList(
}
@Composable
fun DisplayRelayFeedsList(
fun DisplayFavoriteRelayList(
baseNote: Note,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: INav,
) {
val relays by accountViewModel.account.relayFeedsListDecryptionCache.observeDecryptedRelayList(baseNote).collectAsStateWithLifecycle(
accountViewModel.account.relayFeedsListDecryptionCache.fastStartValueForRelayList(baseNote),
val relays by accountViewModel.account.favoriteRelayListDecryptionCache.observeDecryptedRelayList(baseNote).collectAsStateWithLifecycle(
accountViewModel.account.favoriteRelayListDecryptionCache.fastStartValueForRelayList(baseNote),
)
DisplayRelaySet(
@@ -66,13 +66,13 @@ import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.FollowSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DisplayFollowList(
fun DisplayStarterPack(
baseNote: Note,
makeItShort: Boolean,
accountViewModel: AccountViewModel,
@@ -60,11 +60,11 @@ fun RenderWikiContent(
) {
val noteEvent = note.event as? WikiArticleEvent ?: return
WikiNoteHeader(noteEvent, note, accountViewModel, nav)
WikiArticleHeader(noteEvent, note, accountViewModel, nav)
}
@Composable
private fun WikiNoteHeader(
private fun WikiArticleHeader(
noteEvent: WikiArticleEvent,
note: Note,
accountViewModel: AccountViewModel,
@@ -97,7 +97,7 @@ fun RenderZappedPost(
}
@Composable
fun RenderLnZap(
fun RenderZapReceipt(
note: Note,
quotesLeft: Int,
backgroundColor: MutableState<Color>,
@@ -108,7 +108,7 @@ fun RenderLnZap(
val card by parseAuthorCommentAndAmount(note, accountViewModel)
RenderLnZapCard(
RenderZapReceiptCard(
note = note,
card = card,
recipientKey = zapEvent.zappedAuthor().firstOrNull(),
@@ -120,7 +120,7 @@ fun RenderLnZap(
}
@Composable
fun RenderLnZapCard(
fun RenderZapReceiptCard(
note: Note,
card: ZapAmountCommentNotification,
recipientKey: String?,
@@ -38,7 +38,7 @@ import com.vitorpamplona.quartz.nip51Lists.articleCurationSet.ArticleCurationSet
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.BookmarkIdTag
import com.vitorpamplona.quartz.nip51Lists.labeledBookmarkList.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkSet.BookmarkSetEvent
import com.vitorpamplona.quartz.nip51Lists.pictureCurationSet.PictureCurationSetEvent
/** NIP-51 kind 10003: the user's bookmarks — notes, articles, hashtags and links. */
@@ -101,7 +101,7 @@ fun RenderOldBookmarkList(
/** NIP-51 kind 30003: "user-defined bookmarks categories, for when bookmarks must be in labeled separate groups". */
@Composable
fun RenderLabeledBookmarkList(
fun RenderBookmarkSet(
baseNote: Note,
quotesLeft: Int,
backgroundColor: MutableState<Color>,
@@ -36,12 +36,12 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.HashtagTag
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
/** NIP-51 kind 10015: the hashtags this user follows. */
@Composable
fun RenderHashtagList(
fun RenderInterestList(
baseNote: Note,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
@@ -303,8 +303,8 @@ class AccountSessionManager(
accountSettings.backupKeyPackageRelayList?.let { client.publish(it, toPost) }
accountSettings.backupSearchRelayList?.let { client.publish(it, toPost) }
accountSettings.backupIndexRelayList?.let { client.publish(it, toPost) }
accountSettings.backupRelayFeedsList?.let { client.publish(it, toPost) }
accountSettings.backupChannelList?.let { client.publish(it, toPost) }
accountSettings.backupFavoriteRelayList?.let { client.publish(it, toPost) }
accountSettings.backupPublicChatList?.let { client.publish(it, toPost) }
accountSettings.backupCashuWallet?.let { client.publish(it, toPost) }
accountSettings.backupNutzapInfo?.let { client.publish(it, toPost) }
}
@@ -329,8 +329,8 @@ class AccountSessionManager(
backupKeyPackageRelayList = bootstrap.keyPackageRelayList,
backupSearchRelayList = bootstrap.searchRelayList,
backupIndexRelayList = bootstrap.indexerRelayList,
backupChannelList = bootstrap.channelList,
backupRelayFeedsList = bootstrap.relayFeedsList,
backupPublicChatList = bootstrap.publicChatList,
backupFavoriteRelayList = bootstrap.favoriteRelayList,
)
}
@@ -43,9 +43,9 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
import com.vitorpamplona.quartz.utils.Log
@@ -162,14 +162,14 @@ class TopNavFilterState(
val livePeopleListsFlow: Flow<List<FeedDefinition>> =
combine(
account.peopleLists.peopleListNotes,
account.followLists.followListNotes,
account.followSets.peopleListNotes,
account.starterPacks.starterPackNotes,
::mergePeopleLists,
).onStart {
emit(
mergePeopleLists(
account.peopleLists.peopleListNotes.value,
account.followLists.followListNotes.value,
account.followSets.peopleListNotes.value,
account.starterPacks.starterPackNotes.value,
),
)
}
@@ -247,10 +247,10 @@ class TopNavFilterState(
@OptIn(ExperimentalCoroutinesApi::class)
val liveInterestFlows: Flow<List<FeedDefinition>> =
combine(
account.hashtagList.flow,
account.interestList.flow,
account.geohashList.flow,
account.communityList.flowNotes,
account.relayFeedsList.flow,
account.favoriteRelayList.flow,
combine(
account.favoriteAlgoFeedsList.flowNotes,
account.interestSets.listFeedFlow,
@@ -261,10 +261,10 @@ class TopNavFilterState(
}.onStart {
emit(
mergeInterests(
account.hashtagList.flow.value,
account.interestList.flow.value,
account.geohashList.flow.value,
account.communityList.flowNotes.value,
account.relayFeedsList.flow.value,
account.favoriteRelayList.flow.value,
account.favoriteAlgoFeedsList.flowNotes.value,
account.interestSets.listFeedFlow.value,
),
@@ -202,7 +202,7 @@ import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.AddressBookmark
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
import com.vitorpamplona.quartz.nip57Zaps.ZapRequestEvent
@@ -2144,11 +2144,11 @@ class AccountViewModel(
fun hide(user: User) = launchSigner { account.hideUser(user.pubkeyHex) }
fun updateContactCardPetName(
fun updateUserAssertionPetName(
user: User,
petName: String?,
summary: String?,
) = launchSigner { account.updateContactCardPetName(user.pubkeyHex, petName, summary) }
) = launchSigner { account.updateUserAssertionPetName(user.pubkeyHex, petName, summary) }
fun hide(word: String) = launchSigner { account.hideWord(word) }
@@ -68,10 +68,10 @@ class EventProcessor(
private val draftHandler = DraftEventHandler(account, cache)
private val giftWrapHandler = GiftWrapEventHandler(account, cache, this)
private val sealHandler = SealedRumorEventHandler(account, cache, this)
private val sealHandler = SealEventHandler(account, cache, this)
private val zapRequest = LnZapRequestEventHandler(account.privateZapsDecryptionCache)
private val zapEvent = LnZapEventHandler(account.privateZapsDecryptionCache)
private val zapRequest = ZapRequestEventHandler(account.privateZapsDecryptionCache)
private val zapEvent = ZapReceiptEventHandler(account.privateZapsDecryptionCache)
private val groupEventHandler = GroupEventHandler(account, cache)
@@ -389,7 +389,7 @@ class GiftWrapEventHandler(
/**
* Shared Marmot Welcome handler used by both [GiftWrapEventHandler]
* (in case a Welcome arrives directly inside a kind:1059 with no Seal
* layer) and [SealedRumorEventHandler] (the actual production path —
* layer) and [SealEventHandler] (the actual production path —
* Welcomes are wrapped GiftWrap → Seal → Welcome per
* [com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeGiftWrap]).
*/
@@ -453,7 +453,7 @@ private suspend fun processMarmotWelcomeFlow(
}
}
class SealedRumorEventHandler(
class SealEventHandler(
private val account: Account,
private val cache: LocalCache,
private val eventProcessor: EventProcessor,
@@ -465,14 +465,14 @@ class SealedRumorEventHandler(
) {
val rumorId = event.innerEventId
if (rumorId == null) {
processNewSealedRumor(event, eventNote, publicNote)
processNewSeal(event, eventNote, publicNote)
} else {
// Replayed seal: re-link the rumor to its delivering envelope so
// broadcast can republish the wrap after a cache rebuild.
// publicNote is the outermost event of this unwrap chain — the
// kind-1059 wrap normally, the seal itself when it arrived bare.
publicNote.event?.let { envelope -> cache.getOrCreateNote(rumorId).recordRumorHost(envelope) }
processExistingSealedRumor(rumorId, publicNote)
processExistingSeal(rumorId, publicNote)
}
}
@@ -488,7 +488,7 @@ class SealedRumorEventHandler(
}
}
private suspend fun processNewSealedRumor(
private suspend fun processNewSeal(
event: SealEvent,
eventNote: Note,
publicNote: Note,
@@ -524,7 +524,7 @@ class SealedRumorEventHandler(
eventNote.flowSet?.metadata?.invalidateData()
}
private suspend fun processExistingSealedRumor(
private suspend fun processExistingSeal(
rumorId: String,
publicNote: Note,
) {
@@ -536,7 +536,7 @@ class SealedRumorEventHandler(
}
}
class LnZapRequestEventHandler(
class ZapRequestEventHandler(
val decryptionCache: PrivateZapCache,
) : EventHandler<ZapRequestEvent> {
override suspend fun add(
@@ -559,7 +559,7 @@ class LnZapRequestEventHandler(
}
}
class LnZapEventHandler(
class ZapReceiptEventHandler(
val decryptionCache: PrivateZapCache,
) : EventHandler<ZapReceiptEvent> {
override suspend fun delete(
@@ -65,10 +65,10 @@ import com.vitorpamplona.quartz.nip01Core.diff.ContentChange
import com.vitorpamplona.quartz.nip01Core.diff.EventDiff
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataDiff
import com.vitorpamplona.quartz.nip02FollowList.ContactListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListDiff
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListDiff
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListDiff
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListDiff
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListDiff
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListDiff
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayListDiff
import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.SimpleGroupListDiff
@@ -130,12 +130,12 @@ private fun headlineOf(
is MetadataDiff -> plural(R.plurals.backup_card_profile_lost_fields, counts.removed)
is AdvertisedRelayListDiff -> plural(R.plurals.backup_card_nip65_lost, diff.relays.removed.size)
is RelayListDiff -> relayListHeadline(conflict.eventType, diff.relays.removed.size)
is ChannelListDiff -> plural(R.plurals.backup_card_leave_chats, diff.channels.removed.size)
is PublicChatListDiff -> plural(R.plurals.backup_card_leave_chats, diff.channels.removed.size)
is CommunityListDiff -> plural(R.plurals.backup_card_leave_communities, diff.communities.removed.size)
is EphemeralChatListDiff -> plural(R.plurals.backup_card_leave_rooms, diff.rooms.removed.size)
is SimpleGroupListDiff -> plural(R.plurals.backup_card_leave_groups, diff.groups.removed.size)
is FavoriteAlgoFeedsListDiff -> plural(R.plurals.backup_card_feeds_removed, diff.feeds.removed.size)
is HashtagListDiff -> plural(R.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size)
is InterestListDiff -> plural(R.plurals.backup_card_hashtags_unfollowed, diff.hashtags.removed.size)
is GeohashListDiff -> plural(R.plurals.backup_card_places_unfollowed, diff.geohashes.removed.size)
is TrustProviderListDiff -> plural(R.plurals.backup_card_trust_services_changed, diff.providers.removed.size + diff.providers.changed.size)
is NutzapInfoDiff ->
@@ -162,7 +162,7 @@ private fun headlineOf(
when {
diff is ContactListDiff -> R.plurals.backup_card_and_followed
diff is MuteListDiff -> R.plurals.backup_card_and_muted
diff is ChannelListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> R.plurals.backup_card_and_joined
diff is PublicChatListDiff || diff is CommunityListDiff || diff is EphemeralChatListDiff || diff is SimpleGroupListDiff -> R.plurals.backup_card_and_joined
conflict.eventType == BackupEventType.BLOCKED_RELAYS -> R.plurals.backup_card_and_blocked
else -> R.plurals.backup_card_and_added
}
@@ -182,12 +182,12 @@ private fun addedToHeadlinedList(
is MuteListDiff -> diff.publicMutes.added.size
is AdvertisedRelayListDiff -> diff.relays.added.size
is RelayListDiff -> if (relayListHasHeadline(type)) diff.relays.added.size else 0
is ChannelListDiff -> diff.channels.added.size
is PublicChatListDiff -> diff.channels.added.size
is CommunityListDiff -> diff.communities.added.size
is EphemeralChatListDiff -> diff.rooms.added.size
is SimpleGroupListDiff -> diff.groups.added.size
is FavoriteAlgoFeedsListDiff -> diff.feeds.added.size
is HashtagListDiff -> diff.hashtags.added.size
is InterestListDiff -> diff.hashtags.added.size
is GeohashListDiff -> diff.geohashes.added.size
is NutzapInfoDiff -> if (diff.p2pkPubkey == null) diff.mints.added.size else 0
is PaymentTargetsDiff -> diff.targets.added.size
@@ -93,7 +93,7 @@ import com.vitorpamplona.quartz.nip01Core.diff.ContentChange
import com.vitorpamplona.quartz.nip01Core.diff.ListDiff
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip17Dm.settings.DmRelayListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListEvent
import com.vitorpamplona.quartz.nip28PublicChat.list.channels
import com.vitorpamplona.quartz.nip37Drafts.privateOutbox.PrivateOutboxRelayListEvent
@@ -102,8 +102,8 @@ import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFee
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListDiff
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListDiff
import com.vitorpamplona.quartz.nip51Lists.hashtagList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListDiff
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
@@ -133,11 +133,11 @@ internal fun LazyListScope.listDiffItems(
): Boolean {
when (val diff = conflict.diff) {
is RelayListDiff -> relayListItems(diff, conflict, nav)
is ChannelListDiff -> publicChatItems(diff, conflict, accountViewModel, nav)
is PublicChatListDiff -> publicChatItems(diff, conflict, accountViewModel, nav)
is CommunityListDiff -> communityItems(diff, conflict, accountViewModel, nav)
is EphemeralChatListDiff -> ephemeralRoomItems(diff, conflict, nav)
is FavoriteAlgoFeedsListDiff -> favoriteFeedItems(diff, conflict, accountViewModel, nav)
is HashtagListDiff -> topicItems(diff.hashtags, (conflict.saved as? InterestListEvent)?.publicHashtags().orEmpty(), diff.privateItems, isPlace = false, nav)
is InterestListDiff -> topicItems(diff.hashtags, (conflict.saved as? InterestListEvent)?.publicHashtags().orEmpty(), diff.privateItems, isPlace = false, nav)
is GeohashListDiff -> topicItems(diff.geohashes, (conflict.saved as? GeohashListEvent)?.publicGeohashes().orEmpty(), diff.privateItems, isPlace = true, nav)
is TrustProviderListDiff -> trustProviderItems(diff, conflict, accountViewModel, nav)
is PaymentTargetsDiff -> paymentTargetItems(diff, conflict)
@@ -446,7 +446,7 @@ private fun SpaceTile(
}
private fun LazyListScope.publicChatItems(
diff: ChannelListDiff,
diff: PublicChatListDiff,
conflict: ReplaceableBackupConflict,
accountViewModel: AccountViewModel,
nav: INav,
@@ -41,10 +41,10 @@ import com.vitorpamplona.quartz.nip01Core.metadata.Birthday
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataDiff
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip02FollowList.ContactListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListDiff
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListDiff
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListDiff
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListDiff
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListDiff
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListDiff
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.EventTag
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.HashtagTag
@@ -104,9 +104,9 @@ fun countsOf(diff: EventDiff): ConflictCounts =
is MuteListDiff -> diff.publicMutes.asCounts() + diff.privateItems.asCounts()
is AdvertisedRelayListDiff -> diff.relays.asCounts()
is RelayListDiff -> diff.relays.asCounts() + diff.privateRelays.asCounts()
is ChannelListDiff -> diff.channels.asCounts() + diff.privateItems.asCounts()
is PublicChatListDiff -> diff.channels.asCounts() + diff.privateItems.asCounts()
is CommunityListDiff -> diff.communities.asCounts() + diff.privateItems.asCounts()
is HashtagListDiff -> diff.hashtags.asCounts() + diff.privateItems.asCounts()
is InterestListDiff -> diff.hashtags.asCounts() + diff.privateItems.asCounts()
is GeohashListDiff -> diff.geohashes.asCounts() + diff.privateItems.asCounts()
is FavoriteAlgoFeedsListDiff -> diff.feeds.asCounts() + diff.privateItems.asCounts()
is EphemeralChatListDiff -> diff.rooms.asCounts() + diff.privateItems.asCounts()
@@ -330,7 +330,7 @@ fun presentationOf(
) to ContentChange.NONE
}
is RelayListDiff -> listOf(listGroup(R.string.backup_entry_relay, diff.relays, { ReviewItem.Relay(it) })) to diff.privateRelays
is ChannelListDiff ->
is PublicChatListDiff ->
listOf(
listGroup(
R.string.backup_entry_public_chat,
@@ -348,7 +348,7 @@ fun presentationOf(
{ ReviewItem.Addressable(it.after.address, arrow(it.before.relayHint?.url, it.after.relayHint?.url)) },
),
) to diff.privateItems
is HashtagListDiff -> listOf(listGroup(R.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems
is InterestListDiff -> listOf(listGroup(R.string.backup_entry_hashtag, diff.hashtags, { ReviewItem.Text("#$it") })) to diff.privateItems
is GeohashListDiff -> listOf(listGroup(R.string.backup_entry_location, diff.geohashes, { ReviewItem.Text(it) })) to diff.privateItems
is FavoriteAlgoFeedsListDiff ->
listOf(
@@ -86,10 +86,10 @@ import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListDif
import com.vitorpamplona.quartz.nip01Core.diff.ContentChange
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataDiff
import com.vitorpamplona.quartz.nip02FollowList.ContactListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.ChannelListDiff
import com.vitorpamplona.quartz.nip28PublicChat.list.PublicChatListDiff
import com.vitorpamplona.quartz.nip51Lists.favoriteAlgoFeedsList.FavoriteAlgoFeedsListDiff
import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListDiff
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListDiff
import com.vitorpamplona.quartz.nip51Lists.interestList.InterestListDiff
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListDiff
import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayListDiff
import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.SimpleGroupListDiff
@@ -373,11 +373,11 @@ private fun actionLabels(conflict: ReplaceableBackupConflict): Pair<String, Stri
stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays)
}
is AdvertisedRelayListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_relays)
is ChannelListDiff -> rejoinLabels(diff.channels.removed.size)
is PublicChatListDiff -> rejoinLabels(diff.channels.removed.size)
is CommunityListDiff -> rejoinLabels(diff.communities.removed.size)
is EphemeralChatListDiff -> rejoinLabels(diff.rooms.removed.size)
is FavoriteAlgoFeedsListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_feeds)
is HashtagListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_topics)
is InterestListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_topics)
// Geohashes are places, not topics: the screen counts "places gone" under "Followed
// locations", so the button has to agree with the page it sits on.
is GeohashListDiff -> stringRes(R.string.backup_conflict_keep_new) to stringRes(R.string.backup_action_restore_places)
@@ -101,7 +101,7 @@ fun BookmarkGroupScreen(
bookmarkType,
broadcastBookmarkGroup = {
accountViewModel.launchSigner {
val groupNote = accountViewModel.account.labeledBookmarkLists.getLabeledBookmarkListNote(bookmarkIdentifier)
val groupNote = accountViewModel.account.bookmarkSets.getBookmarkSetNote(bookmarkIdentifier)
groupNote?.let {
accountViewModel.broadcast(it)
}
@@ -40,8 +40,8 @@ class BookmarkGroupViewModel(
val bookmarkGroupIdentifier: String,
) : ViewModel() {
val selectedBookmarkGroupFlow =
account.labeledBookmarkLists
.getLabeledBookmarkListFlow(bookmarkGroupIdentifier)
account.bookmarkSets
.getBookmarkSetFlow(bookmarkGroupIdentifier)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(2500), null)
fun publicPosts() =
@@ -69,7 +69,7 @@ class BookmarkGroupViewModel(
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
suspend fun deleteBookmarkGroup(groupIdentifier: String) {
account.labeledBookmarkLists.deleteBookmarkList(groupIdentifier, account)
account.bookmarkSets.deleteBookmarkList(groupIdentifier, account)
}
suspend fun addBookmarkToGroup(
@@ -77,7 +77,7 @@ class BookmarkGroupViewModel(
bookmark: BookmarkIdTag,
isPrivate: Boolean,
) {
account.labeledBookmarkLists.addBookmarkToList(
account.bookmarkSets.addBookmarkToList(
bookmark,
groupIdentifier,
isPrivate,
@@ -108,7 +108,7 @@ class BookmarkGroupViewModel(
bookmark: BookmarkIdTag,
isCurrentlyPrivate: Boolean,
) {
account.labeledBookmarkLists.moveBookmarkInList(
account.bookmarkSets.moveBookmarkInList(
bookmark,
groupIdentifier,
isCurrentlyPrivate,
@@ -147,7 +147,7 @@ class BookmarkGroupViewModel(
bookmark: BookmarkIdTag,
isPrivate: Boolean,
) {
account.labeledBookmarkLists.removeBookmarkFromList(
account.bookmarkSets.removeBookmarkFromList(
bookmark,
groupIdentifier,
isPrivate,
@@ -160,7 +160,7 @@ class BookmarkGroupViewModel(
deletedEventIds: Set<String>,
deletedAddresses: Set<Address>,
) {
account.labeledBookmarkLists.removeDeletedBookmarksFromList(
account.bookmarkSets.removeDeletedBookmarksFromList(
groupIdentifier,
deletedEventIds,
deletedAddresses,
@@ -40,7 +40,7 @@ import com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.GitRepositoryListState
import com.vitorpamplona.amethyst.commons.model.nip51Lists.OldBookmarkListState
import com.vitorpamplona.amethyst.commons.model.nip51Lists.PinListState
import com.vitorpamplona.amethyst.commons.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.bookmarkSets.BookmarkSet
import com.vitorpamplona.amethyst.commons.nip51Lists.ui.ListOfBookmarkGroupsFeedView
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.bookmark_lists
@@ -64,7 +64,7 @@ fun ListOfBookmarkGroupsScreen(
oldBookmarks = accountViewModel.account.oldBookmarkState,
pinnedNotes = accountViewModel.account.pinState,
repositories = accountViewModel.account.gitRepositoryListState,
listSource = accountViewModel.account.labeledBookmarkLists.listFeedFlow,
listSource = accountViewModel.account.bookmarkSets.listFeedFlow,
openDefaultBookmarks = { nav.nav(Route.Bookmarks) },
openOldBookmarks = { nav.nav(Route.OldBookmarks) },
openPinnedNotes = { nav.nav(Route.PinnedNotes) },
@@ -82,7 +82,7 @@ fun ListOfBookmarkGroupsScreen(
},
cloneBookmarkGroup = { bookmarkGroup, customName, customDesc ->
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.cloneBookmarkList(
accountViewModel.account.bookmarkSets.cloneBookmarkList(
currentBookmarkList = bookmarkGroup,
customCloneName = customName,
customCloneDescription = customDesc,
@@ -92,7 +92,7 @@ fun ListOfBookmarkGroupsScreen(
},
deleteBookmarkGroup = { bookmarkGroup ->
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.deleteBookmarkList(
accountViewModel.account.bookmarkSets.deleteBookmarkList(
bookmarkListIdentifier = bookmarkGroup.identifier,
account = accountViewModel.account,
)
@@ -109,7 +109,7 @@ fun ListOfBookmarkGroupsFeed(
oldBookmarks: OldBookmarkListState,
pinnedNotes: PinListState,
repositories: GitRepositoryListState,
listSource: StateFlow<List<LabeledBookmarkList>>,
listSource: StateFlow<List<BookmarkSet>>,
openDefaultBookmarks: () -> Unit,
openOldBookmarks: () -> Unit,
openPinnedNotes: () -> Unit,
@@ -117,10 +117,10 @@ fun ListOfBookmarkGroupsFeed(
openPodcasts: () -> Unit,
addBookmarkGroup: () -> Unit,
openBookmarkGroup: (identifier: String, bookmarkType: BookmarkType) -> Unit,
renameBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit,
changeBookmarkGroupDescription: (bookmarkGroup: LabeledBookmarkList) -> Unit,
cloneBookmarkGroup: (bookmarkGroup: LabeledBookmarkList, customName: String?, customDesc: String?) -> Unit,
deleteBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit,
renameBookmarkGroup: (bookmarkGroup: BookmarkSet) -> Unit,
changeBookmarkGroupDescription: (bookmarkGroup: BookmarkSet) -> Unit,
cloneBookmarkGroup: (bookmarkGroup: BookmarkSet, customName: String?, customDesc: String?) -> Unit,
deleteBookmarkGroup: (bookmarkGroup: BookmarkSet) -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
) {
@@ -31,7 +31,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.bookmarkSets.BookmarkSet
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed
import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details
@@ -59,7 +59,7 @@ class BookmarkGroupMetadataViewModel : ViewModel() {
private lateinit var accountViewModel: AccountViewModel
private lateinit var account: Account
var bookmarkGroup by mutableStateOf<LabeledBookmarkList?>(null)
var bookmarkGroup by mutableStateOf<BookmarkSet?>(null)
val isNewList by derivedStateOf { bookmarkGroup == null }
val name = mutableStateOf(TextFieldValue())
@@ -83,7 +83,7 @@ class BookmarkGroupMetadataViewModel : ViewModel() {
}
fun load(dTag: String) {
bookmarkGroup = account.labeledBookmarkLists.getBookmarkList(dTag)
bookmarkGroup = account.bookmarkSets.getBookmarkList(dTag)
name.value = TextFieldValue(bookmarkGroup?.title ?: "")
picture.value = TextFieldValue(bookmarkGroup?.image ?: "")
description.value = TextFieldValue(bookmarkGroup?.description ?: "")
@@ -95,14 +95,14 @@ class BookmarkGroupMetadataViewModel : ViewModel() {
accountViewModel.launchSigner {
val bookmarkGroup = bookmarkGroup
if (bookmarkGroup == null) {
accountViewModel.account.labeledBookmarkLists.addLabeledBookmarkList(
accountViewModel.account.bookmarkSets.addBookmarkSet(
listName = name.value.text,
listDescription = description.value.text,
listImage = picture.value.text,
account = accountViewModel.account,
)
} else {
accountViewModel.account.labeledBookmarkLists.updateMetadata(
accountViewModel.account.bookmarkSets.updateMetadata(
listName = name.value.text,
listDescription = description.value.text,
listImage = picture.value.text,
@@ -37,7 +37,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.bookmarkSets.BookmarkSet
import com.vitorpamplona.amethyst.commons.nip51Lists.ui.BookmarkGroupManagementItem
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.article_bookmark_management_title
@@ -109,7 +109,7 @@ private fun ListManagementViewBody(
accountViewModel: AccountViewModel,
nav: INav,
) {
val bookmarkGroups by accountViewModel.account.labeledBookmarkLists.listFeedFlow
val bookmarkGroups by accountViewModel.account.bookmarkSets.listFeedFlow
.collectAsStateWithLifecycle()
val defaultBookmarks by accountViewModel.account.bookmarkState.bookmarks
@@ -146,7 +146,7 @@ private fun ListManagementViewBody(
)
}
itemsIndexed(items = bookmarkGroups, key = { _: Int, item: LabeledBookmarkList -> item.identifier }) { _, bookmarkList ->
itemsIndexed(items = bookmarkGroups, key = { _: Int, item: BookmarkSet -> item.identifier }) { _, bookmarkList ->
val maybePublicBookmark = bookmarkList.publicArticleBookmarks.firstOrNull { it.address == note.address }
val maybePrivateBookmark = bookmarkList.privateArticleBookmarks.firstOrNull { it.address == note.address }
BookmarkGroupManagementItem(
@@ -159,7 +159,7 @@ private fun ListManagementViewBody(
onClick = { nav.nav(Route.BookmarkGroupView(bookmarkList.identifier, BookmarkType.ArticleBookmark)) },
onAddBookmarkToGroup = { shouldBePrivate ->
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.addBookmarkToList(
accountViewModel.account.bookmarkSets.addBookmarkToList(
bookmark = AddressBookmark(address = note.address, relayHint = note.relayHintUrl()),
bookmarkListIdentifier = bookmarkList.identifier,
isBookmarkPrivate = shouldBePrivate,
@@ -169,7 +169,7 @@ private fun ListManagementViewBody(
},
onRemoveBookmarkFromGroup = {
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.removeBookmarkFromList(
accountViewModel.account.bookmarkSets.removeBookmarkFromList(
bookmark = AddressBookmark(address = note.address),
bookmarkListIdentifier = bookmarkList.identifier,
isBookmarkPrivate = maybePrivateBookmark != null,
@@ -37,7 +37,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.bookmarkSets.BookmarkSet
import com.vitorpamplona.amethyst.commons.nip51Lists.ui.BookmarkGroupManagementItem
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.bookmarks_title
@@ -108,7 +108,7 @@ private fun ListManagementViewBody(
accountViewModel: AccountViewModel,
nav: INav,
) {
val bookmarkGroups by accountViewModel.account.labeledBookmarkLists.listFeedFlow
val bookmarkGroups by accountViewModel.account.bookmarkSets.listFeedFlow
.collectAsStateWithLifecycle()
val defaultBookmarks by accountViewModel.account.bookmarkState.bookmarks
@@ -145,7 +145,7 @@ private fun ListManagementViewBody(
)
}
itemsIndexed(items = bookmarkGroups, key = { _: Int, item: LabeledBookmarkList -> item.identifier }) { _, bookmarkList ->
itemsIndexed(items = bookmarkGroups, key = { _: Int, item: BookmarkSet -> item.identifier }) { _, bookmarkList ->
val maybePublicBookmark = bookmarkList.publicPostBookmarks.firstOrNull { it.eventId == note.idHex }
val maybePrivateBookmark = bookmarkList.privatePostBookmarks.firstOrNull { it.eventId == note.idHex }
BookmarkGroupManagementItem(
@@ -158,7 +158,7 @@ private fun ListManagementViewBody(
onClick = { nav.nav(Route.BookmarkGroupView(bookmarkList.identifier, BookmarkType.PostBookmark)) },
onAddBookmarkToGroup = { shouldBePrivate ->
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.addBookmarkToList(
accountViewModel.account.bookmarkSets.addBookmarkToList(
bookmark = EventBookmark(eventId = note.idHex, relay = note.relayHintUrl(), author = note.author?.pubkeyHex),
bookmarkListIdentifier = bookmarkList.identifier,
isBookmarkPrivate = shouldBePrivate,
@@ -168,7 +168,7 @@ private fun ListManagementViewBody(
},
onRemoveBookmarkFromGroup = {
accountViewModel.launchSigner {
accountViewModel.account.labeledBookmarkLists.removeBookmarkFromList(
accountViewModel.account.bookmarkSets.removeBookmarkFromList(
bookmark = EventBookmark(eventId = note.idHex),
bookmarkListIdentifier = bookmarkList.identifier,
isBookmarkPrivate = maybePrivateBookmark != null,
@@ -250,7 +250,7 @@ private fun WatchAccountForRelayGroupDiscovery(
.collectAsStateWithLifecycle()
val joinedServers by accountViewModel.account.relayGroupList.liveRelayGroupServers
.collectAsStateWithLifecycle()
val favoriteRelays by accountViewModel.account.relayFeedsList.flow
val favoriteRelays by accountViewModel.account.favoriteRelayList.flow
.collectAsStateWithLifecycle()
// Discovery only shows groups whose 39000 is signed by the host relay's own key (NIP-29's
@@ -349,7 +349,7 @@ private fun RelayRailHeader(
val info = loadRelayInfo(relay)
val host = relay.displayUrl()
val name = info.value.name?.takeIf { it.isNotBlank() } ?: host
val favoriteRelays by accountViewModel.account.relayFeedsList.flow
val favoriteRelays by accountViewModel.account.favoriteRelayList.flow
.collectAsStateWithLifecycle()
val isFavorite = relay in favoriteRelays
@@ -408,7 +408,7 @@ private fun RelayGroupMemberRow(
)
}
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
val declaredRoles = channel.supportedRoles
val declaredRoles = channel.groupRoles
if (declaredRoles.isNotEmpty()) {
// The relay declares its own role set (kind 39003) — offer exactly those
// instead of the built-in admin/moderator pair. Roles are privilege grants,
@@ -100,7 +100,7 @@ class RelayGroupsDiscoverySubAssembler(
account.relayGroupList.liveRelayGroupServers.value.forEach { server ->
RelayUrlNormalizer.normalizeOrNull(server)?.let(::add)
}
addAll(account.relayFeedsList.flow.value)
addAll(account.favoriteRelayList.flow.value)
}
override fun user(key: RelayGroupsDiscoveryQueryState) = key.account.userProfile()
@@ -156,7 +156,7 @@ class RelayGroupsDiscoverySubAssembler(
}
},
key.account.scope.launch(Dispatchers.IO) {
key.account.relayFeedsList.flow.sample(500).collectLatest {
key.account.favoriteRelayList.flow.sample(500).collectLatest {
invalidateFilters()
}
},
@@ -45,7 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.RenderCo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.RenderClassifiedsThumb
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
@@ -88,7 +88,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.Discove
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.TabItem
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
import com.vitorpamplona.amethyst.ui.dal.sortedByDefaultFeedOrder
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
open class DiscoverFollowSetsFeedFilter(
val account: Account,
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageBannerBackground
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -87,7 +87,7 @@ import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.DVMCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.NIP90ContentDiscoveryFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.DvmContentDiscoveryFeedViewModel
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
@@ -263,22 +263,22 @@ fun PrepareViewContentDiscoveryModels(
accountViewModel: AccountViewModel,
nav: INav,
) {
val resultFeedViewModel: NIP90ContentDiscoveryFeedViewModel =
val resultFeedViewModel: DvmContentDiscoveryFeedViewModel =
viewModel(
key = "NostrNIP90ContentDiscoveryFeedViewModel${dvm.pubkeyHex}$dvmRequestId",
factory = NIP90ContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmKey = dvm.pubkeyHex, requestId = dvmRequestId),
factory = DvmContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmKey = dvm.pubkeyHex, requestId = dvmRequestId),
)
LaunchedEffect(key1 = dvmRequestId, latestResponse.id) {
resultFeedViewModel.invalidateData()
}
RenderNostrNIP90ContentDiscoveryScreen(resultFeedViewModel, onRefresh, accountViewModel, nav)
RenderNostrDvmContentDiscoveryScreen(resultFeedViewModel, onRefresh, accountViewModel, nav)
}
@Composable
fun RenderNostrNIP90ContentDiscoveryScreen(
resultFeedViewModel: NIP90ContentDiscoveryFeedViewModel,
fun RenderNostrDvmContentDiscoveryScreen(
resultFeedViewModel: DvmContentDiscoveryFeedViewModel,
onRefresh: () -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
@@ -27,17 +27,17 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.screen.AndroidFeedViewModel
@Stable
class NIP90ContentDiscoveryFeedViewModel(
class DvmContentDiscoveryFeedViewModel(
val account: Account,
dvmKey: String,
requestId: String,
) : AndroidFeedViewModel(NIP90ContentDiscoveryResponseFilter(account, dvmKey, requestId)) {
) : AndroidFeedViewModel(DvmContentDiscoveryResponseFilter(account, dvmKey, requestId)) {
class Factory(
val account: Account,
val dvmKey: String,
val requestId: String,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = NIP90ContentDiscoveryFeedViewModel(account, dvmKey, requestId) as T
override fun <T : ViewModel> create(modelClass: Class<T>): T = DvmContentDiscoveryFeedViewModel(account, dvmKey, requestId) as T
}
}
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
import com.vitorpamplona.quartz.nip90Dvms.contentDiscoveryResponse.DvmContentDiscoveryResponseEvent
open class NIP90ContentDiscoveryResponseFilter(
open class DvmContentDiscoveryResponseFilter(
val account: Account,
val dvmkey: String,
val request: String,
@@ -315,7 +315,7 @@ private fun EmojiListToggleRow(
LoadAddressableNote(packAddress) {
it?.let { packNote ->
LoadAddressableNote(
accountViewModel.account.emoji.getEmojiPackSelectionAddress(),
accountViewModel.account.emoji.getEmojiListAddress(),
) { selectionNote ->
selectionNote?.let { usersEmojiList ->
val hasAddedThis by observeNoteAndMap(usersEmojiList, accountViewModel) {
@@ -121,7 +121,7 @@ private fun EmojiPackSelectionBody(
) {
item {
LoadAddressableNote(
address = accountViewModel.account.emoji.getEmojiPackSelectionAddress(),
address = accountViewModel.account.emoji.getEmojiListAddress(),
) { selectionNote ->
selectionNote?.let {
val hasAddedThis by observeNoteAndMap(it, accountViewModel) { currentNote ->
@@ -79,7 +79,7 @@ fun MyEmojiListScreen(
nav: INav,
) {
LoadAddressableNote(
address = accountViewModel.account.emoji.getEmojiPackSelectionAddress(),
address = accountViewModel.account.emoji.getEmojiListAddress(),
) { selectionNote ->
selectionNote?.let {
MyEmojiListView(
@@ -85,7 +85,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.dal.Follow
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.dal.FollowPackMembersUserFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.datasource.FollowPackFeedFilterAssemblerSubscription
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@@ -37,7 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
@@ -46,7 +46,7 @@ import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiArticleEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
@@ -25,7 +25,7 @@ import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache.checkGetOrCreateUser
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
class FollowPackMembersFeedFilter(
val followPackNote: AddressableNote,
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.home.nip65Follows.filterHo
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
class FollowPackFeedFilterSubAssembler(
client: INostrClient,
@@ -38,7 +38,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.ChannelCardCompose
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
@Composable
fun FollowPacksTopBar(
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip51Lists.peopleList.FollowSetEvent
import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -57,7 +57,7 @@ class PeopleListViewModel : ViewModel() {
selectedDTag
.transformLatest {
emitAll(
account.peopleLists.selectListFlow(it).flowOn(Dispatchers.IO),
account.followSets.selectListFlow(it).flowOn(Dispatchers.IO),
)
}.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
@@ -79,23 +79,23 @@ class PeopleListViewModel : ViewModel() {
}
suspend fun deleteFollowSet() {
account.peopleLists.deleteFollowSet(selectedDTag.value, account)
account.followSets.deleteFollowSet(selectedDTag.value, account)
}
fun loadNote(): AddressableNote? = account.peopleLists.getPeopleListNote(selectedDTag.value)
fun loadNote(): AddressableNote? = account.followSets.getPeopleListNote(selectedDTag.value)
suspend fun removeUserFromSet(
user: User,
isPrivate: Boolean,
) {
account.peopleLists.removeUserFromSet(user, isPrivate, selectedDTag.value, account)
account.followSets.removeUserFromSet(user, isPrivate, selectedDTag.value, account)
}
suspend fun addUserToSet(
user: User,
isPrivate: Boolean,
) {
account.peopleLists.addUserToSet(user, selectedDTag.value, isPrivate, account)
account.followSets.addUserToSet(user, selectedDTag.value, isPrivate, account)
}
fun hasUserFlow(
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip51Lists.followList.StarterPackEvent
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -57,7 +57,7 @@ class FollowPackViewModel : ViewModel() {
selectedDTag
.transformLatest {
emitAll(
account.followLists.selectListFlow(it).flowOn(Dispatchers.IO),
account.starterPacks.selectListFlow(it).flowOn(Dispatchers.IO),
)
}.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
@@ -79,17 +79,17 @@ class FollowPackViewModel : ViewModel() {
}
suspend fun deleteFollowSet() {
account.followLists.deleteFollowSet(selectedDTag.value, account)
account.starterPacks.deleteFollowSet(selectedDTag.value, account)
}
fun loadNote(): AddressableNote? = account.followLists.getPeopleListNote(selectedDTag.value)
fun loadNote(): AddressableNote? = account.starterPacks.getPeopleListNote(selectedDTag.value)
suspend fun removeUserFromSet(user: User) {
account.followLists.removeUserFromSet(user, selectedDTag.value, account)
account.starterPacks.removeUserFromSet(user, selectedDTag.value, account)
}
suspend fun addUserToSet(user: User) {
account.followLists.addUserToSet(user, selectedDTag.value, account)
account.starterPacks.addUserToSet(user, selectedDTag.value, account)
}
fun hasUserFlow(user: User): Flow<Boolean> =
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Stable
@@ -33,7 +33,7 @@ class FollowPackViewModel : ViewModel() {
this.accountViewModel = accountViewModel
}
fun listFlow() = accountViewModel.account.followLists.uiListFlow
fun listFlow() = accountViewModel.account.starterPacks.uiListFlow
fun cloneItem(
followSet: PeopleList,
@@ -41,7 +41,7 @@ class FollowPackViewModel : ViewModel() {
customDescription: String?,
) {
accountViewModel.launchSigner {
accountViewModel.account.followLists.cloneFollowSet(
accountViewModel.account.starterPacks.cloneFollowSet(
currentFollowPack = followSet,
customCloneName = customName,
customCloneDescription = customDescription,
@@ -52,7 +52,7 @@ class FollowPackViewModel : ViewModel() {
fun deleteItem(followSet: PeopleList) {
accountViewModel.launchSigner {
accountViewModel.account.followLists.deleteFollowSet(
accountViewModel.account.starterPacks.deleteFollowSet(
identifierTag = followSet.identifierTag,
account = accountViewModel.account,
)
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list
import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Stable
@@ -33,7 +33,7 @@ class PeopleListViewModel : ViewModel() {
this.accountViewModel = accountViewModel
}
fun listFlow() = accountViewModel.account.peopleLists.uiListFlow
fun listFlow() = accountViewModel.account.followSets.uiListFlow
fun cloneItem(
followSet: PeopleList,
@@ -41,7 +41,7 @@ class PeopleListViewModel : ViewModel() {
customDescription: String?,
) {
accountViewModel.launchSigner {
accountViewModel.account.peopleLists.cloneFollowSet(
accountViewModel.account.followSets.cloneFollowSet(
currentPeopleList = followSet,
customCloneName = customName,
customCloneDescription = customDescription,
@@ -52,7 +52,7 @@ class PeopleListViewModel : ViewModel() {
fun deleteItem(followSet: PeopleList) {
accountViewModel.launchSigner {
accountViewModel.account.peopleLists.deleteFollowSet(
accountViewModel.account.followSets.deleteFollowSet(
identifierTag = followSet.identifierTag,
account = accountViewModel.account,
)
@@ -31,7 +31,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed
import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details
@@ -83,7 +83,7 @@ class FollowPackMetadataViewModel : ViewModel() {
}
fun load(dTag: String) {
peopleList = account.followLists.selectList(dTag)
peopleList = account.starterPacks.selectList(dTag)
name.value = TextFieldValue(peopleList?.title ?: "")
picture.value = TextFieldValue(peopleList?.image ?: "")
description.value = TextFieldValue(peopleList?.description ?: "")
@@ -94,14 +94,14 @@ class FollowPackMetadataViewModel : ViewModel() {
val peopleList = peopleList
if (peopleList == null) {
val newListIdentifier =
accountViewModel.account.followLists.addFollowList(
accountViewModel.account.starterPacks.addFollowList(
name = name.value.text,
desc = description.value.text,
image = picture.value.text,
account = accountViewModel.account,
)
} else {
accountViewModel.account.followLists.updateMetadata(
accountViewModel.account.starterPacks.updateMetadata(
name = name.value.text,
desc = description.value.text,
image = picture.value.text,
@@ -31,7 +31,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
import com.vitorpamplona.amethyst.commons.model.nip51Lists.peopleList.PeopleList
import com.vitorpamplona.amethyst.commons.model.nip51Lists.followSets.PeopleList
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed
import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details
@@ -83,7 +83,7 @@ class PeopleListMetadataViewModel : ViewModel() {
}
fun load(dTag: String) {
peopleList = account.peopleLists.selectList(dTag)
peopleList = account.followSets.selectList(dTag)
name.value = TextFieldValue(peopleList?.title ?: "")
picture.value = TextFieldValue(peopleList?.image ?: "")
description.value = TextFieldValue(peopleList?.description ?: "")
@@ -95,14 +95,14 @@ class PeopleListMetadataViewModel : ViewModel() {
accountViewModel.launchSigner {
val peopleList = peopleList
if (peopleList == null) {
accountViewModel.account.peopleLists.addFollowList(
accountViewModel.account.followSets.addFollowList(
listName = name.value.text,
listDescription = description.value.text,
listImage = picture.value.text,
account = accountViewModel.account,
)
} else {
accountViewModel.account.peopleLists.updateMetadata(
accountViewModel.account.followSets.updateMetadata(
listName = name.value.text,
listDescription = description.value.text,
listImage = picture.value.text,
@@ -63,9 +63,9 @@ fun FollowListAndPackAndUserView(
accountViewModel: AccountViewModel,
nav: INav,
) {
val followSetsState by accountViewModel.account.peopleLists.uiListFlow
val followSetsState by accountViewModel.account.followSets.uiListFlow
.collectAsStateWithLifecycle()
val followPackFeedState by accountViewModel.account.followLists.uiListFlow
val followPackFeedState by accountViewModel.account.starterPacks.uiListFlow
.collectAsStateWithLifecycle()
val userName by observeUserName(userToAddOrRemove, accountViewModel)
@@ -114,7 +114,7 @@ fun FollowListAndPackAndUserView(
userIsPublicMember = list.publicMembers.contains(userToAddOrRemove),
onRemoveUser = {
accountViewModel.launchSigner {
accountViewModel.account.peopleLists.removeUserFromSet(
accountViewModel.account.followSets.removeUserFromSet(
userToAddOrRemove,
isPrivate = list.privateMembers.contains(userToAddOrRemove),
list.identifierTag,
@@ -129,7 +129,7 @@ fun FollowListAndPackAndUserView(
},
onAddUserToList = { userShouldBePrivate ->
accountViewModel.launchSigner {
accountViewModel.account.peopleLists.addUserToSet(
accountViewModel.account.followSets.addUserToSet(
userToAddOrRemove,
list.identifierTag,
userShouldBePrivate,
@@ -189,7 +189,7 @@ fun FollowListAndPackAndUserView(
},
onRemoveUser = {
accountViewModel.launchSigner {
accountViewModel.account.followLists.removeUserFromSet(
accountViewModel.account.starterPacks.removeUserFromSet(
userToAddOrRemove,
list.identifierTag,
accountViewModel.account,
@@ -199,7 +199,7 @@ fun FollowListAndPackAndUserView(
memberSize = list.publicMembers.size,
onAddUserToList = {
accountViewModel.launchSigner {
accountViewModel.account.followLists.addUserToSet(
accountViewModel.account.starterPacks.addUserToSet(
userToAddOrRemove,
list.identifierTag,
accountViewModel.account,
@@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.number_followers
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsFollowerCount
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAssertionsFollowerCount
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel
@@ -38,7 +38,7 @@ fun FollowersTabHeader(
followersFeedViewModel: UserProfileFollowersUserFeedViewModel,
accountViewModel: AccountViewModel,
) {
val followerCount by observeUserContactCardsFollowerCount(baseUser, accountViewModel)
val followerCount by observeUserAssertionsFollowerCount(baseUser, accountViewModel)
if (followerCount == "--") {
FollowersTabHeaderLocal(followersFeedViewModel, accountViewModel)
@@ -73,8 +73,8 @@ fun UserNicknameCard(
WatchAndLoadMyEmojiList(accountViewModel)
EditNicknameDialog(
user = baseUser,
contactCards = accountViewModel.account.contactCards,
onSave = { petName, summary -> accountViewModel.updateContactCardPetName(baseUser, petName, summary) },
userAssertions = accountViewModel.account.userAssertions,
onSave = { petName, summary -> accountViewModel.updateUserAssertionPetName(baseUser, petName, summary) },
onDismiss = { isEditDialogOpen.value = false },
)
}

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