mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
feat(desktop): profile header polish — banner, rich-text bio, CLINK offer field
- Render kind-0 banner image in the profile card header - Render bio as rich text (mentions/hashtags/links) via DesktopRichText - Add CLINK offer (noffer) field to Edit Profile + shared EditProfileFields Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b2b5bccec1
commit
e862b3ecac
+4
@@ -54,6 +54,7 @@ class EditProfileFields {
|
||||
val nip05 = MutableStateFlow("")
|
||||
val lnAddress = MutableStateFlow("")
|
||||
val lnURL = MutableStateFlow("")
|
||||
val clinkOffer = MutableStateFlow("")
|
||||
|
||||
// Social proofs (NIP-39)
|
||||
val twitter = MutableStateFlow("")
|
||||
@@ -79,6 +80,7 @@ class EditProfileFields {
|
||||
nip05.value = info.nip05 ?: ""
|
||||
lnAddress.value = info.lud16 ?: ""
|
||||
lnURL.value = info.lud06 ?: ""
|
||||
clinkOffer.value = info.clinkOffer ?: ""
|
||||
}
|
||||
|
||||
twitter.value = ""
|
||||
@@ -114,6 +116,7 @@ class EditProfileFields {
|
||||
nip05.value = ""
|
||||
lnAddress.value = ""
|
||||
lnURL.value = ""
|
||||
clinkOffer.value = ""
|
||||
twitter.value = ""
|
||||
github.value = ""
|
||||
mastodon.value = ""
|
||||
@@ -132,6 +135,7 @@ class EditProfileFields {
|
||||
"nip05" to nip05.value,
|
||||
"lnAddress" to lnAddress.value,
|
||||
"lnURL" to lnURL.value,
|
||||
"clinkOffer" to clinkOffer.value,
|
||||
"twitter" to twitter.value,
|
||||
"github" to github.value,
|
||||
"mastodon" to mastodon.value,
|
||||
|
||||
+38
-5
@@ -65,16 +65,21 @@ 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.input.pointer.isSecondaryPressed
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.nip02FollowList.FollowAction
|
||||
import com.vitorpamplona.amethyst.commons.profile.ProfileBroadcastStatus
|
||||
import com.vitorpamplona.amethyst.commons.profile.ui.ProfileBroadcastBanner
|
||||
import com.vitorpamplona.amethyst.commons.richtext.CachedRichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.state.FollowState
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingState
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||
@@ -89,6 +94,8 @@ import com.vitorpamplona.amethyst.desktop.subscriptions.createContactListSubscri
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.generateSubId
|
||||
import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription
|
||||
import com.vitorpamplona.amethyst.desktop.ui.media.LightboxOverlay
|
||||
import com.vitorpamplona.amethyst.desktop.ui.note.DesktopRichText
|
||||
import com.vitorpamplona.amethyst.desktop.ui.note.RichTextCallbacks
|
||||
import com.vitorpamplona.amethyst.desktop.ui.note.WoTBadgedAvatar
|
||||
import com.vitorpamplona.amethyst.desktop.ui.profile.EditProfileDialog
|
||||
import com.vitorpamplona.amethyst.desktop.ui.profile.GalleryTab
|
||||
@@ -149,6 +156,15 @@ fun UserProfileScreen(
|
||||
)
|
||||
}
|
||||
var picture by remember { mutableStateOf(cachedMetadata?.profilePicture()) }
|
||||
var banner by remember {
|
||||
mutableStateOf(
|
||||
cachedMetadata
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.banner,
|
||||
)
|
||||
}
|
||||
var nip05 by remember {
|
||||
mutableStateOf(
|
||||
cachedMetadata
|
||||
@@ -334,6 +350,7 @@ fun UserProfileScreen(
|
||||
displayName = metadata.displayName ?: metadata.name
|
||||
about = metadata.about
|
||||
picture = metadata.picture
|
||||
banner = metadata.banner
|
||||
nip05 = metadata.nip05
|
||||
website = metadata.website
|
||||
lnAddress = metadata.lnAddress()
|
||||
@@ -735,6 +752,19 @@ fun UserProfileScreen(
|
||||
),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
banner?.takeIf { it.isNotBlank() }?.let { bannerUrl ->
|
||||
AsyncImage(
|
||||
model = bannerUrl,
|
||||
contentDescription = "Profile banner",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(120.dp)
|
||||
.clip(MaterialTheme.shapes.medium),
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.Top,
|
||||
@@ -798,12 +828,15 @@ fun UserProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (about != null) {
|
||||
about?.takeIf { it.isNotBlank() }?.let { bio ->
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text(
|
||||
about!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
val bioState = remember(bio) { CachedRichTextParser.parseText(bio, EmptyTagList) }
|
||||
DesktopRichText(
|
||||
content = bio,
|
||||
state = bioState,
|
||||
localCache = localCache,
|
||||
callbacks = RichTextCallbacks(onMentionClick = onNavigateToProfile),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -232,6 +232,7 @@ fun EditProfileDialog(
|
||||
nip05 = fields.nip05.value,
|
||||
lnAddress = fields.lnAddress.value,
|
||||
lnURL = fields.lnURL.value,
|
||||
clinkOffer = fields.clinkOffer.value,
|
||||
)
|
||||
} else {
|
||||
MetadataEvent.createNew(
|
||||
@@ -245,6 +246,7 @@ fun EditProfileDialog(
|
||||
nip05 = fields.nip05.value,
|
||||
lnAddress = fields.lnAddress.value,
|
||||
lnURL = fields.lnURL.value,
|
||||
clinkOffer = fields.clinkOffer.value,
|
||||
)
|
||||
}
|
||||
val signedMetadata = account.signer.sign(metadataTemplate)
|
||||
@@ -374,6 +376,7 @@ fun EditProfileContent(
|
||||
val nip05Value by fields.nip05.collectAsState()
|
||||
val lnAddressValue by fields.lnAddress.collectAsState()
|
||||
val lnURLValue by fields.lnURL.collectAsState()
|
||||
val clinkOfferValue by fields.clinkOffer.collectAsState()
|
||||
val twitterValue by fields.twitter.collectAsState()
|
||||
val githubValue by fields.github.collectAsState()
|
||||
val mastodonValue by fields.mastodon.collectAsState()
|
||||
@@ -676,6 +679,13 @@ fun EditProfileContent(
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = clinkOfferValue,
|
||||
onValueChange = { fields.clinkOffer.value = it },
|
||||
label = { Text("CLINK offer (noffer…)") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
// Social Proofs — collapsible
|
||||
SocialProofsSection(
|
||||
|
||||
Reference in New Issue
Block a user