remove nostr view model

This commit is contained in:
2026-07-12 08:25:10 +07:00
parent 8194f5eccb
commit 77ff35fbf7
11 changed files with 75 additions and 75 deletions

View File

@@ -11,7 +11,7 @@ import kotlinx.datetime.toLocalDateTime
import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.Timestamp
import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.viewmodel.NostrViewModel
import su.reya.coop.viewmodel.ProfileCache
import kotlin.time.Clock
import kotlin.time.Instant
@@ -72,7 +72,7 @@ data class RoomUiState(
)
fun Room.uiStateFlow(
nostrViewModel: NostrViewModel,
profileCache: ProfileCache,
currentUser: PublicKey? = null
): Flow<RoomUiState> {
val displayMembers = if (isGroup()) members.take(2) else members.take(1)
@@ -81,7 +81,7 @@ fun Room.uiStateFlow(
return flowOf(RoomUiState(name = subject.sanitizeName(), isGroup = isGroup()))
}
return combine(displayMembers.map { nostrViewModel.getMetadata(it) }) { profiles ->
return combine(displayMembers.map { profileCache.getMetadata(it) }) { profiles ->
val names = profiles.mapIndexed { i, profile ->
profile?.name?.sanitizeName() ?: displayMembers[i].short()
}

View File

@@ -1,7 +1,8 @@
package su.reya.coop.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -16,33 +17,36 @@ import su.reya.coop.Profile
import su.reya.coop.nostr.Nostr
import kotlin.time.Duration.Companion.milliseconds
class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by createErrorHost() {
/**
* Application-level singleton cache for Nostr user profiles.
*
* Replaces [NostrViewModel] as a non-ViewModel component with its own lifecycle scope.
* This is appropriate because profile caching is not screen-specific it's a shared
* concern used by every screen that displays user metadata.
*
* Long-running tasks ([runObserver], [runMetadataBatching]) run in a dedicated
* [CoroutineScope] that outlives any individual screen, ensuring continuous operation
* regardless of navigation.
*/
class ProfileCache(
private val nostr: Nostr,
) : ErrorHost by createErrorHost() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val profilesMutex = Mutex()
private val profiles = mutableMapOf<PublicKey, MutableStateFlow<Profile?>>()
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
private val seenPublicKeys = mutableSetOf<PublicKey>()
init {
// Launch continuous background observers
viewModelScope.launch { runObserver() }
viewModelScope.launch { runMetadataBatching() }
// Automatically reconnect bootstrap relays
reconnect()
// Get all local stored metadata
getCacheMetadata()
}
private fun reconnect() {
viewModelScope.launch {
scope.launch { runObserver() }
scope.launch { runMetadataBatching() }
scope.launch {
nostr.waitUntilInitialized()
nostr.reconnect()
loadCacheMetadata()
}
}
private suspend fun runObserver() = coroutineScope {
// Observe metadata updates
launch {
nostr.profiles.metadataUpdates.collect { (pubkey, metadata) ->
updateMetadata(pubkey, Profile(pubkey, metadata))
@@ -57,7 +61,6 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
val firstKey = metadataRequestChannel.receive()
val batch = mutableSetOf(firstKey)
// Collect up to 10 keys that arrive within 500ms
while (batch.size < 10) {
val nextKey =
withTimeoutOrNull(500.milliseconds) { metadataRequestChannel.receive() }
@@ -69,18 +72,14 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
}
}
private fun getCacheMetadata() {
viewModelScope.launch {
// Wait until the client is ready
nostr.waitUntilInitialized()
val cache = nostr.profiles.getAllCacheMetadata()
private suspend fun loadCacheMetadata() {
val cache = nostr.profiles.getAllCacheMetadata()
profilesMutex.withLock {
cache.forEach { (pubkey, metadata) ->
val profile = Profile(pubkey, metadata)
profiles.getOrPut(pubkey) { MutableStateFlow(null) }.value = profile
seenPublicKeys.add(pubkey)
}
profilesMutex.withLock {
cache.forEach { (pubkey, metadata) ->
val profile = Profile(pubkey, metadata)
profiles.getOrPut(pubkey) { MutableStateFlow(null) }.value = profile
seenPublicKeys.add(pubkey)
}
}
}
@@ -97,11 +96,13 @@ class NostrViewModel(private val nostr: Nostr) : ViewModel(), ErrorHost by creat
}
}
/**
* Returns a [StateFlow] for the profile of the given [pubkey].
* Triggers a metadata fetch if the profile is not yet cached.
*/
fun getMetadata(pubkey: PublicKey): StateFlow<Profile?> {
val flow = profiles.getOrPut(pubkey) { MutableStateFlow(null) }
if (flow.value == null) requestMetadata(pubkey)
return flow.asStateFlow()
}
}