From ab841c649095b9da3f2cbbe4b0b33f9cb55c44ac Mon Sep 17 00:00:00 2001
From: Zoltan <34719275+ZsZolee@users.noreply.github.com>
Date: Fri, 5 May 2023 10:38:53 +0200
Subject: [PATCH 01/41] Follow list Hungarian translation
---
app/src/main/res/values-hu/strings.xml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index c47fda57e..64568685b 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -333,5 +333,11 @@
Saját csomópontjaid (NIP-95)
A fájlokat a csomópontokra töltik fel és ott tárolják. Rögzített URL-től mentesek (harmadik féltől való függőség). Győződj meg róla, hogy legalább egy NIP-95 csomópont a csomópontlistában szerepel
+
+
+
+ Követek Lista
+ Mindenki akit követek
+ Globális
From 5b6b6f19e5797edd077fd51507b48107db382e9b Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 10:01:37 -0400
Subject: [PATCH 02/41] Reverting background refactoring due to color being
stuck in recompositions.
---
.../amethyst/ui/note/NoteCompose.kt | 36 ++++++++-----------
1 file changed, 15 insertions(+), 21 deletions(-)
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 eff2915bd..ad71c37ae 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
@@ -114,9 +114,6 @@ fun NoteComposeInner(
accountViewModel: AccountViewModel,
navController: NavController
) {
- val defaultBackgroundColor = MaterialTheme.colors.background
- val defaultPrimaryColor = MaterialTheme.colors.primary
-
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val loggedIn = account.userProfile()
@@ -178,7 +175,7 @@ fun NoteComposeInner(
} else if (noteEvent is FileStorageHeaderEvent) {
FileStorageHeaderDisplay(note)
} else {
- var backgroundColor by remember { mutableStateOf(defaultBackgroundColor) }
+ var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = routeForLastRead) {
withContext(Dispatchers.IO) {
@@ -186,27 +183,25 @@ fun NoteComposeInner(
val lastTime = NotificationCache.load(it)
val createdAt = note.createdAt()
- val isNew = if (createdAt != null) {
+ if (createdAt != null) {
NotificationCache.markAsRead(it, createdAt)
- createdAt > lastTime
- } else {
- false
- }
-
- backgroundColor = if (isNew) {
- val newColor = defaultPrimaryColor.copy(0.12f)
- if (parentBackgroundColor != null) {
- newColor.compositeOver(parentBackgroundColor)
- } else {
- newColor.compositeOver(defaultBackgroundColor)
- }
- } else {
- parentBackgroundColor ?: defaultBackgroundColor
+ isNew = createdAt > lastTime
}
}
}
}
+ val backgroundColor = if (isNew) {
+ val newColor = MaterialTheme.colors.primary.copy(0.12f)
+ if (parentBackgroundColor != null) {
+ newColor.compositeOver(parentBackgroundColor)
+ } else {
+ newColor.compositeOver(MaterialTheme.colors.background)
+ }
+ } else {
+ parentBackgroundColor ?: MaterialTheme.colors.background
+ }
+
Column(
modifier = modifier.combinedClickable(
onClick = {
@@ -234,8 +229,7 @@ fun NoteComposeInner(
}
},
onLongClick = { popupExpanded = true }
- )
- .background(backgroundColor)
+ ).background(backgroundColor)
) {
Row(
modifier = Modifier
From dabeea43ea48971611bfca77c2dfa26eff01445d Mon Sep 17 00:00:00 2001
From: Believethehype
Date: Fri, 5 May 2023 18:59:24 +0200
Subject: [PATCH 03/41] added hoster nostrfiles.dev
---
.../amethyst/ui/actions/ImageUploader.kt | 26 ++++++++++++++++---
.../amethyst/ui/actions/NewMediaModel.kt | 2 ++
.../amethyst/ui/actions/NewMediaView.kt | 12 ++++++---
.../amethyst/ui/actions/NewPostView.kt | 8 +++---
.../amethyst/ui/actions/NewPostViewModel.kt | 2 +-
app/src/main/res/values/strings.xml | 8 ++++++
6 files changed, 47 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
index 766056a87..9db73c03e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
@@ -43,6 +43,8 @@ object ImageUploader {
NostrImgServer()
} else if (server == ServersAvailable.NOSTR_BUILD) {
NostrBuildServer()
+ } else if (server == ServersAvailable.NOSTRFILES_DEV) {
+ NostrfilesDevServer()
} else {
ImgurServer()
}
@@ -62,7 +64,7 @@ object ImageUploader {
onSuccess: (String, String?) -> Unit,
onError: (Throwable) -> Unit
) {
- val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
+ var category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
val fileName = randomChars()
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
@@ -71,12 +73,17 @@ object ImageUploader {
val requestBody: RequestBody
val requestBuilder = Request.Builder()
- if (serverType == ServersAvailable.NOSTR_BUILD) {
- requestBuilder.addHeader("Content-Type", "multipart/form-data")
+ if (serverType == ServersAvailable.NOSTR_BUILD || serverType == ServersAvailable.NOSTRFILES_DEV) {
+ if (serverType == ServersAvailable.NOSTR_BUILD) {
+ requestBuilder.addHeader("Content-Type", "multipart/form-data")
+ category = "fileToUpload"
+ } else if (serverType == ServersAvailable.NOSTRFILES_DEV) {
+ category = "file"
+ }
requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
- "fileToUpload",
+ category,
"$fileName.$extension",
file.asRequestBody(contentType?.toMediaType())
@@ -212,3 +219,14 @@ class NostrBuildServer : FileServer() {
override fun clientID() = null
}
+
+class NostrfilesDevServer : FileServer() {
+ override fun postUrl(contentType: String?) = "https://nostrfiles.dev/upload_image"
+ override fun parseUrlFromSucess(body: String): String? {
+ val tree = jacksonObjectMapper().readTree(body)
+ val url = tree?.get("url")?.asText()
+ return url
+ }
+
+ override fun clientID() = null
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
index ac812b2c2..edc977ec5 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
@@ -46,6 +46,8 @@ open class NewMediaModel : ViewModel() {
selectedServer = ServersAvailable.NOSTRIMG_NIP_94
} else if (selectedServer == ServersAvailable.NOSTR_BUILD) {
selectedServer = ServersAvailable.NOSTR_BUILD_NIP_94
+ } else if (selectedServer == ServersAvailable.NOSTRFILES_DEV) {
+ selectedServer = ServersAvailable.NOSTRFILES_DEV_NIP_94
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
index c1489f126..c56bcbdfb 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
@@ -109,6 +109,14 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
}
}
+fun isNIP94Server(selectedServer: ServersAvailable?): Boolean {
+ return selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
+ selectedServer == ServersAvailable.IMGUR_NIP_94 ||
+ selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 ||
+ selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
+}
+
+
@Composable
fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
val scope = rememberCoroutineScope()
@@ -191,9 +199,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
)
}
- if (postViewModel.selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
- postViewModel.selectedServer == ServersAvailable.IMGUR_NIP_94 ||
- postViewModel.selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 ||
+ if (isNIP94Server(postViewModel.selectedServer) ||
postViewModel.selectedServer == ServersAvailable.NIP95
) {
Row(
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 370bf8728..620e393c5 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
@@ -588,9 +588,11 @@ enum class ServersAvailable {
IMGUR,
NOSTR_BUILD,
NOSTRIMG,
+ NOSTRFILES_DEV,
IMGUR_NIP_94,
NOSTRIMG_NIP_94,
NOSTR_BUILD_NIP_94,
+ NOSTRFILES_DEV_NIP_94,
NIP95
}
@@ -612,9 +614,11 @@ fun ImageVideoDescription(
Triple(ServersAvailable.IMGUR, stringResource(id = R.string.upload_server_imgur), stringResource(id = R.string.upload_server_imgur_explainer)),
Triple(ServersAvailable.NOSTRIMG, stringResource(id = R.string.upload_server_nostrimg), stringResource(id = R.string.upload_server_nostrimg_explainer)),
Triple(ServersAvailable.NOSTR_BUILD, stringResource(id = R.string.upload_server_nostrbuild), stringResource(id = R.string.upload_server_nostrbuild_explainer)),
+ Triple(ServersAvailable.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)),
Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)),
Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)),
Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)),
+ Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer))
)
@@ -742,9 +746,7 @@ fun ImageVideoDescription(
)
}
- if (selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
- selectedServer == ServersAvailable.IMGUR_NIP_94 ||
- selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 ||
+ if (isNIP94Server(selectedServer) ||
selectedServer == ServersAvailable.NIP95
) {
Row(
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
index 42590becc..8ea846834 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
@@ -148,7 +148,7 @@ open class NewPostViewModel : ViewModel() {
context = context,
contentResolver = contentResolver,
onSuccess = { imageUrl, mimeType ->
- if (server == ServersAvailable.IMGUR_NIP_94 || server == ServersAvailable.NOSTRIMG_NIP_94 || server == ServersAvailable.NOSTR_BUILD_NIP_94) {
+ if (isNIP94Server(server)) {
createNIP94Record(imageUrl, mimeType, description)
} else {
isUploadingImage = false
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 143f5b6b1..703bb8e12 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -336,6 +336,10 @@
nostr.build - trusted
Nostr.build can modify the file
+ nostrfiles.dev - trusted
+ Nostrfiles.dev can modify the file
+
+
Verifiable Imgur (NIP-94)
Checks if Imgur modified the file. New NIP: other clients might not see it
@@ -345,6 +349,10 @@
Verifiable Nostr.build (NIP-94)
Checks if Nostr.build modified the file. New NIP: other clients might not see it
+ Verifiable Nostrfiles.dev (NIP-94)
+ Checks if Nostrfiles.dev modified the file. New NIP: other clients might not see it
+
+
Your relays (NIP-95)
Files are hosted by your relays. New NIP: check if they support
From 3c3b6c97c02c5de60d91c7f079237c4f3506cce4 Mon Sep 17 00:00:00 2001
From: Believethehype
Date: Fri, 5 May 2023 19:21:25 +0200
Subject: [PATCH 04/41] deactivate nostrfiles_dev selection for now (because of
space)
---
.../java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt | 4 ++--
1 file changed, 2 insertions(+), 2 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 620e393c5..571565a3b 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
@@ -614,11 +614,11 @@ fun ImageVideoDescription(
Triple(ServersAvailable.IMGUR, stringResource(id = R.string.upload_server_imgur), stringResource(id = R.string.upload_server_imgur_explainer)),
Triple(ServersAvailable.NOSTRIMG, stringResource(id = R.string.upload_server_nostrimg), stringResource(id = R.string.upload_server_nostrimg_explainer)),
Triple(ServersAvailable.NOSTR_BUILD, stringResource(id = R.string.upload_server_nostrbuild), stringResource(id = R.string.upload_server_nostrbuild_explainer)),
- Triple(ServersAvailable.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)),
+ // Triple(ServersAvailable.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)),
Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)),
Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)),
Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)),
- Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
+ // Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer))
)
From dd62d2bae41b33489d8c98852f1fb6cf413755cf Mon Sep 17 00:00:00 2001
From: Believethehype
Date: Fri, 5 May 2023 19:25:31 +0200
Subject: [PATCH 05/41] activated nostrfiles_dev for stories feed.
---
.../com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
index c56bcbdfb..cb01b6e80 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
@@ -110,13 +110,9 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
}
fun isNIP94Server(selectedServer: ServersAvailable?): Boolean {
- return selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
- selectedServer == ServersAvailable.IMGUR_NIP_94 ||
- selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 ||
- selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
+ return selectedServer == ServersAvailable.NOSTRIMG_NIP_94 || selectedServer == ServersAvailable.IMGUR_NIP_94 || selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 || selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
}
-
@Composable
fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
val scope = rememberCoroutineScope()
@@ -125,6 +121,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) {
Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)),
Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)),
Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)),
+ Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer))
)
From 49a8778975ce0ce8136f8789f2ea75d59d616e22 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 13:26:55 -0400
Subject: [PATCH 06/41] BugFix for NPE on null ZappedNote
---
app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index ddacd9993..81623a192 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -191,8 +191,8 @@ class Account(
)
}
- fun calculateIfNoteWasZappedByAccount(zappedNote: Note): Boolean {
- return zappedNote.isZappedBy(userProfile(), this) == true
+ fun calculateIfNoteWasZappedByAccount(zappedNote: Note?): Boolean {
+ return zappedNote?.isZappedBy(userProfile(), this) == true
}
fun calculateZappedAmount(zappedNote: Note?): BigDecimal {
From cffcc158f5bc684280dd567001a8a24b4c7f93ac Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 13:29:20 -0400
Subject: [PATCH 07/41] 0.42.3
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index d7de6c61b..5d7307edb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 144
- versionName "0.42.2"
+ versionCode 145
+ versionName "0.42.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
From 86adac95c90220733a0f2431d67b33a0fd3f2c53 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 13:49:18 -0400
Subject: [PATCH 08/41] Adds protections for calculating zap amounts without a
private key
---
.../vitorpamplona/amethyst/model/Account.kt | 23 +++++++++++--------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index 81623a192..6c7b55d39 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -178,17 +178,23 @@ class Account(
}
fun isNIP47Author(pubkeyHex: String?): Boolean {
- val privKey = zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey!!
+ val privKey = zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey
+
+ if (privKey == null) return false
+
val pubKey = Utils.pubkeyCreate(privKey).toHexKey()
return (pubKey == pubkeyHex)
}
fun decryptZapPaymentResponseEvent(zapResponseEvent: LnZapPaymentResponseEvent): Response? {
val myNip47 = zapPaymentRequest ?: return null
- return zapResponseEvent.response(
- myNip47.secret?.toByteArray() ?: loggedIn.privKey!!,
- myNip47.pubKeyHex.toByteArray()
- )
+
+ val privKey = myNip47.secret?.toByteArray() ?: loggedIn.privKey
+ val pubKey = myNip47.pubKeyHex.toByteArray()
+
+ if (privKey == null) return null
+
+ return zapResponseEvent.response(privKey, pubKey)
}
fun calculateIfNoteWasZappedByAccount(zappedNote: Note?): Boolean {
@@ -196,10 +202,9 @@ class Account(
}
fun calculateZappedAmount(zappedNote: Note?): BigDecimal {
- return zappedNote?.zappedAmount(
- zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey!!,
- zapPaymentRequest?.pubKeyHex?.toByteArray()
- ) ?: BigDecimal.ZERO
+ val privKey = zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey
+ val pubKey = zapPaymentRequest?.pubKeyHex?.toByteArray()
+ return zappedNote?.zappedAmount(privKey, pubKey) ?: BigDecimal.ZERO
}
fun sendZapPaymentRequestFor(bolt11: String, zappedNote: Note?, onResponse: (Response?) -> Unit) {
From 7167d57d225bb59c997a410c5a2604ea91ab68b3 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 13:49:41 -0400
Subject: [PATCH 09/41] v0.42.4
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 5d7307edb..60f23a644 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 145
- versionName "0.42.3"
+ versionCode 146
+ versionName "0.42.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
From 3519e20a98d7c8191cf964a16a2310a774ea85ba Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 18:48:37 -0400
Subject: [PATCH 10/41] BugFix for the empty selection
---
.../vitorpamplona/amethyst/ui/navigation/AppTopBar.kt | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
index 4415e4648..5aeb3a5f9 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
+import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
@@ -87,7 +88,7 @@ fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, ac
@Composable
fun StoriesTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account ->
- FollowList(account.defaultStoriesFollowList, true) { listName ->
+ FollowList(account.defaultStoriesFollowList, account.userProfile(),true) { listName ->
account.changeDefaultStoriesFollowList(listName)
}
}
@@ -96,7 +97,7 @@ fun StoriesTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
@Composable
fun HomeTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account ->
- FollowList(account.defaultHomeFollowList, false) { listName ->
+ FollowList(account.defaultHomeFollowList, account.userProfile(),false) { listName ->
account.changeDefaultHomeFollowList(listName)
}
}
@@ -226,7 +227,7 @@ private fun LoggedInUserPictureDrawer(
}
@Composable
-fun FollowList(listName: String, withGlobal: Boolean, onChange: (String) -> Unit) {
+fun FollowList(listName: String, loggedIn: User, withGlobal: Boolean, onChange: (String) -> Unit) {
// Notification
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
@@ -254,7 +255,7 @@ fun FollowList(listName: String, withGlobal: Boolean, onChange: (String) -> Unit
}
SimpleTextSpinner(
- placeholder = followLists.firstOrNull { it.first == listName }?.first ?: KIND3_FOLLOWS,
+ placeholder = followLists.firstOrNull { it.first == listName }?.second ?: "Select an Option",
options = followNames.value,
onSelect = {
onChange(followLists.getOrNull(it)?.first ?: KIND3_FOLLOWS)
From 0c673e91c6dd8f5930015d20bb58ecb10da15b19 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 18:49:13 -0400
Subject: [PATCH 11/41] BugFix for displaying other people's list after loading
them from their profile
---
.../com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
index 5aeb3a5f9..3cc324086 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
@@ -88,7 +88,7 @@ fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, ac
@Composable
fun StoriesTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account ->
- FollowList(account.defaultStoriesFollowList, account.userProfile(),true) { listName ->
+ FollowList(account.defaultStoriesFollowList, account.userProfile(), true) { listName ->
account.changeDefaultStoriesFollowList(listName)
}
}
@@ -97,7 +97,7 @@ fun StoriesTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
@Composable
fun HomeTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account ->
- FollowList(account.defaultHomeFollowList, account.userProfile(),false) { listName ->
+ FollowList(account.defaultHomeFollowList, account.userProfile(), false) { listName ->
account.changeDefaultHomeFollowList(listName)
}
}
@@ -245,7 +245,7 @@ fun FollowList(listName: String, loggedIn: User, withGlobal: Boolean, onChange:
followLists = defaultOptions + LocalCache.addressables.mapNotNull {
val event = (it.value.event as? PeopleListEvent)
// Has to have an list
- if (event != null && (event.tags.size > 1 || event.content.length > 50)) {
+ if (event != null && event.pubKey == loggedIn.pubkeyHex && (event.tags.size > 1 || event.content.length > 50)) {
Pair(event.dTag(), event.dTag())
} else {
null
From 635279d59af22313d48a7d54509a9f66f5c04973 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 18:49:32 -0400
Subject: [PATCH 12/41] Testing removing the gradient of quoted posts.
---
.../vitorpamplona/amethyst/ui/components/RichTextViewer.kt | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
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 31d03d4a5..b2b0e7824 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
@@ -380,8 +380,7 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
RoundedCornerShape(15.dp)
),
- parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f)
- .compositeOver(backgroundColor),
+ parentBackgroundColor = backgroundColor,
isQuotedNote = true,
navController = navController
)
@@ -530,8 +529,7 @@ fun TagLink(word: String, tags: List>, canPreview: Boolean, backgro
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
RoundedCornerShape(15.dp)
),
- parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f)
- .compositeOver(backgroundColor),
+ parentBackgroundColor = backgroundColor,
isQuotedNote = true,
navController = navController
)
From e3ccf71a84c190926562c6c5709e4dbe2b5acca7 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Fri, 5 May 2023 18:50:19 -0400
Subject: [PATCH 13/41] v0.42.5
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 60f23a644..bfcfb4b40 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 146
- versionName "0.42.4"
+ versionCode 147
+ versionName "0.42.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
From f3316f53eabbd2d32fa161b8c28de85edc173be1 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 09:24:39 -0400
Subject: [PATCH 14/41] Renames Hex.toByteArray() to avoid confusing with
String.toByteArray()
---
.../vitorpamplona/amethyst/LocalPreferences.kt | 4 ++--
.../vitorpamplona/amethyst/model/Account.kt | 18 +++++++++---------
.../com/vitorpamplona/amethyst/model/Hex.kt | 4 ++--
.../amethyst/service/model/ATag.kt | 4 ++--
.../amethyst/service/model/GeneralListEvent.kt | 4 ++--
.../service/model/LnZapPaymentRequestEvent.kt | 4 ++--
.../service/model/LnZapRequestEvent.kt | 6 +++---
.../amethyst/service/model/MuteListEvent.kt | 4 ++--
.../amethyst/service/nip19/Nip19.kt | 6 +++---
.../ui/screen/AccountStateViewModel.kt | 4 ++--
10 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
index 3882218b1..a3a16dda8 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
@@ -9,7 +9,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.RelaySetupInfo
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
@@ -276,7 +276,7 @@ object LocalPreferences {
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false)
val a = Account(
- Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
+ Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
followingChannels,
hiddenUsers,
localRelays,
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index 6c7b55d39..b15189cec 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -178,7 +178,7 @@ class Account(
}
fun isNIP47Author(pubkeyHex: String?): Boolean {
- val privKey = zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey
+ val privKey = zapPaymentRequest?.secret?.hexToByteArray() ?: loggedIn.privKey
if (privKey == null) return false
@@ -189,8 +189,8 @@ class Account(
fun decryptZapPaymentResponseEvent(zapResponseEvent: LnZapPaymentResponseEvent): Response? {
val myNip47 = zapPaymentRequest ?: return null
- val privKey = myNip47.secret?.toByteArray() ?: loggedIn.privKey
- val pubKey = myNip47.pubKeyHex.toByteArray()
+ val privKey = myNip47.secret?.hexToByteArray() ?: loggedIn.privKey
+ val pubKey = myNip47.pubKeyHex.hexToByteArray()
if (privKey == null) return null
@@ -202,8 +202,8 @@ class Account(
}
fun calculateZappedAmount(zappedNote: Note?): BigDecimal {
- val privKey = zapPaymentRequest?.secret?.toByteArray() ?: loggedIn.privKey
- val pubKey = zapPaymentRequest?.pubKeyHex?.toByteArray()
+ val privKey = zapPaymentRequest?.secret?.hexToByteArray() ?: loggedIn.privKey
+ val pubKey = zapPaymentRequest?.pubKeyHex?.hexToByteArray()
return zappedNote?.zappedAmount(privKey, pubKey) ?: BigDecimal.ZERO
}
@@ -211,16 +211,16 @@ class Account(
if (!isWriteable()) return
zapPaymentRequest?.let { nip47 ->
- val event = LnZapPaymentRequestEvent.create(bolt11, nip47.pubKeyHex, nip47.secret?.toByteArray() ?: loggedIn.privKey!!)
+ val event = LnZapPaymentRequestEvent.create(bolt11, nip47.pubKeyHex, nip47.secret?.hexToByteArray() ?: loggedIn.privKey!!)
val wcListener = NostrLnZapPaymentResponseDataSource(nip47.pubKeyHex, event.pubKey, event.id)
wcListener.start()
LocalCache.consume(event, zappedNote) {
// After the response is received.
- val privKey = nip47.secret?.toByteArray()
+ val privKey = nip47.secret?.hexToByteArray()
if (privKey != null) {
- onResponse(it.response(privKey, nip47.pubKeyHex.toByteArray()))
+ onResponse(it.response(privKey, nip47.pubKeyHex.hexToByteArray()))
}
}
@@ -826,7 +826,7 @@ class Account(
pubkeyToUse = recepientPK
}
- event.plainContent(loggedIn.privKey!!, pubkeyToUse.toByteArray())
+ event.plainContent(loggedIn.privKey!!, pubkeyToUse.hexToByteArray())
} else if (event is LnZapRequestEvent && loggedIn.privKey != null) {
decryptZapContentAuthor(note)?.content()
} else {
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 eed0507d5..a9ab40817 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt
@@ -29,7 +29,7 @@ fun ByteArray.toHexKey(): HexKey {
return toHex()
}
-fun HexKey.toByteArray(): ByteArray {
+fun HexKey.hexToByteArray(): ByteArray {
return Hex.decode(this)
}
@@ -39,7 +39,7 @@ fun HexKey.toDisplayHexKey(): String {
fun decodePublicKey(key: String): ByteArray {
val parsed = Nip19.uriToRoute(key)
- val pubKeyParsed = parsed?.hex?.toByteArray()
+ val pubKeyParsed = parsed?.hex?.hexToByteArray()
return if (key.startsWith("nsec")) {
Persona(privKey = key.bechToBytes()).pubKey
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt
index 27a39b4d8..24d705835 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ATag.kt
@@ -1,7 +1,7 @@
package com.vitorpamplona.amethyst.service.model
import android.util.Log
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.service.nip19.Tlv
import fr.acinq.secp256k1.Hex
@@ -14,7 +14,7 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
fun toNAddr(): String {
val kind = kind.toByteArray()
- val author = pubKeyHex.toByteArray()
+ val author = pubKeyHex.hexToByteArray()
val dTag = dTag.toByteArray(Charsets.UTF_8)
val relay = relay?.toByteArray(Charsets.UTF_8)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt
index dd236c96c..6b1940e47 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GeneralListEvent.kt
@@ -3,7 +3,7 @@ package com.vitorpamplona.amethyst.service.model
import android.util.Log
import com.google.gson.reflect.TypeToken
import com.vitorpamplona.amethyst.model.HexKey
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import nostr.postr.Utils
abstract class GeneralListEvent(
@@ -24,7 +24,7 @@ abstract class GeneralListEvent(
fun plainContent(privKey: ByteArray): String? {
return try {
- val sharedSecret = Utils.getSharedSecret(privKey, pubKey.toByteArray())
+ val sharedSecret = Utils.getSharedSecret(privKey, pubKey.hexToByteArray())
return Utils.decrypt(content, sharedSecret)
} catch (e: Exception) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
index 966ed230d..63464b5d9 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
@@ -6,7 +6,7 @@ import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.vitorpamplona.amethyst.model.HexKey
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import nostr.postr.Utils
import java.lang.reflect.Type
@@ -53,7 +53,7 @@ class LnZapPaymentRequestEvent(
val content = Utils.encrypt(
serializedRequest,
privateKey,
- walletServicePubkey.toByteArray()
+ walletServicePubkey.hexToByteArray()
)
val tags = mutableListOf>()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
index 0fc12d565..44cfb5213 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
@@ -59,7 +59,7 @@ class LnZapRequestEvent(
} else if (zapType == LnZapEvent.ZapType.PRIVATE) {
var encryptionPrivateKey = createEncryptionPrivateKey(privateKey.toHexKey(), originalNote.id(), createdAt)
var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson()
- var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, originalNote.pubKey().toByteArray())
+ var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, originalNote.pubKey().hexToByteArray())
tags = tags + listOf(listOf("anon", encryptedContent))
content = "" // make sure public content is empty, as the content is encrypted
privkey = encryptionPrivateKey // sign event with generated privkey
@@ -92,7 +92,7 @@ class LnZapRequestEvent(
} else if (zapType == LnZapEvent.ZapType.PRIVATE) {
var encryptionPrivateKey = createEncryptionPrivateKey(privateKey.toHexKey(), userHex, createdAt)
var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson()
- var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, userHex.toByteArray())
+ var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, userHex.hexToByteArray())
tags = tags + listOf(listOf("anon", encryptedContent))
content = ""
privkey = encryptionPrivateKey
@@ -157,7 +157,7 @@ class LnZapRequestEvent(
val encnote = anonTag[1]
if (encnote.isNotBlank()) {
try {
- val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.toByteArray())
+ val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.hexToByteArray())
val decryptedEvent = fromJson(note)
if (decryptedEvent.kind == 9733) {
return decryptedEvent
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt
index cb1f8aef7..a9f639a36 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MuteListEvent.kt
@@ -3,7 +3,7 @@ package com.vitorpamplona.amethyst.service.model
import android.util.Log
import com.google.gson.reflect.TypeToken
import com.vitorpamplona.amethyst.model.HexKey
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import nostr.postr.Utils
import java.util.Date
@@ -21,7 +21,7 @@ class MuteListEvent(
fun plainContent(privKey: ByteArray): String? {
return try {
- val sharedSecret = Utils.getSharedSecret(privKey, pubKey.toByteArray())
+ val sharedSecret = Utils.getSharedSecret(privKey, pubKey.hexToByteArray())
return Utils.decrypt(content, sharedSecret)
} catch (e: Exception) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt
index 6aa8dd754..4a42c1adf 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt
@@ -1,7 +1,7 @@
package com.vitorpamplona.amethyst.service.nip19
import android.util.Log
-import com.vitorpamplona.amethyst.model.toByteArray
+import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import nostr.postr.Bech32
import nostr.postr.bechToBytes
@@ -147,8 +147,8 @@ object Nip19 {
public fun createNEvent(idHex: String, author: String?, kind: Int?, relay: String?): String {
val kind = kind?.toByteArray()
- val author = author?.toByteArray()
- val idHex = idHex.toByteArray()
+ val author = author?.hexToByteArray()
+ val idHex = idHex.hexToByteArray()
val relay = relay?.toByteArray(Charsets.UTF_8)
var fullArray = byteArrayOf(Tlv.Type.SPECIAL.id, idHex.size.toByte()) + idHex
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 0118af459..04a81c790 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,7 +4,7 @@ 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.model.hexToByteArray
import com.vitorpamplona.amethyst.service.nip19.Nip19
import fr.acinq.secp256k1.Hex
import kotlinx.coroutines.CoroutineScope
@@ -42,7 +42,7 @@ class AccountStateViewModel() : ViewModel() {
fun startUI(key: String) {
val pattern = Pattern.compile(".+@.+\\.[a-z]+")
val parsed = Nip19.uriToRoute(key)
- val pubKeyParsed = parsed?.hex?.toByteArray()
+ val pubKeyParsed = parsed?.hex?.hexToByteArray()
val account =
if (key.startsWith("nsec")) {
From 97d56d61d1a0223543f1d6cf871d55afb43a0ed2 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 09:46:25 -0400
Subject: [PATCH 15/41] Caching Private Zap decryption
---
.../vitorpamplona/amethyst/PrivateZapTests.kt | 5 +-
.../vitorpamplona/amethyst/model/Account.kt | 4 +-
.../service/model/LnZapPaymentRequestEvent.kt | 12 ++-
.../model/LnZapPaymentResponseEvent.kt | 27 ++++--
.../service/model/LnZapRequestEvent.kt | 85 ++++++-------------
5 files changed, 60 insertions(+), 73 deletions(-)
diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt
index c131a8af3..3b35cdad1 100644
--- a/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt
+++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt
@@ -78,7 +78,7 @@ class PrivateZapTests {
if (recepientPK != null && recepientPost != null) {
val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt)
val decodedPrivateZap =
- LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK)
+ privateZapRequest.getPrivateZapEvent(privateKey, recepientPK)
println(decodedPrivateZap?.toJson())
assertNotNull(decodedPrivateZap)
@@ -127,8 +127,7 @@ class PrivateZapTests {
if (recepientPK != null && recepientPost != null) {
val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt)
- val decodedPrivateZap =
- LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK)
+ val decodedPrivateZap = privateZapRequest.getPrivateZapEvent(privateKey, recepientPK)
println(decodedPrivateZap?.toJson())
assertNotNull(decodedPrivateZap)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index b15189cec..2cb3cdf18 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -847,7 +847,7 @@ class Account(
val privateKeyToUse = loggedInPrivateKey
val pubkeyToUse = event.pubKey
- LnZapRequestEvent.checkForPrivateZap(event, privateKeyToUse, pubkeyToUse)
+ event.getPrivateZapEvent(privateKeyToUse, pubkeyToUse)
} else {
// if the sender is logged in, these are the params
val altPubkeyToUse = recipientPK
@@ -868,7 +868,7 @@ class Account(
}
if (altPrivateKeyToUse != null && altPubkeyToUse != null) {
- val result = LnZapRequestEvent.checkForPrivateZap(event, altPrivateKeyToUse, altPubkeyToUse)
+ val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse)
if (result == null) {
Log.w("Private ZAP Decrypt", "Fail to decrypt Zap from ${note.author?.toBestDisplayName()} ${note.idNote()}")
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
index 63464b5d9..4946fb4c9 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt
@@ -21,9 +21,17 @@ class LnZapPaymentRequestEvent(
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
+ // Once one of an app user decrypts the payment, all users else can see it.
+ @Transient
+ private var lnInvoice: String? = null
+
fun walletServicePubKey() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1)
fun lnInvoice(privKey: ByteArray, pubkey: ByteArray): String? {
+ if (lnInvoice != null) {
+ return lnInvoice
+ }
+
return try {
val sharedSecret = Utils.getSharedSecret(privKey, pubkey)
@@ -31,7 +39,9 @@ class LnZapPaymentRequestEvent(
val payInvoiceMethod = gson.fromJson(jsonText, Request::class.java)
- return (payInvoiceMethod as? PayInvoiceMethod)?.params?.invoice
+ lnInvoice = (payInvoiceMethod as? PayInvoiceMethod)?.params?.invoice
+
+ return lnInvoice
} catch (e: Exception) {
Log.w("BookmarkList", "Error decrypting the message ${e.message}")
null
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt
index 42f71f7c5..5d8ec2e95 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt
@@ -19,10 +19,14 @@ class LnZapPaymentResponseEvent(
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
+ // Once one of an app user decrypts the payment, all users else can see it.
+ @Transient
+ private var response: Response? = null
+
fun requestAuthor() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1)
fun requestId() = tags.firstOrNull() { it.size > 1 && it[0] == "e" }?.get(1)
- fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? {
+ private fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? {
return try {
val sharedSecret = Utils.getSharedSecret(privKey, pubKey)
@@ -39,16 +43,21 @@ class LnZapPaymentResponseEvent(
}
}
- fun response(privKey: ByteArray, pubKey: ByteArray): Response? = try {
- if (content.isNotEmpty()) {
- val decrypted = decrypt(privKey, pubKey)
- gson.fromJson(decrypted, Response::class.java)
- } else {
+ fun response(privKey: ByteArray, pubKey: ByteArray): Response? {
+ if (response != null) response
+
+ return try {
+ if (content.isNotEmpty()) {
+ val decrypted = decrypt(privKey, pubKey)
+ response = gson.fromJson(decrypted, Response::class.java)
+ response
+ } else {
+ null
+ }
+ } catch (e: Exception) {
+ Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e)
null
}
- } catch (e: Exception) {
- Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e)
- null
}
companion object {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
index 44cfb5213..8586849a6 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt
@@ -20,12 +20,37 @@ class LnZapRequestEvent(
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
+ @Transient
+ private var privateZapEvent: Event? = null
+
fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
fun isPrivateZap() = tags.any { t -> t.size >= 2 && t[0] == "anon" && t[1].isNotBlank() }
+ fun getPrivateZapEvent(loggedInUserPrivKey: ByteArray, pubKey: HexKey): Event? {
+ if (privateZapEvent != null) return privateZapEvent
+
+ val anonTag = tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" }
+ if (anonTag != null) {
+ val encnote = anonTag[1]
+ if (encnote.isNotBlank()) {
+ try {
+ val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.hexToByteArray())
+ val decryptedEvent = fromJson(note)
+ if (decryptedEvent.kind == 9733) {
+ privateZapEvent = decryptedEvent
+ return privateZapEvent
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ return null
+ }
+
companion object {
const val kind = 9734
@@ -109,7 +134,7 @@ class LnZapRequestEvent(
return sha256.digest(strbyte)
}
- fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String {
+ private fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String {
var sharedSecret = Utils.getSharedSecret(privkey, pubkey)
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
@@ -128,7 +153,7 @@ class LnZapRequestEvent(
return encryptedMsgBech32 + "_" + ivBech32
}
- fun decryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String {
+ private fun decryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String {
var sharedSecret = Utils.getSharedSecret(privkey, pubkey)
if (sharedSecret.size != 16 && sharedSecret.size != 32) {
throw IllegalArgumentException("Invalid shared secret size")
@@ -150,61 +175,5 @@ class LnZapRequestEvent(
throw IllegalArgumentException("Bad padding: ${ex.message}")
}
}
-
- fun checkForPrivateZap(zapRequest: LnZapRequestEvent, loggedInUserPrivKey: ByteArray, pubKey: HexKey): Event? {
- val anonTag = zapRequest.tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" }
- if (anonTag != null) {
- val encnote = anonTag[1]
- if (encnote.isNotBlank()) {
- try {
- val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.hexToByteArray())
- val decryptedEvent = fromJson(note)
- if (decryptedEvent.kind == 9733) {
- return decryptedEvent
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- }
- return null
- }
}
}
-/*
-{
- "pubkey": "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",
- "content": "",
- "id": "d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d",
- "created_at": 1674164539,
- "sig": "77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d",
- "kind": 9734,
- "tags": [
- [
- "e",
- "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"
- ],
- [
- "p",
- "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
- ],
- [
- "relays",
- "wss://relay.damus.io",
- "wss://nostr-relay.wlvs.space",
- "wss://nostr.fmt.wiz.biz",
- "wss://relay.nostr.bg",
- "wss://nostr.oxtr.dev",
- "wss://nostr.v0l.io",
- "wss://brb.io",
- "wss://nostr.bitcoiner.social",
- "ws://monad.jb55.com:8080",
- "wss://relay.snort.social"
- ],
- [
- "poll_option", "n"
- ]
- ],
- "ots": // TODO
-}
-*/
From fbbb9b0d72e3f83e8d7c01dcd351abf9d8718191 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 09:53:44 -0400
Subject: [PATCH 16/41] Increasing sensitivity for Language identification
options
---
.../amethyst/service/lang/LanguageTranslatorService.kt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt b/app/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt
index 89c1e38de..e5d99a649 100644
--- a/app/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt
+++ b/app/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt
@@ -4,6 +4,7 @@ import android.util.LruCache
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.mlkit.nl.languageid.LanguageIdentification
+import com.google.mlkit.nl.languageid.LanguageIdentificationOptions
import com.google.mlkit.nl.translate.TranslateLanguage
import com.google.mlkit.nl.translate.Translation
import com.google.mlkit.nl.translate.Translator
@@ -20,7 +21,8 @@ class ResultOrError(
)
object LanguageTranslatorService {
- private val languageIdentification = LanguageIdentification.getClient()
+ private val options = LanguageIdentificationOptions.Builder().setConfidenceThreshold(0.6f).build()
+ private val languageIdentification = LanguageIdentification.getClient(options)
val lnRegex = Pattern.compile("\\blnbc[a-z0-9]+\\b", Pattern.CASE_INSENSITIVE)
val tagRegex = Pattern.compile("(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)", Pattern.CASE_INSENSITIVE)
From 79da9cee01bed3c9a6a0c3e0e021086da24e74d4 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 11:49:53 -0400
Subject: [PATCH 17/41] NoteCompose refactoring
---
.../amethyst/service/model/PrivateDmEvent.kt | 5 +
.../amethyst/ui/components/RichTextViewer.kt | 4 +-
.../amethyst/ui/note/BadgeCompose.kt | 12 +-
.../amethyst/ui/note/BlankNote.kt | 2 +-
.../amethyst/ui/note/BoostSetCompose.kt | 13 +-
.../amethyst/ui/note/LikeSetCompose.kt | 13 +-
.../amethyst/ui/note/MessageSetCompose.kt | 30 +-
.../amethyst/ui/note/MultiSetCompose.kt | 34 +-
.../amethyst/ui/note/NoteCompose.kt | 882 ++++++++++++------
.../amethyst/ui/note/ZapSetCompose.kt | 13 +-
.../amethyst/ui/note/ZapUserSetCompose.kt | 2 +-
.../amethyst/ui/screen/CardFeedView.kt | 115 +--
.../amethyst/ui/screen/ThreadFeedView.kt | 2 +-
13 files changed, 665 insertions(+), 462 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
index c45c27848..e67083cc0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
@@ -31,6 +31,11 @@ class PrivateDmEvent(
*/
fun replyTo() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
+ fun with(pubkeyHex: String): Boolean {
+ return pubkeyHex == pubKey ||
+ tags.firstOrNull { it.size > 1 && it[0] == "p" }?.getOrNull(1) == pubkeyHex
+ }
+
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
return try {
val sharedSecret = Utils.getSharedSecret(privKey, pubKey)
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 b2b0e7824..f0e8a89b8 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
@@ -359,9 +359,9 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
baseNotePair = Pair(note, it.additionalChars)
}
- } else {
- nip19Route = it
}
+
+ nip19Route = it
}
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
index 5e4fef8b2..85325fc6c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
@@ -34,7 +34,6 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
@@ -48,7 +47,6 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
val context = LocalContext.current.applicationContext
- val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
if (note == null) {
@@ -73,15 +71,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent !is ChannelMessageEvent) {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- } else {
- note.channel()?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- }
+ routeFor(note, accountViewModel.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt
index e25b5ba26..98aa24cdb 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt
@@ -74,7 +74,7 @@ fun HiddenNote(reports: Set, loggedIn: User, modifier: Modifier = Modifier
FlowRow(modifier = Modifier.padding(top = 10.dp)) {
reports.forEach {
NoteAuthorPicture(
- note = it,
+ baseNote = it,
navController = navController,
userAccount = loggedIn,
size = 35.dp
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt
index da4d768e0..107e02fe1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt
@@ -28,7 +28,6 @@ import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.BoostSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
@@ -68,15 +67,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent !is ChannelMessageEvent) {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- } else {
- note.channel()?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- }
+ routeFor(note, account.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
@@ -109,7 +100,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
FlowRow() {
boostSetCard.boostEvents.forEach {
NoteAuthorPicture(
- note = it,
+ baseNote = it,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt
index 2d347b9f7..9436512a8 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt
@@ -28,7 +28,6 @@ import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.LikeSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
@@ -68,15 +67,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent !is ChannelMessageEvent) {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- } else {
- note.channel()?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- }
+ routeFor(note, account.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
@@ -109,7 +100,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
FlowRow() {
likeSetCard.likeEvents.forEach {
NoteAuthorPicture(
- note = it,
+ baseNote = it,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
index 8deb5bcb1..f7f41e3cd 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
@@ -26,9 +26,6 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
-import com.vitorpamplona.amethyst.model.LocalCache
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
-import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
@@ -66,30 +63,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent is ChannelMessageEvent) {
- note.channel()?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- } else if (noteEvent is PrivateDmEvent) {
- val replyAuthorBase =
- (note.event as? PrivateDmEvent)
- ?.recipientPubKey()
- ?.let { LocalCache.getOrCreateUser(it) }
-
- var userToComposeOn = note.author!!
-
- if (replyAuthorBase != null) {
- if (note.author == accountViewModel.userProfile()) {
- userToComposeOn = replyAuthorBase
- }
- }
-
- navController.navigate("Room/${userToComposeOn.pubkeyHex}")
- } else {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- }
+ routeFor(note, accountViewModel.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
@@ -120,7 +94,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
NoteCompose(
- baseNote = note,
+ baseNote = messageSetCard.note,
routeForLastRead = null,
isBoostedNote = true,
addMarginTop = false,
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index 95433ce74..a27c7e9df 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -37,9 +37,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
-import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -83,32 +81,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
.background(backgroundColor)
.combinedClickable(
onClick = {
- if (noteEvent is ChannelMessageEvent) {
- note
- .channel()
- ?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- } else if (noteEvent is PrivateDmEvent) {
- val replyAuthorBase =
- (note.event as? PrivateDmEvent)
- ?.recipientPubKey()
- ?.let { LocalCache.getOrCreateUser(it) }
-
- var userToComposeOn = note.author!!
-
- if (replyAuthorBase != null) {
- if (note.author == accountViewModel.userProfile()) {
- userToComposeOn = replyAuthorBase
- }
- }
-
- navController.navigate("Room/${userToComposeOn.pubkeyHex}")
- } else {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- }
+ routeFor(note, account.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
@@ -195,7 +168,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
}
NoteCompose(
- baseNote = note,
+ baseNote = multiSetCard.note,
routeForLastRead = null,
modifier = Modifier.padding(top = 5.dp),
isBoostedNote = true,
@@ -225,7 +198,7 @@ fun AuthorGalleryZaps(
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
authorNotes.forEach {
- AuthorPictureAndComment(it.key, it.value, navController, accountUser, accountViewModel)
+ AuthorPictureAndComment(it.key, navController, accountUser, accountViewModel)
}
}
}
@@ -234,7 +207,6 @@ fun AuthorGalleryZaps(
@Composable
private fun AuthorPictureAndComment(
zapRequest: Note,
- zapEvent: Note,
navController: NavController,
accountUser: User,
accountViewModel: AccountViewModel
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 ad71c37ae..545650749 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
@@ -127,10 +127,6 @@ fun NoteComposeInner(
var popupExpanded by remember { mutableStateOf(false) }
var showHiddenNote by remember { mutableStateOf(false) }
- val context = LocalContext.current.applicationContext
-
- var moreActionsExpanded by remember { mutableStateOf(false) }
-
var isAcceptable by remember { mutableStateOf(true) }
var canPreview by remember { mutableStateOf(true) }
@@ -205,28 +201,7 @@ fun NoteComposeInner(
Column(
modifier = modifier.combinedClickable(
onClick = {
- if (noteEvent is ChannelMessageEvent) {
- baseChannel?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- } else if (noteEvent is PrivateDmEvent) {
- val replyAuthorBase =
- (note.event as? PrivateDmEvent)
- ?.recipientPubKey()
- ?.let { LocalCache.getOrCreateUser(it) }
-
- var userToComposeOn = note.author!!
-
- if (replyAuthorBase != null) {
- if (note.author == accountViewModel.userProfile()) {
- userToComposeOn = replyAuthorBase
- }
- }
-
- navController.navigate("Room/${userToComposeOn.pubkeyHex}")
- } else {
- navController.navigate("Note/${note.idHex}")
- }
+ routeFor(note, loggedIn)?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
).background(backgroundColor)
@@ -244,281 +219,88 @@ fun NoteComposeInner(
}
Column(
- modifier = Modifier.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
+ modifier = Modifier
+ .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (isQuotedNote) {
- NoteAuthorPicture(note, navController, loggedIn, 25.dp)
- Spacer(Modifier.padding(horizontal = 5.dp))
- NoteUsernameDisplay(note, Modifier.weight(1f))
- } else {
- NoteUsernameDisplay(note, Modifier.weight(1f))
- }
+ FirstUserInfoRow(
+ baseNote = baseNote,
+ showAuthorPicture = isQuotedNote,
+ account = account,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
- if (noteEvent is RepostEvent) {
- Text(
- " ${stringResource(id = R.string.boosted)}",
- fontWeight = FontWeight.Bold,
- color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
- )
- } else {
- DisplayFollowingHashtagsInPost(noteEvent, account, navController)
- }
-
- Text(
- timeAgo(note.createdAt(), context = context),
- color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
- maxLines = 1
+ if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
+ SecondUserInfoRow(
+ note,
+ account,
+ navController
)
-
- IconButton(
- modifier = Modifier.size(24.dp),
- onClick = { moreActionsExpanded = true }
- ) {
- Icon(
- imageVector = Icons.Default.MoreVert,
- null,
- modifier = Modifier.size(15.dp),
- tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
- )
-
- NoteDropDownMenu(baseNote, moreActionsExpanded, { moreActionsExpanded = false }, accountViewModel)
- }
- }
-
- if (noteEvent !is RepostEvent && note.author != null && !makeItShort && !isQuotedNote) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- ObserveDisplayNip05Status(note.author!!, Modifier.weight(1f))
-
- val baseReward = noteEvent.getReward()
- if (baseReward != null) {
- DisplayReward(baseReward, baseNote, account, navController)
- }
-
- val pow = noteEvent.getPoWRank()
- if (pow > 20) {
- DisplayPoW(pow)
- }
- }
}
Spacer(modifier = Modifier.height(2.dp))
- if (!makeItShort && noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.mentions().isNotEmpty())) {
- val replyingDirectlyTo = note.replyTo?.lastOrNull()
- if (replyingDirectlyTo != null && unPackReply) {
- NoteCompose(
- baseNote = replyingDirectlyTo,
- isQuotedNote = true,
- modifier = Modifier
- .padding(top = 5.dp)
- .fillMaxWidth()
- .clip(shape = RoundedCornerShape(15.dp))
- .border(
- 1.dp,
- MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
- RoundedCornerShape(15.dp)
- ),
- unPackReply = false,
- makeItShort = true,
- parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f).compositeOver(backgroundColor),
- accountViewModel = accountViewModel,
- navController = navController
- )
- } else {
- ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController)
- }
- Spacer(modifier = Modifier.height(5.dp))
- } else if (!makeItShort && noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.mentions().isNotEmpty())) {
- val sortedMentions = noteEvent.mentions()
- .mapNotNull { LocalCache.checkGetOrCreateUser(it) }
- .toSet()
- .sortedBy { loggedIn.isFollowingCached(it) }
-
- note.channel()?.let {
- ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
- }
- Spacer(modifier = Modifier.height(5.dp))
+ if (!makeItShort) {
+ ReplyRow(
+ note,
+ unPackReply,
+ backgroundColor,
+ account,
+ accountViewModel,
+ navController
+ )
}
- if (noteEvent is ReactionEvent || noteEvent is RepostEvent) {
- note.replyTo?.lastOrNull()?.let {
- NoteCompose(
- it,
- modifier = Modifier,
- isBoostedNote = true,
- unPackReply = false,
- parentBackgroundColor = backgroundColor,
- accountViewModel = accountViewModel,
- navController = navController
+ when (noteEvent) {
+ is ReactionEvent -> {
+ RenderReaction(note, backgroundColor, accountViewModel, navController)
+ }
+
+ is RepostEvent -> {
+ RenderRepost(note, backgroundColor, accountViewModel, navController)
+ }
+
+ is ReportEvent -> {
+ RenderReport(note)
+ }
+
+ is LongTextNoteEvent -> {
+ RenderLongFormContent(note, loggedIn, accountViewModel, navController)
+ }
+
+ is BadgeAwardEvent -> {
+ RenderBadgeAward(note, backgroundColor, accountViewModel, navController)
+ }
+
+ is PrivateDmEvent -> {
+ RenderPrivateMessage(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController)
+ }
+
+ is HighlightEvent -> {
+ RenderHighlight(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController)
+ }
+
+ is PollNoteEvent -> {
+ RenderPoll(
+ note,
+ makeItShort,
+ canPreview,
+ backgroundColor,
+ accountViewModel,
+ navController
)
}
- // Reposts have trash in their contents.
- if (noteEvent is ReactionEvent) {
- val refactorReactionText =
- if (noteEvent.content == "+") "❤" else noteEvent.content
-
- Text(
- text = refactorReactionText
+ else -> {
+ RenderTextEvent(
+ note,
+ makeItShort,
+ canPreview,
+ backgroundColor,
+ accountViewModel,
+ navController
)
}
- } else if (noteEvent is ReportEvent) {
- val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map {
- when (it.reportType) {
- ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
- ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
- ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
- ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
- ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
- ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
- }
- }.toSet().joinToString(", ")
-
- Text(
- text = reportType
- )
-
- Divider(
- modifier = Modifier.padding(top = 40.dp),
- thickness = 0.25.dp
- )
- } else if (noteEvent is LongTextNoteEvent) {
- LongFormHeader(noteEvent, note, loggedIn)
-
- ReactionsRow(note, accountViewModel, navController)
-
- Divider(
- modifier = Modifier.padding(top = 10.dp),
- thickness = 0.25.dp
- )
- } else if (noteEvent is BadgeAwardEvent && !note.replyTo.isNullOrEmpty()) {
- Text(text = stringResource(R.string.award_granted_to))
-
- FlowRow(modifier = Modifier.padding(top = 5.dp)) {
- noteEvent.awardees()
- .map { LocalCache.getOrCreateUser(it) }
- .forEach {
- UserPicture(
- user = it,
- navController = navController,
- userAccount = loggedIn,
- size = 35.dp
- )
- }
- }
-
- note.replyTo?.firstOrNull()?.let {
- NoteCompose(
- it,
- modifier = Modifier,
- isBoostedNote = false,
- isQuotedNote = true,
- unPackReply = false,
- parentBackgroundColor = backgroundColor,
- accountViewModel = accountViewModel,
- navController = navController
- )
- }
-
- ReactionsRow(note, accountViewModel, navController)
-
- Divider(
- modifier = Modifier.padding(top = 10.dp),
- thickness = 0.25.dp
- )
- } else if (noteEvent is PrivateDmEvent &&
- noteEvent.recipientPubKey() != loggedIn.pubkeyHex &&
- note.author !== loggedIn
- ) {
- val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) }
-
- TranslatableRichTextViewer(
- stringResource(
- id = R.string.private_conversation_notification,
- "@${note.author?.pubkeyNpub()}",
- "@${recipient?.pubkeyNpub()}"
- ),
- canPreview = !makeItShort,
- Modifier.fillMaxWidth(),
- noteEvent.tags(),
- backgroundColor,
- accountViewModel,
- navController
- )
-
- if (!makeItShort) {
- ReactionsRow(note, accountViewModel, navController)
- }
-
- Divider(
- modifier = Modifier.padding(top = 10.dp),
- thickness = 0.25.dp
- )
- } else if (noteEvent is HighlightEvent) {
- DisplayHighlight(
- noteEvent.quote(),
- noteEvent.author(),
- noteEvent.inUrl(),
- makeItShort,
- canPreview,
- backgroundColor,
- accountViewModel,
- navController
- )
-
- if (!makeItShort) {
- ReactionsRow(note, accountViewModel, navController)
- }
-
- Divider(
- modifier = Modifier.padding(top = 10.dp),
- thickness = 0.25.dp
- )
- } else {
- val eventContent = accountViewModel.decrypt(note)
-
- if (eventContent != null) {
- if (makeItShort && note.author == loggedIn) {
- Text(
- text = eventContent,
- color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
- maxLines = 2,
- overflow = TextOverflow.Ellipsis
- )
- } else {
- TranslatableRichTextViewer(
- eventContent,
- canPreview = canPreview && !makeItShort,
- Modifier.fillMaxWidth(),
- noteEvent.tags(),
- backgroundColor,
- accountViewModel,
- navController
- )
-
- DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController)
- }
-
- if (noteEvent is PollNoteEvent) {
- PollNote(
- note,
- canPreview = canPreview && !makeItShort,
- backgroundColor,
- accountViewModel,
- navController
- )
- }
- }
-
- if (!makeItShort) {
- ReactionsRow(note, accountViewModel, navController)
- }
-
- Divider(
- modifier = Modifier.padding(top = 10.dp),
- thickness = 0.25.dp
- )
}
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
@@ -528,6 +310,508 @@ fun NoteComposeInner(
}
}
+fun routeFor(note: Note, loggedIn: User): String? {
+ val noteEvent = note.event
+
+ if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) {
+ note.channel()?.let {
+ return "Channel/${it.idHex}"
+ }
+ } else if (noteEvent is PrivateDmEvent) {
+ val replyAuthorBase =
+ (note.event as? PrivateDmEvent)
+ ?.recipientPubKey()
+ ?.let { LocalCache.getOrCreateUser(it) }
+
+ var userToComposeOn = note.author!!
+
+ if (replyAuthorBase != null) {
+ if (note.author == loggedIn) {
+ userToComposeOn = replyAuthorBase
+ }
+ }
+
+ return "Room/${userToComposeOn.pubkeyHex}"
+ } else {
+ return "Note/${note.idHex}"
+ }
+
+ return null
+}
+
+@Composable
+private fun RenderTextEvent(
+ note: Note,
+ makeItShort: Boolean,
+ canPreview: Boolean,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event ?: return
+
+ val eventContent = accountViewModel.decrypt(note)
+
+ if (eventContent != null) {
+ if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
+ Text(
+ text = eventContent,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis
+ )
+ } else {
+ TranslatableRichTextViewer(
+ content = eventContent,
+ canPreview = canPreview && !makeItShort,
+ modifier = Modifier.fillMaxWidth(),
+ tags = noteEvent.tags(),
+ backgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+
+ DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController)
+ }
+ }
+
+ if (!makeItShort) {
+ ReactionsRow(note, accountViewModel, navController)
+ }
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderPoll(
+ note: Note,
+ makeItShort: Boolean,
+ canPreview: Boolean,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event as? PollNoteEvent ?: return
+ val eventContent = noteEvent.content()
+
+ if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
+ Text(
+ text = eventContent,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis
+ )
+ } else {
+ TranslatableRichTextViewer(
+ eventContent,
+ canPreview = canPreview && !makeItShort,
+ Modifier.fillMaxWidth(),
+ noteEvent.tags(),
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
+
+ DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController)
+
+ PollNote(
+ note,
+ canPreview = canPreview && !makeItShort,
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
+ }
+
+ if (!makeItShort) {
+ ReactionsRow(note, accountViewModel, navController)
+ }
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderHighlight(
+ note: Note,
+ makeItShort: Boolean,
+ canPreview: Boolean,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event as? HighlightEvent ?: return
+
+ DisplayHighlight(
+ noteEvent.quote(),
+ noteEvent.author(),
+ noteEvent.inUrl(),
+ makeItShort,
+ canPreview,
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
+
+ if (!makeItShort) {
+ ReactionsRow(note, accountViewModel, navController)
+ }
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderPrivateMessage(
+ note: Note,
+ makeItShort: Boolean,
+ canPreview: Boolean,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event as? PrivateDmEvent ?: return
+ val withMe = noteEvent.with(accountViewModel.userProfile().pubkeyHex)
+
+ if (withMe) {
+ val eventContent = accountViewModel.decrypt(note)
+
+ if (eventContent != null) {
+ if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
+ Text(
+ text = eventContent,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis
+ )
+ } else {
+ TranslatableRichTextViewer(
+ content = eventContent,
+ canPreview = canPreview && !makeItShort,
+ modifier = Modifier.fillMaxWidth(),
+ tags = noteEvent.tags(),
+ backgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+
+ DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController)
+ }
+ }
+ } else {
+ val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) }
+
+ TranslatableRichTextViewer(
+ stringResource(
+ id = R.string.private_conversation_notification,
+ "@${note.author?.pubkeyNpub()}",
+ "@${recipient?.pubkeyNpub()}"
+ ),
+ canPreview = !makeItShort,
+ Modifier.fillMaxWidth(),
+ noteEvent.tags(),
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
+ }
+
+ if (!makeItShort) {
+ ReactionsRow(note, accountViewModel, navController)
+ }
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderBadgeAward(
+ note: Note,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ if (note.replyTo.isNullOrEmpty()) return
+
+ val noteEvent = note.event as? BadgeAwardEvent ?: return
+
+ Text(text = stringResource(R.string.award_granted_to))
+
+ FlowRow(modifier = Modifier.padding(top = 5.dp)) {
+ noteEvent.awardees()
+ .map { LocalCache.getOrCreateUser(it) }
+ .forEach {
+ UserPicture(
+ user = it,
+ navController = navController,
+ userAccount = accountViewModel.userProfile(),
+ size = 35.dp
+ )
+ }
+ }
+
+ note.replyTo?.firstOrNull()?.let {
+ NoteCompose(
+ it,
+ modifier = Modifier,
+ isBoostedNote = false,
+ isQuotedNote = true,
+ unPackReply = false,
+ parentBackgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
+
+ ReactionsRow(note, accountViewModel, navController)
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderReaction(
+ note: Note,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ note.replyTo?.lastOrNull()?.let {
+ NoteCompose(
+ it,
+ modifier = Modifier,
+ isBoostedNote = true,
+ unPackReply = false,
+ parentBackgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
+
+ // Reposts have trash in their contents.
+ val refactorReactionText =
+ if (note.event?.content() == "+") "❤" else note.event?.content() ?: ""
+
+ Text(
+ text = refactorReactionText
+ )
+}
+
+@Composable
+private fun RenderRepost(
+ note: Note,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ note.replyTo?.lastOrNull()?.let {
+ NoteCompose(
+ it,
+ modifier = Modifier,
+ isBoostedNote = true,
+ unPackReply = false,
+ parentBackgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
+}
+
+@Composable
+private fun RenderLongFormContent(
+ note: Note,
+ loggedIn: User,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event as? LongTextNoteEvent ?: return
+
+ LongFormHeader(noteEvent, note, loggedIn)
+
+ ReactionsRow(note, accountViewModel, navController)
+
+ Divider(
+ modifier = Modifier.padding(top = 10.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun RenderReport(note: Note) {
+ val noteEvent = note.event as? ReportEvent ?: return
+
+ val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map {
+ when (it.reportType) {
+ ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
+ ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
+ ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
+ ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
+ ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
+ ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
+ }
+ }.toSet().joinToString(", ")
+
+ Text(
+ text = reportType
+ )
+
+ Divider(
+ modifier = Modifier.padding(top = 40.dp),
+ thickness = 0.25.dp
+ )
+}
+
+@Composable
+private fun ReplyRow(
+ note: Note,
+ unPackReply: Boolean,
+ backgroundColor: Color,
+ account: Account,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ val noteEvent = note.event
+
+ if (noteEvent is TextNoteEvent &&
+ (note.replyTo != null || noteEvent.mentions().isNotEmpty())
+ ) {
+ val replyingDirectlyTo = note.replyTo?.lastOrNull()
+ if (replyingDirectlyTo != null && unPackReply) {
+ NoteCompose(
+ baseNote = replyingDirectlyTo,
+ isQuotedNote = true,
+ modifier = Modifier
+ .padding(top = 5.dp)
+ .fillMaxWidth()
+ .clip(shape = RoundedCornerShape(15.dp))
+ .border(
+ 1.dp,
+ MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
+ RoundedCornerShape(15.dp)
+ ),
+ unPackReply = false,
+ makeItShort = true,
+ parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f)
+ .compositeOver(backgroundColor),
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ } else {
+ ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController)
+ }
+
+ Spacer(modifier = Modifier.height(5.dp))
+ } else if (
+ noteEvent is ChannelMessageEvent &&
+ (note.replyTo != null || noteEvent.mentions().isNotEmpty())
+ ) {
+ val sortedMentions = noteEvent.mentions()
+ .mapNotNull { LocalCache.checkGetOrCreateUser(it) }
+ .toSet()
+ .sortedBy { account.isFollowing(it) }
+
+ note.channel()?.let {
+ ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
+ }
+
+ Spacer(modifier = Modifier.height(5.dp))
+ }
+}
+
+@Composable
+private fun SecondUserInfoRow(
+ note: Note,
+ account: Account,
+ navController: NavController
+) {
+ val noteEvent = note.event ?: return
+ val noteAuthor = note.author ?: return
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f))
+
+ val baseReward = noteEvent.getReward()
+ if (baseReward != null) {
+ DisplayReward(baseReward, note, account, navController)
+ }
+
+ val pow = noteEvent.getPoWRank()
+ if (pow > 20) {
+ DisplayPoW(pow)
+ }
+ }
+}
+
+@Composable
+private fun FirstUserInfoRow(
+ baseNote: Note,
+ showAuthorPicture: Boolean,
+ account: Account,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ var moreActionsExpanded by remember { mutableStateOf(false) }
+ val context = LocalContext.current.applicationContext
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ if (showAuthorPicture) {
+ NoteAuthorPicture(baseNote, navController, account.userProfile(), 25.dp)
+ Spacer(Modifier.padding(horizontal = 5.dp))
+ NoteUsernameDisplay(baseNote, Modifier.weight(1f))
+ } else {
+ NoteUsernameDisplay(baseNote, Modifier.weight(1f))
+ }
+
+ if (baseNote.event is RepostEvent) {
+ Text(
+ " ${stringResource(id = R.string.boosted)}",
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
+ )
+ } else {
+ baseNote.event?.let {
+ DisplayFollowingHashtagsInPost(it, account, navController)
+ }
+ }
+
+ Text(
+ timeAgo(baseNote.createdAt(), context = context),
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 1
+ )
+
+ IconButton(
+ modifier = Modifier.size(24.dp),
+ onClick = { moreActionsExpanded = true }
+ ) {
+ Icon(
+ imageVector = Icons.Default.MoreVert,
+ null,
+ modifier = Modifier.size(15.dp),
+ tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
+ )
+
+ NoteDropDownMenu(
+ baseNote,
+ moreActionsExpanded,
+ { moreActionsExpanded = false },
+ accountViewModel
+ )
+ }
+ }
+}
+
@Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
val baseChannel = baseNote.channel()
@@ -626,17 +910,15 @@ fun DisplayHighlight(
) {
val quote = highlight.split("\n").map { "> *${it.removeSuffix(" ")}*" }.joinToString("\n")
- if (quote != null) {
- TranslatableRichTextViewer(
- quote,
- canPreview = canPreview && !makeItShort,
- Modifier.fillMaxWidth(),
- emptyList(),
- backgroundColor,
- accountViewModel,
- navController
- )
- }
+ TranslatableRichTextViewer(
+ quote,
+ canPreview = canPreview && !makeItShort,
+ Modifier.fillMaxWidth(),
+ emptyList(),
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
FlowRow() {
authorHex?.let { authorHex ->
@@ -1158,13 +1440,13 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) {
@Composable
fun NoteAuthorPicture(
- note: Note,
+ baseNote: Note,
navController: NavController,
userAccount: User,
size: Dp,
pictureModifier: Modifier = Modifier
) {
- NoteAuthorPicture(note, userAccount, size, pictureModifier) {
+ NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) {
navController.navigate("User/${it.pubkeyHex}")
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt
index 8e9c2ed0d..d0c8d7b78 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt
@@ -29,7 +29,6 @@ import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
-import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
@@ -70,15 +69,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent !is ChannelMessageEvent) {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- } else {
- note.channel()?.let {
- navController.navigate("Channel/${it.idHex}")
- }
- }
+ routeFor(note, account.userProfile())?.let { navController.navigate(it) }
},
onLongClick = { popupExpanded = true }
)
@@ -113,7 +104,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
FlowRow() {
zapSetCard.zapEvents.forEach {
NoteAuthorPicture(
- note = it.key,
+ baseNote = it.key,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt
index 7d0094295..70aa994f7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt
@@ -94,7 +94,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
FlowRow() {
zapSetCard.zapEvents.forEach {
NoteAuthorPicture(
- note = it.key,
+ baseNote = it.key,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
index fb3797759..f355c6fd9 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen
+import android.util.Log
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
@@ -32,6 +33,8 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
+import kotlin.time.ExperimentalTime
+import kotlin.time.measureTimedValue
@OptIn(ExperimentalMaterialApi::class)
@Composable
@@ -85,6 +88,7 @@ fun CardFeedView(
}
}
+@OptIn(ExperimentalTime::class)
@Composable
private fun FeedLoaded(
state: CardFeedState.Loaded,
@@ -114,61 +118,64 @@ private fun FeedLoaded(
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
- when (item) {
- is NoteCard -> NoteCompose(
- item.note,
- isBoostedNote = false,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is ZapSetCard -> ZapSetCompose(
- item,
- isInnerNote = false,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is ZapUserSetCard -> ZapUserSetCompose(
- item,
- isInnerNote = false,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is LikeSetCard -> LikeSetCompose(
- item,
- isInnerNote = false,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is BoostSetCard -> BoostSetCompose(
- item,
- isInnerNote = false,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is MultiSetCard -> MultiSetCompose(
- item,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is BadgeCard -> BadgeCompose(
- item,
- accountViewModel = accountViewModel,
- navController = navController,
- routeForLastRead = routeForLastRead
- )
- is MessageSetCard -> MessageSetCompose(
- messageSetCard = item,
- routeForLastRead = routeForLastRead,
- accountViewModel = accountViewModel,
- navController = navController
- )
+ val (value, elapsed) = measureTimedValue {
+ when (item) {
+ is NoteCard -> NoteCompose(
+ item.note,
+ isBoostedNote = false,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is ZapSetCard -> ZapSetCompose(
+ item,
+ isInnerNote = false,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is ZapUserSetCard -> ZapUserSetCompose(
+ item,
+ isInnerNote = false,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is LikeSetCard -> LikeSetCompose(
+ item,
+ isInnerNote = false,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is BoostSetCard -> BoostSetCompose(
+ item,
+ isInnerNote = false,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is MultiSetCard -> MultiSetCompose(
+ item,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is BadgeCard -> BadgeCompose(
+ item,
+ accountViewModel = accountViewModel,
+ navController = navController,
+ routeForLastRead = routeForLastRead
+ )
+ is MessageSetCard -> MessageSetCompose(
+ messageSetCard = item,
+ routeForLastRead = routeForLastRead,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
}
+ Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed")
}
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt
index 045ce13d8..0fbcd2ecc 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt
@@ -249,7 +249,7 @@ fun NoteMaster(
})
) {
NoteAuthorPicture(
- note = baseNote,
+ baseNote = baseNote,
navController = navController,
userAccount = account.userProfile(),
size = 55.dp
From 795120e9325606839f04467c61bf77553f33bfd9 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 19:17:39 -0400
Subject: [PATCH 18/41] Improving performance of Badge events
---
.../amethyst/service/model/BadgeAwardEvent.kt | 8 ++------
.../amethyst/service/model/BadgeDefinitionEvent.kt | 10 +++++-----
2 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeAwardEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeAwardEvent.kt
index 6269f63c0..c16abc20e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeAwardEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeAwardEvent.kt
@@ -10,13 +10,9 @@ class BadgeAwardEvent(
content: String,
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
- fun awardees() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
- fun awardDefinition() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {
- val aTagValue = it.getOrNull(1)
- val relay = it.getOrNull(2)
+ fun awardees() = taggedUsers()
- if (aTagValue != null) ATag.parse(aTagValue, relay) else null
- }
+ fun awardDefinition() = taggedAddresses()
companion object {
const val kind = 8
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeDefinitionEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeDefinitionEvent.kt
index e7c974812..42f559877 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeDefinitionEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeDefinitionEvent.kt
@@ -10,13 +10,13 @@ class BadgeDefinitionEvent(
content: String,
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
- fun dTag() = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: ""
+ fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
fun address() = ATag(kind, pubKey, dTag(), null)
- fun name() = tags.filter { it.firstOrNull() == "name" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
- fun thumb() = tags.filter { it.firstOrNull() == "thumb" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
- fun image() = tags.filter { it.firstOrNull() == "image" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
- fun description() = tags.filter { it.firstOrNull() == "description" }.mapNotNull { it.getOrNull(1) }.firstOrNull()
+ fun name() = tags.firstOrNull { it.size > 1 && it[0] == "name" }?.get(1)
+ fun thumb() = tags.firstOrNull { it.size > 1 && it[0] == "thumb" }?.get(1)
+ fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
+ fun description() = tags.firstOrNull { it.size > 1 && it[0] == "description" }?.get(1)
companion object {
const val kind = 30009
From fdca0af97baec3f0c0302796f886738b8ef565ad Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 19:18:15 -0400
Subject: [PATCH 19/41] Improving performance of the UserNameDisplay by making
variables immutable
---
.../amethyst/ui/note/UsernameDisplay.kt | 38 +++++++++++++------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt
index ddb394276..836d1aa8b 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt
@@ -28,41 +28,55 @@ fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return
- if (user.bestUsername() != null && user.bestDisplayName() != null) {
+ val bestUserName = user.bestUsername()
+ val bestDisplayName = user.bestDisplayName()
+ val npubDisplay = user.pubkeyDisplayHex()
+
+ UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, weight)
+}
+
+@Composable
+private fun UserNameDisplay(
+ bestUserName: String?,
+ bestDisplayName: String?,
+ npubDisplay: String,
+ modifier: Modifier
+) {
+ if (bestUserName != null && bestDisplayName != null) {
Text(
- user.bestDisplayName() ?: "",
+ bestDisplayName,
fontWeight = FontWeight.Bold
)
Text(
- "@${(user.bestUsername() ?: "")}",
+ "@$bestUserName",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
- modifier = weight
+ modifier = modifier
)
- } else if (user.bestDisplayName() != null) {
+ } else if (bestDisplayName != null) {
Text(
- user.bestDisplayName() ?: "",
+ bestDisplayName,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
- modifier = weight
+ modifier = modifier
)
- } else if (user.bestUsername() != null) {
+ } else if (bestUserName != null) {
Text(
- "@${(user.bestUsername() ?: "")}",
+ "@$bestUserName",
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
- modifier = weight
+ modifier = modifier
)
} else {
Text(
- user.pubkeyDisplayHex(),
+ npubDisplay,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
- modifier = weight
+ modifier = modifier
)
}
}
From bba45dd0a7602b8bec754292237e29284a5ebe6f Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 19:19:57 -0400
Subject: [PATCH 20/41] Moving clickable to Row and not AsyncImage because it's
faster
---
.../amethyst/ui/note/MultiSetCompose.kt | 42 ++--
.../amethyst/ui/note/NoteCompose.kt | 228 +++++++++++-------
.../amethyst/ui/screen/CardFeedView.kt | 2 +-
3 files changed, 162 insertions(+), 110 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index a27c7e9df..38f4932b3 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -54,7 +55,6 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
- val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
if (note == null) {
@@ -160,12 +160,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
}
Row(Modifier.fillMaxWidth()) {
- Box(
- modifier = Modifier
- .width(65.dp)
- .padding(0.dp)
- ) {
- }
+ Spacer(modifier = Modifier.width(65.dp))
NoteCompose(
baseNote = multiSetCard.note,
@@ -216,14 +211,16 @@ private fun AuthorPictureAndComment(
var content by remember { mutableStateOf>(Pair(author, null)) }
LaunchedEffect(key1 = zapRequest.idHex) {
- (zapRequest.event as? LnZapRequestEvent)?.let {
- val decryptedContent = accountViewModel.decryptZap(zapRequest)
- if (decryptedContent != null) {
- val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
- content = Pair(author, decryptedContent.content)
- } else {
- if (!zapRequest.event?.content().isNullOrBlank()) {
- content = Pair(author, zapRequest.event?.content())
+ withContext(Dispatchers.IO) {
+ (zapRequest.event as? LnZapRequestEvent)?.let {
+ val decryptedContent = accountViewModel.decryptZap(zapRequest)
+ if (decryptedContent != null) {
+ val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
+ content = Pair(author, decryptedContent.content)
+ } else {
+ if (!zapRequest.event?.content().isNullOrBlank()) {
+ content = Pair(author, zapRequest.event?.content())
+ }
}
}
}
@@ -246,10 +243,14 @@ private fun AuthorPictureAndComment(
Modifier
}
- Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
+ Row(
+ modifier = modifier.clickable {
+ navController.navigate("User/${author.pubkeyHex}")
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
FastNoteAuthorPicture(
author = author,
- navController = navController,
userAccount = accountUser,
size = 35.dp
)
@@ -294,7 +295,6 @@ fun AuthorGallery(
@Composable
fun FastNoteAuthorPicture(
author: User,
- navController: NavController,
userAccount: User,
size: Dp,
pictureModifier: Modifier = Modifier
@@ -303,14 +303,12 @@ fun FastNoteAuthorPicture(
val user = userState?.user ?: return
val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
+
UserPicture(
userHex = user.pubkeyHex,
userPicture = user.profilePicture(),
showFollowingMark = showFollowingMark,
size = size,
- modifier = pictureModifier,
- onClick = {
- navController.navigate("User/${user.pubkeyHex}")
- }
+ modifier = pictureModifier
)
}
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 545650749..964045df6 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
@@ -42,6 +42,7 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.get
import androidx.navigation.NavController
import coil.compose.AsyncImage
+import coil.compose.AsyncImagePainter
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R
@@ -99,7 +100,7 @@ fun NoteCompose(
Log.d("Time", "Note Compose in $elapsed for ${baseNote.idHex} ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
}
-@OptIn(ExperimentalFoundationApi::class)
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
@Composable
fun NoteComposeInner(
baseNote: Note,
@@ -199,12 +200,14 @@ fun NoteComposeInner(
}
Column(
- modifier = modifier.combinedClickable(
- onClick = {
- routeFor(note, loggedIn)?.let { navController.navigate(it) }
- },
- onLongClick = { popupExpanded = true }
- ).background(backgroundColor)
+ modifier = modifier
+ .combinedClickable(
+ onClick = {
+ routeFor(note, loggedIn)?.let { navController.navigate(it) }
+ },
+ onLongClick = { popupExpanded = true }
+ )
+ .background(backgroundColor)
) {
Row(
modifier = Modifier
@@ -222,20 +225,26 @@ fun NoteComposeInner(
modifier = Modifier
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
) {
- FirstUserInfoRow(
- baseNote = baseNote,
- showAuthorPicture = isQuotedNote,
- account = account,
- accountViewModel = accountViewModel,
- navController = navController
- )
+ val (value, elapsed1) = measureTimedValue {
+ FirstUserInfoRow(
+ baseNote = baseNote,
+ showAuthorPicture = isQuotedNote,
+ account = account,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
+ Log.d("Time", "$elapsed1 Line 1 of each post ${noteEvent.content()}")
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
- SecondUserInfoRow(
- note,
- account,
- navController
- )
+ val (value, elapsed2) = measureTimedValue {
+ SecondUserInfoRow(
+ note,
+ account,
+ navController
+ )
+ }
+ Log.d("Time", "$elapsed2 Line 2 of each post")
}
Spacer(modifier = Modifier.height(2.dp))
@@ -543,34 +552,46 @@ private fun RenderBadgeAward(
if (note.replyTo.isNullOrEmpty()) return
val noteEvent = note.event as? BadgeAwardEvent ?: return
+ var awardees by remember { mutableStateOf>(listOf()) }
Text(text = stringResource(R.string.award_granted_to))
-
- FlowRow(modifier = Modifier.padding(top = 5.dp)) {
- noteEvent.awardees()
- .map { LocalCache.getOrCreateUser(it) }
- .forEach {
- UserPicture(
- user = it,
- navController = navController,
- userAccount = accountViewModel.userProfile(),
- size = 35.dp
- )
+ /*
+ LaunchedEffect(key1 = note) {
+ withContext(Dispatchers.IO) {
+ awardees = noteEvent.awardees().mapNotNull { hex ->
+ LocalCache.checkGetOrCreateUser(hex)
+ }
}
- }
+ }
- note.replyTo?.firstOrNull()?.let {
- NoteCompose(
- it,
- modifier = Modifier,
- isBoostedNote = false,
- isQuotedNote = true,
- unPackReply = false,
- parentBackgroundColor = backgroundColor,
- accountViewModel = accountViewModel,
- navController = navController
- )
- }
+ FlowRow(modifier = Modifier.padding(top = 5.dp)) {
+ awardees.forEach { user ->
+ Row(modifier = Modifier.clickable {
+ navController.navigate("User/${user.pubkeyHex}")
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ UserPicture(
+ baseUser = user,
+ baseUserAccount = accountViewModel.userProfile(),
+ size = 35.dp
+ )
+ }
+ }
+ }
+
+ note.replyTo?.firstOrNull()?.let {
+ NoteCompose(
+ it,
+ modifier = Modifier,
+ isBoostedNote = false,
+ isQuotedNote = true,
+ unPackReply = false,
+ parentBackgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }*/
ReactionsRow(note, accountViewModel, navController)
@@ -762,34 +783,30 @@ private fun FirstUserInfoRow(
navController: NavController
) {
var moreActionsExpanded by remember { mutableStateOf(false) }
- val context = LocalContext.current.applicationContext
+ val eventNote = baseNote.event ?: return
+ val time = baseNote.createdAt() ?: return
+ val loggedIn = account.userProfile()
Row(verticalAlignment = Alignment.CenterVertically) {
if (showAuthorPicture) {
- NoteAuthorPicture(baseNote, navController, account.userProfile(), 25.dp)
+ NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp)
Spacer(Modifier.padding(horizontal = 5.dp))
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
} else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
}
- if (baseNote.event is RepostEvent) {
+ if (eventNote is RepostEvent) {
Text(
" ${stringResource(id = R.string.boosted)}",
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
} else {
- baseNote.event?.let {
- DisplayFollowingHashtagsInPost(it, account, navController)
- }
+ DisplayFollowingHashtagsInPost(eventNote, account, navController)
}
- Text(
- timeAgo(baseNote.createdAt(), context = context),
- color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
- maxLines = 1
- )
+ TimeAgo(time)
IconButton(
modifier = Modifier.size(24.dp),
@@ -812,6 +829,25 @@ private fun FirstUserInfoRow(
}
}
+@Composable
+fun TimeAgo(time: Long) {
+ val context = LocalContext.current
+
+ var timeStr by remember { mutableStateOf("") }
+
+ LaunchedEffect(key1 = time) {
+ withContext(Dispatchers.IO) {
+ timeStr = timeAgo(time, context = context)
+ }
+ }
+
+ Text(
+ timeStr,
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 1
+ )
+}
+
@Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
val baseChannel = baseNote.channel()
@@ -963,7 +999,7 @@ fun DisplayFollowingHashtagsInPost(
) {
var firstTag by remember { mutableStateOf(null) }
- LaunchedEffect(key1 = noteEvent) {
+ LaunchedEffect(key1 = noteEvent.id()) {
withContext(Dispatchers.IO) {
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
}
@@ -1102,7 +1138,24 @@ fun DisplayReward(
fun BadgeDisplay(baseNote: Note) {
val background = MaterialTheme.colors.background
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
- var backgroundFromImage by remember { mutableStateOf(background) }
+
+ val image = badgeData.image()
+ val name = badgeData.name()
+ val description = badgeData.description()
+
+ var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
+ var imageResult by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = imageResult) {
+ withContext(Dispatchers.IO) {
+ imageResult?.let {
+ val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
+ val colorFromImage = Color(backgroundColor)
+ val textBackground = if (colorFromImage.luminance() > 0.5) lightColors().onBackground else darkColors().onBackground
+ backgroundFromImage = Pair(colorFromImage, textBackground)
+ }
+ }
+ }
Row(
modifier = Modifier
@@ -1113,26 +1166,25 @@ fun BadgeDisplay(baseNote: Note) {
MaterialTheme.colors.primary.copy(alpha = 0.32f),
CutCornerShape(20)
)
- .background(backgroundFromImage)
+ .background(backgroundFromImage.first)
) {
Column {
- badgeData.image()?.let {
+ image.let {
AsyncImage(
model = it,
contentDescription = stringResource(
R.string.badge_award_image_for,
- it
+ name ?: ""
),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
onSuccess = {
- val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
- backgroundFromImage = Color(backgroundColor)
+ imageResult = it
}
)
}
- badgeData.name()?.let {
+ name?.let {
Text(
text = it,
style = MaterialTheme.typography.body1,
@@ -1140,11 +1192,11 @@ fun BadgeDisplay(baseNote: Note) {
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp),
- color = if (backgroundFromImage.luminance() > 0.5) lightColors().onBackground else darkColors().onBackground
+ color = backgroundFromImage.second
)
}
- badgeData.description()?.let {
+ description?.let {
Text(
text = it,
style = MaterialTheme.typography.caption,
@@ -1499,6 +1551,7 @@ fun UserPicture(
}
}
+@OptIn(ExperimentalFoundationApi::class)
@Composable
fun UserPicture(
baseUser: User,
@@ -1516,27 +1569,38 @@ fun UserPicture(
val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser
- UserPicture(
- userHex = user.pubkeyHex,
- userPicture = user.profilePicture(),
- showFollowingMark = showFollowingMark,
- size = size,
- modifier = modifier,
- onClick = onClick?.let { { it(user) } },
- onLongClick = onLongClick?.let { { it(user) } }
- )
+ Row(
+ modifier = Modifier
+ .run {
+ if (onClick != null && onLongClick != null) {
+ this.combinedClickable(
+ onClick = { onClick(user) },
+ onLongClick = { onLongClick(user) }
+ )
+ } else if (onClick != null) {
+ this.clickable(onClick = { onClick(user) })
+ } else {
+ this
+ }
+ }
+ ) {
+ UserPicture(
+ userHex = user.pubkeyHex,
+ userPicture = user.profilePicture(),
+ showFollowingMark = showFollowingMark,
+ size = size,
+ modifier = modifier
+ )
+ }
}
-@OptIn(ExperimentalFoundationApi::class)
@Composable
fun UserPicture(
userHex: String,
userPicture: String?,
showFollowingMark: Boolean,
size: Dp,
- modifier: Modifier = Modifier,
- onClick: (() -> Unit)? = null,
- onLongClick: (() -> Unit)? = null
+ modifier: Modifier = Modifier
) {
Box(
Modifier
@@ -1552,16 +1616,6 @@ fun UserPicture(
.height(size)
.clip(shape = CircleShape)
.background(MaterialTheme.colors.background)
- .run {
- if (onClick != null && onLongClick != null) {
- this.combinedClickable(onClick = onClick, onLongClick = onLongClick)
- } else if (onClick != null) {
- this.clickable(onClick = onClick)
- } else {
- this
- }
- }
-
)
if (showFollowingMark) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
index f355c6fd9..b7d1344bb 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt
@@ -175,7 +175,7 @@ private fun FeedLoaded(
)
}
}
- Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed")
+ Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed ${item.id()}")
}
}
}
From 90227e1cdc193cf3b1be49aad59ff0cd79779940 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 20:02:54 -0400
Subject: [PATCH 21/41] Refactoring NIP05 verification
---
.../amethyst/service/model/Event.kt | 4 +-
.../ui/note/NIP05VerificationDisplay.kt | 114 ++++++++++--------
.../amethyst/ui/note/NoteCompose.kt | 105 ++++++++--------
3 files changed, 113 insertions(+), 110 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
index 0be651a7d..8accaaf8a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
@@ -38,6 +38,8 @@ open class Event(
override fun toJson(): String = gson.toJson(this)
+ fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" }
+
fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
@@ -83,7 +85,7 @@ open class Event(
override fun getReward(): BigDecimal? {
return try {
- tags.filter { it.firstOrNull() == "reward" }.mapNotNull { it.getOrNull(1)?.let { BigDecimal(it) } }.firstOrNull()
+ tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
} catch (e: Exception) {
null
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
index cebfe1fdd..306b32596 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
@@ -23,6 +23,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
@@ -92,64 +93,75 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return
- val uri = LocalUriHandler.current
-
user.nip05()?.let { nip05 ->
- if (nip05.split("@").size == 2) {
+ val parts = nip05.split("@")
+ if (parts.size == 2) {
+ val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
+
Column(modifier = columnModifier) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (nip05.split("@")[0] != "_") {
- Text(
- text = AnnotatedString(nip05.split("@")[0]),
- color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
-
- val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
- if (nip05Verified == null) {
- Icon(
- tint = Color.Yellow,
- imageVector = Icons.Default.Downloading,
- contentDescription = "Downloading",
- modifier = Modifier
- .size(14.dp)
- .padding(top = 1.dp)
- )
- } else if (nip05Verified == true) {
- Icon(
- painter = painterResource(R.drawable.ic_verified),
- "NIP-05 Verified",
- tint = Nip05.copy(0.52f),
- modifier = Modifier
- .size(14.dp)
- .padding(top = 1.dp)
- )
- } else {
- Icon(
- tint = Color.Red,
- imageVector = Icons.Default.Report,
- contentDescription = "Invalid Nip05",
- modifier = Modifier
- .size(14.dp)
- .padding(top = 1.dp)
- )
- }
-
- ClickableText(
- text = AnnotatedString(nip05.split("@")[1]),
- onClick = { nip05.let { runCatching { uri.openUri("https://${it.split("@")[1]}") } } },
- style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
- maxLines = 1,
- overflow = TextOverflow.Visible
- )
- }
+ DisplayNIP05(parts[0], parts[1], nip05Verified)
}
}
}
}
+@Composable
+private fun DisplayNIP05(
+ user: String,
+ domain: String,
+ nip05Verified: Boolean?
+) {
+ val uri = LocalUriHandler.current
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ if (user != "_") {
+ Text(
+ text = AnnotatedString(user),
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+
+ if (nip05Verified == null) {
+ Icon(
+ tint = Color.Yellow,
+ imageVector = Icons.Default.Downloading,
+ contentDescription = "Downloading",
+ modifier = Modifier
+ .size(14.dp)
+ .padding(top = 1.dp)
+ )
+ } else if (nip05Verified == true) {
+ Icon(
+ painter = painterResource(R.drawable.ic_verified),
+ "NIP-05 Verified",
+ tint = Nip05.copy(0.52f),
+ modifier = Modifier
+ .size(14.dp)
+ .padding(top = 1.dp)
+ )
+ } else {
+ Icon(
+ tint = Color.Red,
+ imageVector = Icons.Default.Report,
+ contentDescription = "Invalid Nip05",
+ modifier = Modifier
+ .size(14.dp)
+ .padding(top = 1.dp)
+ )
+ }
+
+ ClickableText(
+ text = AnnotatedString(domain),
+ onClick = { runCatching { uri.openUri("https://${domain}") } },
+ style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
+ maxLines = 1,
+ overflow = TextOverflow.Visible
+ )
+ }
+}
+
@Composable
fun DisplayNip05ProfileStatus(user: User) {
val uri = LocalUriHandler.current
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 964045df6..9a4c1ca31 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
@@ -225,26 +225,20 @@ fun NoteComposeInner(
modifier = Modifier
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
) {
- val (value, elapsed1) = measureTimedValue {
- FirstUserInfoRow(
- baseNote = baseNote,
- showAuthorPicture = isQuotedNote,
- account = account,
- accountViewModel = accountViewModel,
- navController = navController
- )
- }
- Log.d("Time", "$elapsed1 Line 1 of each post ${noteEvent.content()}")
+ FirstUserInfoRow(
+ baseNote = baseNote,
+ showAuthorPicture = isQuotedNote,
+ account = account,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
- val (value, elapsed2) = measureTimedValue {
- SecondUserInfoRow(
- note,
- account,
- navController
- )
- }
- Log.d("Time", "$elapsed2 Line 2 of each post")
+ SecondUserInfoRow(
+ note,
+ account,
+ navController
+ )
}
Spacer(modifier = Modifier.height(2.dp))
@@ -555,43 +549,43 @@ private fun RenderBadgeAward(
var awardees by remember { mutableStateOf>(listOf()) }
Text(text = stringResource(R.string.award_granted_to))
- /*
- LaunchedEffect(key1 = note) {
- withContext(Dispatchers.IO) {
- awardees = noteEvent.awardees().mapNotNull { hex ->
- LocalCache.checkGetOrCreateUser(hex)
- }
+
+ LaunchedEffect(key1 = note) {
+ withContext(Dispatchers.IO) {
+ awardees = noteEvent.awardees().mapNotNull { hex ->
+ LocalCache.checkGetOrCreateUser(hex)
}
}
+ }
- FlowRow(modifier = Modifier.padding(top = 5.dp)) {
- awardees.forEach { user ->
- Row(modifier = Modifier.clickable {
- navController.navigate("User/${user.pubkeyHex}")
- },
- verticalAlignment = Alignment.CenterVertically
- ) {
- UserPicture(
- baseUser = user,
- baseUserAccount = accountViewModel.userProfile(),
- size = 35.dp
- )
- }
- }
- }
-
- note.replyTo?.firstOrNull()?.let {
- NoteCompose(
- it,
- modifier = Modifier,
- isBoostedNote = false,
- isQuotedNote = true,
- unPackReply = false,
- parentBackgroundColor = backgroundColor,
- accountViewModel = accountViewModel,
- navController = navController
+ FlowRow(modifier = Modifier.padding(top = 5.dp)) {
+ awardees.forEach { user ->
+ Row(modifier = Modifier.clickable {
+ navController.navigate("User/${user.pubkeyHex}")
+ },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ UserPicture(
+ baseUser = user,
+ baseUserAccount = accountViewModel.userProfile(),
+ size = 35.dp
)
- }*/
+ }
+ }
+ }
+
+ note.replyTo?.firstOrNull()?.let {
+ NoteCompose(
+ it,
+ modifier = Modifier,
+ isBoostedNote = false,
+ isQuotedNote = true,
+ unPackReply = false,
+ parentBackgroundColor = backgroundColor,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
ReactionsRow(note, accountViewModel, navController)
@@ -704,9 +698,7 @@ private fun ReplyRow(
) {
val noteEvent = note.event
- if (noteEvent is TextNoteEvent &&
- (note.replyTo != null || noteEvent.mentions().isNotEmpty())
- ) {
+ if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
val replyingDirectlyTo = note.replyTo?.lastOrNull()
if (replyingDirectlyTo != null && unPackReply) {
NoteCompose(
@@ -733,10 +725,7 @@ private fun ReplyRow(
}
Spacer(modifier = Modifier.height(5.dp))
- } else if (
- noteEvent is ChannelMessageEvent &&
- (note.replyTo != null || noteEvent.mentions().isNotEmpty())
- ) {
+ } else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
val sortedMentions = noteEvent.mentions()
.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
.toSet()
From 25a34dd2e859d84161819260c3a342ebcd5a98aa Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 20:03:04 -0400
Subject: [PATCH 22/41] formatting
---
.../vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt | 3 +--
.../java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 3 ++-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
index 306b32596..31dc03ec1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt
@@ -23,7 +23,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
@@ -154,7 +153,7 @@ private fun DisplayNIP05(
ClickableText(
text = AnnotatedString(domain),
- onClick = { runCatching { uri.openUri("https://${domain}") } },
+ onClick = { runCatching { uri.openUri("https://$domain") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
maxLines = 1,
overflow = TextOverflow.Visible
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 9a4c1ca31..7c37b71e9 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
@@ -560,7 +560,8 @@ private fun RenderBadgeAward(
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
awardees.forEach { user ->
- Row(modifier = Modifier.clickable {
+ Row(
+ modifier = Modifier.clickable {
navController.navigate("User/${user.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically
From dc0fe75b3da7aa4456da8cfe390f8f3474aafe12 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 20:59:09 -0400
Subject: [PATCH 23/41] Removing crossfades that might reduce the loading speed
---
.../amethyst/service/BlurHashImage.kt | 1 -
.../amethyst/ui/components/Robohash.kt | 1 -
.../ui/components/ZoomableContentView.kt | 3 ++-
.../amethyst/ui/note/PollNote.kt | 24 ++++++++++---------
4 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/BlurHashImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/BlurHashImage.kt
index b94f35e91..8e6ae470d 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/BlurHashImage.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/BlurHashImage.kt
@@ -59,7 +59,6 @@ object BlurHashRequester {
.Builder(context)
.data("bluehash:$encodedMessage")
.fetcherFactory(BlurHashFetcher.Factory)
- .crossfade(100)
.build()
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt
index dedb0cdd4..648922a35 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/Robohash.kt
@@ -92,7 +92,6 @@ object Robohash {
.data("robohash:$message")
.fetcherFactory(HashImageFetcher.Factory)
.size(robotSize)
- .crossfade(100)
.build()
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index c2139a7f0..2dd82ba12 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -337,9 +337,10 @@ private fun aspectRatio(dim: String?): Float? {
@Composable
private fun DisplayUrlWithLoadingSymbol(content: ZoomableContent) {
var cnt by remember { mutableStateOf(null) }
+ val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
delay(200)
cnt = content
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
index b5a4a91f2..d5cbcd80b 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
@@ -92,18 +92,20 @@ private fun OptionNote(
var optionTally by remember { mutableStateOf(Pair(BigDecimal.ZERO, defaultColor)) }
LaunchedEffect(key1 = optionNumber, key2 = pollViewModel) {
- val myTally = pollViewModel.optionVoteTally(optionNumber)
- val color = if (
- pollViewModel.consensusThreshold != null &&
- myTally >= pollViewModel.consensusThreshold!!
- ) {
- Color.Green.copy(alpha = 0.32f)
- } else {
- defaultColor
- }
+ withContext(Dispatchers.IO) {
+ val myTally = pollViewModel.optionVoteTally(optionNumber)
+ val color = if (
+ pollViewModel.consensusThreshold != null &&
+ myTally >= pollViewModel.consensusThreshold!!
+ ) {
+ Color.Green.copy(alpha = 0.32f)
+ } else {
+ defaultColor
+ }
- if (myTally > optionTally.first || color != optionTally.second) {
- optionTally = Pair(myTally, color)
+ if (myTally > optionTally.first || color != optionTally.second) {
+ optionTally = Pair(myTally, color)
+ }
}
}
From 70d8d9e2e0645678daf8f2289a6639ad01fbe862 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 20:59:24 -0400
Subject: [PATCH 24/41] formatting
---
.../vitorpamplona/amethyst/ui/components/ZoomableContentView.kt | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index 2dd82ba12..71bae61c6 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -75,7 +75,6 @@ import com.vitorpamplona.amethyst.ui.theme.Nip05
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
import java.io.File
From e5de2c45de098d1bf0070aec47859f6ce794f04a Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 21:58:25 -0400
Subject: [PATCH 25/41] Moving withContext to scope.launch to avoid waiting the
results.
---
.../amethyst/ui/components/UrlPreview.kt | 6 ++--
.../amethyst/ui/navigation/AppBottomBar.kt | 6 ++--
.../amethyst/ui/note/BadgeCompose.kt | 31 +++++++++++++------
.../amethyst/ui/note/ChatroomCompose.kt | 9 ++++--
.../ui/note/ChatroomMessageCompose.kt | 6 ++--
.../amethyst/ui/note/MessageSetCompose.kt | 7 +++--
.../amethyst/ui/note/MultiSetCompose.kt | 10 ++++--
.../amethyst/ui/note/NoteCompose.kt | 5 ++-
.../amethyst/ui/note/ReactionsRow.kt | 6 ++--
.../amethyst/ui/note/ZapNoteCompose.kt | 3 +-
.../components/TranslatableRichTextViewer.kt | 7 +++--
11 files changed, 63 insertions(+), 33 deletions(-)
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 fd012bc0b..14fb7bd9f 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
@@ -7,6 +7,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.baha.url.preview.IUrlPreviewCallback
@@ -14,7 +15,7 @@ import com.baha.url.preview.UrlInfoItem
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
@Composable
fun UrlPreview(url: String, urlText: String) {
@@ -28,11 +29,12 @@ fun UrlPreview(url: String, urlText: String) {
val context = LocalContext.current
var urlPreviewState by remember { mutableStateOf(default) }
+ val scope = rememberCoroutineScope()
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
LaunchedEffect(url) {
if (urlPreviewState == UrlPreviewState.Loading) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
UrlCachedPreviewer.previewInfo(
url,
object : IUrlPreviewCallback {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt
index 95048de58..26f872111 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt
@@ -41,7 +41,6 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import kotlin.time.ExperimentalTime
val bottomNavigationItems = listOf(
@@ -159,15 +158,16 @@ private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: Ac
val notif = notifState.value ?: return
var hasNewItems by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
LaunchedEffect(key1 = notif) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, emptySet())
}
}
LaunchedEffect(key1 = db) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, db)
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
index 85325fc6c..3f71245ce 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -37,7 +38,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -48,6 +49,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
val context = LocalContext.current.applicationContext
var popupExpanded by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
if (note == null) {
BlankNote(Modifier, isInnerNote)
@@ -55,7 +57,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = likeSetCard) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt())
@@ -69,12 +71,17 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
}
Column(
- modifier = Modifier.background(backgroundColor).combinedClickable(
- onClick = {
- routeFor(note, accountViewModel.userProfile())?.let { navController.navigate(it) }
- },
- onLongClick = { popupExpanded = true }
- )
+ modifier = Modifier
+ .background(backgroundColor)
+ .combinedClickable(
+ onClick = {
+ routeFor(
+ note,
+ accountViewModel.userProfile()
+ )?.let { navController.navigate(it) }
+ },
+ onLongClick = { popupExpanded = true }
+ )
) {
Row(
modifier = Modifier
@@ -94,7 +101,9 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
Icon(
imageVector = Icons.Default.MilitaryTech,
null,
- modifier = Modifier.size(25.dp).align(Alignment.TopEnd),
+ modifier = Modifier
+ .size(25.dp)
+ .align(Alignment.TopEnd),
tint = MaterialTheme.colors.primary
)
}
@@ -105,7 +114,9 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
Text(
stringResource(R.string.new_badge_award_notif),
fontWeight = FontWeight.Bold,
- modifier = Modifier.padding(bottom = 5.dp).weight(1f)
+ modifier = Modifier
+ .padding(bottom = 5.dp)
+ .weight(1f)
)
Text(
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
index 246b1fef2..f51a77899 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
@@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -50,7 +51,7 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
@Composable
fun ChatroomCompose(
@@ -64,6 +65,8 @@ fun ChatroomCompose(
val notificationCacheState = NotificationCache.live.observeAsState()
val notificationCache = notificationCacheState.value ?: return
+ val scope = rememberCoroutineScope()
+
if (note?.event == null) {
BlankNote(Modifier)
} else if (note.channel() != null) {
@@ -86,7 +89,7 @@ fun ChatroomCompose(
var hasNewMessages by remember { mutableStateOf(false) }
LaunchedEffect(key1 = notificationCache, key2 = note) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
note.createdAt()?.let { timestamp ->
hasNewMessages =
timestamp > notificationCache.cache.load("Channel/${chan.idHex}")
@@ -148,7 +151,7 @@ fun ChatroomCompose(
var hasNewMessages by remember { mutableStateOf(false) }
LaunchedEffect(key1 = notificationCache, key2 = note) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
noteEvent?.let {
hasNewMessages = it.createdAt() > notificationCache.cache.load(
"Room/${userToComposeOn.pubkeyHex}"
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt
index 290988501..21e7bb60c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt
@@ -31,6 +31,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -63,7 +64,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
@@ -94,6 +95,7 @@ fun ChatroomMessageCompose(
var showHiddenNote by remember { mutableStateOf(false) }
val context = LocalContext.current.applicationContext
+ val scope = rememberCoroutineScope()
if (note?.event == null) {
BlankNote(Modifier)
@@ -135,7 +137,7 @@ fun ChatroomMessageCompose(
LaunchedEffect(key1 = routeForLastRead) {
routeForLastRead?.let {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
val lastTime = NotificationCache.load(it)
val createdAt = note.createdAt()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
index f7f41e3cd..1557a09b0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
@@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -29,7 +30,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -40,13 +41,15 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
+
if (note == null) {
BlankNote(Modifier, isInnerNote)
} else {
var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = messageSetCard.createdAt()) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
isNew =
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index 38f4932b3..c23b48d83 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -44,7 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -57,13 +58,15 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
var popupExpanded by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
+
if (note == null) {
BlankNote(Modifier, false)
} else {
var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = multiSetCard.createdAt()) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt)
@@ -209,9 +212,10 @@ private fun AuthorPictureAndComment(
val author = zapRequest.author ?: return
var content by remember { mutableStateOf>(Pair(author, null)) }
+ val scope = rememberCoroutineScope()
LaunchedEffect(key1 = zapRequest.idHex) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
(zapRequest.event as? LnZapRequestEvent)?.let {
val decryptedContent = accountViewModel.decryptZap(zapRequest)
if (decryptedContent != null) {
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 7c37b71e9..4913cb79c 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
@@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Following
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.math.BigDecimal
@@ -174,8 +175,10 @@ fun NoteComposeInner(
} else {
var isNew by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
+
LaunchedEffect(key1 = routeForLastRead) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
routeForLastRead?.let {
val lastTime = NotificationCache.load(it)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
index cc3591e3e..03371eda7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
@@ -61,7 +61,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.math.roundToInt
@@ -322,7 +321,7 @@ fun ZapReaction(
var zapAmount by remember { mutableStateOf(null) }
LaunchedEffect(key1 = zapsState) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
if (!wasZappedByLoggedInUser) {
wasZappedByLoggedInUser = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote)
}
@@ -333,7 +332,8 @@ fun ZapReaction(
Row(
verticalAlignment = CenterVertically,
- modifier = Modifier.size(iconSize)
+ modifier = Modifier
+ .size(iconSize)
.combinedClickable(
role = Role.Button,
interactionSource = remember { MutableInteractionSource() },
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
index 9801bf21c..aa881b1f0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
@@ -34,7 +34,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import java.math.BigDecimal
@Composable
@@ -95,7 +94,7 @@ fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewMode
var zapAmount by remember { mutableStateOf(null) }
LaunchedEffect(key1 = noteZap) {
- withContext(Dispatchers.IO) {
+ coroutineScope.launch(Dispatchers.IO) {
zapAmount = (noteZap.event as? LnZapEvent)?.amount
}
}
diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
index 8100a3e9b..7c4c04457 100644
--- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
+++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
@@ -18,6 +18,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -34,7 +35,7 @@ import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService
import com.vitorpamplona.amethyst.service.lang.ResultOrError
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
import java.util.Locale
@Composable
@@ -57,8 +58,10 @@ fun TranslatableRichTextViewer(
val accountState by accountViewModel.accountLanguagesLiveData.observeAsState()
val account = accountState?.account ?: return
+ val scope = rememberCoroutineScope()
+
LaunchedEffect(accountState) {
- withContext(Dispatchers.IO) {
+ scope.launch(Dispatchers.IO) {
LanguageTranslatorService.autoTranslate(
content,
account.dontTranslateFrom,
From b093873eb06cbb881a05ca17b39f6b1bfa1b07b6 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 22:17:43 -0400
Subject: [PATCH 26/41] Avoids trying to decrypt a zap that was not sent by the
author.
---
.../vitorpamplona/amethyst/model/Account.kt | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index 2cb3cdf18..490846511 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -868,11 +868,21 @@ class Account(
}
if (altPrivateKeyToUse != null && altPubkeyToUse != null) {
- val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse)
- if (result == null) {
- Log.w("Private ZAP Decrypt", "Fail to decrypt Zap from ${note.author?.toBestDisplayName()} ${note.idNote()}")
+ val altPubKeyFromPrivate = Utils.pubkeyCreate(altPrivateKeyToUse).toHexKey()
+
+ if (altPubKeyFromPrivate == event.pubKey) {
+ val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse)
+
+ if (result == null) {
+ Log.w(
+ "Private ZAP Decrypt",
+ "Fail to decrypt Zap from ${note.author?.toBestDisplayName()} ${note.idNote()}"
+ )
+ }
+ result
+ } else {
+ null
}
- result
} else {
null
}
From dea35552d5a44b9f50559e0bc0642871f7f60002 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sat, 6 May 2023 22:27:00 -0400
Subject: [PATCH 27/41] Calculating ZapAmount and converting to the screen
before exiting the IO thread
---
.../java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
index 03371eda7..77cf78c93 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
@@ -318,7 +318,7 @@ fun ZapReaction(
var zappingProgress by remember { mutableStateOf(0f) }
var wasZappedByLoggedInUser by remember { mutableStateOf(false) }
- var zapAmount by remember { mutableStateOf(null) }
+ var zapAmountTxt by remember { mutableStateOf("") }
LaunchedEffect(key1 = zapsState) {
scope.launch(Dispatchers.IO) {
@@ -326,7 +326,7 @@ fun ZapReaction(
wasZappedByLoggedInUser = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote)
}
- zapAmount = account.calculateZappedAmount(zappedNote)
+ zapAmountTxt = showAmount(account.calculateZappedAmount(zappedNote))
}
}
@@ -456,7 +456,7 @@ fun ZapReaction(
}
Text(
- showAmount(zapAmount),
+ zapAmountTxt,
fontSize = 14.sp,
color = grayTint,
modifier = textModifier
From fcc67932448160f08430e0c572b8e107ba8ce3ff Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 08:53:52 -0400
Subject: [PATCH 28/41] Limiting AuthorGalleries to 50 people
---
.../amethyst/ui/note/MultiSetCompose.kt | 7 +-
.../amethyst/ui/note/NoteCompose.kt | 85 +++++++++++++++++--
2 files changed, 82 insertions(+), 10 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index c23b48d83..5b4464834 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.runtime.Composable
@@ -286,12 +287,16 @@ fun AuthorGallery(
Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() {
- authorNotes.forEach {
+ authorNotes.take(50).forEach {
val author = it.author
if (author != null) {
AuthorPictureAndComment(author, null, navController, accountUser, accountViewModel)
}
}
+
+ if (authorNotes.size > 50) {
+ Text(" and ${authorNotes.size - 50} others")
+ }
}
}
}
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 4913cb79c..2b55b3dd8 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
@@ -3,19 +3,50 @@ package com.vitorpamplona.amethyst.ui.note
import android.content.Intent
import android.graphics.Bitmap
import android.util.Log
-import androidx.compose.foundation.*
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
-import androidx.compose.material.*
+import androidx.compose.material.Divider
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.darkColors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.MoreVert
-import androidx.compose.runtime.*
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+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
@@ -51,8 +82,38 @@ import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
-import com.vitorpamplona.amethyst.service.model.*
-import com.vitorpamplona.amethyst.ui.components.*
+import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
+import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
+import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
+import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
+import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
+import com.vitorpamplona.amethyst.service.model.EventInterface
+import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
+import com.vitorpamplona.amethyst.service.model.FileStorageEvent
+import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
+import com.vitorpamplona.amethyst.service.model.HighlightEvent
+import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
+import com.vitorpamplona.amethyst.service.model.PollNoteEvent
+import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
+import com.vitorpamplona.amethyst.service.model.ReactionEvent
+import com.vitorpamplona.amethyst.service.model.ReportEvent
+import com.vitorpamplona.amethyst.service.model.RepostEvent
+import com.vitorpamplona.amethyst.service.model.TextNoteEvent
+import com.vitorpamplona.amethyst.ui.components.ClickableUrl
+import com.vitorpamplona.amethyst.ui.components.CreateClickableText
+import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
+import com.vitorpamplona.amethyst.ui.components.ResizeImage
+import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
+import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
+import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
+import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
+import com.vitorpamplona.amethyst.ui.components.ZoomableContent
+import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
+import com.vitorpamplona.amethyst.ui.components.ZoomableLocalImage
+import com.vitorpamplona.amethyst.ui.components.ZoomableLocalVideo
+import com.vitorpamplona.amethyst.ui.components.ZoomableUrlImage
+import com.vitorpamplona.amethyst.ui.components.ZoomableUrlVideo
+import com.vitorpamplona.amethyst.ui.components.imageExtensions
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
@@ -551,20 +612,22 @@ private fun RenderBadgeAward(
val noteEvent = note.event as? BadgeAwardEvent ?: return
var awardees by remember { mutableStateOf>(listOf()) }
+ val account = accountViewModel.userProfile()
+
Text(text = stringResource(R.string.award_granted_to))
LaunchedEffect(key1 = note) {
withContext(Dispatchers.IO) {
awardees = noteEvent.awardees().mapNotNull { hex ->
LocalCache.checkGetOrCreateUser(hex)
- }
+ }.sortedBy { account.isFollowing(it) }.reversed()
}
}
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
- awardees.forEach { user ->
+ awardees.take(100).forEach { user ->
Row(
- modifier = Modifier.clickable {
+ modifier = Modifier.size(size = 35.dp).clickable {
navController.navigate("User/${user.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically
@@ -576,6 +639,10 @@ private fun RenderBadgeAward(
)
}
}
+
+ if (awardees.size > 100) {
+ Text(" and ${awardees.size - 100} others")
+ }
}
note.replyTo?.firstOrNull()?.let {
From 1c0fcc4ab89c0e4e9863c684ce8aae219dfe3514 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 12:50:17 -0400
Subject: [PATCH 29/41] Solves NPE when loading an invalid npub in a citation
---
.../amethyst/service/model/BaseTextNoteEvent.kt | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BaseTextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BaseTextNoteEvent.kt
index 9b03abaa9..93f576f05 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BaseTextNoteEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BaseTextNoteEvent.kt
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.service.model
+import android.util.Log
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.tagSearch
import com.vitorpamplona.amethyst.service.nip19.Nip19
@@ -41,17 +42,18 @@ open class BaseTextNoteEvent(
val key = matcher2.group(3) // bech32
val additionalChars = matcher2.group(4) // additional chars
- val parsed = Nip19.parseComponents(uriScheme, type, key, additionalChars)
+ try {
+ val parsed = Nip19.parseComponents(uriScheme, type, key, additionalChars)
- if (parsed != null) {
- try {
+ if (parsed != null) {
val tag = tags.firstOrNull { it.size > 1 && it[1] == parsed.hex }
if (tag != null && tag[0] == "p") {
returningList.add(tag[1])
}
- } catch (e: Exception) {
}
+ } catch (e: Exception) {
+ Log.w("Unable to parse cited users that matched a NIP19 regex", e)
}
}
From f44549de41ed63663d03408a13bdcab34d6ac216 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 12:51:12 -0400
Subject: [PATCH 30/41] Loading Users and Notes in Launched Effects only
because of @Synchronized in the getOrCreate methods in LocalCache
---
.../vitorpamplona/amethyst/model/Account.kt | 2 +-
.../amethyst/model/LocalCache.kt | 4 +-
.../amethyst/model/ThreadAssembler.kt | 2 +-
.../com/vitorpamplona/amethyst/model/User.kt | 2 +-
.../service/NostrUserProfileDataSource.kt | 10 +-
.../amethyst/service/model/PrivateDmEvent.kt | 6 +-
.../amethyst/ui/components/ClickableRoute.kt | 212 +++++++++++++++---
.../amethyst/ui/components/VideoView.kt | 6 +-
.../ui/components/ZoomableContentView.kt | 10 +-
.../ui/dal/UserProfileBookmarksFeedFilter.kt | 4 +-
.../dal/UserProfileConversationsFeedFilter.kt | 5 +-
.../ui/dal/UserProfileFollowersFeedFilter.kt | 4 +-
.../ui/dal/UserProfileFollowsFeedFilter.kt | 4 +-
.../ui/dal/UserProfileNewThreadFeedFilter.kt | 4 +-
.../ui/dal/UserProfileReportsFeedFilter.kt | 5 +-
.../ui/dal/UserProfileZapsFeedFilter.kt | 5 +-
.../amethyst/ui/note/ChatroomCompose.kt | 2 +-
.../amethyst/ui/note/NoteCompose.kt | 66 +++---
.../amethyst/ui/note/ReplyInformation.kt | 45 +++-
.../amethyst/ui/screen/CardFeedViewModel.kt | 2 +-
.../ui/screen/ChatroomListFeedView.kt | 2 +-
.../ui/screen/loggedIn/ProfileScreen.kt | 43 +++-
22 files changed, 322 insertions(+), 123 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
index 490846511..726d8db7c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -820,7 +820,7 @@ class Account(
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
var pubkeyToUse = event.pubKey
- val recepientPK = event.recipientPubKey()
+ val recepientPK = event.verifiedRecipientPubKey()
if (note.author == userProfile() && recepientPK != null) {
pubkeyToUse = recepientPK
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 2ecfcf63e..20ca49cd8 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
@@ -359,7 +359,7 @@ object LocalCache {
// Already processed this event.
if (note.event != null) return
- val recipient = event.recipientPubKey()?.let { getOrCreateUser(it) }
+ val recipient = event.verifiedRecipientPubKey()?.let { getOrCreateUser(it) }
// Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
@@ -407,7 +407,7 @@ object LocalCache {
if (deleteNote.event is PrivateDmEvent) {
val author = deleteNote.author
- val recipient = (deleteNote.event as? PrivateDmEvent)?.recipientPubKey()?.let { checkGetOrCreateUser(it) }
+ val recipient = (deleteNote.event as? PrivateDmEvent)?.verifiedRecipientPubKey()?.let { checkGetOrCreateUser(it) }
if (recipient != null && author != null) {
author.removeMessage(recipient, deleteNote)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt
index 636ee52b9..25498f702 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt
@@ -6,7 +6,7 @@ import kotlin.time.measureTimedValue
class ThreadAssembler {
- fun searchRoot(note: Note, testedNotes: MutableSet = mutableSetOf()): Note? {
+ private fun searchRoot(note: Note, testedNotes: MutableSet = mutableSetOf()): Note? {
if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note
testedNotes.add(note)
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 13a0cdd93..392cc17f8 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
@@ -181,7 +181,7 @@ class User(val pubkeyHex: String) {
}
@Synchronized
- fun getOrCreatePrivateChatroom(user: User): Chatroom {
+ private fun getOrCreatePrivateChatroom(user: User): Chatroom {
return privateChatrooms[user] ?: run {
val privateChatroom = Chatroom(setOf())
privateChatrooms = privateChatrooms + Pair(user, privateChatroom)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt
index 23e2196e4..44222d800 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.service
-import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
@@ -10,13 +9,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
var user: User? = null
- fun loadUserProfile(userId: String?) {
- if (userId != null) {
- user = LocalCache.getOrCreateUser(userId)
- } else {
- user = null
- }
-
+ fun loadUserProfile(user: User?) {
+ this.user = user
resetFilters()
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
index e67083cc0..c10837f3a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
@@ -21,7 +21,11 @@ class PrivateDmEvent(
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
* for initial messages.
*/
- fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
+ fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }
+
+ fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this[1]) }?.getOrNull()
+
+ fun verifiedRecipientPubKey() = recipientPubKey()?.runCatching { Hex.decode(this[1]).toHexKey() }?.getOrNull() // makes sure its a valid one
/**
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt
index f9117a13e..c94931a40 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt
@@ -5,15 +5,23 @@ 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.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.LocalCache
+import com.vitorpamplona.amethyst.model.Note
+import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.nip19.Nip19
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
@Composable
fun ClickableRoute(
@@ -21,56 +29,190 @@ fun ClickableRoute(
navController: NavController
) {
if (nip19.type == Nip19.Type.USER) {
- val userBase = LocalCache.getOrCreateUser(nip19.hex)
-
- val userState by userBase.live().metadata.observeAsState()
- val user = userState?.user ?: return
-
- CreateClickableText(user.toBestDisplayName(), nip19.additionalChars, "User/${nip19.hex}", navController)
+ DisplayUser(nip19, navController)
} else if (nip19.type == Nip19.Type.ADDRESS) {
- val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
+ DisplayAddress(nip19, navController)
+ } else if (nip19.type == Nip19.Type.NOTE) {
+ DisplayNote(nip19, navController)
+ } else if (nip19.type == Nip19.Type.EVENT) {
+ DisplayEvent(nip19, navController)
+ } else {
+ Text(
+ "@${nip19.hex}${nip19.additionalChars} "
+ )
+ }
+}
- if (noteBase == null) {
- Text(
- "@${nip19.hex}${nip19.additionalChars} "
+@Composable
+private fun DisplayEvent(
+ nip19: Nip19.Return,
+ navController: NavController
+) {
+ var noteBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = nip19.hex) {
+ withContext(Dispatchers.IO) {
+ noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
+ }
+ }
+
+ noteBase?.let {
+ val noteState by it.live().metadata.observeAsState()
+ val note = noteState?.note ?: return
+ val channel = note.channel()
+
+ if (note.event is ChannelCreateEvent) {
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Channel/${nip19.hex}",
+ navController
+ )
+ } else if (note.event is PrivateDmEvent) {
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Room/${note.author?.pubkeyHex}",
+ navController
+ )
+ } else if (channel != null) {
+ CreateClickableText(
+ channel.toBestDisplayName(),
+ nip19.additionalChars,
+ "Channel/${note.channel()?.idHex}",
+ navController
)
} else {
- val noteState by noteBase.live().metadata.observeAsState()
- val note = noteState?.note ?: return
-
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Event/${nip19.hex}",
+ navController
+ )
}
- } else if (nip19.type == Nip19.Type.NOTE) {
- val noteBase = LocalCache.getOrCreateNote(nip19.hex)
- val noteState by noteBase.live().metadata.observeAsState()
+ }
+
+ if (noteBase == null) {
+ Text(
+ "@${nip19.hex}${nip19.additionalChars} "
+ )
+ }
+}
+
+@Composable
+private fun DisplayNote(
+ nip19: Nip19.Return,
+ navController: NavController
+) {
+ var noteBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = nip19.hex) {
+ withContext(Dispatchers.IO) {
+ noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
+ }
+ }
+
+ noteBase?.let {
+ val noteState by it.live().metadata.observeAsState()
val note = noteState?.note ?: return
val channel = note.channel()
if (note.event is ChannelCreateEvent) {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController)
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Channel/${nip19.hex}",
+ navController
+ )
} else if (note.event is PrivateDmEvent) {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController)
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Room/${note.author?.pubkeyHex}",
+ navController
+ )
} else if (channel != null) {
- CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController)
+ CreateClickableText(
+ channel.toBestDisplayName(),
+ nip19.additionalChars,
+ "Channel/${note.channel()?.idHex}",
+ navController
+ )
} else {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Note/${nip19.hex}",
+ navController
+ )
}
- } else if (nip19.type == Nip19.Type.EVENT) {
- val noteBase = LocalCache.getOrCreateNote(nip19.hex)
- val noteState by noteBase.live().metadata.observeAsState()
- val note = noteState?.note ?: return
- val channel = note.channel()
+ }
- if (note.event is ChannelCreateEvent) {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController)
- } else if (note.event is PrivateDmEvent) {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController)
- } else if (channel != null) {
- CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController)
- } else {
- CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Event/${nip19.hex}", navController)
+ if (noteBase == null) {
+ Text(
+ "@${nip19.hex}${nip19.additionalChars} "
+ )
+ }
+}
+
+@Composable
+private fun DisplayAddress(
+ nip19: Nip19.Return,
+ navController: NavController
+) {
+ var noteBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = nip19.hex) {
+ withContext(Dispatchers.IO) {
+ noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
}
- } else {
+ }
+
+ noteBase?.let {
+ val noteState by it.live().metadata.observeAsState()
+ val note = noteState?.note ?: return
+
+ CreateClickableText(
+ note.idDisplayNote(),
+ nip19.additionalChars,
+ "Note/${nip19.hex}",
+ navController
+ )
+ }
+
+ if (noteBase == null) {
+ Text(
+ "@${nip19.hex}${nip19.additionalChars} "
+ )
+ }
+}
+
+@Composable
+private fun DisplayUser(
+ nip19: Nip19.Return,
+ navController: NavController
+) {
+ var userBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = nip19.hex) {
+ withContext(Dispatchers.IO) {
+ userBase = LocalCache.checkGetOrCreateUser(nip19.hex)
+ }
+ }
+
+ userBase?.let {
+ val userState by it.live().metadata.observeAsState()
+ val user = userState?.user ?: return
+
+ CreateClickableText(
+ user.toBestDisplayName(),
+ nip19.additionalChars,
+ "User/${nip19.hex}",
+ navController
+ )
+ }
+
+ if (userBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
index c4305a3c6..7a6441fcb 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
@@ -60,8 +60,10 @@ import java.io.File
public var muted = mutableStateOf(true)
@Composable
-fun VideoView(localFile: File, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
- VideoView(localFile.toUri(), description, onDialog)
+fun VideoView(localFile: File?, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
+ if (localFile != null) {
+ VideoView(localFile.toUri(), description, onDialog)
+ }
}
@Composable
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index 71bae61c6..dbd57c466 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -111,7 +111,7 @@ class ZoomableUrlVideo(
) : ZoomableUrlContent(url, description, hash, dim, uri)
abstract class ZoomablePreloadedContent(
- val localFile: File,
+ val localFile: File?,
description: String? = null,
val mimeType: String? = null,
val isVerified: Boolean? = null,
@@ -120,7 +120,7 @@ abstract class ZoomablePreloadedContent(
) : ZoomableContent(description, dim)
class ZoomableLocalImage(
- localFile: File,
+ localFile: File?,
mimeType: String? = null,
description: String? = null,
val blurhash: String? = null,
@@ -130,7 +130,7 @@ class ZoomableLocalImage(
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri)
class ZoomableLocalVideo(
- localFile: File,
+ localFile: File?,
mimeType: String? = null,
description: String? = null,
dim: String? = null,
@@ -219,7 +219,7 @@ private fun LocalImageView(
}
val contentScale = if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
- if (content.localFile.exists()) {
+ if (content.localFile != null && content.localFile.exists()) {
AsyncImage(
model = content.localFile,
contentDescription = content.description,
@@ -246,7 +246,7 @@ private fun LocalImageView(
}
}
- if (imageState is AsyncImagePainter.State.Error || !content.localFile.exists()) {
+ if (imageState is AsyncImagePainter.State.Error || content.localFile == null || !content.localFile.exists()) {
BlankNote()
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt
index db26758a1..b2c0829fa 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt
@@ -9,9 +9,9 @@ object UserProfileBookmarksFeedFilter : FeedFilter() {
lateinit var account: Account
var user: User? = null
- fun loadUserProfile(accountLoggedIn: Account, userId: String) {
+ fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
- user = LocalCache.users[userId]
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt
index 27ac42c82..681865c78 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt
@@ -1,7 +1,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
-import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -9,9 +8,9 @@ object UserProfileConversationsFeedFilter : FeedFilter() {
var account: Account? = null
var user: User? = null
- fun loadUserProfile(accountLoggedIn: Account, userId: String) {
+ fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
- user = LocalCache.checkGetOrCreateUser(userId)
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt
index 2fe984757..d29148a21 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt
@@ -8,9 +8,9 @@ object UserProfileFollowersFeedFilter : FeedFilter() {
lateinit var account: Account
var user: User? = null
- fun loadUserProfile(accountLoggedIn: Account, userId: String) {
+ fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
- user = LocalCache.users[userId]
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt
index 4afb38c7a..fbe4f54ca 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt
@@ -8,9 +8,9 @@ object UserProfileFollowsFeedFilter : FeedFilter() {
lateinit var account: Account
var user: User? = null
- fun loadUserProfile(accountLoggedIn: Account, userId: String) {
+ fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
- user = LocalCache.users[userId]
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt
index 138e08217..327b40917 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt
@@ -9,9 +9,9 @@ object UserProfileNewThreadFeedFilter : FeedFilter() {
var account: Account? = null
var user: User? = null
- fun loadUserProfile(accountLoggedIn: Account, userId: String) {
+ fun loadUserProfile(accountLoggedIn: Account, user: User) {
account = accountLoggedIn
- user = LocalCache.checkGetOrCreateUser(userId)
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt
index 025143a49..50716ac5b 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt
@@ -1,14 +1,13 @@
package com.vitorpamplona.amethyst.ui.dal
-import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
object UserProfileReportsFeedFilter : FeedFilter() {
var user: User? = null
- fun loadUserProfile(userId: String) {
- user = LocalCache.checkGetOrCreateUser(userId)
+ fun loadUserProfile(user: User?) {
+ this.user = user
}
override fun feed(): List {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt
index 2e6b6ee7d..70cb680ed 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.dal
-import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
@@ -8,8 +7,8 @@ import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
object UserProfileZapsFeedFilter : FeedFilter>() {
var user: User? = null
- fun loadUserProfile(userId: String) {
- user = LocalCache.checkGetOrCreateUser(userId)
+ fun loadUserProfile(user: User?) {
+ this.user = user
}
override fun feed(): List> {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
index f51a77899..b4711e887 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt
@@ -134,7 +134,7 @@ fun ChatroomCompose(
} else {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
- ?.recipientPubKey()
+ ?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
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 2b55b3dd8..b1c4d1307 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
@@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.EventInterface
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
-import com.vitorpamplona.amethyst.service.model.FileStorageEvent
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
@@ -122,6 +121,7 @@ import com.vitorpamplona.amethyst.ui.theme.Following
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import nostr.postr.toNpub
import java.io.File
import java.math.BigDecimal
import java.net.URL
@@ -387,7 +387,7 @@ fun routeFor(note: Note, loggedIn: User): String? {
} else if (noteEvent is PrivateDmEvent) {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
- ?.recipientPubKey()
+ ?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
@@ -573,13 +573,13 @@ private fun RenderPrivateMessage(
}
}
} else {
- val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) }
+ val recipient = noteEvent.recipientPubKeyBytes()?.toNpub() ?: "Someone"
TranslatableRichTextViewer(
stringResource(
id = R.string.private_conversation_notification,
"@${note.author?.pubkeyNpub()}",
- "@${recipient?.pubkeyNpub()}"
+ "@$recipient"
),
canPreview = !makeItShort,
Modifier.fillMaxWidth(),
@@ -797,13 +797,8 @@ private fun ReplyRow(
Spacer(modifier = Modifier.height(5.dp))
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
- val sortedMentions = noteEvent.mentions()
- .mapNotNull { LocalCache.checkGetOrCreateUser(it) }
- .toSet()
- .sortedBy { account.isFollowing(it) }
-
note.channel()?.let {
- ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
+ ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, navController)
}
Spacer(modifier = Modifier.height(5.dp))
@@ -1016,11 +1011,19 @@ fun DisplayHighlight(
navController
)
+ var userBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ if (authorHex != null) {
+ userBase = LocalCache.checkGetOrCreateUser(authorHex)
+ }
+ }
+ }
+
FlowRow() {
authorHex?.let { authorHex ->
- val userBase = LocalCache.checkGetOrCreateUser(authorHex)
-
- if (userBase != null) {
+ userBase?.let { userBase ->
val userState by userBase.live().metadata.observeAsState()
val user = userState?.user
@@ -1307,20 +1310,23 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
val appContext = LocalContext.current.applicationContext
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
- val fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) } ?: return
+ var fileNote by remember { mutableStateOf(null) }
- val noteState by fileNote.live().metadata.observeAsState()
- val note = noteState?.note
+ LaunchedEffect(key1 = eventHeader.id) {
+ withContext(Dispatchers.IO) {
+ fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
+ }
+ }
- val eventBytes = (note?.event as? FileStorageEvent)
+ val noteState = fileNote?.live()?.metadata?.observeAsState()
+ val note = noteState?.value?.note
var content by remember { mutableStateOf(null) }
- LaunchedEffect(key1 = eventHeader.id, key2 = noteState) {
+ LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
withContext(Dispatchers.IO) {
val uri = "nostr:" + baseNote.toNEvent()
- val localDir = File(File(appContext.externalCacheDir, "NIP95"), fileNote.idHex)
- val bytes = eventBytes?.decode()
+ val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
val blurHash = eventHeader.blurhash()
val dimensions = eventHeader.dimensions()
val description = eventHeader.content
@@ -1337,18 +1343,14 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
uri = uri
)
} else {
- if (bytes != null) {
- ZoomableLocalVideo(
- localFile = localDir,
- mimeType = mimeType,
- description = description,
- dim = dimensions,
- isVerified = true,
- uri = uri
- )
- } else {
- null
- }
+ ZoomableLocalVideo(
+ localFile = localDir,
+ mimeType = mimeType,
+ description = description,
+ dim = dimensions,
+ isVerified = true,
+ uri = uri
+ )
}
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt
index 8c2686973..18446bf03 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt
@@ -5,6 +5,7 @@ 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.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -17,13 +18,23 @@ import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
@Composable
fun ReplyInformation(replyTo: List?, mentions: List, account: Account, navController: NavController) {
- val dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
+ var dupMentions by remember { mutableStateOf?>(null) }
- ReplyInformation(replyTo, dupMentions, account) {
- navController.navigate("User/${it.pubkeyHex}")
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
+ }
+ }
+
+ if (dupMentions != null) {
+ ReplyInformation(replyTo, dupMentions, account) {
+ navController.navigate("User/${it.pubkeyHex}")
+ }
}
}
@@ -102,6 +113,34 @@ fun ReplyInformation(replyTo: List?, dupMentions: List?, account: Ac
}
}
+@Composable
+fun ReplyInformationChannel(replyTo: List?, mentions: List, channel: Channel, account: Account, navController: NavController) {
+ var sortedMentions by remember { mutableStateOf?>(null) }
+
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ sortedMentions = mentions
+ .mapNotNull { LocalCache.checkGetOrCreateUser(it) }
+ .toSet()
+ .sortedBy { account.isFollowing(it) }
+ }
+ }
+
+ if (sortedMentions != null) {
+ ReplyInformationChannel(
+ replyTo,
+ sortedMentions,
+ channel,
+ onUserTagClick = {
+ navController.navigate("User/${it.pubkeyHex}")
+ },
+ onChannelTagClick = {
+ navController.navigate("Channel/${it.idHex}")
+ }
+ )
+ }
+}
+
@Composable
fun ReplyInformationChannel(replyTo: List?, mentions: List?, channel: Channel, navController: NavController) {
ReplyInformationChannel(
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt
index cf9a85301..defff2d01 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt
@@ -175,7 +175,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() {
}
}
- fun refreshFromOldState(newItems: Set) {
+ private fun refreshFromOldState(newItems: Set) {
val oldNotesState = _feedContent.value
val thisAccount = (localFilter as? NotificationFeedFilter)?.account
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt
index a62afa8f1..8238ad33e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt
@@ -104,7 +104,7 @@ private fun FeedLoaded(
} else {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
- ?.recipientPubKey()
+ ?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
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 4a9ab3c11..fffd2a126 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
@@ -100,23 +100,42 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
-@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) {
+ if (userId == null) return
+
+ var userBase by remember { mutableStateOf(null) }
+
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ userBase = LocalCache.checkGetOrCreateUser(userId)
+ }
+ }
+
+ userBase?.let {
+ ProfileScreen(
+ user = it,
+ accountViewModel = accountViewModel,
+ navController = navController
+ )
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
- if (userId == null) return
+ UserProfileNewThreadFeedFilter.loadUserProfile(account, user)
+ UserProfileConversationsFeedFilter.loadUserProfile(account, user)
+ UserProfileFollowersFeedFilter.loadUserProfile(account, user)
+ UserProfileFollowsFeedFilter.loadUserProfile(account, user)
+ UserProfileZapsFeedFilter.loadUserProfile(user)
+ UserProfileReportsFeedFilter.loadUserProfile(user)
+ UserProfileBookmarksFeedFilter.loadUserProfile(account, user)
- UserProfileNewThreadFeedFilter.loadUserProfile(account, userId)
- UserProfileConversationsFeedFilter.loadUserProfile(account, userId)
- UserProfileFollowersFeedFilter.loadUserProfile(account, userId)
- UserProfileFollowsFeedFilter.loadUserProfile(account, userId)
- UserProfileZapsFeedFilter.loadUserProfile(userId)
- UserProfileReportsFeedFilter.loadUserProfile(userId)
- UserProfileBookmarksFeedFilter.loadUserProfile(account, userId)
-
- NostrUserProfileDataSource.loadUserProfile(userId)
+ NostrUserProfileDataSource.loadUserProfile(user)
val lifeCycleOwner = LocalLifecycleOwner.current
@@ -124,7 +143,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Profidle Start")
- NostrUserProfileDataSource.loadUserProfile(userId)
+ NostrUserProfileDataSource.loadUserProfile(user)
NostrUserProfileDataSource.start()
}
if (event == Lifecycle.Event.ON_PAUSE) {
From 936f750826b858e52c8638007087321cd30f6fde Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 16:11:40 -0400
Subject: [PATCH 31/41] Moving private messages and channel loading to a
thread.
---
.../amethyst/ui/dal/ChannelFeedFilter.kt | 15 ++++++++-----
.../amethyst/ui/dal/ChatroomFeedFilter.kt | 13 +++++++-----
.../amethyst/ui/note/NoteCompose.kt | 21 +++++++++++--------
3 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt
index aa2921dce..74a104ae4 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt
@@ -1,21 +1,23 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
-import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
object ChannelFeedFilter : AdditiveFeedFilter() {
lateinit var account: Account
- lateinit var channel: Channel
+ var channelId: String? = null
- fun loadMessagesBetween(accountLoggedIn: Account, channelId: String) {
- account = accountLoggedIn
- channel = LocalCache.getOrCreateChannel(channelId)
+ fun loadMessagesBetween(accountLoggedIn: Account, channelId: String?) {
+ this.account = accountLoggedIn
+ this.channelId = channelId
}
// returns the last Note of each user.
override fun feed(): List {
+ val processingChannel = channelId ?: return emptyList()
+ val channel = LocalCache.getOrCreateChannel(processingChannel)
+
return channel.notes
.values
.filter { account.isAcceptable(it) }
@@ -24,6 +26,9 @@ object ChannelFeedFilter : AdditiveFeedFilter() {
}
override fun applyFilter(collection: Set): Set {
+ val processingChannel = channelId ?: return emptySet()
+ val channel = LocalCache.getOrCreateChannel(processingChannel)
+
return collection
.filter { it.idHex in channel.notes.keys && account.isAcceptable(it) }
.toSet()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt
index f96c68681..3cdb265f7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomFeedFilter.kt
@@ -3,21 +3,22 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
-import com.vitorpamplona.amethyst.model.User
object ChatroomFeedFilter : AdditiveFeedFilter() {
var account: Account? = null
- var withUser: User? = null
+ var withUser: String? = null
fun loadMessagesBetween(accountIn: Account, userId: String) {
account = accountIn
- withUser = LocalCache.checkGetOrCreateUser(userId)
+ withUser = userId
}
// returns the last Note of each user.
override fun feed(): List {
+ val processingUser = withUser ?: return emptyList()
+
val myAccount = account
- val myUser = withUser
+ val myUser = LocalCache.checkGetOrCreateUser(processingUser)
if (myAccount == null || myUser == null) return emptyList()
@@ -32,8 +33,10 @@ object ChatroomFeedFilter : AdditiveFeedFilter() {
}
override fun applyFilter(collection: Set): Set {
+ val processingUser = withUser ?: return emptySet()
+
val myAccount = account
- val myUser = withUser
+ val myUser = LocalCache.checkGetOrCreateUser(processingUser)
if (myAccount == null || myUser == null) return emptySet()
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 b1c4d1307..f5a9d90d4 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
@@ -190,16 +190,19 @@ fun NoteComposeInner(
var popupExpanded by remember { mutableStateOf(false) }
var showHiddenNote by remember { mutableStateOf(false) }
- var isAcceptable by remember { mutableStateOf(true) }
- var canPreview by remember { mutableStateOf(true) }
+ var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
LaunchedEffect(key1 = noteReportsState) {
withContext(Dispatchers.IO) {
- canPreview = note?.author === loggedIn ||
+ val newCanPreview = note?.author === loggedIn ||
(note?.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
!noteForReports.hasAnyReports()
- isAcceptable = account.isAcceptable(noteForReports)
+ val newIsAcceptable = account.isAcceptable(noteForReports)
+
+ if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) {
+ isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview)
+ }
}
}
@@ -214,7 +217,7 @@ fun NoteComposeInner(
),
isBoostedNote
)
- } else if (!isAcceptable && !showHiddenNote) {
+ } else if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
if (!account.isHidden(noteForReports.author!!)) {
HiddenNote(
account.getRelevantReports(noteForReports),
@@ -340,18 +343,18 @@ fun NoteComposeInner(
}
is PrivateDmEvent -> {
- RenderPrivateMessage(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController)
+ RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
}
is HighlightEvent -> {
- RenderHighlight(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController)
+ RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
}
is PollNoteEvent -> {
RenderPoll(
note,
makeItShort,
- canPreview,
+ isAcceptableAndCanPreview.second,
backgroundColor,
accountViewModel,
navController
@@ -362,7 +365,7 @@ fun NoteComposeInner(
RenderTextEvent(
note,
makeItShort,
- canPreview,
+ isAcceptableAndCanPreview.second,
backgroundColor,
accountViewModel,
navController
From 13de5973ca2385c8b584e46301ec6bbb06af0b45 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 16:12:28 -0400
Subject: [PATCH 32/41] Improving performance of the RichText Viewer
---
.../ui/components/ExpandableRichTextViewer.kt | 10 +-
.../amethyst/ui/components/RichTextViewer.kt | 458 +++++++++++-------
.../ui/components/ZoomableContentView.kt | 9 +-
.../amethyst/ui/note/MessageSetCompose.kt | 7 +-
.../amethyst/ui/note/MultiSetCompose.kt | 6 +-
.../amethyst/ui/note/NoteCompose.kt | 31 +-
.../components/TranslatableRichTextViewer.kt | 2 -
7 files changed, 330 insertions(+), 193 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
index 3b62e83bd..27abc13e7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
@@ -42,11 +42,13 @@ fun ExpandableRichTextViewer(
) {
var showFullText by remember { mutableStateOf(false) }
- // Cuts the text in the first space after 350
- val firstSpaceAfterCut = content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
- val firstNewLineAfterCut = content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
+ val whereToCut = remember {
+ // Cuts the text in the first space after 350
+ val firstSpaceAfterCut = content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
+ val firstNewLineAfterCut = content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
- val whereToCut = minOf(firstSpaceAfterCut, firstNewLineAfterCut)
+ minOf(firstSpaceAfterCut, firstNewLineAfterCut)
+ }
val text = if (showFullText) {
content
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 f0e8a89b8..bc02931e5 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
@@ -75,6 +75,14 @@ fun isValidURL(url: String?): Boolean {
val richTextDefaults = RichTextStyle().resolveDefaults()
+fun isMarkdown(content: String): Boolean {
+ return content.startsWith("> ") ||
+ content.startsWith("# ") ||
+ content.contains("##") ||
+ content.contains("__") ||
+ content.contains("```")
+}
+
@Composable
fun RichTextViewer(
content: String,
@@ -85,58 +93,39 @@ fun RichTextViewer(
accountViewModel: AccountViewModel,
navController: NavController
) {
+ val isMarkdown = remember { isMarkdown(content) }
+
Column(modifier = modifier) {
- if (content.startsWith("> ") ||
- content.startsWith("# ") ||
- content.contains("##") ||
- content.contains("__") ||
- content.contains("```")
- ) {
- val myMarkDownStyle = richTextDefaults.copy(
- codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
- textStyle = TextStyle(
- fontFamily = FontFamily.Monospace,
- fontSize = 14.sp
- ),
- modifier = Modifier
- .padding(0.dp)
- .fillMaxWidth()
- .clip(shape = RoundedCornerShape(15.dp))
- .border(
- 1.dp,
- MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
- RoundedCornerShape(15.dp)
- )
- .background(
- MaterialTheme.colors.onSurface
- .copy(alpha = 0.05f)
- .compositeOver(backgroundColor)
- )
- ),
- stringStyle = richTextDefaults.stringStyle?.copy(
- linkStyle = SpanStyle(
- textDecoration = TextDecoration.Underline,
- color = MaterialTheme.colors.primary
- ),
- codeStyle = SpanStyle(
- fontFamily = FontFamily.Monospace,
- fontSize = 14.sp,
- background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f).compositeOver(backgroundColor)
- )
- )
- )
-
- val markdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
-
- MaterialRichText(
- style = myMarkDownStyle
- ) {
- Markdown(
- content = markdownWithSpecialContent,
- markdownParseOptions = MarkdownParseOptions.Default
- )
- }
+ if (isMarkdown) {
+ RenderContentAsMarkdown(content, backgroundColor)
} else {
+ RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController)
+ }
+ }
+}
+
+class RichTextViewerState(
+ val content: String,
+ val urlSet: LinkedHashSet,
+ val imagesForPager: Map,
+ val imageList: List
+)
+
+@Composable
+private fun RenderRegular(
+ content: String,
+ tags: List>?,
+ canPreview: Boolean,
+ backgroundColor: Color,
+ accountViewModel: AccountViewModel,
+ navController: NavController
+) {
+ var processedState by remember {
+ mutableStateOf(null)
+ }
+
+ LaunchedEffect(key1 = content) {
+ withContext(Dispatchers.IO) {
val urls = UrlDetector(content, UrlDetectorOptions.Default).detect()
val urlSet = urls.mapTo(LinkedHashSet(urls.size)) { it.originalUrl }
val imagesForPager = urlSet.mapNotNull { fullUrl ->
@@ -151,67 +140,71 @@ fun RichTextViewer(
}.associateBy { it.url }
val imageList = imagesForPager.values.toList()
- // FlowRow doesn't work well with paragraphs. So we need to split them
- content.split('\n').forEach { paragraph ->
- FlowRow() {
- val s = if (isArabic(paragraph)) paragraph.trim().split(' ').reversed() else paragraph.trim().split(' ')
- s.forEach { word: String ->
- if (canPreview) {
- // Explicit URL
- val img = imagesForPager[word]
- if (img != null) {
- ZoomableContentView(img, imageList)
- } else if (urlSet.contains(word)) {
- UrlPreview(word, "$word ")
- } else if (word.startsWith("lnbc", true)) {
- MayBeInvoicePreview(word)
- } else if (word.startsWith("lnurl", true)) {
- MayBeWithdrawal(word)
- } else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
- ClickableEmail(word)
- } else if (word.length > 6 && Patterns.PHONE.matcher(word).matches()) {
- ClickablePhone(word)
- } else if (isBechLink(word)) {
- BechLink(
+ processedState = RichTextViewerState(content, urlSet, imagesForPager, imageList)
+ }
+ }
+
+ // FlowRow doesn't work well with paragraphs. So we need to split them
+ processedState?.let { state ->
+ content.split('\n').forEach { paragraph ->
+ FlowRow() {
+ val s = if (isArabic(paragraph)) {
+ paragraph.trim().split(' ')
+ .reversed()
+ } else {
+ paragraph.trim().split(' ')
+ }
+ s.forEach { word: String ->
+ if (canPreview) {
+ // Explicit URL
+ val img = state.imagesForPager[word]
+ if (img != null) {
+ ZoomableContentView(img, state.imageList)
+ } else if (state.urlSet.contains(word)) {
+ UrlPreview(word, "$word ")
+ } else if (word.startsWith("lnbc", true)) {
+ MayBeInvoicePreview(word)
+ } else if (word.startsWith("lnurl", true)) {
+ MayBeWithdrawal(word)
+ } else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
+ ClickableEmail(word)
+ } else if (word.length > 6 && Patterns.PHONE.matcher(word).matches()) {
+ ClickablePhone(word)
+ } else if (isBechLink(word)) {
+ BechLink(
+ word,
+ canPreview,
+ backgroundColor,
+ accountViewModel,
+ navController
+ )
+ } else if (word.startsWith("#")) {
+ if (tagIndex.matcher(word).matches() && tags != null) {
+ TagLink(
word,
+ tags,
canPreview,
backgroundColor,
accountViewModel,
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, 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()
- val url = matcher.group(1) // url
- val additionalChars = matcher.group(4) ?: "" // additional chars
+ } else if (hashTagsPattern.matcher(word).matches()) {
+ HashTag(word, 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()
+ val url = matcher.group(1) // url
+ val additionalChars = matcher.group(4) ?: "" // additional chars
- if (url != null) {
- ClickableUrl(url, "https://$url")
- Text("$additionalChars ")
- } else {
- Text(
- text = "$word ",
- style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
- )
- }
+ if (url != null) {
+ ClickableUrl(url, "https://$url")
+ Text("$additionalChars ")
} else {
Text(
text = "$word ",
@@ -219,69 +212,74 @@ fun RichTextViewer(
)
}
} else {
- if (urlSet.contains(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,
- canPreview,
- backgroundColor,
- accountViewModel,
- 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, 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()
- val url = matcher.group(1) // url
- val additionalChars = matcher.group(4) ?: "" // additional chars
-
- if (url != null) {
- ClickableUrl(url, "https://$url")
- Text("$additionalChars ")
- } else {
- Text(
- text = "$word ",
- style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
- )
- }
+ Text(
+ text = "$word ",
+ style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
+ )
+ }
+ } else {
+ if (state.urlSet.contains(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,
+ canPreview,
+ backgroundColor,
+ accountViewModel,
+ 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, 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()
+ val url = matcher.group(1) // url
+ val additionalChars = matcher.group(4) ?: "" // additional chars
+
+ if (url != null) {
+ ClickableUrl(url, "https://$url")
+ Text("$additionalChars ")
+ } else {
+ Text(
+ text = "$word ",
+ style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
+ )
+ }
+ } else {
+ Text(
+ text = "$word ",
+ style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
+ )
}
}
}
@@ -291,21 +289,137 @@ fun RichTextViewer(
}
@Composable
-private fun getDisplayNameFromUserNip19(parsedNip19: Nip19.Return): String? {
- if (parsedNip19.type == Nip19.Type.USER) {
- val userHex = parsedNip19.hex
- val userBase = LocalCache.getOrCreateUser(userHex)
+private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
+ val myMarkDownStyle = richTextDefaults.copy(
+ codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
+ textStyle = TextStyle(
+ fontFamily = FontFamily.Monospace,
+ fontSize = 14.sp
+ ),
+ modifier = Modifier
+ .padding(0.dp)
+ .fillMaxWidth()
+ .clip(shape = RoundedCornerShape(15.dp))
+ .border(
+ 1.dp,
+ MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
+ RoundedCornerShape(15.dp)
+ )
+ .background(
+ MaterialTheme.colors.onSurface
+ .copy(alpha = 0.05f)
+ .compositeOver(backgroundColor)
+ )
+ ),
+ stringStyle = richTextDefaults.stringStyle?.copy(
+ linkStyle = SpanStyle(
+ textDecoration = TextDecoration.Underline,
+ color = MaterialTheme.colors.primary
+ ),
+ codeStyle = SpanStyle(
+ fontFamily = FontFamily.Monospace,
+ fontSize = 14.sp,
+ background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f)
+ .compositeOver(backgroundColor)
+ )
+ )
+ )
- val userState by userBase.live().metadata.observeAsState()
- val displayName = userState?.user?.bestDisplayName()
- if (displayName !== null) {
- return displayName
+ var markdownWithSpecialContent by remember { mutableStateOf(null) }
+ var nip19References by remember { mutableStateOf>(emptyList()) }
+ var refresh by remember { mutableStateOf(0) }
+
+ LaunchedEffect(key1 = content) {
+ withContext(Dispatchers.IO) {
+ nip19References = returnNIP19References(content)
+ markdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
+ }
+ }
+
+ LaunchedEffect(key1 = refresh) {
+ withContext(Dispatchers.IO) {
+ val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
+ if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
+ markdownWithSpecialContent = newMarkdownWithSpecialContent
+ }
+ }
+ }
+
+ nip19References.forEach {
+ var baseUser by remember { mutableStateOf(null) }
+ var baseNote by remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = it.hex) {
+ withContext(Dispatchers.IO) {
+ if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
+ LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
+ baseNote = note
+ }
+ }
+
+ if (it.type == Nip19.Type.USER) {
+ LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
+ baseUser = user
+ }
+ }
+ }
+ }
+
+ baseNote?.let {
+ val noteState by it.live().metadata.observeAsState()
+ if (noteState?.note?.event != null) {
+ refresh++
+ }
+ }
+ baseUser?.let {
+ val userState by it.live().metadata.observeAsState()
+ if (userState?.user?.info != null) {
+ refresh++
+ }
+ }
+ }
+
+ markdownWithSpecialContent?.let {
+ MaterialRichText(
+ style = myMarkDownStyle
+ ) {
+ Markdown(
+ content = it,
+ markdownParseOptions = MarkdownParseOptions.Default
+ )
}
}
- return null
}
-@Composable
+private fun getDisplayNameFromNip19(nip19: Nip19.Return): String? {
+ if (nip19.type == Nip19.Type.USER) {
+ return LocalCache.users[nip19.hex]?.bestDisplayName()
+ } else if (nip19.type == Nip19.Type.NOTE) {
+ return LocalCache.notes[nip19.hex]?.idDisplayNote()
+ } else if (nip19.type == Nip19.Type.ADDRESS) {
+ return LocalCache.addressables[nip19.hex]?.idDisplayNote()
+ } else if (nip19.type == Nip19.Type.EVENT) {
+ return LocalCache.notes[nip19.hex]?.idDisplayNote() ?: LocalCache.addressables[nip19.hex]?.idDisplayNote()
+ } else {
+ return null
+ }
+}
+
+private fun returnNIP19References(content: String): List {
+ val listOfReferences = mutableListOf()
+ content.split('\n').forEach { paragraph ->
+ paragraph.split(' ').forEach { word: String ->
+ if (isBechLink(word)) {
+ val parsedNip19 = Nip19.uriToRoute(word)
+ parsedNip19?.let {
+ listOfReferences.add(it)
+ }
+ }
+ }
+ }
+ return listOfReferences
+}
+
private fun returnMarkdownWithSpecialContent(content: String): String {
var returnContent = ""
content.split('\n').forEach { paragraph ->
@@ -319,7 +433,7 @@ private fun returnMarkdownWithSpecialContent(content: String): String {
} else if (isBechLink(word)) {
val parsedNip19 = Nip19.uriToRoute(word)
returnContent += if (parsedNip19 !== null) {
- val displayName = getDisplayNameFromUserNip19(parsedNip19)
+ val displayName = getDisplayNameFromNip19(parsedNip19)
if (displayName != null) {
"[@$displayName](nostr://$word) "
} else {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index dbd57c466..fec774000 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -209,7 +209,9 @@ private fun LocalImageView(
mutableStateOf(null)
}
- val ratio = aspectRatio(content.dim)
+ val ratio = remember {
+ aspectRatio(content.dim)
+ }
BoxWithConstraints(contentAlignment = Alignment.Center) {
val myModifier = mainImageModifier.also {
@@ -257,6 +259,7 @@ private fun UrlImageView(
content: ZoomableUrlImage,
mainImageModifier: Modifier
) {
+ println("UrlImageView")
val scope = rememberCoroutineScope()
val context = LocalContext.current
@@ -269,7 +272,9 @@ private fun UrlImageView(
mutableStateOf(null)
}
- val ratio = aspectRatio(content.dim)
+ val ratio = remember {
+ aspectRatio(content.dim)
+ }
LaunchedEffect(key1 = content.url, key2 = imageState) {
if (imageState is AsyncImagePainter.State.Success) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
index 1557a09b0..094f6f033 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt
@@ -38,7 +38,6 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
val noteState by messageSetCard.note.live().metadata.observeAsState()
val note = noteState?.note
- val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
@@ -50,10 +49,14 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
LaunchedEffect(key1 = messageSetCard.createdAt()) {
scope.launch(Dispatchers.IO) {
- isNew =
+ val newIsNew =
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
+
+ if (newIsNew != isNew) {
+ isNew = newIsNew
+ }
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index 5b4464834..7b454639a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -68,9 +68,13 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
LaunchedEffect(key1 = multiSetCard.createdAt()) {
scope.launch(Dispatchers.IO) {
- isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
+ val newIsNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt)
+
+ if (newIsNew != isNew) {
+ isNew = newIsNew
+ }
}
}
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 f5a9d90d4..d552c92a3 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
@@ -249,7 +249,11 @@ fun NoteComposeInner(
val createdAt = note.createdAt()
if (createdAt != null) {
NotificationCache.markAsRead(it, createdAt)
- isNew = createdAt > lastTime
+
+ val newIsNew = createdAt > lastTime
+ if (newIsNew != isNew) {
+ isNew = newIsNew
+ }
}
}
}
@@ -420,7 +424,8 @@ private fun RenderTextEvent(
) {
val noteEvent = note.event ?: return
- val eventContent = accountViewModel.decrypt(note)
+ val eventContent = remember { accountViewModel.decrypt(note) }
+ val modifier = remember { Modifier.fillMaxWidth() }
if (eventContent != null) {
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
@@ -434,7 +439,7 @@ private fun RenderTextEvent(
TranslatableRichTextViewer(
content = eventContent,
canPreview = canPreview && !makeItShort,
- modifier = Modifier.fillMaxWidth(),
+ modifier = modifier,
tags = noteEvent.tags(),
backgroundColor = backgroundColor,
accountViewModel = accountViewModel,
@@ -548,10 +553,10 @@ private fun RenderPrivateMessage(
navController: NavController
) {
val noteEvent = note.event as? PrivateDmEvent ?: return
- val withMe = noteEvent.with(accountViewModel.userProfile().pubkeyHex)
+ val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) }
if (withMe) {
- val eventContent = accountViewModel.decrypt(note)
+ val eventContent = remember { accountViewModel.decrypt(note) }
if (eventContent != null) {
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
@@ -908,7 +913,7 @@ fun TimeAgo(time: Long) {
@Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
- val baseChannel = baseNote.channel()
+ val baseChannel = remember { baseNote.channel() }
Column(Modifier.width(55.dp)) {
// Draws the boosted picture outside the boosted card.
@@ -1002,7 +1007,13 @@ fun DisplayHighlight(
accountViewModel: AccountViewModel,
navController: NavController
) {
- val quote = highlight.split("\n").map { "> *${it.removeSuffix(" ")}*" }.joinToString("\n")
+ val quote =
+ remember {
+ highlight
+ .split("\n")
+ .map { "> *${it.removeSuffix(" ")}*" }
+ .joinToString("\n")
+ }
TranslatableRichTextViewer(
quote,
@@ -1205,9 +1216,9 @@ fun BadgeDisplay(baseNote: Note) {
val background = MaterialTheme.colors.background
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
- val image = badgeData.image()
- val name = badgeData.name()
- val description = badgeData.description()
+ val image = remember { badgeData.image() }
+ val name = remember { badgeData.name() }
+ val description = remember { badgeData.description() }
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
var imageResult by remember { mutableStateOf(null) }
diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
index 7c4c04457..4c6e17a6c 100644
--- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
+++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt
@@ -73,8 +73,6 @@ fun TranslatableRichTextViewer(
showOriginal = preference == task.result.sourceLang
}
translatedTextState.value = task.result
- } else {
- translatedTextState.value = ResultOrError(content, null, null, null)
}
}
}
From eba14cf9f1ac911a638c013522506c4c04dc882e Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 16:14:18 -0400
Subject: [PATCH 33/41] v0.43.0
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index bfcfb4b40..5ac6bb1b6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 147
- versionName "0.42.5"
+ versionCode 148
+ versionName "0.43.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
From cb09a26ef5deea0fd3698ac2f37838d2e85ff8d8 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 18:37:33 -0400
Subject: [PATCH 34/41] Refactoring the nostr.build uploader
---
.../amethyst/ImageUploadTesting.kt | 20 +++---
.../amethyst/ui/actions/ImageUploader.kt | 70 +++++++++----------
.../amethyst/ui/actions/NewMediaModel.kt | 25 ++++---
.../amethyst/ui/actions/NewPostViewModel.kt | 1 -
.../ui/actions/NewUserMetadataViewModel.kt | 1 -
5 files changed, 59 insertions(+), 58 deletions(-)
diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
index c8803f82d..bdcbca26d 100644
--- a/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
+++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
@@ -9,7 +9,6 @@ import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.fail
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Base64
@@ -21,12 +20,12 @@ class ImageUploadTesting {
@Test()
fun testImgurUpload() = runBlocking {
- val inputStream = Base64.getDecoder().decode(image).inputStream()
-
- println("Uploading")
+ val bytes = Base64.getDecoder().decode(image)
+ val inputStream = bytes.inputStream()
ImageUploader.uploadImage(
inputStream,
+ bytes.size.toLong(),
"image/gif",
ImgurServer(),
onSuccess = { url, contentType ->
@@ -43,14 +42,13 @@ class ImageUploadTesting {
}
@Test()
- @Ignore
fun testNostrBuildUpload() = runBlocking {
- val inputStream = Base64.getDecoder().decode(image).inputStream()
-
- println("Uploading")
+ val bytes = Base64.getDecoder().decode(image)
+ val inputStream = bytes.inputStream()
ImageUploader.uploadImage(
inputStream,
+ bytes.size.toLong(),
"image/gif",
NostrBuildServer(),
onSuccess = { url, contentType ->
@@ -68,12 +66,12 @@ class ImageUploadTesting {
@Test()
fun testNostrImgUpload() = runBlocking {
- val inputStream = Base64.getDecoder().decode(image).inputStream()
-
- println("Uploading")
+ val bytes = Base64.getDecoder().decode(image)
+ val inputStream = bytes.inputStream()
ImageUploader.uploadImage(
inputStream,
+ bytes.size.toLong(),
"image/gif",
NostrImgServer(),
onSuccess = { url, contentType ->
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
index 766056a87..10de05922 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
@@ -10,7 +10,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.RequestBody.Companion.asRequestBody
import okio.BufferedSink
import okio.source
import java.io.File
@@ -27,13 +26,19 @@ object ImageUploader {
fun uploadImage(
uri: Uri,
server: ServersAvailable,
- context: Context,
contentResolver: ContentResolver,
onSuccess: (String, String?) -> Unit,
onError: (Throwable) -> Unit
) {
val contentType = contentResolver.getType(uri)
val imageInputStream = contentResolver.openInputStream(uri)
+
+ val length = contentResolver.query(uri, null, null, null, null)?.use {
+ it.moveToFirst()
+ val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
+ it.getLong(sizeIndex)
+ } ?: 0
+
checkNotNull(imageInputStream) {
"Can't open the image input stream"
}
@@ -47,23 +52,17 @@ object ImageUploader {
ImgurServer()
}
- val file = getRealPathFromURI(uri, context)?.let { File(it) } // create path from uri
- if (file != null) {
- uploadImage(file, imageInputStream, contentType, myServer, server, onSuccess, onError)
- }
+ uploadImage(imageInputStream, length, contentType, myServer, onSuccess, onError)
}
fun uploadImage(
- file: File,
inputStream: InputStream,
+ length: Long,
contentType: String?,
server: FileServer,
- serverType: ServersAvailable,
onSuccess: (String, String?) -> Unit,
onError: (Throwable) -> Unit
) {
- val category = contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
-
val fileName = randomChars()
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
@@ -71,35 +70,23 @@ object ImageUploader {
val requestBody: RequestBody
val requestBuilder = Request.Builder()
- if (serverType == ServersAvailable.NOSTR_BUILD) {
- requestBuilder.addHeader("Content-Type", "multipart/form-data")
- requestBody = MultipartBody.Builder()
- .setType(MultipartBody.FORM)
- .addFormDataPart(
- "fileToUpload",
- "$fileName.$extension",
- file.asRequestBody(contentType?.toMediaType())
+ requestBody = MultipartBody.Builder()
+ .setType(MultipartBody.FORM)
+ .addFormDataPart(
+ server.inputParameterName(contentType),
+ "$fileName.$extension",
- )
- .build()
- } else {
- requestBody = MultipartBody.Builder()
- .setType(MultipartBody.FORM)
- .addFormDataPart(
- category,
- "$fileName.$extension",
+ object : RequestBody() {
+ override fun contentType() = contentType?.toMediaType()
- object : RequestBody() {
- override fun contentType(): MediaType? =
- contentType?.toMediaType()
+ override fun contentLength() = length
- override fun writeTo(sink: BufferedSink) {
- inputStream.source().use(sink::writeAll)
- }
+ override fun writeTo(sink: BufferedSink) {
+ inputStream.source().use(sink::writeAll)
}
- )
- .build()
- }
+ }
+ )
+ .build()
server.clientID()?.let {
requestBuilder.addHeader("Authorization", it)
@@ -172,6 +159,7 @@ fun getRealPathFromURI(uri: Uri, context: Context): String? {
abstract class FileServer {
abstract fun postUrl(contentType: String?): String
abstract fun parseUrlFromSucess(body: String): String?
+ abstract fun inputParameterName(contentType: String?): String
open fun clientID(): String? = null
}
@@ -185,6 +173,10 @@ class NostrImgServer : FileServer() {
return url
}
+ override fun inputParameterName(contentType: String?): String {
+ return contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
+ }
+
override fun clientID() = null
}
@@ -200,6 +192,10 @@ class ImgurServer : FileServer() {
return url
}
+ override fun inputParameterName(contentType: String?): String {
+ return contentType?.toMediaType()?.toString()?.split("/")?.get(0) ?: "image"
+ }
+
override fun clientID() = "Client-ID e6aea87296f3f96"
}
@@ -210,5 +206,9 @@ class NostrBuildServer : FileServer() {
return url.toString().replace("\"", "")
}
+ override fun inputParameterName(contentType: String?): String {
+ return "fileToUpload"
+ }
+
override fun clientID() = null
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
index ac812b2c2..d15fa0c8a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
@@ -77,7 +77,6 @@ open class NewMediaModel : ViewModel() {
ImageUploader.uploadImage(
uri = uri,
server = serverToUse,
- context = context,
contentResolver = contentResolver,
onSuccess = { imageUrl, mimeType ->
createNIP94Record(imageUrl, mimeType, description)
@@ -115,18 +114,24 @@ open class NewMediaModel : ViewModel() {
uploadingDescription.value = "Server Processing"
// Images don't seem to be ready immediately after upload
- if (mimeType?.startsWith("image/") == true) {
- delay(2000)
- } else {
- delay(15000)
+ var imageData: ByteArray? = null
+ var tentatives = 0
+
+ // Servers are usually not ready.. so tries to download it for 15 times/seconds.
+ while (imageData == null && tentatives < 15) {
+ imageData = try {
+ URL(imageUrl).readBytes()
+ } catch (e: Exception) {
+ tentatives++
+ delay(1000)
+ null
+ }
}
uploadingDescription.value = "Downloading"
uploadingPercentage.value = 0.60f
- try {
- val imageData = URL(imageUrl).readBytes()
-
+ if (imageData != null) {
uploadingPercentage.value = 0.80f
uploadingDescription.value = "Hashing"
@@ -154,8 +159,8 @@ open class NewMediaModel : ViewModel() {
}
}
)
- } catch (e: Exception) {
- Log.e("ImageDownload", "Couldn't download image from server: ${e.message}")
+ } else {
+ Log.e("ImageDownload", "Couldn't download image from server")
cancel()
uploadingPercentage.value = 0.00f
uploadingDescription.value = null
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
index 42590becc..488a9ce1a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
@@ -145,7 +145,6 @@ open class NewPostViewModel : ViewModel() {
ImageUploader.uploadImage(
uri = it,
server = server,
- context = context,
contentResolver = contentResolver,
onSuccess = { imageUrl, mimeType ->
if (server == ServersAvailable.IMGUR_NIP_94 || server == ServersAvailable.NOSTRIMG_NIP_94 || server == ServersAvailable.NOSTR_BUILD_NIP_94) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt
index 80abdf2cf..269f22871 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt
@@ -171,7 +171,6 @@ class NewUserMetadataViewModel : ViewModel() {
ImageUploader.uploadImage(
uri = it,
server = account.defaultFileServer,
- context = context,
contentResolver = context.contentResolver,
onSuccess = { imageUrl, mimeType ->
onUploading(false)
From f2bbdd44a6b2272c2d8943d156bdcdad512b9b29 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 18:40:24 -0400
Subject: [PATCH 35/41] Removing file transform function
---
.../amethyst/ui/actions/ImageUploader.kt | 36 -------------------
1 file changed, 36 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
index 10de05922..212522d48 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
@@ -1,10 +1,8 @@
package com.vitorpamplona.amethyst.ui.actions
import android.content.ContentResolver
-import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
-import android.util.Log
import android.webkit.MimeTypeMap
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
@@ -12,8 +10,6 @@ import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okio.BufferedSink
import okio.source
-import java.io.File
-import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
@@ -124,38 +120,6 @@ object ImageUploader {
}
}
-fun getRealPathFromURI(uri: Uri, context: Context): String? {
- val returnCursor = context.contentResolver.query(uri, null, null, null, null)
- val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
- returnCursor.moveToFirst()
- val name = returnCursor.getString(nameIndex)
- val file = File(context.filesDir, name)
- try {
- val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
- val outputStream = FileOutputStream(file)
- var read = 0
- val maxBufferSize = 1 * 1024 * 1024
- val bytesAvailable: Int = inputStream?.available() ?: 0
- val bufferSize = Math.min(bytesAvailable, maxBufferSize)
- val buffers = ByteArray(bufferSize)
- while (inputStream?.read(buffers).also {
- if (it != null) {
- read = it
- }
- } != -1
- ) {
- outputStream.write(buffers, 0, read)
- }
- Log.e("File Size", "Size " + file.length())
- inputStream?.close()
- outputStream.close()
- Log.e("File Path", "Path " + file.path)
- } catch (e: java.lang.Exception) {
- Log.e("Exception", e.message!!)
- }
- return file.path
-}
-
abstract class FileServer {
abstract fun postUrl(contentType: String?): String
abstract fun parseUrlFromSucess(body: String): String?
From aa07fbfc94e93cfd04a61363398b8f62724f5276 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 18:47:39 -0400
Subject: [PATCH 36/41] Adjusting NostrFilesDev to the latest API
---
.../amethyst/ImageUploadTesting.kt | 26 ++++++++++++++++++-
.../amethyst/ui/actions/ImageUploader.kt | 8 ++++--
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
index bdcbca26d..bb25fe513 100644
--- a/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
+++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
@@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.ui.actions.ImageUploader
import com.vitorpamplona.amethyst.ui.actions.ImgurServer
import com.vitorpamplona.amethyst.ui.actions.NostrBuildServer
+import com.vitorpamplona.amethyst.ui.actions.NostrFilesDevServer
import com.vitorpamplona.amethyst.ui.actions.NostrImgServer
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.fail
@@ -38,7 +39,7 @@ class ImageUploadTesting {
}
)
- delay(1000)
+ delay(5000)
}
@Test()
@@ -86,4 +87,27 @@ class ImageUploadTesting {
delay(1000)
}
+
+ @Test()
+ fun testNostrFilesDevUpload() = runBlocking {
+ val bytes = Base64.getDecoder().decode(image)
+ val inputStream = bytes.inputStream()
+
+ ImageUploader.uploadImage(
+ inputStream,
+ bytes.size.toLong(),
+ "image/gif",
+ NostrFilesDevServer(),
+ onSuccess = { url, contentType ->
+ println("Uploaded to $url")
+ assertNotNull(url)
+ },
+ onError = {
+ println("Failed to Upload")
+ fail("${it.message}")
+ }
+ )
+
+ delay(5000)
+ }
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
index 1a9226a46..b85bfaec0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
@@ -45,7 +45,7 @@ object ImageUploader {
} else if (server == ServersAvailable.NOSTR_BUILD) {
NostrBuildServer()
} else if (server == ServersAvailable.NOSTRFILES_DEV) {
- NostrfilesDevServer()
+ NostrFilesDevServer()
} else {
ImgurServer()
}
@@ -179,7 +179,7 @@ class NostrBuildServer : FileServer() {
override fun clientID() = null
}
-class NostrfilesDevServer : FileServer() {
+class NostrFilesDevServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostrfiles.dev/upload_image"
override fun parseUrlFromSucess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
@@ -187,5 +187,9 @@ class NostrfilesDevServer : FileServer() {
return url
}
+ override fun inputParameterName(contentType: String?): String {
+ return "file"
+ }
+
override fun clientID() = null
}
From 462c73341f15550e0a69792da760a5b2421ce648 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 21:45:38 -0400
Subject: [PATCH 37/41] Fixes cutting text in the wrong language
---
.../amethyst/ui/components/ExpandableRichTextViewer.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
index 27abc13e7..ba0d79464 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt
@@ -42,7 +42,7 @@ fun ExpandableRichTextViewer(
) {
var showFullText by remember { mutableStateOf(false) }
- val whereToCut = remember {
+ val whereToCut = remember(content) {
// Cuts the text in the first space after 350
val firstSpaceAfterCut = content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
val firstNewLineAfterCut = content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
From d6d273e34cf03dffaad5f844a8dff8a2dd651b0d Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Sun, 7 May 2023 21:46:01 -0400
Subject: [PATCH 38/41] v0.43.1
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 5ac6bb1b6..86ba12fd4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 148
- versionName "0.43.0"
+ versionCode 149
+ versionName "0.43.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
From ae268cc197b3fe528813b6ffb0f12befab886658 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Mon, 8 May 2023 09:39:44 -0400
Subject: [PATCH 39/41] Fixing NostrFiles dev downloader
---
.../amethyst/service/FileHeader.kt | 11 +++--
.../amethyst/ui/actions/ImageDownloader.kt | 47 +++++++++++++++++++
.../amethyst/ui/actions/ImageUploader.kt | 41 +++++++++-------
.../amethyst/ui/actions/NewMediaModel.kt | 16 ++-----
.../amethyst/ui/actions/NewMediaView.kt | 5 +-
.../amethyst/ui/actions/NewPostView.kt | 4 +-
.../amethyst/ui/actions/NewPostViewModel.kt | 7 ---
.../ui/components/ZoomableContentView.kt | 1 -
8 files changed, 87 insertions(+), 45 deletions(-)
create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
index 941aab60e..81a09ec10 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log
import com.vitorpamplona.amethyst.model.toHexKey
+import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import io.trbl.blurhash.BlurHash
import java.net.URL
import java.security.MessageDigest
@@ -19,11 +20,15 @@ class FileHeader(
val description: String? = null
) {
companion object {
- fun prepare(fileUrl: String, mimeType: String?, description: String?, onReady: (FileHeader) -> Unit, onError: () -> Unit) {
+ suspend fun prepare(fileUrl: String, mimeType: String?, description: String?, onReady: (FileHeader) -> Unit, onError: () -> Unit) {
try {
- val imageData = URL(fileUrl).readBytes()
+ val imageData: ByteArray? = ImageDownloader().waitAndGetImage(fileUrl)
- prepare(imageData, fileUrl, mimeType, description, onReady, onError)
+ if (imageData != null)
+ prepare(imageData, fileUrl, mimeType, description, onReady, onError)
+ else {
+ onError()
+ }
} catch (e: Exception) {
Log.e("ImageDownload", "Couldn't download image from server: ${e.message}")
onError()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
new file mode 100644
index 000000000..c2bd13abf
--- /dev/null
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
@@ -0,0 +1,47 @@
+package com.vitorpamplona.amethyst.ui.actions
+
+import kotlinx.coroutines.delay
+import java.net.HttpURLConnection
+import java.net.URL
+
+class ImageDownloader {
+ suspend fun waitAndGetImage(imageUrl: String): ByteArray? {
+ var imageData: ByteArray? = null
+ var tentatives = 0
+
+ // Servers are usually not ready.. so tries to download it for 15 times/seconds.
+ while (imageData == null && tentatives < 15) {
+ imageData = try {
+ HttpURLConnection.setFollowRedirects(true)
+ var url = URL(imageUrl)
+ var huc = url.openConnection() as HttpURLConnection
+ huc.instanceFollowRedirects = true
+ var responseCode = huc.responseCode
+
+ if (responseCode in 300..400) {
+ val newUrl: String = huc.getHeaderField("Location")
+
+ // open the new connnection again
+ url = URL(newUrl)
+ huc = url.openConnection() as HttpURLConnection
+ responseCode = huc.responseCode
+ }
+
+ if (responseCode in 200..300) {
+ huc.inputStream.use { it.readBytes() }
+ } else {
+ tentatives++
+ delay(1000)
+
+ null
+ }
+ } catch (e: Exception) {
+ tentatives++
+ delay(1000)
+ null
+ }
+ }
+
+ return imageData
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
index b85bfaec0..52a83020f 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt
@@ -38,16 +38,22 @@ object ImageUploader {
checkNotNull(imageInputStream) {
"Can't open the image input stream"
}
- val myServer = if (server == ServersAvailable.IMGUR) {
- ImgurServer()
- } else if (server == ServersAvailable.NOSTRIMG) {
- NostrImgServer()
- } else if (server == ServersAvailable.NOSTR_BUILD) {
- NostrBuildServer()
- } else if (server == ServersAvailable.NOSTRFILES_DEV) {
- NostrFilesDevServer()
- } else {
- ImgurServer()
+ val myServer = when (server) {
+ ServersAvailable.IMGUR, ServersAvailable.IMGUR_NIP_94 -> {
+ ImgurServer()
+ }
+ ServersAvailable.NOSTRIMG, ServersAvailable.NOSTRIMG_NIP_94 -> {
+ NostrImgServer()
+ }
+ ServersAvailable.NOSTR_BUILD, ServersAvailable.NOSTR_BUILD_NIP_94 -> {
+ NostrBuildServer()
+ }
+ ServersAvailable.NOSTRFILES_DEV, ServersAvailable.NOSTRFILES_DEV_NIP_94 -> {
+ NostrFilesDevServer()
+ }
+ else -> {
+ ImgurServer()
+ }
}
uploadImage(imageInputStream, length, contentType, myServer, onSuccess, onError)
@@ -101,7 +107,7 @@ object ImageUploader {
try {
check(response.isSuccessful)
response.body.use { body ->
- val url = server.parseUrlFromSucess(body.string())
+ val url = server.parseUrlFromSuccess(body.string())
checkNotNull(url) {
"There must be an uploaded image URL in the response"
}
@@ -124,7 +130,7 @@ object ImageUploader {
abstract class FileServer {
abstract fun postUrl(contentType: String?): String
- abstract fun parseUrlFromSucess(body: String): String?
+ abstract fun parseUrlFromSuccess(body: String): String?
abstract fun inputParameterName(contentType: String?): String
open fun clientID(): String? = null
@@ -133,7 +139,7 @@ abstract class FileServer {
class NostrImgServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostrimg.com/api/upload"
- override fun parseUrlFromSucess(body: String): String? {
+ override fun parseUrlFromSuccess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
val url = tree?.get("data")?.get("link")?.asText()
return url
@@ -152,7 +158,7 @@ class ImgurServer : FileServer() {
return if (category == "image") "https://api.imgur.com/3/image" else "https://api.imgur.com/3/upload"
}
- override fun parseUrlFromSucess(body: String): String? {
+ override fun parseUrlFromSuccess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
val url = tree?.get("data")?.get("link")?.asText()
return url
@@ -167,7 +173,7 @@ class ImgurServer : FileServer() {
class NostrBuildServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostr.build/api/upload/android.php"
- override fun parseUrlFromSucess(body: String): String? {
+ override fun parseUrlFromSuccess(body: String): String? {
val url = jacksonObjectMapper().readTree(body) // return url.toString()
return url.toString().replace("\"", "")
}
@@ -181,10 +187,9 @@ class NostrBuildServer : FileServer() {
class NostrFilesDevServer : FileServer() {
override fun postUrl(contentType: String?) = "https://nostrfiles.dev/upload_image"
- override fun parseUrlFromSucess(body: String): String? {
+ override fun parseUrlFromSuccess(body: String): String? {
val tree = jacksonObjectMapper().readTree(body)
- val url = tree?.get("url")?.asText()
- return url
+ return tree?.get("url")?.asText()
}
override fun inputParameterName(contentType: String?): String {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
index 73703fe6b..5ee0705fa 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
@@ -14,8 +14,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
+import java.net.HttpURLConnection
import java.net.URL
+
open class NewMediaModel : ViewModel() {
var account: Account? = null
@@ -116,19 +118,7 @@ open class NewMediaModel : ViewModel() {
uploadingDescription.value = "Server Processing"
// Images don't seem to be ready immediately after upload
- var imageData: ByteArray? = null
- var tentatives = 0
-
- // Servers are usually not ready.. so tries to download it for 15 times/seconds.
- while (imageData == null && tentatives < 15) {
- imageData = try {
- URL(imageUrl).readBytes()
- } catch (e: Exception) {
- tentatives++
- delay(1000)
- null
- }
- }
+ val imageData: ByteArray? = ImageDownloader().waitAndGetImage(imageUrl)
uploadingDescription.value = "Downloading"
uploadingPercentage.value = 0.60f
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
index cb01b6e80..0d3c4ae74 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
@@ -110,7 +110,10 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
}
fun isNIP94Server(selectedServer: ServersAvailable?): Boolean {
- return selectedServer == ServersAvailable.NOSTRIMG_NIP_94 || selectedServer == ServersAvailable.IMGUR_NIP_94 || selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 || selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
+ return selectedServer == ServersAvailable.NOSTRIMG_NIP_94
+ || selectedServer == ServersAvailable.IMGUR_NIP_94
+ || selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94
+ || selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
}
@Composable
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 571565a3b..620e393c5 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
@@ -614,11 +614,11 @@ fun ImageVideoDescription(
Triple(ServersAvailable.IMGUR, stringResource(id = R.string.upload_server_imgur), stringResource(id = R.string.upload_server_imgur_explainer)),
Triple(ServersAvailable.NOSTRIMG, stringResource(id = R.string.upload_server_nostrimg), stringResource(id = R.string.upload_server_nostrimg_explainer)),
Triple(ServersAvailable.NOSTR_BUILD, stringResource(id = R.string.upload_server_nostrbuild), stringResource(id = R.string.upload_server_nostrbuild_explainer)),
- // Triple(ServersAvailable.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)),
+ Triple(ServersAvailable.NOSTRFILES_DEV, stringResource(id = R.string.upload_server_nostrfilesdev), stringResource(id = R.string.upload_server_nostrfilesdev_explainer)),
Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_explainer)),
Triple(ServersAvailable.NOSTRIMG_NIP_94, stringResource(id = R.string.upload_server_nostrimg_nip94), stringResource(id = R.string.upload_server_nostrimg_nip94_explainer)),
Triple(ServersAvailable.NOSTR_BUILD_NIP_94, stringResource(id = R.string.upload_server_nostrbuild_nip94), stringResource(id = R.string.upload_server_nostrbuild_nip94_explainer)),
- // Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
+ Triple(ServersAvailable.NOSTRFILES_DEV_NIP_94, stringResource(id = R.string.upload_server_nostrfilesdev_nip94), stringResource(id = R.string.upload_server_nostrfilesdev_nip94_explainer)),
Triple(ServersAvailable.NIP95, stringResource(id = R.string.upload_server_relays_nip95), stringResource(id = R.string.upload_server_relays_nip95_explainer))
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
index ded3776fb..8899b7beb 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
@@ -287,13 +287,6 @@ open class NewPostViewModel : ViewModel() {
fun createNIP94Record(imageUrl: String, mimeType: String?, description: String) {
viewModelScope.launch(Dispatchers.IO) {
// Images don't seem to be ready immediately after upload
-
- if (mimeType?.startsWith("image/") == true) {
- delay(2000)
- } else {
- delay(5000)
- }
-
FileHeader.prepare(
imageUrl,
mimeType,
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index fec774000..5c34d567f 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -259,7 +259,6 @@ private fun UrlImageView(
content: ZoomableUrlImage,
mainImageModifier: Modifier
) {
- println("UrlImageView")
val scope = rememberCoroutineScope()
val context = LocalContext.current
From d0eadcf30d38f3006bcda41812a23a5937b8ad1e Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Mon, 8 May 2023 09:39:54 -0400
Subject: [PATCH 40/41] Formatting
---
.../java/com/vitorpamplona/amethyst/service/FileHeader.kt | 5 ++---
.../vitorpamplona/amethyst/ui/actions/ImageDownloader.kt | 2 +-
.../vitorpamplona/amethyst/ui/actions/NewMediaModel.kt | 4 ----
.../com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt | 8 ++++----
.../vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt | 1 -
5 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
index 81a09ec10..dbd5ec342 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt
@@ -6,7 +6,6 @@ import android.util.Log
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import io.trbl.blurhash.BlurHash
-import java.net.URL
import java.security.MessageDigest
import kotlin.math.roundToInt
@@ -24,9 +23,9 @@ class FileHeader(
try {
val imageData: ByteArray? = ImageDownloader().waitAndGetImage(fileUrl)
- if (imageData != null)
+ if (imageData != null) {
prepare(imageData, fileUrl, mimeType, description, onReady, onError)
- else {
+ } else {
onError()
}
} catch (e: Exception) {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
index c2bd13abf..7e2657522 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageDownloader.kt
@@ -44,4 +44,4 @@ class ImageDownloader {
return imageData
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
index 5ee0705fa..e8a6398ec 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt
@@ -11,12 +11,8 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.*
import com.vitorpamplona.amethyst.service.FileHeader
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
-import java.net.HttpURLConnection
-import java.net.URL
-
open class NewMediaModel : ViewModel() {
var account: Account? = null
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
index 0d3c4ae74..955e0a4fc 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
@@ -110,10 +110,10 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
}
fun isNIP94Server(selectedServer: ServersAvailable?): Boolean {
- return selectedServer == ServersAvailable.NOSTRIMG_NIP_94
- || selectedServer == ServersAvailable.IMGUR_NIP_94
- || selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94
- || selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
+ return selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
+ selectedServer == ServersAvailable.IMGUR_NIP_94 ||
+ selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 ||
+ selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94
}
@Composable
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
index 8899b7beb..4b12675d4 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
@@ -19,7 +19,6 @@ import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.ui.components.isValidURL
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
From 70890def82686374e00dc96ad7f81e034fc334a4 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Mon, 8 May 2023 09:43:31 -0400
Subject: [PATCH 41/41] v0.43.2
---
app/build.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 86ba12fd4..1086fcee1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 33
- versionCode 149
- versionName "0.43.1"
+ versionCode 150
+ versionName "0.43.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {