update
This commit is contained in:
@@ -20,6 +20,7 @@ import androidx.compose.foundation.text.KeyboardActions
|
|||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -27,9 +28,11 @@ import androidx.compose.material3.LoadingIndicator
|
|||||||
import androidx.compose.material3.MaterialShapes
|
import androidx.compose.material3.MaterialShapes
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.toShape
|
import androidx.compose.material3.toShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -55,6 +58,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import su.reya.coop.LocalSnackbarHostState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -69,16 +73,27 @@ fun ProfileEditor(
|
|||||||
onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit
|
onConfirm: (name: String, bio: String, pictureBytes: ByteArray?, contentType: String?) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
var name by remember(initialName) { mutableStateOf(initialName) }
|
var name by remember(initialName) { mutableStateOf(initialName) }
|
||||||
var bio by remember(initialBio) { mutableStateOf(initialBio) }
|
var bio by remember(initialBio) { mutableStateOf(initialBio) }
|
||||||
var picture by remember(initialPicture) { mutableStateOf(initialPicture) }
|
var picture by remember(initialPicture) { mutableStateOf(initialPicture) }
|
||||||
|
|
||||||
|
val hasPicture = remember(picture) {
|
||||||
|
when (picture) {
|
||||||
|
null -> false
|
||||||
|
is String -> (picture as CharSequence).isNotBlank()
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
picture = uri
|
picture = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(title, style = MaterialTheme.typography.titleMediumEmphasized) },
|
title = { Text(title, style = MaterialTheme.typography.titleMediumEmphasized) },
|
||||||
@@ -89,7 +104,10 @@ fun ProfileEditor(
|
|||||||
contentDescription = "Back"
|
contentDescription = "Back"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
@@ -112,7 +130,7 @@ fun ProfileEditor(
|
|||||||
.clickable { launcher.launch("image/*") },
|
.clickable { launcher.launch("image/*") },
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
if (picture != null) {
|
if (hasPicture) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = picture,
|
model = picture,
|
||||||
contentDescription = "Profile picture",
|
contentDescription = "Profile picture",
|
||||||
@@ -121,16 +139,15 @@ fun ProfileEditor(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
color = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(Res.drawable.ic_plus),
|
painter = painterResource(Res.drawable.ic_plus),
|
||||||
contentDescription = "Pick avatar",
|
contentDescription = "Pick avatar",
|
||||||
modifier = Modifier.size(48.dp),
|
modifier = Modifier.size(48.dp),
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.onTertiaryFixed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +260,9 @@ fun ProfileEditor(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
Button(
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.size(ButtonDefaults.MediumContainerHeight),
|
||||||
onClick = {
|
onClick = {
|
||||||
val scope = CoroutineScope(Dispatchers.Main)
|
val scope = CoroutineScope(Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -258,7 +278,14 @@ fun ProfileEditor(
|
|||||||
},
|
},
|
||||||
enabled = name.isNotBlank() && !isBusy
|
enabled = name.isNotBlank() && !isBusy
|
||||||
) {
|
) {
|
||||||
if (isBusy) LoadingIndicator() else Text(buttonLabel)
|
if (isBusy) {
|
||||||
|
LoadingIndicator()
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = buttonLabel,
|
||||||
|
style = MaterialTheme.typography.titleMediumEmphasized,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ class Nostr {
|
|||||||
name: String? = null,
|
name: String? = null,
|
||||||
bio: String? = null,
|
bio: String? = null,
|
||||||
picture: String? = null
|
picture: String? = null
|
||||||
) {
|
): Metadata {
|
||||||
val currentUser = signer.currentUser ?: throw IllegalStateException("User not signed in")
|
val currentUser = signer.currentUser ?: throw IllegalStateException("User not signed in")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -512,13 +512,16 @@ class Nostr {
|
|||||||
about = bio ?: record.about,
|
about = bio ?: record.about,
|
||||||
picture = picture ?: record.picture
|
picture = picture ?: record.picture
|
||||||
)
|
)
|
||||||
val event = EventBuilder.metadata(Metadata.fromRecord(newRecord)).signAsync(signer)
|
val newMetadata = Metadata.fromRecord(newRecord)
|
||||||
|
val event = EventBuilder.metadata(newMetadata).signAsync(signer)
|
||||||
|
|
||||||
client?.sendEvent(
|
client?.sendEvent(
|
||||||
event = event,
|
event = event,
|
||||||
target = SendEventTarget.broadcast(),
|
target = SendEventTarget.broadcast(),
|
||||||
ackPolicy = AckPolicy.none()
|
ackPolicy = AckPolicy.none()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return newMetadata
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IllegalStateException("Failed to update identity: ${e.message}", e)
|
throw IllegalStateException("Failed to update identity: ${e.message}", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,8 +346,6 @@ class NostrViewModel(
|
|||||||
|
|
||||||
private suspend fun blossomUpload(file: ByteArray, contentType: String): String? {
|
private suspend fun blossomUpload(file: ByteArray, contentType: String): String? {
|
||||||
try {
|
try {
|
||||||
var avatarUrl: String? = null
|
|
||||||
|
|
||||||
// Upload picture to Blossom
|
// Upload picture to Blossom
|
||||||
val blossom = BlossomClient(
|
val blossom = BlossomClient(
|
||||||
url = "https://blossom.band",
|
url = "https://blossom.band",
|
||||||
@@ -365,12 +363,10 @@ class NostrViewModel(
|
|||||||
val descriptor = blossom.upload(
|
val descriptor = blossom.upload(
|
||||||
file = file,
|
file = file,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
signer = nostr.signer
|
signer = nostr.signer.get()
|
||||||
)
|
)
|
||||||
|
|
||||||
avatarUrl = descriptor?.url
|
return descriptor?.url
|
||||||
|
|
||||||
return avatarUrl
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showError("Error: ${e.message}")
|
showError("Error: ${e.message}")
|
||||||
return null
|
return null
|
||||||
@@ -383,11 +379,16 @@ class NostrViewModel(
|
|||||||
picture: ByteArray? = null,
|
picture: ByteArray? = null,
|
||||||
contentType: String? = null
|
contentType: String? = null
|
||||||
) {
|
) {
|
||||||
|
_isLoggedIn.value = true
|
||||||
try {
|
try {
|
||||||
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") }
|
val avatarUrl = picture?.let { blossomUpload(it, contentType ?: "image/jpeg") }
|
||||||
nostr.updateProfile(name, bio, avatarUrl)
|
val newMetadata = nostr.updateProfile(name, bio, avatarUrl)
|
||||||
|
// Update the metadata state after successfully published
|
||||||
|
updateMetadata(nostr.signer.currentUser!!, newMetadata)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showError("Error: ${e.message}")
|
showError("Error: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
_isLoggedIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +415,7 @@ class NostrViewModel(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showError("Error: ${e.message}")
|
showError("Error: ${e.message}")
|
||||||
} finally {
|
} finally {
|
||||||
_isLoggedIn.value = true
|
_isLoggedIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user