This commit is contained in:
2026-07-02 15:29:49 +07:00
parent 830375f562
commit 771441e97e
11 changed files with 22 additions and 53 deletions

View File

@@ -47,6 +47,9 @@ import su.reya.coop.screens.RelayScreen
import su.reya.coop.screens.RequestListScreen import su.reya.coop.screens.RequestListScreen
import su.reya.coop.screens.ScanScreen import su.reya.coop.screens.ScanScreen
import su.reya.coop.screens.UpdateProfileScreen import su.reya.coop.screens.UpdateProfileScreen
import su.reya.coop.viewmodel.AuthViewModel
import su.reya.coop.viewmodel.ChatViewModel
import su.reya.coop.viewmodel.NostrViewModel
val LocalNostrViewModel = staticCompositionLocalOf<NostrViewModel> { val LocalNostrViewModel = staticCompositionLocalOf<NostrViewModel> {
error("No NostrViewModel provided") error("No NostrViewModel provided")

View File

@@ -14,6 +14,9 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import su.reya.coop.coop.storage.SecretStore import su.reya.coop.coop.storage.SecretStore
import su.reya.coop.nostr.NostrManager import su.reya.coop.nostr.NostrManager
import su.reya.coop.viewmodel.AuthViewModel
import su.reya.coop.viewmodel.ChatViewModel
import su.reya.coop.viewmodel.NostrViewModel
import kotlin.system.exitProcess import kotlin.system.exitProcess
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {

View File

@@ -60,7 +60,6 @@ import androidx.compose.material3.rememberTooltipState
import androidx.compose.material3.toShape import androidx.compose.material3.toShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -621,7 +620,6 @@ fun HomeScreen() {
fun NewRequests(requests: List<Room>) { fun NewRequests(requests: List<Room>) {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val nostrViewModel = LocalNostrViewModel.current val nostrViewModel = LocalNostrViewModel.current
val authViewModel = LocalAuthViewModel.current
val total = requests.size val total = requests.size
val firstRoom = requests.getOrNull(0) val firstRoom = requests.getOrNull(0)
@@ -700,7 +698,6 @@ fun NewRequests(requests: List<Room>) {
@Composable @Composable
fun ChatRoom(room: Room, onClick: () -> Unit) { fun ChatRoom(room: Room, onClick: () -> Unit) {
val nostrViewModel = LocalNostrViewModel.current val nostrViewModel = LocalNostrViewModel.current
val authViewModel = LocalAuthViewModel.current
val roomState by room.rememberUiState(nostrViewModel) val roomState by room.rememberUiState(nostrViewModel)
ListItem( ListItem(

View File

@@ -7,13 +7,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import su.reya.coop.LocalAuthViewModel import su.reya.coop.LocalAuthViewModel
import su.reya.coop.LocalNavigator import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.Screen import su.reya.coop.Screen
import su.reya.coop.shared.ProfileEditor import su.reya.coop.shared.ProfileEditor
@Composable @Composable
fun NewIdentityScreen() { fun NewIdentityScreen() {
val nostrViewModel = LocalNostrViewModel.current
val authViewModel = LocalAuthViewModel.current val authViewModel = LocalAuthViewModel.current
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()

View File

@@ -47,7 +47,6 @@ import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import su.reya.coop.LocalAuthViewModel import su.reya.coop.LocalAuthViewModel
import su.reya.coop.LocalNavigator import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen import su.reya.coop.Screen
import su.reya.coop.shared.getExpressiveFontFamily import su.reya.coop.shared.getExpressiveFontFamily
@@ -58,7 +57,6 @@ fun OnboardingScreen() {
val context = LocalContext.current val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val nostrViewModel = LocalNostrViewModel.current
val authViewModel = LocalAuthViewModel.current val authViewModel = LocalAuthViewModel.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()

View File

@@ -39,7 +39,6 @@ import coop.composeapp.generated.resources.ic_arrow_back
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import su.reya.coop.LocalChatViewModel import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.RoomKind import su.reya.coop.RoomKind
import su.reya.coop.Screen import su.reya.coop.Screen
@@ -49,7 +48,6 @@ import su.reya.coop.Screen
fun RequestListScreen() { fun RequestListScreen() {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val nostrViewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current val chatViewModel = LocalChatViewModel.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()

View File

@@ -15,6 +15,7 @@ import kotlinx.datetime.toLocalDateTime
import rust.nostr.sdk.PublicKey import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.Timestamp import rust.nostr.sdk.Timestamp
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.viewmodel.NostrViewModel
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,21 +1,16 @@
package su.reya.coop package su.reya.coop.viewmodel
import androidx.lifecycle.viewModelScope 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.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.Json
import rust.nostr.sdk.AsyncNostrSigner import rust.nostr.sdk.AsyncNostrSigner
import rust.nostr.sdk.Keys import rust.nostr.sdk.Keys
import rust.nostr.sdk.NostrConnect import rust.nostr.sdk.NostrConnect
import rust.nostr.sdk.NostrConnectUri import rust.nostr.sdk.NostrConnectUri
import rust.nostr.sdk.PublicKey import rust.nostr.sdk.PublicKey
import su.reya.coop.blossom.BlossomClient
import su.reya.coop.nostr.ExternalSignerHandler 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
@@ -241,11 +236,15 @@ class AuthViewModel(
val secret = keys.secretKey().toBech32() val secret = keys.secretKey().toBech32()
try { try {
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") } val avatarUrl =
picture?.let { 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)
// Persist the secret in the secret storage // Persist the secret in the secret storage
secretStore.set(KEY_USER_SIGNER, secret) secretStore.set(KEY_USER_SIGNER, secret)
// Update local states // Update local states
_state.update { it.copy(isBusy = false, signerRequired = false) } _state.update { it.copy(isBusy = false, signerRequired = false) }
} catch (e: Exception) { } catch (e: Exception) {
@@ -253,35 +252,4 @@ class AuthViewModel(
_state.update { it.copy(isBusy = false) } _state.update { it.copy(isBusy = false) }
} }
} }
private suspend fun blossomUpload(
file: ByteArray,
contentType: String? = "image/jpeg"
): 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) {
showError("Error: ${e.message}")
return null
}
}
} }

View File

@@ -1,4 +1,4 @@
package su.reya.coop package su.reya.coop.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@@ -6,6 +6,7 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import rust.nostr.sdk.AsyncNostrSigner import rust.nostr.sdk.AsyncNostrSigner
import su.reya.coop.ErrorManager
import su.reya.coop.blossom.BlossomClient import su.reya.coop.blossom.BlossomClient
abstract class BaseViewModel : ViewModel() { abstract class BaseViewModel : ViewModel() {

View File

@@ -1,10 +1,9 @@
package su.reya.coop package su.reya.coop.viewmodel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow 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.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -19,7 +18,9 @@ import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.RelayUrl import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.Tag import rust.nostr.sdk.Tag
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.Room
import su.reya.coop.nostr.Nostr import su.reya.coop.nostr.Nostr
import su.reya.coop.roomId
data class ChatState( data class ChatState(
val isSyncing: Boolean = false, val isSyncing: Boolean = false,

View File

@@ -1,4 +1,4 @@
package su.reya.coop package su.reya.coop.viewmodel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -27,6 +27,7 @@ import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.RelayMetadata import rust.nostr.sdk.RelayMetadata
import rust.nostr.sdk.RelayUrl import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.Timestamp import rust.nostr.sdk.Timestamp
import su.reya.coop.Profile
import su.reya.coop.nostr.Nostr import su.reya.coop.nostr.Nostr
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds