Improvements to profile page
This commit is contained in:
+3
-3
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -92,15 +93,14 @@ fun DisplayLNAddress(
|
||||
onClick = { zapExpanded = !zapExpanded },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||
.weight(1f),
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (zapExpanded) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 5.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp),
|
||||
) {
|
||||
InvoiceRequestCard(
|
||||
lud16,
|
||||
|
||||
+187
-140
@@ -20,8 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header
|
||||
|
||||
import android.content.ClipData
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -35,12 +39,14 @@ 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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -48,15 +54,18 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.Nip05State
|
||||
import com.vitorpamplona.amethyst.commons.util.toShortDisplay
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.appendLink
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.DrawPlayName
|
||||
import com.vitorpamplona.amethyst.ui.note.ObserveAndRenderNIP05VerifiedSymbol
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortDisplay
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations
|
||||
@@ -65,8 +74,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.badges.Displ
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.identity.UserExternalIdentitiesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy3dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@@ -75,9 +83,11 @@ import com.vitorpamplona.quartz.nip39ExtIdentities.IdentityClaimTag
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.TelegramIdentity
|
||||
import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val IDENTITY_ICON_CACHE_KEY = 0
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DrawAdditionalInfo(
|
||||
baseUser: User,
|
||||
@@ -89,147 +99,186 @@ fun DrawAdditionalInfo(
|
||||
val userState by observeUserInfo(baseUser, accountViewModel)
|
||||
val user = userState ?: return
|
||||
val uri = LocalUriHandler.current
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val clipboard = LocalClipboard.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val identities by externalIdentities.identities.collectAsStateWithLifecycle()
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 7.dp)) {
|
||||
CreateTextWithEmoji(
|
||||
text = user.info.bestName() ?: baseUser.pubkeyDisplayHex(),
|
||||
tags = user.tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 25.sp,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
user.info.pronouns?.let {
|
||||
val displayName = user.info.bestName()
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = SpacedBy3dp) {
|
||||
if (displayName != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = 7.dp),
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = displayName,
|
||||
tags = user.tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 22.sp,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
user.info.pronouns?.let {
|
||||
Text(
|
||||
text = "($it)",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
}
|
||||
|
||||
DrawPlayName(displayName)
|
||||
}
|
||||
}
|
||||
|
||||
if (displayName != user.info.name && !user.info.name.isNullOrBlank()) {
|
||||
Text(
|
||||
text = "($it)",
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
}
|
||||
|
||||
user.info.bestName()?.let {
|
||||
DrawPlayName(it)
|
||||
}
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = baseUser.pubkeyDisplayHex(),
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier =
|
||||
Modifier
|
||||
.size(25.dp)
|
||||
.padding(start = 5.dp),
|
||||
onClick = { clipboardManager.setText(AnnotatedString(baseUser.pubkeyNpub())) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
contentDescription = stringRes(id = R.string.copy_npub_to_clipboard),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
text = "@" + user.info.name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Size25Modifier,
|
||||
onClick = { nav.nav(Route.QRDisplay(baseUser.pubkeyHex)) },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.ic_qrcode, 1),
|
||||
contentDescription = stringRes(id = R.string.show_npub_as_a_qr_code),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DisplayBadges(baseUser, accountViewModel, nav)
|
||||
|
||||
DisplayNip05ProfileStatus(baseUser, accountViewModel)
|
||||
|
||||
val website = user.info.website
|
||||
if (!website.isNullOrEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
imageVector = Icons.Default.Link,
|
||||
contentDescription = stringRes(R.string.website),
|
||||
modifier = Modifier.size(16.dp),
|
||||
Text(
|
||||
text = baseUser.pubkeyDisplayHex(),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = website.removePrefix("https://"),
|
||||
IconButton(
|
||||
modifier = Modifier.size(23.dp).padding(start = 5.dp),
|
||||
onClick = {
|
||||
website.let {
|
||||
runCatching {
|
||||
if (it.contains("://")) {
|
||||
uri.openUri(it)
|
||||
} else {
|
||||
uri.openUri("http://$it")
|
||||
}
|
||||
}
|
||||
scope.launch {
|
||||
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("npub", baseUser.pubkeyNpub())))
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val lud16 =
|
||||
remember(userState) {
|
||||
userState?.info?.lud16?.trim()
|
||||
?: userState?.info?.lud06?.trim()
|
||||
}
|
||||
DisplayLNAddress(lud16, baseUser, accountViewModel, nav)
|
||||
|
||||
val displayIdentities = identities.ifEmpty { user.identities }
|
||||
if (displayIdentities.isNotEmpty()) {
|
||||
displayIdentities.forEach { identity: IdentityClaimTag ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
) {
|
||||
Icon(
|
||||
tint = Color.Unspecified,
|
||||
painter = painterRes(resourceId = getIdentityClaimIcon(identity), IDENTITY_ICON_CACHE_KEY),
|
||||
contentDescription = stringRes(getIdentityClaimDescription(identity)),
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = identity.identity,
|
||||
onClick = { runCatching { uri.openUri(identity.toProofUrl()) } },
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
|
||||
.weight(1f),
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
contentDescription = stringRes(id = R.string.copy_npub_to_clipboard),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.info.about?.let {
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp),
|
||||
) {
|
||||
val defaultBackground = MaterialTheme.colorScheme.background
|
||||
val background = remember { mutableStateOf(defaultBackground) }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = false,
|
||||
quotesLeft = 1,
|
||||
tags = user.tags,
|
||||
backgroundColor = background,
|
||||
id = it,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = baseUser.toNProfile().toShortDisplay(6),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DisplayAppRecommendations(appRecommendations, accountViewModel, nav)
|
||||
IconButton(
|
||||
modifier = Modifier.size(23.dp).padding(start = 5.dp),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("nprofile", baseUser.toNProfile())))
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ContentCopy,
|
||||
contentDescription = stringRes(id = R.string.copy_nprofile_to_clipboard),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(23.dp),
|
||||
onClick = { nav.nav(Route.QRDisplay(baseUser.pubkeyHex)) },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.ic_qrcode, 1),
|
||||
contentDescription = stringRes(id = R.string.show_nprofile_as_a_qr_code),
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DisplayNip05ProfileStatus(baseUser, accountViewModel)
|
||||
|
||||
val lud16 =
|
||||
remember(userState) {
|
||||
userState?.info?.lud16?.trim()
|
||||
?: userState?.info?.lud06?.trim()
|
||||
}
|
||||
DisplayLNAddress(lud16, baseUser, accountViewModel, nav)
|
||||
|
||||
val website = user.info.website
|
||||
if (!website.isNullOrEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
tint = MaterialTheme.colorScheme.placeholderText,
|
||||
imageVector = Icons.Default.Link,
|
||||
contentDescription = stringRes(R.string.website),
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = website.removePrefix("https://").removePrefix("http://").removeSuffix("/"),
|
||||
onClick = {
|
||||
runCatching {
|
||||
if (website.contains("://")) {
|
||||
uri.openUri(website)
|
||||
} else {
|
||||
uri.openUri("http://$website")
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(vertical = 1.dp, horizontal = 5.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val displayIdentities = identities.ifEmpty { user.identities }
|
||||
if (displayIdentities.isNotEmpty()) {
|
||||
displayIdentities.forEach { identity: IdentityClaimTag ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
tint = Color.Unspecified,
|
||||
painter = painterRes(resourceId = getIdentityClaimIcon(identity), IDENTITY_ICON_CACHE_KEY),
|
||||
contentDescription = stringRes(getIdentityClaimDescription(identity)),
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = identity.identity,
|
||||
onClick = { runCatching { uri.openUri(identity.toProofUrl()) } },
|
||||
modifier = Modifier.padding(horizontal = 5.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisplayBadges(baseUser, accountViewModel, nav)
|
||||
|
||||
user.info.about?.let {
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 10.dp, bottom = 5.dp),
|
||||
) {
|
||||
val defaultBackground = MaterialTheme.colorScheme.background
|
||||
val background = remember { mutableStateOf(defaultBackground) }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = it,
|
||||
canPreview = false,
|
||||
quotesLeft = 1,
|
||||
tags = user.tags,
|
||||
backgroundColor = background,
|
||||
id = it,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DisplayAppRecommendations(appRecommendations, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -242,24 +291,22 @@ fun DisplayNip05ProfileStatus(
|
||||
when (val nip05State = nip05StateMetadata) {
|
||||
is Nip05State.Exists -> {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = SpacedBy5dp) {
|
||||
ObserveAndRenderNIP05VerifiedSymbol(nip05State, 2, Size16Modifier, accountViewModel)
|
||||
|
||||
if (nip05State.nip05.name != "_") {
|
||||
Text(
|
||||
text = "${nip05State.nip05.name}@",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
ObserveAndRenderNIP05VerifiedSymbol(nip05State, 2, Size15Modifier, accountViewModel)
|
||||
|
||||
val uri = LocalUriHandler.current
|
||||
val color = MaterialTheme.colorScheme.primary
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = nip05State.nip05.domain,
|
||||
onClick = { runCatching { uri.openUri("https://${nip05State.nip05.domain}") } },
|
||||
Text(
|
||||
text =
|
||||
remember(nip05State) {
|
||||
buildAnnotatedString {
|
||||
appendLink(nip05State.nip05.toValue(), color) {
|
||||
runCatching { uri.openUri("https://${nip05State.nip05.domain}") }
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp),
|
||||
softWrap = true,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
@@ -97,6 +97,7 @@ val HalfDoubleVertSpacer = Modifier.height(7.dp)
|
||||
|
||||
val Size0dp = 0.dp
|
||||
val Size2dp = 2.dp
|
||||
val Size3dp = 3.dp
|
||||
val Size5dp = 5.dp
|
||||
val Size6dp = 6.dp
|
||||
val Size8dp = 8.dp
|
||||
@@ -375,6 +376,7 @@ val PaddingHorizontal12Modifier = Modifier.padding(horizontal = 12.dp)
|
||||
val QuickActionPopupShadow = Modifier.shadow(elevation = Size6dp, shape = SmallestBorder)
|
||||
|
||||
val SpacedBy2dp = Arrangement.spacedBy(Size2dp)
|
||||
val SpacedBy3dp = Arrangement.spacedBy(Size3dp)
|
||||
val SpacedBy5dp = Arrangement.spacedBy(Size5dp)
|
||||
val SpacedBy10dp = Arrangement.spacedBy(Size10dp)
|
||||
val SpacedBy55dp = Arrangement.spacedBy(Size55dp)
|
||||
|
||||
@@ -1004,6 +1004,7 @@
|
||||
<string name="copy_stack_to_clipboard">Copy Stack</string>
|
||||
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
<string name="copy_nprofile_to_clipboard">Copy nprofile to clipboard</string>
|
||||
<string name="copy_npub_to_clipboard">Copy npub to clipboard</string>
|
||||
<string name="share_or_save">Share or Save</string>
|
||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
||||
@@ -1282,6 +1283,7 @@
|
||||
<string name="add_content_warning">Add content warning</string>
|
||||
<string name="remove_content_warning">Remove content warning</string>
|
||||
<string name="show_npub_as_a_qr_code">Show npub as a QR code</string>
|
||||
<string name="show_nprofile_as_a_qr_code">Show nprofile as a QR code</string>
|
||||
|
||||
<string name="invalid_nip19_uri">Invalid address</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
||||
|
||||
Reference in New Issue
Block a user