fixes imports

This commit is contained in:
nrobi144
2026-01-01 07:55:31 +02:00
parent 3750c7e26e
commit 4bc7e86448
14 changed files with 125 additions and 70 deletions
@@ -108,6 +108,7 @@ import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
import com.vitorpamplona.quartz.utils.DualCase
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.image
@@ -237,14 +238,21 @@ class Account(
val cache: LocalCache,
val client: INostrClient,
val scope: CoroutineScope,
) {
) : IAccount {
private var userProfileCache: User? = null
fun userProfile(): User = userProfileCache ?: cache.getOrCreateUser(signer.pubKey).also { userProfileCache = it }
override fun userProfile(): User = userProfileCache ?: cache.getOrCreateUser(signer.pubKey).also { userProfileCache = it }
// IAccount interface properties
override val pubKey: String get() = signer.pubKey
override val showSensitiveContent: Boolean? get() = hiddenUsers.flow.value.showSensitiveContent
override val hiddenWordsCase: List<DualCase> get() = hiddenUsers.flow.value.hiddenWordsCase
override val hiddenUsersHashCodes: Set<Int> get() = hiddenUsers.flow.value.hiddenUsersHashCodes
override val spammersHashCodes: Set<Int> get() = hiddenUsers.flow.value.spammersHashCodes
val userMetadata = UserMetadataState(signer, cache, scope, settings)
val nip47SignerState = NwcSignerState(signer, nwcFilterAssembler, cache, scope, settings)
override val nip47SignerState = NwcSignerState(signer, nwcFilterAssembler, cache, scope, settings)
val nip65RelayList = Nip65RelayListState(signer, cache, scope, settings)
val localRelayList = LocalRelayListState(signer, cache, scope, settings)
@@ -340,7 +348,7 @@ class Account(
val allFollows = MergedFollowListsState(kind3FollowList, peopleLists, followLists, hashtagList, geohashList, communityList, scope)
val privateDMDecryptionCache = PrivateDMCache(signer)
val privateZapsDecryptionCache = PrivateZapCache(signer)
override val privateZapsDecryptionCache = PrivateZapCache(signer)
val draftsDecryptionCache = DraftEventCache(signer)
val chatroomList = cache.getOrCreateChatroomList(signer.pubKey)
@@ -423,7 +431,7 @@ class Account(
val liveNotificationFollowListsPerRelay = OutboxLoaderState(liveNotificationFollowLists, cache, scope).flow
fun isWriteable(): Boolean = settings.isWriteable()
override fun isWriteable(): Boolean = settings.isWriteable()
suspend fun updateWarnReports(warnReports: Boolean): Boolean {
if (settings.updateWarnReports(warnReports)) {
@@ -22,9 +22,9 @@ package com.vitorpamplona.amethyst.model
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.NotesGatherer
import com.vitorpamplona.amethyst.commons.model.User
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.dal.ListChange
@@ -24,9 +24,7 @@ import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.User
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.commons.model.User
import com.vitorpamplona.amethyst.commons.model.UserState
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.model.nip47WalletConnect
import com.vitorpamplona.amethyst.commons.model.INwcSignerState
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
@@ -66,7 +67,7 @@ class NwcSignerState(
val cache: LocalCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
) : INwcSignerState {
/**
* Derives a NIP-47 signer from the zap payment request in settings.
* If there's no valid configuration, it defaults to the main signer.
@@ -112,7 +113,7 @@ class NwcSignerState(
fun hasWalletConnectSetup(): Boolean = settings.zapPaymentRequest.value != null
fun isNIP47Author(pubkeyHex: String?): Boolean = nip47Signer.value.pubKey == pubkeyHex
override fun isNIP47Author(pubkeyHex: String?): Boolean = nip47Signer.value.pubKey == pubkeyHex
/**
* Decrypts a NIP-47 payment request using the current signer.
@@ -120,7 +121,7 @@ class NwcSignerState(
* @param nwcRequest the NIP-47 payment request event to decrypt
* @return the decrypted request or null if not set up or decryption fails
*/
suspend fun decryptRequest(nwcRequest: LnZapPaymentRequestEvent): Request? {
override suspend fun decryptRequest(nwcRequest: LnZapPaymentRequestEvent): Request? {
if (!hasWalletConnectSetup()) return null
return zapPaymentRequestDecryptionCache.value.decryptRequest(nwcRequest)
}
@@ -131,7 +132,7 @@ class NwcSignerState(
* @param nwsResponse the NIP-47 payment response event to decrypt
* @return the decrypted response or null if not set up or decryption fails
*/
suspend fun decryptResponse(nwsResponse: LnZapPaymentResponseEvent): Response? {
override suspend fun decryptResponse(nwsResponse: LnZapPaymentResponseEvent): Response? {
if (!hasWalletConnectSetup()) return null
return zapPaymentResponseDecryptionCache.value.decryptResponse(nwsResponse)
}
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.model.nip51Lists
import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.User
@@ -52,33 +53,25 @@ class HiddenUsersState(
) {
var transientHiddenUsers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf())
@Immutable
class LiveHiddenUsers(
val hiddenUsers: Set<String>,
val spammers: Set<String>,
val hiddenWords: Set<String>,
val showSensitiveContent: Boolean?,
) {
// speeds up isHidden calculations
val hiddenUsersHashCodes = hiddenUsers.mapTo(HashSet()) { it.hashCode() }
val spammersHashCodes = spammers.mapTo(HashSet()) { it.hashCode() }
val hiddenWordsCase = hiddenWords.map { DualCase(it.lowercase(), it.uppercase()) }
fun isUserHidden(userHex: HexKey) = hiddenUsers.contains(userHex) || spammers.contains(userHex)
}
suspend fun assembleLiveHiddenUsers(
blockList: List<MuteTag>,
muteList: List<MuteTag>,
transientHiddenUsers: Set<String>,
showSensitiveContent: Boolean?,
): LiveHiddenUsers =
LiveHiddenUsers(
hiddenUsers = blockList.mapNotNullTo(mutableSetOf()) { if (it is UserTag) it.pubKey else null } + muteList.mapNotNull { if (it is UserTag) it.pubKey else null },
hiddenWords = blockList.mapNotNullTo(mutableSetOf()) { if (it is WordTag) it.word else null } + muteList.mapNotNull { if (it is WordTag) it.word else null },
spammers = transientHiddenUsers,
): LiveHiddenUsers {
val hiddenUsers = blockList.mapNotNullTo(mutableSetOf()) { if (it is UserTag) it.pubKey else null } + muteList.mapNotNull { if (it is UserTag) it.pubKey else null }
val hiddenWords = blockList.mapNotNullTo(mutableSetOf()) { if (it is WordTag) it.word else null } + muteList.mapNotNull { if (it is WordTag) it.word else null }
return LiveHiddenUsers(
showSensitiveContent = showSensitiveContent,
hiddenWordsCase = hiddenWords.map { DualCase(it.lowercase(), it.uppercase()) },
hiddenUsersHashCodes = hiddenUsers.mapTo(HashSet()) { it.hashCode() },
spammersHashCodes = transientHiddenUsers.mapTo(HashSet()) { it.hashCode() },
hiddenUsers = hiddenUsers,
spammers = transientHiddenUsers,
hiddenWords = hiddenWords,
)
}
val flow: StateFlow<LiveHiddenUsers> =
combineTransform(
@@ -102,7 +95,15 @@ class HiddenUsersState(
.stateIn(
scope,
SharingStarted.Eagerly,
LiveHiddenUsers(emptySet(), emptySet(), emptySet(), null),
LiveHiddenUsers(
showSensitiveContent = null,
hiddenWordsCase = emptyList(),
hiddenUsersHashCodes = emptySet(),
spammersHashCodes = emptySet(),
hiddenUsers = emptySet(),
spammers = emptySet(),
hiddenWords = emptySet(),
),
)
fun resetTransientUsers() {
@@ -24,9 +24,7 @@ import com.vitorpamplona.amethyst.commons.model.AddressableNote
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.User
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.commons.model.User
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.model.trustedAssertions
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.model.NoteState
import com.vitorpamplona.amethyst.commons.model.trustedAssertions.TrustProviderListState as ITrustProviderListState
import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.TrustProviderListEvent
@@ -49,7 +50,7 @@ class TrustProviderListState(
val decryptionCache: TrustProviderListDecryptionCache,
val scope: CoroutineScope,
val settings: AccountSettings,
) {
) : ITrustProviderListState {
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
val trustProviderListNote = cache.getOrCreateAddressableNote(getTrustProviderListAddress())
@@ -79,7 +80,7 @@ class TrustProviderListState(
)
@OptIn(ExperimentalCoroutinesApi::class)
val liveUserRankProvider: StateFlow<ServiceProviderTag?> =
override val liveUserRankProvider: StateFlow<ServiceProviderTag?> =
liveTrustProviderList
.map {
it.firstOrNull { it.service == ProviderTypes.rank }
@@ -97,7 +98,7 @@ class TrustProviderListState(
)
@OptIn(ExperimentalCoroutinesApi::class)
val liveUserFollowerCount: StateFlow<ServiceProviderTag?> =
override val liveUserFollowerCount: StateFlow<ServiceProviderTag?> =
liveTrustProviderList
.map { tagList ->
tagList.firstOrNull { it.service == ProviderTypes.followerCount }
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavFilter
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsByOutboxTopNavFilter
@@ -36,7 +36,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
class FilterByListParams(
val isHiddenList: Boolean,
val followLists: IFeedTopNavFilter?,
val hiddenLists: HiddenUsersState.LiveHiddenUsers,
val hiddenLists: LiveHiddenUsers,
val now: Long = TimeUtils.oneMinuteFromNow(),
) {
fun isNotHidden(userHex: String) = !(hiddenLists.hiddenUsers.contains(userHex) || hiddenLists.spammers.contains(userHex))
@@ -87,7 +87,7 @@ class FilterByListParams(
fun create(
followLists: IFeedTopNavFilter?,
hiddenUsers: HiddenUsersState.LiveHiddenUsers,
hiddenUsers: LiveHiddenUsers,
): FilterByListParams =
FilterByListParams(
isHiddenList = followLists is MutedAuthorsByOutboxTopNavFilter || followLists is MutedAuthorsByProxyTopNavFilter,
@@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.model.UiSettingsFlow
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
import com.vitorpamplona.amethyst.model.observables.CreatedAtComparator
@@ -341,7 +342,7 @@ class AccountViewModel(
fun isNoteAcceptable(
note: Note,
accountChoices: HiddenUsersState.LiveHiddenUsers,
accountChoices: LiveHiddenUsers,
followUsers: Set<HexKey>,
): NoteComposeReportState {
checkNotInMainThread()
@@ -24,8 +24,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Request
import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.IPrivateZapsDecryptionCache
import com.vitorpamplona.quartz.utils.DualCase
/**
@@ -40,16 +39,6 @@ interface INwcSignerState {
fun isNIP47Author(pubKey: String?): Boolean
}
/**
* Interface for private zap decryption cache.
* Used by Note.kt for checking private zap status.
*/
interface IPrivateZapsDecryptionCache {
fun cachedPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent?
suspend fun decryptPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent?
}
/**
* Hidden content settings for filtering notes.
* Used by Note.isHiddenFor() to check if content should be hidden.
@@ -59,7 +48,13 @@ data class LiveHiddenUsers(
val hiddenWordsCase: List<DualCase>,
val hiddenUsersHashCodes: Set<Int>,
val spammersHashCodes: Set<Int>,
)
// Raw sets for amethyst-specific usage
val hiddenUsers: Set<String> = emptySet(),
val spammers: Set<String> = emptySet(),
val hiddenWords: Set<String> = emptySet(),
) {
fun isUserHidden(userHex: String) = hiddenUsers.contains(userHex) || spammers.contains(userHex)
}
/**
* Interface for account operations needed by Note.kt.
@@ -0,0 +1,33 @@
/**
* 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.commons.model.trustedAssertions
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.tags.ServiceProviderTag
import kotlinx.coroutines.flow.StateFlow
/**
* Interface for trust provider list state.
* Used by UserCardsCache for accessing user rank and follower count providers.
*/
interface TrustProviderListState {
val liveUserRankProvider: StateFlow<ServiceProviderTag?>
val liveUserFollowerCount: StateFlow<ServiceProviderTag?>
}
@@ -136,15 +136,3 @@ class UserCardsCache : UserDependencies {
}
}.flowOn(Dispatchers.IO)
}
// Placeholder for TrustProviderListState - will be properly extracted later
// For now, this allows UserCardsCache to compile
interface TrustProviderListState {
val liveUserRankProvider: kotlinx.coroutines.flow.StateFlow<ServiceProviderTag?>
val liveUserFollowerCount: kotlinx.coroutines.flow.StateFlow<ServiceProviderTag?>
}
// Placeholder for ServiceProviderTag
interface ServiceProviderTag {
val pubkey: String
}
@@ -0,0 +1,31 @@
/**
* 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.quartz.nip57Zaps
/**
* Interface for private zap decryption cache.
* Used by Note.kt for checking private zap status.
*/
interface IPrivateZapsDecryptionCache {
fun cachedPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent?
suspend fun decryptPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent?
}
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.caches.DecryptCache
class PrivateZapCache(
signer: NostrSigner,
) {
) : IPrivateZapsDecryptionCache {
private val decryptionCache =
object : LruCache<LnZapRequestEvent, PrivateZapDecryptCache>(1000) {
override fun create(key: LnZapRequestEvent): PrivateZapDecryptCache? {
@@ -43,9 +43,9 @@ class PrivateZapCache(
decryptionCache.remove(event)
}
fun cachedPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent? = decryptionCache[event]?.cached()
override fun cachedPrivateZap(event: LnZapRequestEvent): LnZapPrivateEvent? = decryptionCache[event]?.cached()
suspend fun decryptPrivateZap(event: LnZapRequestEvent) = decryptionCache[event]?.decrypt(event)
override suspend fun decryptPrivateZap(event: LnZapRequestEvent) = decryptionCache[event]?.decrypt(event)
}
class PrivateZapDecryptCache(