From 9afc94c050788930948d3fdb92ae1ec6a1eebc54 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 2 Apr 2023 11:32:38 -0400 Subject: [PATCH 01/14] Display relay info in spam message logs --- .../java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt | 5 +++-- .../java/com/vitorpamplona/amethyst/model/LocalCache.kt | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt index 001069c08..34b26bba3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt @@ -4,6 +4,7 @@ import android.util.Log import android.util.LruCache import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.model.Event +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.BundledUpdate import kotlinx.coroutines.Dispatchers @@ -14,7 +15,7 @@ class AntiSpamFilter { val spamMessages = LruCache(1000) @Synchronized - fun isSpam(event: Event): Boolean { + fun isSpam(event: Event, relay: Relay?): Boolean { val idHex = event.id // if short message, ok @@ -27,7 +28,7 @@ class AntiSpamFilter { val hash = (event.content + event.tags.flatten().joinToString(",")).hashCode() if ((recentMessages[hash] != null && recentMessages[hash] != idHex) || spamMessages[hash] != null) { - Log.w("Potential SPAM Message", "${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${event.content.replace("\n", " | ")}") + Log.w("Potential SPAM Message", "${event.id} ${recentMessages[hash]} ${spamMessages[hash] != null} ${relay?.url} ${event.content.replace("\n", " | ")}") // Log down offenders if (spamMessages.get(hash) == null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index da8686412..eed0b602b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -187,7 +187,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - if (antiSpam.isSpam(event)) { + if (antiSpam.isSpam(event, relay)) { relay?.let { it.spamCounter++ } @@ -223,7 +223,7 @@ object LocalCache { // Already processed this event. if (note.event?.id() == event.id()) return - if (antiSpam.isSpam(event)) { + if (antiSpam.isSpam(event, relay)) { relay?.let { it.spamCounter++ } @@ -543,7 +543,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - if (antiSpam.isSpam(event)) { + if (antiSpam.isSpam(event, relay)) { relay?.let { it.spamCounter++ } From 67bcc52b25a6e824404508649f923fb290ebd3a0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 2 Apr 2023 11:42:11 -0400 Subject: [PATCH 02/14] Faster user in follow list algo --- app/src/main/java/com/vitorpamplona/amethyst/model/User.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4b9d5a5ac..909a4d705 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -273,7 +273,7 @@ class User(val pubkeyHex: String) { } fun transientFollowerCount(): Int { - return LocalCache.users.values.count { it.latestContactList?.let { pubkeyHex in it.unverifiedFollowKeySet() } ?: false } + return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false } } fun cachedFollowingKeySet(): Set { @@ -289,7 +289,7 @@ class User(val pubkeyHex: String) { } fun cachedFollowerCount(): Int { - return LocalCache.users.values.count { it.latestContactList?.let { pubkeyHex in it.unverifiedFollowKeySet() } ?: false } + return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false } } fun hasSentMessagesTo(user: User?): Boolean { From 407ee74a5546e730242d24b99bea936fdda64c15 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 2 Apr 2023 11:42:38 -0400 Subject: [PATCH 03/14] Faster processing of Contact List events --- .../java/com/vitorpamplona/amethyst/model/LocalCache.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index eed0b602b..90e260cd2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -302,12 +302,10 @@ object LocalCache { fun consume(event: ContactListEvent) { val user = getOrCreateUser(event.pubKey) - val follows = event.unverifiedFollowKeySet() - if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !follows.isNullOrEmpty()) { - // Saves relay list only if it's a user that is currently been seen + // avoids processing empty contact lists. + if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !event.tags.isEmpty()) { user.updateContactList(event) - // Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}") } } From b8ca1a15fb12ef258968dd9e62786e0c316ba87d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 2 Apr 2023 11:43:15 -0400 Subject: [PATCH 04/14] Adds event kind to the NoteCompose log --- .../main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 50b561b2c..d7d18be4b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -112,7 +112,7 @@ fun NoteCompose( ) } - Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}") + Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}") } @OptIn(ExperimentalFoundationApi::class) From 4db0faeda439f580da8b22d84f2bd10b221272a8 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 2 Apr 2023 11:54:40 -0400 Subject: [PATCH 05/14] Printing time on EOSETime class for logs --- .../java/com/vitorpamplona/amethyst/service/relays/EOSE.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt index b55904bb7..ba2193a97 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt @@ -2,7 +2,11 @@ package com.vitorpamplona.amethyst.service.relays import com.vitorpamplona.amethyst.model.User -class EOSETime(var time: Long) +class EOSETime(var time: Long) { + override fun toString(): String { + return time.toString() + } +} class EOSERelayList(var relayList: Map = emptyMap()) { fun addOrUpdate(relayUrl: String, time: Long) { From e8e696dcead63c91bb1bca037ae6e0517b6bb60f Mon Sep 17 00:00:00 2001 From: Believethehype Date: Sun, 2 Apr 2023 23:25:37 +0200 Subject: [PATCH 06/14] added message to LnZapEvent and interface --- .../com/vitorpamplona/amethyst/service/model/LnZapEvent.kt | 4 ++++ .../amethyst/service/model/LnZapEventInterface.kt | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt index ff42b7676..7d1300c49 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt @@ -36,6 +36,10 @@ class LnZapEvent( null } } + override fun message(): String { + return message + } + val message = content override fun containedPost(): Event? = try { description()?.let { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt index cc95d34b3..1ebe85dcb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt @@ -13,4 +13,6 @@ interface LnZapEventInterface : EventInterface { fun amount(): BigDecimal? fun containedPost(): Event? + + fun message(): String } From 37d839504c47e13ec6c85244642bf3fbef5256bc Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 11:53:02 -0400 Subject: [PATCH 07/14] Avoiding crashes when zap description is blank --- .../java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt index ff42b7676..f1796fe9a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt @@ -38,7 +38,7 @@ class LnZapEvent( } override fun containedPost(): Event? = try { - description()?.let { + description()?.ifBlank { null }?.let { fromJson(it, Client.lenient) } } catch (e: Exception) { From f4d66d9cbfb1033305287613855ceda5d5b5da0a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 12:02:26 -0400 Subject: [PATCH 08/14] Speeding up RichText rendering parsers --- .../amethyst/ui/actions/NewPostView.kt | 4 +- .../amethyst/ui/components/RichTextViewer.kt | 106 +++++++++++++----- .../ui/components/ZoomableImageView.kt | 6 +- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 100b3fabe..8ef31d698 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -167,7 +167,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n if (isValidURL(myUrlPreview)) { val removedParamsFromUrl = myUrlPreview.split("?")[0].lowercase() - if (imageExtensions.any { removedParamsFromUrl.endsWith(it, true) }) { + if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { AsyncImage( model = myUrlPreview, contentDescription = myUrlPreview, @@ -182,7 +182,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n RoundedCornerShape(15.dp) ) ) - } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it, true) }) { + } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) { VideoView(myUrlPreview) } else { UrlPreview(myUrlPreview, myUrlPreview) 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 02ff82e40..1dc0ce154 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 @@ -15,7 +15,6 @@ import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -138,10 +137,10 @@ fun RichTextViewer( // sequence of images will render in a slideview if (isValidURL(word)) { val removedParamsFromUrl = word.split("?")[0].lowercase() - if (imageExtensions.any { word.endsWith(it, true) }) { + if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { imagesForPager.add(word) } - if (videoExtensions.any { word.endsWith(it, true) }) { + if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) { imagesForPager.add(word) } } @@ -155,28 +154,59 @@ fun RichTextViewer( s.forEach { word: String -> if (canPreview) { // Explicit URL - val lnInvoice = LnInvoiceUtil.findInvoice(word) - val lnWithdrawal = LnWithdrawalUtil.findWithdrawal(word) - if (isValidURL(word)) { val removedParamsFromUrl = word.split("?")[0].lowercase() - if (imageExtensions.any { word.endsWith(it, true) }) { + if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { ZoomableImageView(word, imagesForPager) - } else if (videoExtensions.any { word.endsWith(it, true) }) { + } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it) }) { ZoomableImageView(word, imagesForPager) } else { UrlPreview(word, "$word ") } - } else if (lnInvoice != null) { - InvoicePreview(lnInvoice) - } else if (lnWithdrawal != null) { - ClickableWithdrawal(withdrawalString = lnWithdrawal) + } else if (word.startsWith("lnbc", true)) { + val lnInvoice = LnInvoiceUtil.findInvoice(word) + if (lnInvoice != null) { + InvoicePreview(lnInvoice) + } else { + Text( + text = "$word ", + style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } + } else if (word.startsWith("lnurl", true)) { + val lnWithdrawal = LnWithdrawalUtil.findWithdrawal(word) + if (lnWithdrawal != null) { + ClickableWithdrawal(withdrawalString = lnWithdrawal) + } else { + Text( + text = "$word ", + style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } } else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) { ClickableEmail(word) - } else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) { + } else if (word.length > 6 && Patterns.PHONE.matcher(word).matches()) { ClickablePhone(word) } else if (isBechLink(word)) { BechLink(word, navController) + } else if (word.startsWith("#")) { + if (tagIndex.matcher(word).matches() && tags != null) { + TagLink( + word, + tags, + canPreview, + backgroundColor, + accountViewModel, + navController + ) + } else if (hashTagsPattern.matcher(word).matches()) { + HashTag(word, accountViewModel, navController) + } else { + Text( + text = "$word ", + style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } } else if (noProtocolUrlValidator.matcher(word).matches()) { val matcher = noProtocolUrlValidator.matcher(word) matcher.find() @@ -185,10 +215,6 @@ fun RichTextViewer( ClickableUrl(url, "https://$url") Text("$additionalChars ") - } else if (tagIndex.matcher(word).matches() && tags != null) { - TagLink(word, tags, canPreview, backgroundColor, accountViewModel, navController) - } else if (hashTagsPattern.matcher(word).matches()) { - HashTag(word, accountViewModel, navController) } else { Text( text = "$word ", @@ -198,12 +224,40 @@ fun RichTextViewer( } else { if (isValidURL(word)) { ClickableUrl("$word ", word) + } else if (word.startsWith("lnurl", true)) { + val lnWithdrawal = LnWithdrawalUtil.findWithdrawal(word) + if (lnWithdrawal != null) { + ClickableWithdrawal(withdrawalString = lnWithdrawal) + } else { + Text( + text = "$word ", + style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } } else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) { ClickableEmail(word) } else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) { ClickablePhone(word) } else if (isBechLink(word)) { BechLink(word, navController) + } else if (word.startsWith("#")) { + if (tagIndex.matcher(word).matches() && tags != null) { + TagLink( + word, + tags, + canPreview, + backgroundColor, + accountViewModel, + navController + ) + } else if (hashTagsPattern.matcher(word).matches()) { + HashTag(word, accountViewModel, navController) + } else { + Text( + text = "$word ", + style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } } else if (noProtocolUrlValidator.matcher(word).matches()) { val matcher = noProtocolUrlValidator.matcher(word) matcher.find() @@ -212,10 +266,6 @@ fun RichTextViewer( ClickableUrl(url, "https://$url") Text("$additionalChars ") - } else if (tagIndex.matcher(word).matches() && tags != null) { - TagLink(word, tags, canPreview, backgroundColor, accountViewModel, navController) - } else if (hashTagsPattern.matcher(word).matches()) { - HashTag(word, accountViewModel, navController) } else { Text( text = "$word ", @@ -235,9 +285,9 @@ private fun isArabic(text: String): Boolean { } fun isBechLink(word: String): Boolean { - val cleaned = word.removePrefix("@").removePrefix("nostr:").removePrefix("@") + val cleaned = word.removePrefix("@").removePrefix("nostr:").removePrefix("@").take(7).lowercase() - return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it, true) } + return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) } } @Composable @@ -340,14 +390,8 @@ fun TagLink(word: String, tags: List>, canPreview: Boolean, backgro if (tags[index][0] == "p") { val baseUser = LocalCache.checkGetOrCreateUser(tags[index][1]) if (baseUser != null) { - val userState = baseUser.live().metadata.observeAsState() - val user = userState.value?.user - if (user != null) { - ClickableUserTag(user, navController) - Text(text = "$extraCharacters ") - } else { - Text(text = "$word ") - } + ClickableUserTag(baseUser, navController) + Text(text = "$extraCharacters ") } else { // if here the tag is not a valid Nostr Hex Text(text = "$word ") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt index 02f5cafe8..19b3b2a24 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt @@ -62,7 +62,8 @@ fun ZoomableImageView(word: String, images: List = listOf(word)) { mutableStateOf(null) } - if (imageExtensions.any { word.endsWith(it, true) }) { + val removedParamsFromUrl = word.split("?")[0].lowercase() + if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { AsyncImage( model = word, contentDescription = word, @@ -171,7 +172,8 @@ fun ZoomableImageDialog(imageUrl: String, allImages: List = listOf(image @Composable private fun RenderImageOrVideo(imageUrl: String) { - if (imageExtensions.any { imageUrl.endsWith(it, true) }) { + val removedParamsFromUrl = imageUrl.split("?")[0].lowercase() + if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { AsyncImage( model = imageUrl, contentDescription = stringResource(id = R.string.profile_image), From af751d56ce9d75c1b2d801fabdbdced3a658cf35 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 12:03:00 -0400 Subject: [PATCH 09/14] Hunting a bug of missing zapRequests --- app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt | 1 + 1 file changed, 1 insertion(+) 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 4b63c8547..d8f633116 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -155,6 +155,7 @@ open class Note(val idHex: String) { } } + @Synchronized fun addZap(zapRequest: Note, zap: Note?) { if (zapRequest !in zaps.keys) { zaps = zaps + Pair(zapRequest, zap) From b51f551b4b11ddcab4ee036a0b7bbdb1291466c1 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 3 Apr 2023 13:49:08 -0300 Subject: [PATCH 10/14] fix formatting --- .../ui/screen/loggedOff/LoginScreen.kt | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index d78673f7f..132c997d0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import java.util.* @@ -51,6 +52,9 @@ fun LoginPage( var termsAcceptanceIsRequired by remember { mutableStateOf("") } val uri = LocalUriHandler.current val context = LocalContext.current + var dialogOpen by remember { + mutableStateOf(false) + } Column( modifier = Modifier @@ -117,17 +121,35 @@ fun LoginPage( ) }, trailingIcon = { - IconButton(onClick = { showPassword = !showPassword }) { - Icon( - imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, - contentDescription = if (showPassword) { - stringResource(R.string.show_password) - } else { - stringResource( - R.string.hide_password - ) + Row { + IconButton(onClick = { showPassword = !showPassword }) { + Icon( + imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, + contentDescription = if (showPassword) { + stringResource(R.string.show_password) + } else { + stringResource( + R.string.hide_password + ) + } + ) + } + if (dialogOpen) { + SimpleQrCodeScanner { + dialogOpen = false + if (!it.isNullOrEmpty()) { + key.value = TextFieldValue(it) + } } - ) + } + IconButton(onClick = { dialogOpen = true }) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colors.primary + ) + } } }, visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), From 20aca144faf2b858f8ea1b7cdf4d6b7952e1f750 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 14:01:00 -0400 Subject: [PATCH 11/14] Moves Login QR Button to a leading QR, not a trailing QR --- .../ui/screen/loggedOff/LoginScreen.kt | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index 132c997d0..844df8e4d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -134,22 +134,24 @@ fun LoginPage( } ) } - if (dialogOpen) { - SimpleQrCodeScanner { - dialogOpen = false - if (!it.isNullOrEmpty()) { - key.value = TextFieldValue(it) - } + } + }, + leadingIcon = { + if (dialogOpen) { + SimpleQrCodeScanner { + dialogOpen = false + if (!it.isNullOrEmpty()) { + key.value = TextFieldValue(it) } } - IconButton(onClick = { dialogOpen = true }) { - Icon( - painter = painterResource(R.drawable.ic_qrcode), - null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colors.primary - ) - } + } + IconButton(onClick = { dialogOpen = true }) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colors.primary + ) } }, visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), From 3e666df896578728fc3e21bbc97f872b731793d0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 14:01:43 -0400 Subject: [PATCH 12/14] Adds full nip19 parsing on key decoding capabilities. --- .../java/com/vitorpamplona/amethyst/model/Hex.kt | 12 ++++++------ .../amethyst/ui/screen/AccountStateViewModel.kt | 8 ++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) 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 c50ff5781..7e16f77d8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt @@ -38,14 +38,14 @@ fun HexKey.toDisplayHexKey(): String { } fun decodePublicKey(key: String): ByteArray { + val parsed = Nip19.uriToRoute(key) + val pubKeyParsed = parsed?.hex?.toByteArray() + return if (key.startsWith("nsec")) { Persona(privKey = key.bechToBytes()).pubKey - } else if (key.startsWith("npub")) { - key.bechToBytes() - } else if (key.startsWith("note")) { - key.bechToBytes() - } else { // if (pattern.matcher(key).matches()) { - // } else { + } else if (pubKeyParsed != null) { + pubKeyParsed + } else { Hex.decode(key) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 0cc9cb31f..58c9140e9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -4,6 +4,8 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.toByteArray +import com.vitorpamplona.amethyst.service.nip19.Nip19 import fr.acinq.secp256k1.Hex import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -39,12 +41,14 @@ class AccountStateViewModel() : ViewModel() { fun login(key: String) { val pattern = Pattern.compile(".+@.+\\.[a-z]+") + val parsed = Nip19.uriToRoute(key) + val pubKeyParsed = parsed?.hex?.toByteArray() val account = if (key.startsWith("nsec")) { Account(Persona(privKey = key.bechToBytes())) - } else if (key.startsWith("npub")) { - Account(Persona(pubKey = key.bechToBytes())) + } else if (pubKeyParsed != null) { + Account(Persona(pubKey = pubKeyParsed)) } else if (pattern.matcher(key).matches()) { // Evaluate NIP-5 Account(Persona()) From 182cf62a3ba13c31d3a5835d145e7b9dd198f8b6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 14:02:19 -0400 Subject: [PATCH 13/14] v0.31.4 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3f269ebef..61ae98107 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 113 - versionName "0.31.3" + versionCode 114 + versionName "0.31.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From e4705312109bc0716160d9ae0d4ab2456ae61333 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 3 Apr 2023 15:04:52 -0400 Subject: [PATCH 14/14] Adds Show QR option to Profile screen --- .../amethyst/ui/qrcode/ShowQRDialog.kt | 22 ++++++------ .../ui/screen/loggedIn/ProfileScreen.kt | 34 ++++++++++++++++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt index b78c33323..30a7ba6b9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt @@ -66,7 +66,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) { modifier = Modifier .fillMaxSize() .padding(horizontal = 10.dp), - verticalArrangement = Arrangement.SpaceBetween + verticalArrangement = Arrangement.SpaceAround ) { if (presenting) { Column(modifier = Modifier.fillMaxWidth()) { @@ -91,22 +91,22 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) { fontSize = 18.sp ) } - - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 35.dp, vertical = 10.dp) - ) { - QrCodeDrawer("nostr:${user.pubkeyNpub()}") - } } Row( horizontalArrangement = Arrangement.Center, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 30.dp, vertical = 10.dp) + .padding(horizontal = 35.dp) + ) { + QrCodeDrawer("nostr:${user.pubkeyNpub()}") + } + + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 30.dp) ) { Button( onClick = { presenting = false }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 88159e4d5..faf0bdbbe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter +import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.screen.FeedView @@ -450,16 +451,41 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode IconButton( modifier = Modifier - .size(30.dp) + .size(25.dp) .padding(start = 5.dp), onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); } ) { Icon( imageVector = Icons.Default.ContentCopy, null, - modifier = Modifier - .padding(end = 5.dp) - .size(15.dp), + modifier = Modifier.size(15.dp), + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + } + + var dialogOpen by remember { + mutableStateOf(false) + } + + if (dialogOpen) { + ShowQRDialog( + user, + onScan = { + dialogOpen = false + navController.navigate(it) + }, + onClose = { dialogOpen = false } + ) + } + + IconButton( + modifier = Modifier.size(25.dp), + onClick = { dialogOpen = true } + ) { + Icon( + painter = painterResource(R.drawable.ic_qrcode), + null, + modifier = Modifier.size(15.dp), tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }