forked from reya/coop-mobile
add get chat rooms
This commit is contained in:
@@ -3,17 +3,20 @@ package su.reya.coop
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import rust.nostr.sdk.Keys
|
||||
import rust.nostr.sdk.Metadata
|
||||
import rust.nostr.sdk.NostrConnect
|
||||
import rust.nostr.sdk.NostrConnectUri
|
||||
import rust.nostr.sdk.PublicKey
|
||||
import su.reya.coop.storage.SecretStorage
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration
|
||||
|
||||
class NostrViewModel(
|
||||
@@ -26,11 +29,61 @@ class NostrViewModel(
|
||||
private val _isCreating = MutableStateFlow(false)
|
||||
val isCreating = _isCreating.asStateFlow()
|
||||
|
||||
// User metadata store
|
||||
private val _chatRooms = MutableStateFlow<Set<Room>>(emptySet())
|
||||
val chatRooms = _chatRooms.asStateFlow()
|
||||
|
||||
private val _metadataStore = mutableMapOf<PublicKey, MutableStateFlow<Metadata?>>()
|
||||
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
|
||||
private val seenPublicKeys = mutableSetOf<PublicKey>()
|
||||
|
||||
init {
|
||||
startMetadataBatchProcessor()
|
||||
}
|
||||
|
||||
private fun startMetadataBatchProcessor() {
|
||||
viewModelScope.launch {
|
||||
val batch = mutableSetOf<PublicKey>()
|
||||
val timeout = 500L // 500ms timeout for batching
|
||||
|
||||
while (true) {
|
||||
val firstKey = metadataRequestChannel.receive()
|
||||
batch.add(firstKey)
|
||||
val lastFlushTime = Clock.System.now().toEpochMilliseconds()
|
||||
|
||||
while (batch.isNotEmpty()) {
|
||||
val nextKey = withTimeoutOrNull(timeout) {
|
||||
metadataRequestChannel.receive()
|
||||
}
|
||||
|
||||
if (nextKey != null) {
|
||||
batch.add(nextKey)
|
||||
}
|
||||
|
||||
val now = Clock.System.now().toEpochMilliseconds()
|
||||
if (batch.size >= 20 || (now - lastFlushTime) >= timeout || nextKey == null) {
|
||||
val keysToRequest = batch.toList()
|
||||
batch.clear()
|
||||
nostr.fetchMetadataBatch(keysToRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requestMetadata(pubkey: PublicKey) {
|
||||
if (seenPublicKeys.add(pubkey)) {
|
||||
viewModelScope.launch {
|
||||
metadataRequestChannel.send(pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMetadata(pubkey: PublicKey): StateFlow<Metadata?> {
|
||||
return _metadataStore.getOrPut(pubkey) { MutableStateFlow(null) }.asStateFlow()
|
||||
val flow = _metadataStore.getOrPut(pubkey) { MutableStateFlow(null) }
|
||||
if (flow.value == null) {
|
||||
requestMetadata(pubkey)
|
||||
}
|
||||
return flow.asStateFlow()
|
||||
}
|
||||
|
||||
fun updateMetadata(pubkey: PublicKey, metadata: Metadata) {
|
||||
@@ -42,37 +95,10 @@ class NostrViewModel(
|
||||
try {
|
||||
// Initialize nostr client
|
||||
nostr.init(dbPath)
|
||||
|
||||
// Connect to bootstrap relays
|
||||
nostr.connect()
|
||||
|
||||
// Get user's signer secret
|
||||
val secret = secretStore.get("user_signer")
|
||||
|
||||
// If no secret is found, show onboarding screen
|
||||
if (secret == null) {
|
||||
_hasSecret.value = false
|
||||
return@launch
|
||||
}
|
||||
_hasSecret.value = true
|
||||
|
||||
// Handle different signer types
|
||||
if (secret.startsWith("nsec1")) {
|
||||
val keys = Keys.parse(secret)
|
||||
nostr.setKeySigner(keys)
|
||||
} else if (secret.startsWith("bunker://")) {
|
||||
val appKeys = getOrInitAppKeys()
|
||||
val bunker = NostrConnectUri.parse(secret)
|
||||
val remote = NostrConnect(
|
||||
uri = bunker,
|
||||
appKeys = appKeys,
|
||||
timeout = Duration.parse("5"),
|
||||
opts = null
|
||||
)
|
||||
nostr.setRemoteSigner(remote)
|
||||
} else {
|
||||
throw IllegalArgumentException("Invalid secret format: $secret")
|
||||
}
|
||||
// Get user's secret
|
||||
getUserSecret()
|
||||
} catch (e: Exception) {
|
||||
println("Failed to connect: ${e.message}")
|
||||
}
|
||||
@@ -87,6 +113,36 @@ class NostrViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUserSecret() {
|
||||
// Get user's signer secret
|
||||
val secret = secretStore.get("user_signer")
|
||||
|
||||
// If no secret is found, show onboarding screen
|
||||
if (secret == null) {
|
||||
_hasSecret.value = false
|
||||
return
|
||||
}
|
||||
_hasSecret.value = true
|
||||
|
||||
// Handle different signer types
|
||||
if (secret.startsWith("nsec1")) {
|
||||
val keys = Keys.parse(secret)
|
||||
nostr.setKeySigner(keys)
|
||||
} else if (secret.startsWith("bunker://")) {
|
||||
val appKeys = getOrInitAppKeys()
|
||||
val bunker = NostrConnectUri.parse(secret)
|
||||
val remote = NostrConnect(
|
||||
uri = bunker,
|
||||
appKeys = appKeys,
|
||||
timeout = Duration.parse("5"),
|
||||
opts = null
|
||||
)
|
||||
nostr.setRemoteSigner(remote)
|
||||
} else {
|
||||
throw IllegalArgumentException("Invalid secret format: $secret")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getOrInitAppKeys(): Keys {
|
||||
val secret = secretStore.get("app_keys")
|
||||
|
||||
@@ -123,6 +179,16 @@ class NostrViewModel(
|
||||
// TODO: Implement import
|
||||
}
|
||||
|
||||
fun getChatRooms() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_chatRooms.value = nostr.getChatRooms() ?: emptySet()
|
||||
} catch (e: Exception) {
|
||||
println("Failed to get chat rooms: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// Ensure all relays are disconnect
|
||||
|
||||
Reference in New Issue
Block a user