diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LnInvoiceUtil.kt b/app/src/main/java/com/vitorpamplona/amethyst/lnurl/LnInvoiceUtil.kt similarity index 99% rename from app/src/main/java/com/vitorpamplona/amethyst/ui/components/LnInvoiceUtil.kt rename to app/src/main/java/com/vitorpamplona/amethyst/lnurl/LnInvoiceUtil.kt index 6beb91e54..11ced892b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/LnInvoiceUtil.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/lnurl/LnInvoiceUtil.kt @@ -1,4 +1,4 @@ -package com.vitorpamplona.amethyst.ui.components +package com.vitorpamplona.amethyst.lnurl import java.math.BigDecimal import java.util.Locale diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt index 846446875..4618cde10 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index c52829404..fcc7d6a2a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -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. diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 991164abc..b1f707fb5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt new file mode 100644 index 000000000..6b2bf6a0e --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt @@ -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) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUrl.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUrl.kt new file mode 100644 index 000000000..7db7a2310 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUrl.kt @@ -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), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt new file mode 100644 index 000000000..550ce7e67 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt @@ -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) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt index 4bd727ded..3abf54f0e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt @@ -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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index bff30ed5c..0d89e8fad 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -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>, 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 ") } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt index 50615f270..538dfb8f0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt @@ -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.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) } } } } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt new file mode 100644 index 000000000..2e18bddc0 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableAsyncImage.kt index c2151c178..14eb5fdc2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableAsyncImage.kt @@ -44,7 +44,6 @@ fun ZoomableAsyncImage(imageUrl: String) { } } ) { - AsyncImage( model = imageUrl, contentDescription = "Profile Image", diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExtendedImageView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt similarity index 100% rename from app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExtendedImageView.kt rename to app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PubKeyFormatter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PubKeyFormatter.kt index 67a100bcd..e51f05f98 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PubKeyFormatter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PubKeyFormatter.kt @@ -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, ":") }