.
This commit is contained in:
@@ -34,6 +34,7 @@ import androidx.navigation3.runtime.entryProvider
|
|||||||
import androidx.navigation3.runtime.rememberNavBackStack
|
import androidx.navigation3.runtime.rememberNavBackStack
|
||||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||||
import androidx.navigation3.ui.NavDisplay
|
import androidx.navigation3.ui.NavDisplay
|
||||||
|
import su.reya.coop.repository.ErrorRepository
|
||||||
import su.reya.coop.screens.ChatScreen
|
import su.reya.coop.screens.ChatScreen
|
||||||
import su.reya.coop.screens.ContactListScreen
|
import su.reya.coop.screens.ContactListScreen
|
||||||
import su.reya.coop.screens.HomeScreen
|
import su.reya.coop.screens.HomeScreen
|
||||||
@@ -117,7 +118,7 @@ fun App(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
ErrorManager.errors.collect { message ->
|
ErrorRepository.errors.collect { message ->
|
||||||
snackbarHostState.showSnackbar(message)
|
snackbarHostState.showSnackbar(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package su.reya.coop
|
package su.reya.coop.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
|
||||||
object ErrorManager {
|
object ErrorRepository {
|
||||||
private val _errors = Channel<String>(Channel.BUFFERED)
|
private val _errors = Channel<String>(Channel.BUFFERED)
|
||||||
val errors = _errors.receiveAsFlow()
|
val errors = _errors.receiveAsFlow()
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package su.reya.coop.repository
|
||||||
|
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import rust.nostr.sdk.AsyncNostrSigner
|
||||||
|
import su.reya.coop.blossom.BlossomClient
|
||||||
|
|
||||||
|
class MediaRepository {
|
||||||
|
private val httpClient = HttpClient {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json(Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
prettyPrint = true
|
||||||
|
isLenient = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun blossomUpload(
|
||||||
|
signer: AsyncNostrSigner,
|
||||||
|
file: ByteArray,
|
||||||
|
contentType: String? = "image/jpeg"
|
||||||
|
): String? {
|
||||||
|
return try {
|
||||||
|
val blossom = BlossomClient(url = "https://blossom.band", client = httpClient)
|
||||||
|
val descriptor = blossom.upload(
|
||||||
|
file = file,
|
||||||
|
contentType = contentType,
|
||||||
|
signer = signer,
|
||||||
|
)
|
||||||
|
descriptor?.url
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Upload failed: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import su.reya.coop.nostr.ExternalSignerHandler
|
|||||||
import su.reya.coop.nostr.ExternalSignerProxy
|
import su.reya.coop.nostr.ExternalSignerProxy
|
||||||
import su.reya.coop.nostr.Nostr
|
import su.reya.coop.nostr.Nostr
|
||||||
import su.reya.coop.nostr.SignerPermissions
|
import su.reya.coop.nostr.SignerPermissions
|
||||||
|
import su.reya.coop.repository.MediaRepository
|
||||||
import su.reya.coop.storage.SecretStorage
|
import su.reya.coop.storage.SecretStorage
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@@ -29,6 +30,8 @@ class AuthViewModel(
|
|||||||
private val secretStore: SecretStorage,
|
private val secretStore: SecretStorage,
|
||||||
private val externalSignerHandler: ExternalSignerHandler? = null,
|
private val externalSignerHandler: ExternalSignerHandler? = null,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
private val mediaRepository = MediaRepository()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val KEY_USER_SIGNER = "user_signer"
|
private const val KEY_USER_SIGNER = "user_signer"
|
||||||
private const val KEY_APP_KEYS = "app_keys"
|
private const val KEY_APP_KEYS = "app_keys"
|
||||||
@@ -236,8 +239,9 @@ class AuthViewModel(
|
|||||||
val secret = keys.secretKey().toBech32()
|
val secret = keys.secretKey().toBech32()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val avatarUrl =
|
val avatarUrl = picture?.let {
|
||||||
picture?.let { blossomUpload(nostr.signer.get(), it, contentType ?: "image/jpeg") }
|
mediaRepository.blossomUpload(nostr.signer.get(), it, contentType ?: "image/jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
// Create identity
|
// Create identity
|
||||||
nostr.profiles.createIdentity(keys = keys, name = name, bio = bio, picture = avatarUrl)
|
nostr.profiles.createIdentity(keys = keys, name = name, bio = bio, picture = avatarUrl)
|
||||||
|
|||||||
@@ -1,47 +1,10 @@
|
|||||||
package su.reya.coop.viewmodel
|
package su.reya.coop.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import io.ktor.client.HttpClient
|
import su.reya.coop.repository.ErrorRepository
|
||||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import rust.nostr.sdk.AsyncNostrSigner
|
|
||||||
import su.reya.coop.ErrorManager
|
|
||||||
import su.reya.coop.blossom.BlossomClient
|
|
||||||
|
|
||||||
abstract class BaseViewModel : ViewModel() {
|
abstract class BaseViewModel : ViewModel() {
|
||||||
protected val httpClient by lazy {
|
|
||||||
HttpClient {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
prettyPrint = true
|
|
||||||
isLenient = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun showError(message: String) {
|
protected fun showError(message: String) {
|
||||||
ErrorManager.showError(message)
|
ErrorRepository.showError(message)
|
||||||
}
|
|
||||||
|
|
||||||
protected suspend fun blossomUpload(
|
|
||||||
signer: AsyncNostrSigner,
|
|
||||||
file: ByteArray,
|
|
||||||
contentType: String? = "image/jpeg"
|
|
||||||
): String? {
|
|
||||||
return try {
|
|
||||||
val blossom = BlossomClient(url = "https://blossom.band", client = httpClient)
|
|
||||||
val descriptor = blossom.upload(
|
|
||||||
file = file,
|
|
||||||
contentType = contentType,
|
|
||||||
signer = signer,
|
|
||||||
)
|
|
||||||
descriptor?.url
|
|
||||||
} catch (e: Exception) {
|
|
||||||
showError("Upload failed: ${e.message}")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
@@ -20,19 +20,27 @@ import rust.nostr.sdk.Tag
|
|||||||
import rust.nostr.sdk.UnsignedEvent
|
import rust.nostr.sdk.UnsignedEvent
|
||||||
import su.reya.coop.Room
|
import su.reya.coop.Room
|
||||||
import su.reya.coop.nostr.Nostr
|
import su.reya.coop.nostr.Nostr
|
||||||
|
import su.reya.coop.repository.MediaRepository
|
||||||
import su.reya.coop.roomId
|
import su.reya.coop.roomId
|
||||||
|
|
||||||
data class ChatState(
|
data class ChatState(
|
||||||
|
val rooms: Set<Room> = emptySet(),
|
||||||
val isSyncing: Boolean = false,
|
val isSyncing: Boolean = false,
|
||||||
val isPartialProcessedGiftWrap: Boolean = false,
|
val isPartialProcessedGiftWrap: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
||||||
private val _state = MutableStateFlow(ChatState())
|
private val mediaRepository = MediaRepository()
|
||||||
val state = _state.asStateFlow()
|
|
||||||
|
|
||||||
private val _chatRooms = MutableStateFlow<Set<Room>>(emptySet())
|
private val _state = MutableStateFlow(ChatState())
|
||||||
val chatRooms = _chatRooms.asStateFlow()
|
val state = combine(
|
||||||
|
_state,
|
||||||
|
nostr.messages.messageSyncState
|
||||||
|
) { local, state -> local.copy(isSyncing = state.isSyncing) }.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5000),
|
||||||
|
ChatState()
|
||||||
|
)
|
||||||
|
|
||||||
private val _newEvents = MutableSharedFlow<UnsignedEvent>(extraBufferCapacity = 100)
|
private val _newEvents = MutableSharedFlow<UnsignedEvent>(extraBufferCapacity = 100)
|
||||||
val newEvents = _newEvents.asSharedFlow()
|
val newEvents = _newEvents.asSharedFlow()
|
||||||
@@ -40,10 +48,13 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
private val _sentReports = MutableSharedFlow<Map<EventId, List<RelayUrl>>>()
|
private val _sentReports = MutableSharedFlow<Map<EventId, List<RelayUrl>>>()
|
||||||
val sentReport = _sentReports.asSharedFlow()
|
val sentReport = _sentReports.asSharedFlow()
|
||||||
|
|
||||||
val isSyncing = nostr.messages.messageSyncState.map { it.isSyncing }
|
val chatRooms = state.map { it.rooms }
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptySet())
|
||||||
|
|
||||||
|
val isSyncing = state.map { it.isSyncing }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
val isPartialProcessedGiftWrap = _state.map { it.isPartialProcessedGiftWrap }
|
val isPartialProcessedGiftWrap = state.map { it.isPartialProcessedGiftWrap }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -52,14 +63,14 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
|
|
||||||
// Observe message sync progress
|
// Observe message sync progress
|
||||||
launch {
|
launch {
|
||||||
nostr.messages.messageSyncState.collect { state ->
|
nostr.messages.messageSyncState.collect { syncState ->
|
||||||
// When at least some messages are processed, allow UI to show the list
|
// When at least some messages are processed, allow UI to show the list
|
||||||
if (state.processedCount > 0) {
|
if (syncState.processedCount > 0) {
|
||||||
_state.update { it.copy(isPartialProcessedGiftWrap = true) }
|
_state.update { it.copy(isPartialProcessedGiftWrap = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh UI every 10 messages OR when sync is fully done
|
// Refresh UI every 10 messages OR when sync is fully done
|
||||||
if (state.processedCount % 10 == 0 || !state.isSyncing) {
|
if (syncState.processedCount % 10 == 0 || !syncState.isSyncing) {
|
||||||
refreshChatRooms()
|
refreshChatRooms()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,12 +80,16 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
launch {
|
launch {
|
||||||
nostr.newEvents.collect { event ->
|
nostr.newEvents.collect { event ->
|
||||||
val roomId = event.roomId()
|
val roomId = event.roomId()
|
||||||
val existingRoom = _chatRooms.value.firstOrNull { it.id == roomId }
|
val existingRoom = _state.value.rooms.firstOrNull { it.id == roomId }
|
||||||
|
|
||||||
if (existingRoom == null) {
|
if (existingRoom == null) {
|
||||||
val currentUser = nostr.signer.getPublicKeyAsync() ?: return@collect
|
val currentUser = nostr.signer.getPublicKeyAsync() ?: return@collect
|
||||||
val newRoom = Room.new(event, currentUser)
|
val newRoom = Room.new(event, currentUser)
|
||||||
_chatRooms.update { (it + newRoom).sortedDescending().toSet() }
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
rooms = (it.rooms + newRoom).sortedDescending().toSet()
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updateRoomList(roomId, event)
|
updateRoomList(roomId, event)
|
||||||
}
|
}
|
||||||
@@ -105,7 +120,7 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
|
|
||||||
// Check if the room already exists
|
// Check if the room already exists
|
||||||
val id = rumor.roomId()
|
val id = rumor.roomId()
|
||||||
val existingRoom = _chatRooms.value.firstOrNull { it.id == id }
|
val existingRoom = _state.value.rooms.firstOrNull { it.id == id }
|
||||||
|
|
||||||
// If the room already exists, return its ID
|
// If the room already exists, return its ID
|
||||||
if (existingRoom != null) {
|
if (existingRoom != null) {
|
||||||
@@ -116,9 +131,7 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
val room = Room.new(rumor, currentUser)
|
val room = Room.new(rumor, currentUser)
|
||||||
|
|
||||||
// Update the chat rooms state
|
// Update the chat rooms state
|
||||||
_chatRooms.update { currentRooms ->
|
_state.update { it.copy(rooms = (it.rooms + room).sortedDescending().toSet()) }
|
||||||
(currentRooms + room).sortedDescending().toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
return room.id
|
return room.id
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -127,20 +140,20 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getChatRoom(id: Long): Room? {
|
fun getChatRoom(id: Long): Room? {
|
||||||
return chatRooms.value.firstOrNull { it.id == id }
|
return _state.value.rooms.firstOrNull { it.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refreshChatRooms() {
|
suspend fun refreshChatRooms() {
|
||||||
try {
|
try {
|
||||||
val rooms = nostr.messages.getChatRooms() ?: emptySet()
|
val rooms = nostr.messages.getChatRooms() ?: emptySet()
|
||||||
_chatRooms.update { currentRooms ->
|
_state.update { currentState ->
|
||||||
val merged = currentRooms.associateBy { it.id }.toMutableMap()
|
val merged = currentState.rooms.associateBy { it.id }.toMutableMap()
|
||||||
// Add or update rooms from the database
|
// Add or update rooms from the database
|
||||||
rooms.forEach { room ->
|
rooms.forEach { room ->
|
||||||
merged[room.id] = room
|
merged[room.id] = room
|
||||||
}
|
}
|
||||||
// Return as a sorted set to maintain UI consistency
|
// Return as a sorted set to maintain UI consistency
|
||||||
merged.values.sortedDescending().toSet()
|
currentState.copy(rooms = merged.values.sortedDescending().toSet())
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showError("Error: ${e.message}")
|
showError("Error: ${e.message}")
|
||||||
@@ -202,7 +215,7 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
if (file == null) return
|
if (file == null) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val uri = blossomUpload(nostr.signer.get(), file, contentType)
|
val uri = mediaRepository.blossomUpload(nostr.signer.get(), file, contentType)
|
||||||
if (uri != null) sendMessage(roomId, uri, replies)
|
if (uri != null) sendMessage(roomId, uri, replies)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IllegalArgumentException("Error: ${e.message}")
|
throw IllegalArgumentException("Error: ${e.message}")
|
||||||
@@ -221,8 +234,8 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateRoomList(roomId: Long, newMessage: UnsignedEvent) {
|
private fun updateRoomList(roomId: Long, newMessage: UnsignedEvent) {
|
||||||
_chatRooms.update { currentRooms ->
|
_state.update { currentState ->
|
||||||
currentRooms.map { room ->
|
val updatedRooms = currentState.rooms.map { room ->
|
||||||
if (room.id == roomId) {
|
if (room.id == roomId) {
|
||||||
room.copy(
|
room.copy(
|
||||||
lastMessage = newMessage.content(),
|
lastMessage = newMessage.content(),
|
||||||
@@ -232,13 +245,14 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
room
|
room
|
||||||
}
|
}
|
||||||
}.sortedDescending().toSet()
|
}.sortedDescending().toSet()
|
||||||
|
currentState.copy(rooms = updatedRooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetInternalState() {
|
fun resetInternalState() {
|
||||||
_chatRooms.value = emptySet()
|
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
rooms = emptySet(),
|
||||||
isPartialProcessedGiftWrap = false,
|
isPartialProcessedGiftWrap = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package su.reya.coop.viewmodel
|
|||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
@@ -21,7 +20,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import rust.nostr.sdk.PublicKey
|
import rust.nostr.sdk.PublicKey
|
||||||
import rust.nostr.sdk.RelayMetadata
|
import rust.nostr.sdk.RelayMetadata
|
||||||
@@ -29,6 +27,7 @@ import rust.nostr.sdk.RelayUrl
|
|||||||
import rust.nostr.sdk.Timestamp
|
import rust.nostr.sdk.Timestamp
|
||||||
import su.reya.coop.Profile
|
import su.reya.coop.Profile
|
||||||
import su.reya.coop.nostr.Nostr
|
import su.reya.coop.nostr.Nostr
|
||||||
|
import su.reya.coop.repository.MediaRepository
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -39,6 +38,8 @@ data class NostrAppState(
|
|||||||
)
|
)
|
||||||
|
|
||||||
class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
||||||
|
private val mediaRepository = MediaRepository()
|
||||||
|
|
||||||
private val alwaysRunTasks = flow {
|
private val alwaysRunTasks = flow {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
val observerJob = launch { runObserver() }
|
val observerJob = launch { runObserver() }
|
||||||
@@ -92,17 +93,6 @@ class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
getCacheMetadata()
|
getCacheMetadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
super.onCleared()
|
|
||||||
|
|
||||||
// Disconnect to all bootstrap relays
|
|
||||||
viewModelScope.launch {
|
|
||||||
withContext(NonCancellable) {
|
|
||||||
nostr.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reconnect() {
|
private fun reconnect() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
nostr.waitUntilInitialized()
|
nostr.waitUntilInitialized()
|
||||||
@@ -248,7 +238,13 @@ class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
_appState.update { it.copy(isBusy = true) }
|
_appState.update { it.copy(isBusy = true) }
|
||||||
try {
|
try {
|
||||||
val avatarUrl =
|
val avatarUrl =
|
||||||
picture?.let { blossomUpload(nostr.signer.get(), it, contentType ?: "image/jpeg") }
|
picture?.let {
|
||||||
|
mediaRepository.blossomUpload(
|
||||||
|
nostr.signer.get(),
|
||||||
|
it,
|
||||||
|
contentType ?: "image/jpeg"
|
||||||
|
)
|
||||||
|
}
|
||||||
val newMetadata = nostr.profiles.updateProfile(name, bio, avatarUrl)
|
val newMetadata = nostr.profiles.updateProfile(name, bio, avatarUrl)
|
||||||
val currentUser = nostr.signer.getPublicKeyAsync() ?: throw Exception("User not found")
|
val currentUser = nostr.signer.getPublicKeyAsync() ?: throw Exception("User not found")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user