Support for User Tags on posts.

This commit is contained in:
Vitor Pamplona 2023-01-16 10:51:10 -05:00
parent f2b831a119
commit f9b86585be
14 changed files with 160 additions and 93 deletions

View File

@ -1,4 +1,4 @@
package com.vitorpamplona.amethyst.ui.components
package com.vitorpamplona.amethyst.lnurl
import java.math.BigDecimal
import java.util.Locale

View File

@ -1,6 +1,6 @@
package com.vitorpamplona.amethyst.model
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import fr.acinq.secp256k1.Hex
import java.util.regex.Pattern
import nostr.postr.Bech32
@ -22,7 +22,7 @@ fun HexKey.toByteArray(): ByteArray {
}
fun HexKey.toDisplayHexKey(): String {
return this.toDisplayHex()
return this.toShortenHex()
}
fun decodePublicKey(key: String): ByteArray {

View File

@ -2,7 +2,7 @@ package com.vitorpamplona.amethyst.model
import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import fr.acinq.secp256k1.Hex
import java.time.Instant
import java.time.ZoneId
@ -18,7 +18,7 @@ class Note(val idHex: String) {
// These fields are always available.
// They are immutable
val id = Hex.decode(idHex)
val idDisplayHex = id.toDisplayHex()
val idDisplayHex = id.toShortenHex()
// These fields are only available after the Text Note event is received.
// They are immutable after that.

View File

@ -2,14 +2,14 @@ package com.vitorpamplona.amethyst.model
import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import nostr.postr.events.ContactListEvent
class User(val pubkey: ByteArray) {
val pubkeyHex = pubkey.toHexKey()
val pubkeyDisplayHex = pubkey.toDisplayHex()
val pubkeyDisplayHex = pubkey.toShortenHex()
var info = UserMetadata()

View File

@ -0,0 +1,26 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.ui.note.toShortenHex
@Composable
fun ClickableNoteTag(
note: Note,
navController: NavController
) {
val innerNoteState by note.live.observeAsState()
ClickableText(
text = AnnotatedString("@${innerNoteState?.note?.id?.toNote()?.toShortenHex()} "),
onClick = { navController.navigate("Note/${innerNoteState?.note?.idHex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
}

View File

@ -0,0 +1,19 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
@Composable
fun ClickableUrl(urlText: String, url: String) {
val uri = LocalUriHandler.current
ClickableText(
text = AnnotatedString("$urlText "),
onClick = { runCatching { uri.openUri(url) } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
)
}

View File

@ -0,0 +1,24 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.User
@Composable
fun ClickableUserTag(
user: User,
navController: NavController
) {
val innerUserState by user.live.observeAsState()
ClickableText(
text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()} "),
onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
}

View File

@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
@Composable
fun InvoicePreview(lnInvoice: String) {

View File

@ -3,22 +3,16 @@ package com.vitorpamplona.amethyst.ui.components
import android.util.Patterns
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import java.net.MalformedURLException
import java.net.URISyntaxException
@ -100,22 +94,19 @@ fun TagLink(word: String, tags: List<List<String>>, navController: NavController
if (tags[index][0] == "p") {
val user = LocalCache.users[tags[index][1]]
if (user != null) {
val innerUserState by user.live.observeAsState()
Text(
"@${innerUserState?.user?.toBestDisplayName()} "
)
ClickableUserTag(user, navController)
} else {
Text(text = "${tags[index][1].toShortenHex()} ")
}
} else if (tags[index][0] == "e") {
val note = LocalCache.notes[tags[index][1]]
if (note != null) {
val innerNoteState by note.live.observeAsState()
ClickableText(
text = AnnotatedString("@${innerNoteState?.note?.id?.toNote()?.toDisplayHex()} "),
onClick = { navController.navigate("Note/${note.idHex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
ClickableNoteTag(note, navController)
} else {
Text(text = "${tags[index][1].toShortenHex()} ")
}
} else
Text(text = "$word ")
}
}
}

View File

@ -1,45 +1,21 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.baha.url.preview.IUrlPreviewCallback
import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun UrlPreview(url: String, urlText: String, showUrlIfError: Boolean = true) {
var urlPreviewState by remember { mutableStateOf<UrlPreviewState>(UrlPreviewState.Loading) }
val uri = LocalUriHandler.current
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
LaunchedEffect(url) {
UrlCachedPreviewer.previewInfo(url, object : IUrlPreviewCallback {
@ -59,50 +35,15 @@ fun UrlPreview(url: String, urlText: String, showUrlIfError: Boolean = true) {
Crossfade(targetState = urlPreviewState) { state ->
when (state) {
is UrlPreviewState.Loaded -> {
Row(
modifier = Modifier.clickable { runCatching { uri.openUri(url) } }
.clip(shape = RoundedCornerShape(15.dp))
.border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(15.dp))
) {
Column {
AsyncImage(
model = state.previewInfo.image,
contentDescription = "Profile Image",
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
Text(
text = state.previewInfo.title,
style = MaterialTheme.typography.body2,
modifier = Modifier.fillMaxWidth().padding(start = 10.dp, end = 10.dp, top= 10.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = state.previewInfo.description,
style = MaterialTheme.typography.caption,
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
color = Color.Gray,
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
}
}
UrlPreviewCard(url, state.previewInfo)
}
else -> {
if (showUrlIfError) {
ClickableText(
text = AnnotatedString("$urlText "),
onClick = { runCatching { uri.openUri(url) } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
)
ClickableUrl(urlText, url)
}
}
}
}
}
}

View File

@ -0,0 +1,66 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.baha.url.preview.UrlInfoItem
@Composable
fun UrlPreviewCard(
url: String,
previewInfo: UrlInfoItem
) {
val uri = LocalUriHandler.current
Row(
modifier = Modifier
.clickable { runCatching { uri.openUri(url) } }
.clip(shape = RoundedCornerShape(15.dp))
.border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(15.dp))
) {
Column {
AsyncImage(
model = previewInfo.image,
contentDescription = "Profile Image",
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
Text(
text = previewInfo.title,
style = MaterialTheme.typography.body2,
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, top = 10.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = previewInfo.description,
style = MaterialTheme.typography.caption,
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
color = Color.Gray,
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
}
}
}

View File

@ -44,7 +44,6 @@ fun ZoomableAsyncImage(imageUrl: String) {
}
}
) {
AsyncImage(
model = imageUrl,
contentDescription = "Profile Image",

View File

@ -2,11 +2,11 @@ package com.vitorpamplona.amethyst.ui.note
import nostr.postr.toHex
fun ByteArray.toDisplayHex(): String {
return toHex().toDisplayHex()
fun ByteArray.toShortenHex(): String {
return toHex().toShortenHex()
}
fun String.toDisplayHex(): String {
fun String.toShortenHex(): String {
return replaceRange(6, length-6, ":")
}