From 401d807d29712bf7a6ac120044f481397c597648 Mon Sep 17 00:00:00 2001
From: Zoltan <34719275+ZsZolee@users.noreply.github.com>
Date: Tue, 11 Apr 2023 10:26:19 +0200
Subject: [PATCH 01/16] Added new Hungarian translations
---
app/src/main/res/values-hu/strings.xml | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 81296d243..dc5073f2a 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -263,9 +263,30 @@
nsec / hex privát kulcs
Hozzájárulás összege sats-ban
+ Szavazás Létrehozása
+ Szükséges mezők:
+ Zap-et kapják
+ Szavazás elsődleges leírása…
+ Szavazás %s megoszlása
+ Szavazás opcióinak leírása
+ Kiegészítő mezők:
+ Zap minimum
+ Zap maximum
+ Konszenzus
+ (0–100)%
+ Szavazás lezárása
+ napok
+ A szavazásra nem lehet már új szavazatot leadni
+ Zap összege
+ Az ilyen típusú szavazásokon felhasználónként csak egy szavazat engedélyezett
+
"%1$s esemény keresése"
Nyilvános üzenet hozzáadása
Köszönöm a kemény munkát!
+
+ Létrehoz és Hozzáad
+ A szavazás létrehozója sajátjára nem szavazhat.
+ #zappoll
From 9d27b8d47e705936b868c065373daff8661f4adc Mon Sep 17 00:00:00 2001
From: nalbaltaji
Date: Sat, 15 Apr 2023 18:59:44 +0200
Subject: [PATCH 02/16] Intercept back press when drawer is opened to close
drawer
---
.../amethyst/ui/screen/loggedIn/MainScreen.kt | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt
index e0293b5d2..49486609d 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
+import androidx.activity.compose.BackHandler
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
@@ -19,6 +20,7 @@ import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
@@ -34,10 +36,12 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.navigation.currentRoute
import com.vitorpamplona.amethyst.ui.screen.AccountState
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
+import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
+ val coroutineScope = rememberCoroutineScope()
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
val sheetState = rememberModalBottomSheetState(
@@ -64,6 +68,9 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
},
drawerContent = {
DrawerContent(navController, scaffoldState, sheetState, accountViewModel)
+ BackHandler(enabled = scaffoldState.drawerState.isOpen) {
+ coroutineScope.launch { scaffoldState.drawerState.close() }
+ }
},
floatingActionButton = {
FloatingButtons(navController, accountStateViewModel)
From 78c997403659ca25a384126ed8b8c3bff8ae12fb Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:41:19 -0400
Subject: [PATCH 03/16] Removes invalidateFilters Logging
---
.../java/com/vitorpamplona/amethyst/service/NostrDataSource.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
index 755fbf607..eb564b247 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
@@ -154,7 +154,7 @@ abstract class NostrDataSource(val debugName: String) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(250, Dispatchers.IO) {
- println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
+ // println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
From 38e18cf0a753bfa39000781db428bf4441f07fb2 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:41:59 -0400
Subject: [PATCH 04/16] Simplifies comparison for Channels via Hex code instead
of objects
---
.../java/com/vitorpamplona/amethyst/model/Note.kt | 13 +++++++------
.../amethyst/ui/dal/GlobalFeedFilter.kt | 6 +++---
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
index f12698a33..7bf4537f7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
@@ -53,13 +53,14 @@ open class Note(val idHex: String) {
open fun idNote() = id().toNote()
open fun idDisplayNote() = idNote().toShortenHex()
- fun channel(): Channel? {
- val channelHex =
- (event as? ChannelMessageEvent)?.channel()
- ?: (event as? ChannelMetadataEvent)?.channel()
- ?: (event as? ChannelCreateEvent)?.id
+ fun channelHex(): HexKey? {
+ return (event as? ChannelMessageEvent)?.channel()
+ ?: (event as? ChannelMetadataEvent)?.channel()
+ ?: (event as? ChannelCreateEvent)?.id
+ }
- return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
+ fun channel(): Channel? {
+ return channelHex()?.let { LocalCache.checkGetOrCreateChannel(it) }
}
open fun address(): ATag? = null
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt
index e902c2f98..bd441642a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt
@@ -9,7 +9,7 @@ object GlobalFeedFilter : FeedFilter() {
lateinit var account: Account
override fun feed(): List {
- val followChannels = account.followingChannels()
+ val followChannels = account.followingChannels
val followUsers = account.followingKeySet()
val now = System.currentTimeMillis() / 1000
@@ -19,7 +19,7 @@ object GlobalFeedFilter : FeedFilter() {
it.event is BaseTextNoteEvent && it.replyTo.isNullOrEmpty()
}
.filter {
- val channel = it.channel()
+ val channel = it.channelHex()
// does not show events already in the public chat list
(channel == null || channel !in followChannels) &&
// does not show people the user already follows
@@ -38,7 +38,7 @@ object GlobalFeedFilter : FeedFilter() {
it.event is LongTextNoteEvent && it.replyTo.isNullOrEmpty()
}
.filter {
- val channel = it.channel()
+ val channel = it.channelHex()
// does not show events already in the public chat list
(channel == null || channel !in followChannels) &&
// does not show people the user already follows
From a2c86589724372d79444c406e525c0d1cebfd98a Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:42:32 -0400
Subject: [PATCH 05/16] Only calculates nip05 after identifying if it is
correct.
---
.../vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 53c570214..cebfe1fdd 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
@@ -96,7 +96,6 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
user.nip05()?.let { nip05 ->
if (nip05.split("@").size == 2) {
- val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
Column(modifier = columnModifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (nip05.split("@")[0] != "_") {
@@ -108,6 +107,7 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
)
}
+ val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
if (nip05Verified == null) {
Icon(
tint = Color.Yellow,
From 35647a836185c9241fd1376aaff657bc8b65a4f0 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:42:58 -0400
Subject: [PATCH 06/16] Fixes DM link in Message Notifications
---
.../amethyst/ui/note/MessageSetCompose.kt | 27 +++++++++++++++----
1 file changed, 22 insertions(+), 5 deletions(-)
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 bf0fa2cdb..7629b5ca0 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,7 +26,9 @@ 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
@@ -64,14 +66,29 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
Column(
modifier = Modifier.background(backgroundColor).combinedClickable(
onClick = {
- if (noteEvent !is ChannelMessageEvent) {
- navController.navigate("Note/${note.idHex}") {
- launchSingleTop = true
- }
- } else {
+ 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
+ }
}
},
onLongClick = { popupExpanded = true }
From f64db1271be744473d9001cc671c447e83d6d7e0 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:44:11 -0400
Subject: [PATCH 07/16] Uses createdAt instead of the card object, which can
change randomly, to determine when to call LaunchedEffects.
---
.../com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt | 2 +-
.../java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt | 2 +-
.../com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
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 7629b5ca0..8deb5bcb1 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
@@ -48,7 +48,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
} else {
var isNew by remember { mutableStateOf(false) }
- LaunchedEffect(key1 = messageSetCard) {
+ LaunchedEffect(key1 = messageSetCard.createdAt()) {
withContext(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 b001cce84..a8c766356 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
@@ -61,7 +61,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} else {
var isNew by remember { mutableStateOf(false) }
- LaunchedEffect(key1 = multiSetCard) {
+ LaunchedEffect(key1 = multiSetCard.createdAt()) {
withContext(Dispatchers.IO) {
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
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 f9512cde8..7d0094295 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
@@ -43,7 +43,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
var isNew by remember { mutableStateOf(false) }
- LaunchedEffect(key1 = zapSetCard) {
+ LaunchedEffect(key1 = zapSetCard.createdAt()) {
withContext(Dispatchers.IO) {
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
From e4fc00669e26e824ec0f2b47128dd84eed772716 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:44:32 -0400
Subject: [PATCH 08/16] Compares users by reference.
---
.../java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 a8c766356..537649905 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
@@ -244,7 +244,7 @@ fun FastNoteAuthorPicture(
val userState by author.live().metadata.observeAsState()
val user = userState?.user ?: return
- val showFollowingMark = userAccount.isFollowingCached(user) || user == userAccount
+ val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
UserPicture(
userHex = user.pubkeyHex,
From 47e2683aa359de9f1572d25dcaaf9801dcb31b19 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:45:02 -0400
Subject: [PATCH 09/16] Moves firstIsTaggedHash search to the IO thread.
---
.../java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt
index be5075c25..807a40898 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
@@ -582,7 +582,9 @@ fun DisplayFollowingHashtagsInPost(
var firstTag by remember { mutableStateOf(null) }
LaunchedEffect(key1 = noteEvent) {
- firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
+ withContext(Dispatchers.IO) {
+ firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
+ }
}
Column() {
From 11bf702e022624431635d10214fe0d771564788c Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:45:34 -0400
Subject: [PATCH 10/16] Moves DropDown state to the IO thread.
---
.../vitorpamplona/amethyst/ui/note/NoteCompose.kt | 14 ++++++++------
1 file changed, 8 insertions(+), 6 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 807a40898..3d03c513c 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
@@ -1110,12 +1110,14 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
}
LaunchedEffect(key1 = note) {
- state = DropDownParams(
- accountViewModel.isFollowing(note.author),
- accountViewModel.isInPrivateBookmarks(note),
- accountViewModel.isInPublicBookmarks(note),
- accountViewModel.isLoggedUser(note.author)
- )
+ withContext(Dispatchers.IO) {
+ state = DropDownParams(
+ accountViewModel.isFollowing(note.author),
+ accountViewModel.isInPrivateBookmarks(note),
+ accountViewModel.isInPublicBookmarks(note),
+ accountViewModel.isLoggedUser(note.author)
+ )
+ }
}
DropdownMenu(
From df45d68b74d13b057e18fc45e76852c18781e05c Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:45:50 -0400
Subject: [PATCH 11/16] Caches user profile in Account.
---
.../main/java/com/vitorpamplona/amethyst/model/Account.kt | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
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 178e5f264..5a36d7297 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -54,8 +54,14 @@ class Account(
val liveLanguages: AccountLiveData = AccountLiveData(this)
val saveable: AccountLiveData = AccountLiveData(this)
+ var userProfileCache: User? = null
+
fun userProfile(): User {
- return LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
+ return userProfileCache ?: run {
+ val myUser: User = LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
+ userProfileCache = myUser
+ myUser
+ }
}
fun followingChannels(): List {
From 12b0afd9c62202178cad8812ac6c8c33e17e986d Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Tue, 18 Apr 2023 08:46:12 -0400
Subject: [PATCH 12/16] Display note IDHex when measuring time.
---
.../main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt
index 3d03c513c..8400d174b 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
@@ -96,7 +96,7 @@ fun NoteCompose(
)
}
- Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
+ 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)
From 5afa333fab37face44b70beae500152919b8bf65 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Wed, 19 Apr 2023 09:58:11 -0400
Subject: [PATCH 13/16] Migration to Android Studio 2022.2.1 and Gradle 8
---
build.gradle | 4 ++--
gradle.properties | 4 +++-
gradle/wrapper/gradle-wrapper.properties | 2 +-
3 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/build.gradle b/build.gradle
index ee67b5b0c..b44afca7a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,8 +10,8 @@ buildscript {
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '7.4.2' apply false
- id 'com.android.library' version '7.4.2' apply false
+ id 'com.android.application' version '8.0.0' apply false
+ id 'com.android.library' version '8.0.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false
}
diff --git a/gradle.properties b/gradle.properties
index c9ecf2f95..e4e68fdb1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,4 +21,6 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
-android.enableR8.fullMode=true
\ No newline at end of file
+android.enableR8.fullMode=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonFinalResIds=false
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c1070955d..289cef284 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Jan 04 09:23:50 EST 2023
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
From 4e09d9e54ac5030d08e3cd9df0b5f8aa36729715 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Wed, 19 Apr 2023 10:33:02 -0400
Subject: [PATCH 14/16] Performance Improvements for Zaps in Polls.
---
.../amethyst/model/LocalCache.kt | 2 +-
.../amethyst/service/NostrDataSource.kt | 2 +-
.../amethyst/service/model/LnZapEvent.kt | 68 +++++++++----------
.../service/model/LnZapEventInterface.kt | 2 -
.../service/model/LnZapRequestEvent.kt | 6 +-
.../amethyst/service/model/PollNoteEvent.kt | 19 ++----
.../amethyst/ui/note/PollNote.kt | 11 ++-
.../amethyst/ui/note/PollNoteViewModel.kt | 65 +++++++++---------
8 files changed, 79 insertions(+), 96 deletions(-)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
index cef63d463..1705175d5 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
@@ -595,7 +595,7 @@ object LocalCache {
// Already processed this event.
if (note.event != null) return
- val zapRequest = event.containedPost()?.id?.let { getOrCreateNote(it) }
+ val zapRequest = event.zapRequest?.id?.let { getOrCreateNote(it) }
val author = getOrCreateUser(event.pubKey)
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
index eb564b247..6d4fbadf9 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt
@@ -76,7 +76,7 @@ abstract class NostrDataSource(val debugName: String) {
is DeletionEvent -> LocalCache.consume(event)
is LnZapEvent -> {
- event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
+ event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
LocalCache.consume(event)
}
is LnZapRequestEvent -> LocalCache.consume(event)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt
index 1812fb803..5f08c6a6e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt
@@ -4,7 +4,6 @@ import android.util.Log
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
import com.vitorpamplona.amethyst.service.relays.Client
-import java.math.BigDecimal
class LnZapEvent(
id: HexKey,
@@ -14,25 +13,37 @@ class LnZapEvent(
content: String,
sig: HexKey
) : LnZapEventInterface, Event(id, pubKey, createdAt, kind, tags, content, sig) {
+ // This event is also kept in LocalCache (same object)
+ @Transient val zapRequest: LnZapRequestEvent?
- override fun zappedPost() = tags
- .filter { it.firstOrNull() == "e" }
- .mapNotNull { it.getOrNull(1) }
-
- override fun zappedPollOption(): Int? = containedPost()?.tags
- ?.filter { it.firstOrNull() == POLL_OPTION }
- ?.getOrNull(0)?.getOrNull(1)?.toInt()
-
- override fun zappedAuthor() = tags
- .filter { it.firstOrNull() == "p" }
- .mapNotNull { it.getOrNull(1) }
-
- override fun zappedRequestAuthor(): String? = containedPost()?.pubKey()
-
- override fun amount(): BigDecimal? {
- return amount
+ private fun containedPost(): LnZapRequestEvent? = try {
+ description()?.ifBlank { null }?.let {
+ fromJson(it, Client.lenient)
+ } as? LnZapRequestEvent
+ } catch (e: Exception) {
+ Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
+ null
}
+ init {
+ zapRequest = containedPost()
+ }
+
+ override fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
+
+ override fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
+
+ override fun zappedPollOption(): Int? = try {
+ zapRequest?.tags?.firstOrNull { it.size > 1 && it[0] == POLL_OPTION }?.get(1)?.toInt()
+ } catch (e: Exception) {
+ Log.e("LnZapEvent", "ZappedPollOption failed to parse", e)
+ null
+ }
+
+ override fun zappedRequestAuthor(): String? = zapRequest?.pubKey()
+
+ override fun amount() = amount
+
// Keeps this as a field because it's a heavier function used everywhere.
val amount by lazy {
try {
@@ -42,29 +53,14 @@ class LnZapEvent(
null
}
}
+
override fun message(): String {
- return message
- }
- val message = content
-
- override fun containedPost(): Event? = try {
- description()?.ifBlank { null }?.let {
- fromJson(it, Client.lenient)
- }
- } catch (e: Exception) {
- Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
- null
+ return content
}
- private fun lnInvoice(): String? = tags
- .filter { it.firstOrNull() == "bolt11" }
- .mapNotNull { it.getOrNull(1) }
- .firstOrNull()
+ private fun lnInvoice() = tags.firstOrNull { it.size > 1 && it[0] == "bolt11" }?.get(1)
- private fun description(): String? = tags
- .filter { it.firstOrNull() == "description" }
- .mapNotNull { it.getOrNull(1) }
- .firstOrNull()
+ private fun description() = tags.firstOrNull { it.size > 1 && it[0] == "description" }?.get(1)
companion object {
const val kind = 9735
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt
index b69e02781..ada0d609d 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt
@@ -16,7 +16,5 @@ interface LnZapEventInterface : EventInterface {
fun amount(): BigDecimal?
- fun containedPost(): Event?
-
fun message(): String
}
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 34fddc02a..eba7a18a9 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
@@ -13,8 +13,10 @@ class LnZapRequestEvent(
content: String,
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
- fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
- fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
+
+ 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] }
companion object {
const val kind = 9734
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
index a6bcc89b8..c91cd4c7a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
@@ -20,24 +20,17 @@ class PollNoteEvent(
content: String,
sig: HexKey
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
- fun pollOptions(): Map {
- val map = mutableMapOf()
- tags.filter { it.first() == POLL_OPTION }
- .forEach { map[it[1].toInt()] = it[2] }
- return map
- }
+ fun pollOptions() =
+ tags.filter { it.size > 2 && it[0] == POLL_OPTION }
+ .associate { it[1].toInt() to it[2] }
fun getTagInt(property: String): Int? {
- val tagList = tags.filter {
- it.firstOrNull() == property
- }
- val tag = tagList.getOrNull(0)
- val s = tag?.getOrNull(1)
+ val number = tags.firstOrNull() { it.size > 1 && it[0] == property }?.get(1)
- return if (s.isNullOrBlank() || s == "null") {
+ return if (number.isNullOrBlank() || number == "null") {
null
} else {
- s.toInt()
+ number.toInt()
}
}
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 da0f35936..26e83cf91 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
@@ -48,9 +48,6 @@ fun PollNote(
accountViewModel: AccountViewModel,
navController: NavController
) {
- val accountState by accountViewModel.accountLiveData.observeAsState()
- val account = accountState?.account ?: return
-
val zapsState by baseNote.live().zaps.observeAsState()
val zappedNote = zapsState?.note ?: return
@@ -72,7 +69,7 @@ fun PollNote(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 3.dp)
) {
- if (zappedNote.author == account.userProfile() || zappedNote.isZappedBy(account.userProfile())) {
+ if (accountViewModel.isLoggedUser(zappedNote.author) || zappedNote.isZappedBy(accountViewModel.userProfile())) {
ZapVote(
baseNote,
accountViewModel,
@@ -90,7 +87,7 @@ fun PollNote(
LinearProgressIndicator(
modifier = Modifier.matchParentSize(),
color = color,
- progress = optionTally
+ progress = optionTally.toFloat()
)
Row(
@@ -101,7 +98,7 @@ fun PollNote(
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
) {
Text(
- text = "${(optionTally * 100).roundToInt()}%",
+ text = "${(optionTally.toFloat() * 100).roundToInt()}%",
fontWeight = FontWeight.Bold
)
}
@@ -210,7 +207,7 @@ fun ZapVote(
)
.show()
}
- } else if (zappedNote?.author == account.userProfile()) {
+ } else if (accountViewModel.isLoggedUser(zappedNote?.author)) {
scope.launch {
Toast
.makeText(
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt
index e5d3d4cd8..4ce21c372 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.*
import java.math.BigDecimal
+import java.math.RoundingMode
import java.util.*
class PollNoteViewModel {
@@ -16,7 +17,9 @@ class PollNoteViewModel {
var valueMaximum: Int? = null
var valueMinimum: Int? = null
private var closedAt: Int? = null
- var consensusThreshold: Float? = null
+ var consensusThreshold: BigDecimal? = null
+
+ var totalZapped: BigDecimal = BigDecimal.ZERO
fun load(note: Note?) {
pollNote = note
@@ -24,8 +27,10 @@ class PollNoteViewModel {
pollOptions = pollEvent?.pollOptions()
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
- consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)
+ consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
closedAt = pollEvent?.getTagInt(CLOSED_AT)
+
+ totalZapped = totalZapped()
}
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
@@ -73,47 +78,39 @@ class PollNoteViewModel {
return false
}
- fun optionVoteTally(op: Int): Float {
- val tally = zappedPollOptionAmount(op).toFloat().div(zappedVoteTotal())
- return if (tally.isNaN()) { // catch div by 0
- 0f
- } else { tally }
- }
-
- private fun zappedVoteTotal(): Float {
- var total = 0f
- pollOptions?.keys?.forEach {
- total += zappedPollOptionAmount(it).toFloat()
+ fun optionVoteTally(op: Int): BigDecimal {
+ return if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
+ zappedPollOptionAmount(op).divide(totalZapped, 2, RoundingMode.HALF_UP)
+ } else {
+ BigDecimal.ZERO
}
- return total
}
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
- if (pollNote?.zaps?.any { it.key.author == user } == true) {
- pollNote!!.zaps.mapNotNull { it.value?.event }
- .filterIsInstance()
- .map {
- val zappedOption = it.zappedPollOption()
- if (zappedOption == option && it.zappedRequestAuthor() == user.pubkeyHex) {
- return true
- }
+ if (pollNote?.zaps?.any { it.key.author === user } == true) {
+ pollNote!!.zaps
+ .any {
+ val event = it.value?.event as? LnZapEvent
+ event?.zappedPollOption() == option && event.zappedRequestAuthor() == user.pubkeyHex
}
}
return false
}
fun zappedPollOptionAmount(option: Int): BigDecimal {
- return if (pollNote != null) {
- pollNote!!.zaps.mapNotNull { it.value?.event }
- .filterIsInstance()
- .mapNotNull {
- val zappedOption = it.zappedPollOption()
- if (zappedOption == option) {
- it.amount
- } else { null }
- }.sumOf { it }
- } else {
- BigDecimal(0)
- }
+ return pollNote?.zaps?.values?.sumOf {
+ val event = it?.event as? LnZapEvent
+ if (event?.zappedPollOption() == option) {
+ event.amount ?: BigDecimal(0)
+ } else {
+ BigDecimal(0)
+ }
+ } ?: BigDecimal(0)
+ }
+
+ fun totalZapped(): BigDecimal {
+ return pollNote?.zaps?.values?.sumOf {
+ (it?.event as? LnZapEvent)?.amount ?: BigDecimal(0)
+ } ?: BigDecimal(0)
}
}
From b55f999efb5ef67353be4a1e589be9e0b146bbcc Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Wed, 19 Apr 2023 10:33:52 -0400
Subject: [PATCH 15/16] Slight performance improvements on tag search.
---
.../vitorpamplona/amethyst/service/model/PrivateDmEvent.kt | 4 ++--
.../com/vitorpamplona/amethyst/service/model/ReactionEvent.kt | 4 ++--
.../com/vitorpamplona/amethyst/service/model/ReportEvent.kt | 4 ++--
.../com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt | 2 +-
4 files changed, 7 insertions(+), 7 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 89e1d98e6..c820d0ca2 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,7 @@ 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.firstOrNull() == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
+ fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
/**
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
@@ -29,7 +29,7 @@ class PrivateDmEvent(
* Nip-18 messages should refer to other events by inline references in the content like
* `[](e/c06f795e1234a9a1aecc731d768d4f3ca73e80031734767067c82d67ce82e506).
*/
- fun replyTo() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
+ fun replyTo() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
return try {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt
index f89bb2977..13188520c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt
@@ -14,8 +14,8 @@ class ReactionEvent(
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
- fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
- fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
+ fun originalPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
+ fun originalAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
companion object {
const val kind = 7
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt
index 42f33c3c5..d3bd9dd97 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt
@@ -30,7 +30,7 @@ class ReportEvent(
}
fun reportedPost() = tags
- .filter { it.firstOrNull() == "e" && it.getOrNull(1) != null }
+ .filter { it.size > 1 && it[0] == "e" }
.map {
ReportedKey(
it[1],
@@ -39,7 +39,7 @@ class ReportEvent(
}
fun reportedAuthor() = tags
- .filter { it.firstOrNull() == "p" && it.getOrNull(1) != null }
+ .filter { it.size > 1 && it[0] == "p" }
.map {
ReportedKey(
it[1],
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 1a98c4e97..8be28a491 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
@@ -72,7 +72,7 @@ open class NewPostViewModel : ViewModel() {
this.mentions = currentMentions.plus(replyUser)
}
}
- } ?: {
+ } ?: run {
replyTos = null
mentions = null
}
From 744a55f4972000a2a48d5cd04eda8bdb99f76d55 Mon Sep 17 00:00:00 2001
From: Vitor Pamplona
Date: Wed, 19 Apr 2023 10:34:10 -0400
Subject: [PATCH 16/16] Caching channel info JSON parse in NoteCompose.
---
.../amethyst/ui/note/ChatroomMessageCompose.kt | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
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 fee45ca0e..bee998015 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
@@ -246,30 +246,32 @@ fun ChatroomMessageCompose(
Row(verticalAlignment = Alignment.CenterVertically) {
val event = note.event
if (event is ChannelCreateEvent) {
+ val channelInfo = event.channelInfo()
Text(
text = note.author?.toBestDisplayName()
.toString() + " ${stringResource(R.string.created)} " + (
- event.channelInfo().name
+ channelInfo.name
?: ""
) + " ${stringResource(R.string.with_description_of)} '" + (
- event.channelInfo().about
+ channelInfo.about
?: ""
) + "', ${stringResource(R.string.and_picture)} '" + (
- event.channelInfo().picture
+ channelInfo.picture
?: ""
) + "'"
)
} else if (event is ChannelMetadataEvent) {
+ val channelInfo = event.channelInfo()
Text(
text = note.author?.toBestDisplayName()
.toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (
- event.channelInfo().name
+ channelInfo.name
?: ""
) + "$', {stringResource(R.string.description_to)} '" + (
- event.channelInfo().about
+ channelInfo.about
?: ""
) + "', ${stringResource(R.string.and_picture_to)} '" + (
- event.channelInfo().picture
+ channelInfo.picture
?: ""
) + "'"
)