feat: add basic support for file upload via blossom (#32)
Reviewed-on: #32
This commit was merged in pull request #32.
This commit is contained in:
@@ -133,6 +133,7 @@ class AccountViewModel(
|
||||
|
||||
suspend fun importIdentity(secret: String) {
|
||||
appViewModel.setBusy(true)
|
||||
|
||||
try {
|
||||
val signer = createSigner(secret)
|
||||
nostr.setSigner(signer)
|
||||
@@ -150,15 +151,14 @@ class AccountViewModel(
|
||||
bio: String?,
|
||||
picture: ByteArray?,
|
||||
contentType: String? = "image/jpeg",
|
||||
profileViewModel: ProfileViewModel
|
||||
) {
|
||||
appViewModel.setBusy(true)
|
||||
|
||||
|
||||
val keys = Keys.generate()
|
||||
val secret = keys.secretKey().toBech32()
|
||||
|
||||
try {
|
||||
val avatarUrl = picture?.let { profileViewModel.blossomUpload(it, contentType) }
|
||||
val avatarUrl = picture?.let { appViewModel.blossomUpload(it, contentType) }
|
||||
nostr.profiles.createIdentity(keys = keys, name = name, bio, picture = avatarUrl)
|
||||
// Set credentials in persistent storage
|
||||
secretStore.set("user_signer", secret)
|
||||
|
||||
@@ -2,6 +2,9 @@ package su.reya.coop.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -9,6 +12,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import su.reya.coop.blossom.BlossomClient
|
||||
import su.reya.coop.nostr.Nostr
|
||||
import su.reya.coop.storage.SecretStorage
|
||||
|
||||
@@ -53,6 +58,29 @@ class AppViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun blossomUpload(file: ByteArray, contentType: String? = "image/jpeg"): String? {
|
||||
val blossom = BlossomClient(
|
||||
url = "https://blossom.band",
|
||||
client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val descriptor = blossom.upload(
|
||||
file = file,
|
||||
contentType = contentType,
|
||||
signer = nostr.signer.get()
|
||||
)
|
||||
|
||||
return descriptor?.url
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -149,7 +149,11 @@ class ChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessage(roomId: Long, message: String, replies: List<EventId> = emptyList()) {
|
||||
fun sendMessage(
|
||||
roomId: Long,
|
||||
message: String,
|
||||
replies: List<EventId> = emptyList()
|
||||
) {
|
||||
if (message.isEmpty()) {
|
||||
appViewModel.showError("Message cannot be empty")
|
||||
return
|
||||
@@ -173,6 +177,24 @@ class ChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendFileMessage(
|
||||
roomId: Long,
|
||||
file: ByteArray?,
|
||||
contentType: String? = "image/jpeg",
|
||||
replies: List<EventId> = emptyList()
|
||||
) {
|
||||
if (file == null) return
|
||||
|
||||
try {
|
||||
val uri = appViewModel.blossomUpload(file, contentType)
|
||||
?: throw IllegalArgumentException("Failed to upload file")
|
||||
|
||||
sendMessage(roomId, uri, replies)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalArgumentException("Error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun isMessageSent(id: EventId): Boolean {
|
||||
val giftWrapId = nostr.messages.rumorMap[id]
|
||||
return if (giftWrapId != null) {
|
||||
|
||||
@@ -2,9 +2,6 @@ package su.reya.coop.viewmodels
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -13,11 +10,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.json.Json
|
||||
import rust.nostr.sdk.Metadata
|
||||
import rust.nostr.sdk.PublicKey
|
||||
import rust.nostr.sdk.Timestamp
|
||||
import su.reya.coop.blossom.BlossomClient
|
||||
import su.reya.coop.nostr.Nostr
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -110,45 +105,18 @@ class ProfileViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun blossomUpload(file: ByteArray, contentType: String?): String? {
|
||||
try {
|
||||
val blossom = BlossomClient(
|
||||
url = "https://blossom.band",
|
||||
client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
prettyPrint = true
|
||||
isLenient = true
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val descriptor = blossom.upload(
|
||||
file = file,
|
||||
contentType = contentType,
|
||||
signer = nostr.signer.get()
|
||||
)
|
||||
|
||||
return descriptor?.url
|
||||
} catch (e: Exception) {
|
||||
appViewModel.showError("Error: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProfile(
|
||||
name: String? = null,
|
||||
bio: String? = null,
|
||||
picture: ByteArray? = null,
|
||||
contentType: String? = null
|
||||
contentType: String? = "image/jpeg"
|
||||
) {
|
||||
appViewModel.setBusy(true)
|
||||
try {
|
||||
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") }
|
||||
val avatarUrl = picture?.let { appViewModel.blossomUpload(it, contentType) }
|
||||
val newMetadata = nostr.profiles.updateProfile(name, bio, avatarUrl)
|
||||
val currentUser = nostr.signer.getPublicKeyAsync() ?: throw Exception("User not found")
|
||||
|
||||
updateMetadata(currentUser, newMetadata)
|
||||
} catch (e: Exception) {
|
||||
appViewModel.showError("Error: ${e.message}")
|
||||
|
||||
Reference in New Issue
Block a user