refactor(chat): fold LocalCache Dao defaults into the Dao interface

Replace the standalone LocalCacheDao object with default method
implementations on the Dao interface itself, backed by LocalCache. The
NewMessageTagger `dao` parameter now defaults to a bare `object : Dao {}`,
so callers with no AccountViewModel (model-layer sends, the notification
receiver) just omit it, while UI callers keep passing their AccountViewModel
(which resolves to the same LocalCache calls).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RcWSCNMTVcvKMXo7xA2hue
This commit is contained in:
Claude
2026-07-27 19:57:09 +00:00
parent d305c03e54
commit b4da12300e
4 changed files with 10 additions and 54 deletions
@@ -158,7 +158,6 @@ import com.vitorpamplona.amethyst.service.relayClient.chatDelivery.ChatDeliveryT
import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model.NotifyRequestsCache
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
import com.vitorpamplona.amethyst.service.uploads.FileHeader
import com.vitorpamplona.amethyst.ui.actions.LocalCacheDao
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.screen.loggedIn.EventProcessor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.concordChannelLastReadRoute
@@ -2432,7 +2431,7 @@ class Account(
// quick reply is notified (`p`) and their reference resolves. The reply-parent author is
// already tagged by each builder below, so drop it from the body mentions to avoid a
// duplicate `p`.
val tagger = NewMessageTagger(text, emptyList(), emptyList(), LocalCacheDao)
val tagger = NewMessageTagger(text)
tagger.run()
val mentions = tagger.pTags?.mapNotNull { it.pubkeyHex.takeIf { pk -> pk != rootEvent.pubKey } }.orEmpty()
val finalText = appendMediaUrls(tagger.message, imetas)
@@ -30,7 +30,6 @@ import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.ui.actions.LocalCacheDao
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
@@ -207,7 +206,7 @@ class NotificationReplyReceiver : BroadcastReceiver() {
// (`p`) and linkable — the same enrichment the in-app composers do. The comment builders
// already tag the reply-parent author, so drop it from the body mentions to avoid a
// duplicate `p` (kind-1 doesn't auto-tag the parent, so nothing is lost there).
val tagger = NewMessageTagger(replyText, emptyList(), emptyList(), LocalCacheDao)
val tagger = NewMessageTagger(replyText)
tagger.run()
val mentions = tagger.pTags?.mapNotNull { pt -> pt.pubkeyHex.takeIf { it != targetEvent.pubKey } }.orEmpty()
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.actions
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.core.Address
import com.vitorpamplona.quartz.nip01Core.core.HexKey
/**
* A [Dao] backed directly by [LocalCache], for resolving `@`/`nostr:` mentions with
* [NewMessageTagger] from paths that have no `AccountViewModel` to hand — the model-layer
* send helpers ([com.vitorpamplona.amethyst.model.Account]) and the background notification
* quick-reply receiver. Mirrors `AccountViewModel`'s own Dao delegation, which is just this.
*
* Resolving an npub/nprofile to its pubkey needs no populated cache (the key is in the
* bech32 itself), so this works even in a cold `:napplet`-free receiver process where
* LocalCache hasn't been rehydrated; only note-author lookups degrade there, which is fine.
*/
object LocalCacheDao : Dao {
override suspend fun getOrCreateUser(hex: HexKey): User = LocalCache.getOrCreateUser(hex)
override suspend fun getOrCreateNote(hex: HexKey): Note = LocalCache.getOrCreateNote(hex)
override fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address)
}
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.actions
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.core.Address
@@ -44,7 +45,10 @@ class NewMessageTagger(
var message: String,
var pTags: List<User>? = null,
var eTags: List<Note>? = null,
var dao: Dao,
// Defaults to a Dao whose methods read straight from LocalCache (see the interface below),
// so callers with no AccountViewModel — model-layer sends, background receivers — can just
// omit it. UI callers still pass their AccountViewModel, which resolves to the same thing.
var dao: Dao = object : Dao {},
) {
val directMentions = mutableSetOf<HexKey>()
val directMentionsNotes = mutableSetOf<Note>()
@@ -260,9 +264,9 @@ class NewMessageTagger(
}
interface Dao {
suspend fun getOrCreateUser(hex: String): User
suspend fun getOrCreateUser(hex: HexKey): User = LocalCache.getOrCreateUser(hex)
suspend fun getOrCreateNote(hex: String): Note
suspend fun getOrCreateNote(hex: HexKey): Note = LocalCache.getOrCreateNote(hex)
fun getOrCreateAddressableNote(address: Address): AddressableNote?
fun getOrCreateAddressableNote(address: Address): AddressableNote? = LocalCache.getOrCreateAddressableNote(address)
}