From 6763a1252089cdf37284a001683c621c987d828d Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sat, 6 Jun 2026 10:00:15 +0700 Subject: [PATCH] add update profile screen --- .../androidMain/kotlin/su/reya/coop/App.kt | 4 + .../kotlin/su/reya/coop/Navigation.kt | 3 + .../kotlin/su/reya/coop/screens/HomeScreen.kt | 5 +- .../su/reya/coop/screens/NewIdentityScreen.kt | 300 +----------------- .../su/reya/coop/screens/ProfileScreen.kt | 2 +- .../su/reya/coop/screens/UpdateProfile.kt | 40 +++ .../su/reya/coop/shared/ProfileEditor.kt | 267 ++++++++++++++++ .../commonMain/kotlin/su/reya/coop/Nostr.kt | 5 +- 8 files changed, 332 insertions(+), 294 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfile.kt create mode 100644 composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt index 3187eaf..d98c73c 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt @@ -64,6 +64,7 @@ import su.reya.coop.screens.OnboardingScreen import su.reya.coop.screens.ProfileScreen import su.reya.coop.screens.RelayScreen import su.reya.coop.screens.ScanScreen +import su.reya.coop.screens.UpdateProfileScreen val LocalNostrViewModel = staticCompositionLocalOf { error("No NostrViewModel provided") @@ -203,6 +204,9 @@ fun App(viewModel: NostrViewModel) { entry { key -> ProfileScreen(pubkey = key.pubkey) } + entry { + UpdateProfileScreen() + } entry { ScanScreen() } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/Navigation.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/Navigation.kt index 68f19dd..a5bd384 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/Navigation.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/Navigation.kt @@ -29,6 +29,9 @@ sealed interface Screen : NavKey { @Serializable data class Profile(val pubkey: String) : Screen + @Serializable + data object UpdateProfile : Screen + @Serializable data object NewChat : Screen diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt index 7dfd11a..1204079 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt @@ -501,9 +501,10 @@ fun BottomMenuList( val viewModel = LocalNostrViewModel.current val defaultMenuList = listOf( - "Relay Management" to { navigator.navigate(Screen.Relay) }, + "Update Profile" to { navigator.navigate(Screen.UpdateProfile) }, + "Contact List" to { }, "Spams & Blocks" to { }, - "Contacts" to { }, + "Relay Management" to { navigator.navigate(Screen.Relay) }, "Settings" to { } ) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt index ea3139c..f6cd375 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/NewIdentityScreen.kt @@ -1,307 +1,31 @@ package su.reya.coop.screens -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LoadingIndicator -import androidx.compose.material3.MaterialShapes -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.toShape import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import coil3.compose.AsyncImage -import coop.composeapp.generated.resources.Res -import coop.composeapp.generated.resources.ic_arrow_back -import coop.composeapp.generated.resources.ic_plus -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.jetbrains.compose.resources.painterResource import su.reya.coop.LocalNavigator import su.reya.coop.LocalNostrViewModel -import su.reya.coop.LocalSnackbarHostState import su.reya.coop.Screen +import su.reya.coop.shared.ProfileEditor -@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun NewIdentityScreen() { - val context = LocalContext.current - val snackbarHostState = LocalSnackbarHostState.current - val focusManager = LocalFocusManager.current - val navigator = LocalNavigator.current val viewModel = LocalNostrViewModel.current - - val isLoggedIn by viewModel.isLoggedIn.collectAsStateWithLifecycle(false) - var name by remember { mutableStateOf("") } - var bio by remember { mutableStateOf("") } - var picture by remember { mutableStateOf(null) } - + val navigator = LocalNavigator.current val scope = rememberCoroutineScope() + val isLoggedIn by viewModel.isLoggedIn.collectAsStateWithLifecycle(false) - val launcher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.GetContent() - ) { uri: Uri? -> - picture = uri - } - - Scaffold( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - snackbarHost = { SnackbarHost(snackbarHostState) }, - topBar = { - TopAppBar( - title = { - Text( - text = "Create a new identity", - style = MaterialTheme.typography.titleMediumEmphasized - ) - }, - navigationIcon = { - IconButton(onClick = { navigator.goBack() }) { - Icon( - painter = painterResource(Res.drawable.ic_arrow_back), - contentDescription = "Back" - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer, - ) - ) - }, - content = { innerPadding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(top = innerPadding.calculateTopPadding()) - .imePadding(), - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .size(120.dp) - .clip(MaterialShapes.Pentagon.toShape()) - .clickable { launcher.launch("image/*") }, - contentAlignment = Alignment.Center - ) { - if (picture != null) { - AsyncImage( - model = picture, - contentDescription = "Profile picture", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) - } else { - Surface( - color = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier.fillMaxSize() - - ) { - Box(contentAlignment = Alignment.Center) { - Icon( - painter = painterResource(Res.drawable.ic_plus), - contentDescription = "Pick avatar", - modifier = Modifier.size(48.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - } - } - } - Surface( - modifier = Modifier - .fillMaxWidth() - .weight(1f, fill = true), - color = MaterialTheme.colorScheme.surface, - shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), - ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(24.dp) - ) { - Column( - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - text = "What others should call you?", - style = MaterialTheme.typography.titleLargeEmphasized.copy( - fontWeight = FontWeight.SemiBold, - ), - ) - BasicTextField( - value = name, - onValueChange = { name = it }, - enabled = !isLoggedIn, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus() - } - ), - textStyle = MaterialTheme.typography.headlineLargeEmphasized.copy( - color = MaterialTheme.colorScheme.tertiaryFixedDim, - fontWeight = FontWeight.SemiBold, - ), - cursorBrush = SolidColor(MaterialTheme.colorScheme.tertiaryContainer), - decorationBox = { innerTextField -> - Box(contentAlignment = Alignment.CenterStart) { - if (name.isEmpty()) { - Text( - "Alice", - style = MaterialTheme.typography.headlineLargeEmphasized.copy( - fontWeight = FontWeight.SemiBold, - ), - color = MaterialTheme.colorScheme.onSurfaceVariant.copy( - alpha = 0.5f - ) - ) - } - innerTextField() - } - } - ) - Spacer(modifier = Modifier.size(8.dp)) - Text( - text = "Your bio (optional)", - style = MaterialTheme.typography.titleLargeEmphasized.copy( - fontWeight = FontWeight.SemiBold, - ), - ) - BasicTextField( - value = bio, - onValueChange = { bio = it }, - enabled = !isLoggedIn, - modifier = Modifier.fillMaxWidth(), - maxLines = 3, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Done, - ), - keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus() - } - ), - textStyle = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.primaryFixed, - fontWeight = FontWeight.SemiBold, - ), - cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary), - decorationBox = { innerTextField -> - Box(contentAlignment = Alignment.CenterStart) { - if (bio.isEmpty()) { - Text( - "I love cat", - style = MaterialTheme.typography.headlineLargeEmphasized.copy( - fontWeight = FontWeight.SemiBold, - ), - color = MaterialTheme.colorScheme.onSurfaceVariant.copy( - alpha = 0.5f - ) - ) - } - innerTextField() - } - } - ) - } - Spacer(modifier = Modifier.size(16.dp)) - Button( - onClick = { - scope.launch { - try { - val imageBytes = withContext(Dispatchers.IO) { - picture?.let { uri -> - context.contentResolver.openInputStream( - uri - )?.use { input -> input.readBytes() } - } - } - - val contentType = - picture?.let { context.contentResolver.getType(it) } - - // Create the identity - viewModel.createIdentity(name, bio, imageBytes, contentType) - - // Navigate to the home screen if successful - navigator.navigate(Screen.Home) - } catch (e: Exception) { - // Error is handled by viewModel.showError inside createIdentity - } - } - }, - modifier = Modifier - .fillMaxWidth() - .height(ButtonDefaults.MediumContainerHeight), - enabled = name.isNotBlank() && !isLoggedIn, - ) { - if (isLoggedIn) { - LoadingIndicator() - } else { - Text( - text = "Continue", - style = MaterialTheme.typography.titleMediumEmphasized, - ) - } - } - } - } + ProfileEditor( + title = "Create a new identity", + buttonLabel = "Continue", + isBusy = isLoggedIn, + onBack = { navigator.goBack() }, + onConfirm = { name, bio, bytes, type -> + scope.launch { + viewModel.createIdentity(name, bio, bytes, type) + navigator.navigate(Screen.Home) } } ) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt index 8721410..2b10250 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt @@ -176,7 +176,7 @@ fun ProfileScreen(pubkey: String) { } Text( text = "Message", - style = MaterialTheme.typography.labelSmall + style = MaterialTheme.typography.labelMedium ) } Column( diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfile.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfile.kt new file mode 100644 index 0000000..e45eca5 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/UpdateProfile.kt @@ -0,0 +1,40 @@ +package su.reya.coop.screens + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.launch +import su.reya.coop.LocalNavigator +import su.reya.coop.LocalNostrViewModel +import su.reya.coop.shared.ProfileEditor + +@Composable +fun UpdateProfileScreen() { + val viewModel = LocalNostrViewModel.current + val navigator = LocalNavigator.current + val scope = rememberCoroutineScope() + + val currentUser = viewModel.currentUser() ?: return + val metadata by viewModel.getMetadata(currentUser).collectAsState(initial = null) + val isBusy by viewModel.isLoggedIn.collectAsStateWithLifecycle(false) + + val profile = metadata?.asRecord() + + ProfileEditor( + title = "Update profile", + buttonLabel = "Save changes", + initialName = profile?.displayName ?: profile?.name ?: "", + initialBio = profile?.about ?: "", + initialPicture = profile?.picture, + isBusy = isBusy, + onBack = { navigator.goBack() }, + onConfirm = { name, bio, bytes, type -> + scope.launch { + //viewModel.updateProfile(name, bio, bytes, type) + navigator.goBack() + } + } + ) +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt new file mode 100644 index 0000000..cfb456d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/shared/ProfileEditor.kt @@ -0,0 +1,267 @@ +package su.reya.coop.shared + +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LoadingIndicator +import androidx.compose.material3.MaterialShapes +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.toShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import coop.composeapp.generated.resources.Res +import coop.composeapp.generated.resources.ic_arrow_back +import coop.composeapp.generated.resources.ic_plus +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ProfileEditor( + title: String, + buttonLabel: String, + initialName: String = "", + initialBio: String = "", + initialPicture: Any? = null, // Accepts Uri (picked) or String (current URL) + isBusy: Boolean = false, + onBack: () -> Unit, + onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit +) { + val context = LocalContext.current + val focusManager = LocalFocusManager.current + var name by remember(initialName) { mutableStateOf(initialName) } + var bio by remember(initialBio) { mutableStateOf(initialBio) } + var picture by remember(initialPicture) { mutableStateOf(initialPicture) } + + val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> + picture = uri + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(title, style = MaterialTheme.typography.titleMediumEmphasized) }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon( + painterResource(Res.drawable.ic_arrow_back), + contentDescription = "Back" + ) + } + } + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = innerPadding.calculateTopPadding()) + .imePadding(), + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(120.dp) + .clip(MaterialShapes.Pentagon.toShape()) + .clickable { launcher.launch("image/*") }, + contentAlignment = Alignment.Center + ) { + if (picture != null) { + AsyncImage( + model = picture, + contentDescription = "Profile picture", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } else { + Surface( + color = MaterialTheme.colorScheme.surfaceVariant, + modifier = Modifier.fillMaxSize() + + ) { + Box(contentAlignment = Alignment.Center) { + Icon( + painter = painterResource(Res.drawable.ic_plus), + contentDescription = "Pick avatar", + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + } + Surface( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true), + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(24.dp) + ) { + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "What others should call you?", + style = MaterialTheme.typography.titleLargeEmphasized.copy( + fontWeight = FontWeight.SemiBold, + ), + ) + BasicTextField( + value = name, + onValueChange = { name = it }, + enabled = !isBusy, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + } + ), + textStyle = MaterialTheme.typography.headlineLargeEmphasized.copy( + color = MaterialTheme.colorScheme.tertiaryFixedDim, + fontWeight = FontWeight.SemiBold, + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.tertiaryContainer), + decorationBox = { innerTextField -> + Box(contentAlignment = Alignment.CenterStart) { + if (name.isEmpty()) { + Text( + "Alice", + style = MaterialTheme.typography.headlineLargeEmphasized.copy( + fontWeight = FontWeight.SemiBold, + ), + color = MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = 0.5f + ) + ) + } + innerTextField() + } + } + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "Your bio (optional)", + style = MaterialTheme.typography.titleLargeEmphasized.copy( + fontWeight = FontWeight.SemiBold, + ), + ) + BasicTextField( + value = bio, + onValueChange = { bio = it }, + enabled = !isBusy, + modifier = Modifier.fillMaxWidth(), + maxLines = 3, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus() + } + ), + textStyle = MaterialTheme.typography.bodyLarge.copy( + color = MaterialTheme.colorScheme.primaryFixed, + fontWeight = FontWeight.SemiBold, + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary), + decorationBox = { innerTextField -> + Box(contentAlignment = Alignment.CenterStart) { + if (bio.isEmpty()) { + Text( + "I love cat", + style = MaterialTheme.typography.headlineLargeEmphasized.copy( + fontWeight = FontWeight.SemiBold, + ), + color = MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = 0.5f + ) + ) + } + innerTextField() + } + } + ) + } + Spacer(modifier = Modifier.size(16.dp)) + Button( + onClick = { + val scope = CoroutineScope(Dispatchers.Main) + scope.launch { + val bytes = withContext(Dispatchers.IO) { + (picture as? Uri)?.let { + context.contentResolver.openInputStream(it)?.readBytes() + } + } + val type = + (picture as? Uri)?.let { context.contentResolver.getType(it) } + onConfirm(name, bio, bytes, type) + } + }, + enabled = name.isNotBlank() && !isBusy + ) { + if (isBusy) LoadingIndicator() else Text(buttonLabel) + } + } + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt index 3c11c4c..82c578f 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt @@ -811,13 +811,12 @@ class Nostr { val kinds = listOf(Kind.fromStd(KindStandard.METADATA)) val filter = Filter().kinds(kinds).search(query).limit(10u) - val target = - ReqTarget.manual(mapOf(RelayUrl.parse("wss://antiprimal.net") to listOf(filter))) + val target = ReqTarget.manual(mapOf(searchRelay to listOf(filter))) val stream = client?.streamEvents( target = target, id = "search", - timeout = Duration.parse("4s"), + timeout = Duration.parse("3s"), policy = ReqExitPolicy.ExitOnEose )