From 2eb0096630b967147af51bf03231d15718192a50 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 20 May 2023 20:35:13 -0400 Subject: [PATCH 01/76] Adds a space in a bookmark author's name --- .../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 b6c279f1f..8412819c6 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 @@ -1366,7 +1366,7 @@ fun DisplayHighlight( if (userDisplayName != null) { CreateClickableTextWithEmoji( clickablePart = userDisplayName, - suffix = "", + suffix = " ", tags = userTags, route = route, navController = navController From 7da87af0fb70b96acfb3c6ece7f9c24f43a49bdc Mon Sep 17 00:00:00 2001 From: KotlinGeek Date: Mon, 22 May 2023 01:29:38 +0100 Subject: [PATCH 02/76] Verify presence of note in backstack before navigating to it. --- .../amethyst/ui/note/NoteCompose.kt | 103 +++--------------- 1 file changed, 14 insertions(+), 89 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 bff36a298..aaadf32f6 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,64 +3,25 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Intent import android.graphics.Bitmap import android.util.Log -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.PaddingValues -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.* +import androidx.compose.foundation.layout.* 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.Button -import androidx.compose.material.ButtonDefaults -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.* 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.material.icons.filled.PushPin -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.* 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.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ColorMatrix -import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -83,49 +44,9 @@ import coil.compose.AsyncImagePainter import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.model.Channel -import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.NoteState -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.model.AudioTrackEvent -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.FileStorageHeaderEvent -import com.vitorpamplona.amethyst.service.model.HighlightEvent -import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent -import com.vitorpamplona.amethyst.service.model.Participant -import com.vitorpamplona.amethyst.service.model.PeopleListEvent -import com.vitorpamplona.amethyst.service.model.PinListEvent -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.ReportedKey -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.CreateClickableTextWithEmoji -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.VideoView -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.model.* +import com.vitorpamplona.amethyst.service.model.* +import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog @@ -138,7 +59,7 @@ import nostr.postr.toNpub import java.io.File import java.math.BigDecimal import java.net.URL -import java.util.Locale +import java.util.* import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue @@ -299,7 +220,11 @@ fun NoteComposeInner( onClick = { scope.launch { routeFor(note, loggedIn)?.let { - navController.navigate(it) + if (note.idHex != navController.currentBackStackEntry?.arguments?.getString("id")) { + navController.navigate(it) + } else { + Log.d("Amethyst-Navigation", "Destination already exists in the backstack!") + } } } }, From 330f3503a6961ef1e5c0fcdf55e751f8a41ba810 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 21 May 2023 20:53:20 -0400 Subject: [PATCH 03/76] Adds nostr: prefix to channel id share. --- .../vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index c6bcc52ab..1af7f02c3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -349,7 +349,7 @@ private fun NoteCopyButton( expanded = popupExpanded, onDismissRequest = { popupExpanded = false } ) { - DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); popupExpanded = false }) { + DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString("nostr:" + note.idNote())); popupExpanded = false }) { Text(stringResource(R.string.copy_channel_id_note_to_the_clipboard)) } } From a0797f710480c0d092d0e42f7c99db05ff2ee0d5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 21 May 2023 20:53:57 -0400 Subject: [PATCH 04/76] Fixes NWC with Auth --- .../vitorpamplona/amethyst/model/Account.kt | 7 ++++++- .../NostrLnZapPaymentResponseDataSource.kt | 20 ++++++++++++++++--- 2 files changed, 23 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 38d71ebda..209a97906 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -217,7 +217,12 @@ class Account( zapPaymentRequest?.let { nip47 -> val event = LnZapPaymentRequestEvent.create(bolt11, nip47.pubKeyHex, nip47.secret?.hexToByteArray() ?: loggedIn.privKey!!) - val wcListener = NostrLnZapPaymentResponseDataSource(nip47.pubKeyHex, event.pubKey, event.id) + val wcListener = NostrLnZapPaymentResponseDataSource( + fromServiceHex = nip47.pubKeyHex, + toUserHex = event.pubKey, + replyingToHex = event.id, + authSigningKey = nip47.secret?.hexToByteArray() ?: loggedIn.privKey!! + ) wcListener.start() LocalCache.consume(event, zappedNote) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt index 57de8be6e..7993306f5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrLnZapPaymentResponseDataSource.kt @@ -1,14 +1,18 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.service.model.LnZapPaymentResponseEvent +import com.vitorpamplona.amethyst.service.model.RelayAuthEvent +import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter +import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.TypedFilter class NostrLnZapPaymentResponseDataSource( - private var fromServiceHex: String, - private var toUserHex: String, - private var replyingToHex: String + private val fromServiceHex: String, + private val toUserHex: String, + private val replyingToHex: String, + private val authSigningKey: ByteArray ) : NostrDataSource("LnZapPaymentResponseFeed") { val feedTypes = setOf(FeedType.WALLET_CONNECT) @@ -36,4 +40,14 @@ class NostrLnZapPaymentResponseDataSource( channel.typedFilters = listOfNotNull(wc).ifEmpty { null } } + + override fun auth(relay: Relay, challenge: String) { + super.auth(relay, challenge) + + val event = RelayAuthEvent.create(relay.url, challenge, authSigningKey) + Client.send( + event, + relay.url + ) + } } From b219b3631c82142f3eaf80d5272a1978f0dd6c34 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 21 May 2023 20:54:42 -0400 Subject: [PATCH 05/76] - Adds hashtags and #[n] rendering to markdown - Shortcuts the link to Amethyst's router. --- .../vitorpamplona/amethyst/ui/MainActivity.kt | 66 ++++---- .../amethyst/ui/components/RichTextViewer.kt | 141 ++++++++++++++---- 2 files changed, 152 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index f09b0735c..9a20166c6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -46,35 +46,7 @@ class MainActivity : FragmentActivity() { val uri = intent?.data?.toString() - val startingPage = if (uri.equals("nostr:Notifications", true)) { - Route.Notification.route.replace("{scrollToTop}", "true") - } else { - val nip19 = Nip19.uriToRoute(uri) - when (nip19?.type) { - Nip19.Type.USER -> "User/${nip19.hex}" - Nip19.Type.NOTE -> "Note/${nip19.hex}" - Nip19.Type.EVENT -> { - if (nip19.kind == PrivateDmEvent.kind) { - "Room/${nip19.author}" - } else if (nip19.kind == ChannelMessageEvent.kind || nip19.kind == ChannelCreateEvent.kind || nip19.kind == ChannelMetadataEvent.kind) { - "Channel/${nip19.hex}" - } else { - "Event/${nip19.hex}" - } - } - - Nip19.Type.ADDRESS -> "Note/${nip19.hex}" - else -> null - } - } ?: try { - uri?.let { - Nip47.parse(it) - val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) - Route.Home.base + "?nip47=" + encodedUri - } - } catch (e: Exception) { - null - } + val startingPage = uriToRoute(uri) // Initializes video cache. VideoCache.init(this.applicationContext) @@ -150,3 +122,39 @@ class GetMediaActivityResultContract : ActivityResultContracts.GetContent() { } } } + +fun uriToRoute(uri: String?): String? { + return if (uri.equals("nostr:Notifications", true)) { + Route.Notification.route.replace("{scrollToTop}", "true") + } else { + if (uri?.startsWith("nostr:Hashtag?id=") == true) { + Route.Hashtag.route.replace("{id}", uri.removePrefix("nostr:Hashtag?id=")) + } else { + val nip19 = Nip19.uriToRoute(uri) + when (nip19?.type) { + Nip19.Type.USER -> "User/${nip19.hex}" + Nip19.Type.NOTE -> "Note/${nip19.hex}" + Nip19.Type.EVENT -> { + if (nip19.kind == PrivateDmEvent.kind) { + "Room/${nip19.author}" + } else if (nip19.kind == ChannelMessageEvent.kind || nip19.kind == ChannelCreateEvent.kind || nip19.kind == ChannelMetadataEvent.kind) { + "Channel/${nip19.hex}" + } else { + "Event/${nip19.hex}" + } + } + + Nip19.Type.ADDRESS -> "Note/${nip19.hex}" + else -> null + } + } ?: try { + uri?.let { + Nip47.parse(it) + val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) + Route.Home.base + "?nip47=" + encodedUri + } + } catch (e: Exception) { + null + } + } +} 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 b2f1b8674..d745aeecf 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 @@ -19,10 +19,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.service.lnurl.LnWithdrawalUtil import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.uriToRoute import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -99,7 +100,7 @@ fun RichTextViewer( Column(modifier = modifier) { if (isMarkdown) { - RenderContentAsMarkdown(content, backgroundColor) + RenderContentAsMarkdown(content, backgroundColor, tags, navController) } else { RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController) } @@ -310,7 +311,7 @@ fun RenderCustomEmoji(word: String, customEmoji: Map) { } @Composable -private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) { +private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List>?, navController: NavController) { val myMarkDownStyle = richTextDefaults.copy( codeBlockStyle = richTextDefaults.codeBlockStyle?.copy( textStyle = TextStyle( @@ -334,7 +335,6 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) { ), stringStyle = richTextDefaults.stringStyle?.copy( linkStyle = SpanStyle( - textDecoration = TextDecoration.Underline, color = MaterialTheme.colors.primary ), codeStyle = SpanStyle( @@ -352,14 +352,14 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) { LaunchedEffect(key1 = content) { withContext(Dispatchers.IO) { - nip19References = returnNIP19References(content) - markdownWithSpecialContent = returnMarkdownWithSpecialContent(content) + nip19References = returnNIP19References(content, tags) + markdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags) } } LaunchedEffect(key1 = refresh) { withContext(Dispatchers.IO) { - val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content) + val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags) if (markdownWithSpecialContent != newMarkdownWithSpecialContent) { markdownWithSpecialContent = newMarkdownWithSpecialContent } @@ -389,17 +389,23 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) { baseNote?.let { val noteState by it.live().metadata.observeAsState() if (noteState?.note?.event != null) { - refresh++ + LaunchedEffect(key1 = noteState) { + refresh++ + } } } baseUser?.let { val userState by it.live().metadata.observeAsState() if (userState?.user?.info != null) { - refresh++ + LaunchedEffect(key1 = userState) { + refresh++ + } } } } + val uri = LocalUriHandler.current + markdownWithSpecialContent?.let { MaterialRichText( style = myMarkDownStyle @@ -407,26 +413,70 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) { Markdown( content = it, markdownParseOptions = MarkdownParseOptions.Default - ) + ) { link -> + val route = uriToRoute(link) + if (route != null) { + navController.navigate(route) + } else { + runCatching { uri.openUri(link) } + } + } } } } -private fun getDisplayNameFromNip19(nip19: Nip19.Return): String? { - if (nip19.type == Nip19.Type.USER) { - return LocalCache.users[nip19.hex]?.toBestDisplayName() - } 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 getDisplayNameAndNIP19FromTag(tag: String, tags: List>): Pair? { + val matcher = tagIndex.matcher(tag) + val (index, suffix) = try { + matcher.find() + Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "") + } catch (e: Exception) { + Log.w("Tag Parser", "Couldn't link tag $tag", e) + Pair(null, null) } + + if (index != null && index >= 0 && index < tags.size) { + val tag = tags[index] + + if (tag.size > 1) { + if (tag[0] == "p") { + LocalCache.checkGetOrCreateUser(tag[1])?.let { + return Pair(it.toBestDisplayName(), it.pubkeyNpub()) + } + } else if (tag[0] == "e" || tag[0] == "a") { + LocalCache.checkGetOrCreateNote(tag[1])?.let { + return Pair(it.idDisplayNote(), it.toNEvent()) + } + } + } + } + + return null } -private fun returnNIP19References(content: String): List { +private fun getDisplayNameFromNip19(nip19: Nip19.Return): Pair? { + if (nip19.type == Nip19.Type.USER) { + LocalCache.users[nip19.hex]?.let { + return Pair(it.toBestDisplayName(), it.pubkeyNpub()) + } + } else if (nip19.type == Nip19.Type.NOTE) { + LocalCache.notes[nip19.hex]?.let { + return Pair(it.idDisplayNote(), it.toNEvent()) + } + } else if (nip19.type == Nip19.Type.ADDRESS) { + LocalCache.addressables[nip19.hex]?.let { + return Pair(it.idDisplayNote(), it.toNEvent()) + } + } else if (nip19.type == Nip19.Type.EVENT) { + LocalCache.notes[nip19.hex]?.let { + return Pair(it.idDisplayNote(), it.toNEvent()) + } + } + + return null +} + +private fun returnNIP19References(content: String, tags: List>?): List { val listOfReferences = mutableListOf() content.split('\n').forEach { paragraph -> paragraph.split(' ').forEach { word: String -> @@ -438,10 +488,21 @@ private fun returnNIP19References(content: String): List { } } } + + tags?.forEach { + if (it[0] == "p" && it.size > 1) { + listOfReferences.add(Nip19.Return(Nip19.Type.USER, it[1], null, null, null, "")) + } else if (it[0] == "e" && it.size > 1) { + listOfReferences.add(Nip19.Return(Nip19.Type.NOTE, it[1], null, null, null, "")) + } else if (it[0] == "a" && it.size > 1) { + listOfReferences.add(Nip19.Return(Nip19.Type.ADDRESS, it[1], null, null, null, "")) + } + } + return listOfReferences } -private fun returnMarkdownWithSpecialContent(content: String): String { +private fun returnMarkdownWithSpecialContent(content: String, tags: List>?): String { var returnContent = "" content.split('\n').forEach { paragraph -> paragraph.split(' ').forEach { word: String -> @@ -454,15 +515,43 @@ private fun returnMarkdownWithSpecialContent(content: String): String { } else if (isBechLink(word)) { val parsedNip19 = Nip19.uriToRoute(word) returnContent += if (parsedNip19 !== null) { - val displayName = getDisplayNameFromNip19(parsedNip19) - if (displayName != null) { - "[@$displayName](nostr://$word) " + val pair = getDisplayNameFromNip19(parsedNip19) + if (pair != null) { + val (displayName, nip19) = pair + "[$displayName](nostr:$nip19) " } else { "$word " } } else { "$word " } + } else if (word.startsWith("#")) { + if (tagIndex.matcher(word).matches() && tags != null) { + val pair = getDisplayNameAndNIP19FromTag(word, tags) + if (pair != null) { + returnContent += "[${pair.first}](nostr:${pair.second}) " + } else { + returnContent += "$word " + } + } else if (hashTagsPattern.matcher(word).matches()) { + val hashtagMatcher = hashTagsPattern.matcher(word) + + val (myTag, mySuffix) = try { + hashtagMatcher.find() + Pair(hashtagMatcher.group(1), hashtagMatcher.group(2)) + } catch (e: Exception) { + Log.e("Hashtag Parser", "Couldn't link hashtag $word", e) + Pair(null, null) + } + + if (myTag != null) { + returnContent += "[#$myTag](nostr:Hashtag?id=$myTag)$mySuffix " + } else { + returnContent += "$word " + } + } else { + returnContent += "$word " + } } else { returnContent += "$word " } From 77668865872214cbad80687e388563c6437cd534 Mon Sep 17 00:00:00 2001 From: KotlinGeek Date: Mon, 22 May 2023 12:56:17 +0100 Subject: [PATCH 06/76] Apply fix to profile navigation as well. --- .../amethyst/ui/note/NoteCompose.kt | 111 ++++++++++++++++-- 1 file changed, 99 insertions(+), 12 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 aaadf32f6..c1dd52281 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,25 +3,64 @@ 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.PaddingValues +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.Button +import androidx.compose.material.ButtonDefaults +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.material.icons.filled.PushPin -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.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -44,9 +83,49 @@ import coil.compose.AsyncImagePainter import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.* -import com.vitorpamplona.amethyst.service.model.* -import com.vitorpamplona.amethyst.ui.components.* +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.NoteState +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.model.AudioTrackEvent +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.FileStorageHeaderEvent +import com.vitorpamplona.amethyst.service.model.HighlightEvent +import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent +import com.vitorpamplona.amethyst.service.model.Participant +import com.vitorpamplona.amethyst.service.model.PeopleListEvent +import com.vitorpamplona.amethyst.service.model.PinListEvent +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.ReportedKey +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.CreateClickableTextWithEmoji +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.VideoView +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 @@ -59,7 +138,7 @@ import nostr.postr.toNpub import java.io.File import java.math.BigDecimal import java.net.URL -import java.util.* +import java.util.Locale import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue @@ -223,7 +302,7 @@ fun NoteComposeInner( if (note.idHex != navController.currentBackStackEntry?.arguments?.getString("id")) { navController.navigate(it) } else { - Log.d("Amethyst-Navigation", "Destination already exists in the backstack!") + Log.d("Amethyst-Navigation", "Note already exists in the backstack!") } } } @@ -1919,7 +1998,11 @@ fun NoteAuthorPicture( pictureModifier: Modifier = Modifier ) { NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) { - navController.navigate("User/${it.pubkeyHex}") + if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { + navController.navigate("User/${it.pubkeyHex}") + } else { + Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!") + } } } @@ -1972,7 +2055,11 @@ fun UserPicture( pictureModifier: Modifier = Modifier ) { UserPicture(user, userAccount, size, pictureModifier) { - navController.navigate("User/${it.pubkeyHex}") + if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { + navController.navigate("User/${it.pubkeyHex}") + } else { + Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!") + } } } From 26634ccb044d5c495747259c385f9e7ed89422f0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 09:18:13 -0400 Subject: [PATCH 07/76] Migrates notification summary to a new day without having to restart the app. --- .../amethyst/ui/note/UserReactionsRow.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 484fbdde2..93ca64ab4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -75,19 +75,19 @@ fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountVie } Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { - UserReplyReaction(model.replies[model.today]) + UserReplyReaction(model.replies[model.today()]) } Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { - UserBoostReaction(model.boosts[model.today]) + UserBoostReaction(model.boosts[model.today()]) } Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { - UserLikeReaction(model.reactions[model.today]) + UserLikeReaction(model.reactions[model.today()]) } Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { - UserZapReaction(model.zaps[model.today]) + UserZapReaction(model.zaps[model.today()]) } } } @@ -103,7 +103,6 @@ class UserReactionsViewModel : ViewModel() { var takenIntoAccount = setOf() val sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd") // SimpleDateFormat() - val today = sdf.format(LocalDateTime.now()) var chartModel by mutableStateOf?>(null) var axisLabels by mutableStateOf>(emptyList()) @@ -125,6 +124,8 @@ class UserReactionsViewModel : ViewModel() { ) } + fun today() = sdf.format(LocalDateTime.now()) + fun initializeSuspend() { val currentUser = user?.pubkeyHex ?: return From ec2fc2f7e34f4a5d8bc3d799fd484bf17df5f508 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 09:49:34 -0400 Subject: [PATCH 08/76] Updates relay UI on messages sent by the logged in user in chat --- .../vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 55fe08669..e59508bff 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 @@ -505,7 +505,7 @@ private fun RelayBadges(baseNote: Note) { var expanded by remember { mutableStateOf(false) } - val relaysToDisplay by remember { + val relaysToDisplay by remember(noteRelaysState) { derivedStateOf { if (expanded) state.noteRelays else state.noteRelaysSimple } From 99d302802a76d3dd029801bf8527fa793078d935 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 09:50:06 -0400 Subject: [PATCH 09/76] Deprecating IMGUR --- .../java/com/vitorpamplona/amethyst/LocalPreferences.kt | 4 ++-- .../java/com/vitorpamplona/amethyst/model/Account.kt | 2 +- .../vitorpamplona/amethyst/ui/actions/ImageUploader.kt | 8 ++++---- .../vitorpamplona/amethyst/ui/actions/NewMediaModel.kt | 7 ++++--- .../vitorpamplona/amethyst/ui/actions/NewMediaView.kt | 4 ++-- .../com/vitorpamplona/amethyst/ui/actions/NewPostView.kt | 9 +++++---- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 55421207f..6984ff9a2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -251,9 +251,9 @@ object LocalPreferences { ) ?: LnZapEvent.ZapType.PUBLIC val defaultFileServer = gson.fromJson( - getString(PrefKeys.DEFAULT_FILE_SERVER, "IMGUR"), + getString(PrefKeys.DEFAULT_FILE_SERVER, "NOSTR_BUILD"), object : TypeToken() {}.type - ) ?: ServersAvailable.IMGUR + ) ?: ServersAvailable.NOSTR_BUILD val zapPaymentRequestServer = try { getString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, null)?.let { 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 209a97906..89c2e96a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -53,7 +53,7 @@ class Account( var translateTo: String = Locale.getDefault().language, var zapAmountChoices: List = listOf(500L, 1000L, 5000L), var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE, - var defaultFileServer: ServersAvailable = ServersAvailable.IMGUR, + var defaultFileServer: ServersAvailable = ServersAvailable.NOSTR_BUILD, var defaultHomeFollowList: String = KIND3_FOLLOWS, var defaultStoriesFollowList: String = GLOBAL_FOLLOWS, var defaultNotificationFollowList: String = GLOBAL_FOLLOWS, 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 4f0d840af..3a0806ff2 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 @@ -40,9 +40,9 @@ object ImageUploader { "Can't open the image input stream" } val myServer = when (server) { - ServersAvailable.IMGUR, ServersAvailable.IMGUR_NIP_94 -> { - ImgurServer() - } + // ServersAvailable.IMGUR, ServersAvailable.IMGUR_NIP_94 -> { + // ImgurServer() + // } ServersAvailable.NOSTRIMG, ServersAvailable.NOSTRIMG_NIP_94 -> { NostrImgServer() } @@ -53,7 +53,7 @@ object ImageUploader { NostrFilesDevServer() } else -> { - ImgurServer() + NostrBuildServer() } } 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 e8a6398ec..4aae8742a 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 @@ -38,9 +38,10 @@ open class NewMediaModel : ViewModel() { this.mediaType = contentType this.selectedServer = defaultServer() - if (selectedServer == ServersAvailable.IMGUR) { - selectedServer = ServersAvailable.IMGUR_NIP_94 - } else if (selectedServer == ServersAvailable.NOSTRIMG) { + // if (selectedServer == ServersAvailable.IMGUR) { + // selectedServer = ServersAvailable.IMGUR_NIP_94 + // } else + if (selectedServer == ServersAvailable.NOSTRIMG) { selectedServer = ServersAvailable.NOSTRIMG_NIP_94 } else if (selectedServer == ServersAvailable.NOSTR_BUILD) { selectedServer = ServersAvailable.NOSTR_BUILD_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 955e0a4fc..cd24d89d5 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 @@ -111,7 +111,7 @@ 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.IMGUR_NIP_94 || selectedServer == ServersAvailable.NOSTR_BUILD_NIP_94 || selectedServer == ServersAvailable.NOSTRFILES_DEV_NIP_94 } @@ -121,7 +121,7 @@ fun ImageVideoPost(postViewModel: NewMediaModel, acc: Account) { val scope = rememberCoroutineScope() val fileServers = listOf( - Triple(ServersAvailable.IMGUR_NIP_94, stringResource(id = R.string.upload_server_imgur_nip94), stringResource(id = R.string.upload_server_imgur_nip94_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)), 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 dc8a01489..b96e0c61c 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 @@ -641,11 +641,12 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier } enum class ServersAvailable { - IMGUR, + // IMGUR, NOSTR_BUILD, NOSTRIMG, NOSTRFILES_DEV, - IMGUR_NIP_94, + + // IMGUR_NIP_94, NOSTRIMG_NIP_94, NOSTR_BUILD_NIP_94, NOSTRFILES_DEV_NIP_94, @@ -668,11 +669,11 @@ fun ImageVideoDescription( val isVideo = mediaType.startsWith("video") val fileServers = listOf( - Triple(ServersAvailable.IMGUR, stringResource(id = R.string.upload_server_imgur), stringResource(id = R.string.upload_server_imgur_explainer)), + // 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.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)), From e5b937ed7ece7bacbb19088e21e15f3707638634 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 14:54:25 -0400 Subject: [PATCH 10/76] Only updates the refreshing state if it was refreshing. --- .../java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt | 4 +++- .../vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt | 3 ++- .../java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt | 3 ++- .../com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) 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 b7d1344bb..89e21bac8 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 @@ -67,7 +67,9 @@ fun CardFeedView( } } is CardFeedState.Loaded -> { - refreshing = false + if (refreshing) + refreshing = false + FeedLoaded( state = state, accountViewModel = accountViewModel, 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 8238ad33e..fb00a11dc 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 @@ -65,7 +65,8 @@ fun ChatroomListFeedView( } is FeedState.Loaded -> { - refreshing = false + if (refreshing) + refreshing = false FeedLoaded(state, accountViewModel, navController, markAsRead) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 3db7f3803..7ea4e8232 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -77,7 +77,8 @@ fun FeedView( } is FeedState.Loaded -> { - refreshing = false + if (refreshing) + refreshing = false FeedLoaded( state, routeForLastRead, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index ab8e591a2..02b8bf10c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -60,7 +60,9 @@ fun LnZapFeedView( } } is LnZapFeedState.Loaded -> { - refreshing = false + if (refreshing) + refreshing = false + LnZapFeedLoaded(state, accountViewModel, navController) } is LnZapFeedState.Loading -> { From df5b5b90af19362ff0301986be79e5a10ee6b5f3 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 14:54:42 -0400 Subject: [PATCH 11/76] Formatting --- .../com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt | 3 ++- .../vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt | 3 ++- .../java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt | 3 ++- .../com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) 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 89e21bac8..6b1d28edc 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 @@ -67,8 +67,9 @@ fun CardFeedView( } } is CardFeedState.Loaded -> { - if (refreshing) + if (refreshing) { refreshing = false + } FeedLoaded( state = state, 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 fb00a11dc..60905dbc3 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 @@ -65,8 +65,9 @@ fun ChatroomListFeedView( } is FeedState.Loaded -> { - if (refreshing) + if (refreshing) { refreshing = false + } FeedLoaded(state, accountViewModel, navController, markAsRead) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 7ea4e8232..02f670801 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -77,8 +77,9 @@ fun FeedView( } is FeedState.Loaded -> { - if (refreshing) + if (refreshing) { refreshing = false + } FeedLoaded( state, routeForLastRead, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index 02b8bf10c..f6811611b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -60,9 +60,10 @@ fun LnZapFeedView( } } is LnZapFeedState.Loaded -> { - if (refreshing) + if (refreshing) { refreshing = false - + } + LnZapFeedLoaded(state, accountViewModel, navController) } is LnZapFeedState.Loading -> { From 5da49c920bf852b5bbadfc0120527f8039edd11b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 14:55:06 -0400 Subject: [PATCH 12/76] Removes the time log for Composing a note --- .../amethyst/ui/note/NoteCompose.kt | 40 +------------------ 1 file changed, 2 insertions(+), 38 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 8412819c6..60ec386af 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 @@ -139,10 +139,8 @@ import java.io.File import java.math.BigDecimal import java.net.URL import java.util.Locale -import kotlin.time.ExperimentalTime -import kotlin.time.measureTimedValue -@OptIn(ExperimentalTime::class) +@OptIn(ExperimentalFoundationApi::class) @Composable fun NoteCompose( baseNote: Note, @@ -156,40 +154,6 @@ fun NoteCompose( parentBackgroundColor: Color? = null, accountViewModel: AccountViewModel, navController: NavController -) { - val (value, elapsed) = measureTimedValue { - NoteComposeInner( - baseNote, - routeForLastRead, - modifier, - isBoostedNote, - isQuotedNote, - unPackReply, - makeItShort, - addMarginTop, - parentBackgroundColor, - accountViewModel, - navController - ) - } - - 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, ExperimentalTime::class) -@Composable -fun NoteComposeInner( - baseNote: Note, - routeForLastRead: String? = null, - modifier: Modifier = Modifier, - isBoostedNote: Boolean = false, - isQuotedNote: Boolean = false, - unPackReply: Boolean = true, - makeItShort: Boolean = false, - addMarginTop: Boolean = true, - parentBackgroundColor: Color? = null, - accountViewModel: AccountViewModel, - navController: NavController ) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return @@ -1870,7 +1834,7 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, loggedIn: U @Composable private fun RelayBadges(baseNote: Note) { val noteRelaysState by baseNote.live().relays.observeAsState() - val noteRelays = noteRelaysState?.note ?: return + val noteRelays = remember(noteRelaysState) { noteRelaysState?.note } ?: return var expanded by remember { mutableStateOf(false) } var showShowMore by remember { mutableStateOf(false) } From 460187c4a4b20c12e89170b3b2d84103f5aa74ba Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 15:07:13 -0400 Subject: [PATCH 13/76] Optimizes Drawer for Recomposition --- .../amethyst/ui/navigation/DrawerContent.kt | 92 ++++++++++--------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 31166e172..dfa223532 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -96,7 +96,7 @@ fun DrawerContent( modifier = Modifier.padding(top = 20.dp) ) ListContent( - account.userProfile(), + account.userProfile().pubkeyHex, navController, scaffoldState, sheetState, @@ -121,16 +121,25 @@ fun ProfileContent( val coroutineScope = rememberCoroutineScope() val accountUserState by baseAccountUser.live().metadata.observeAsState() - val accountUser = accountUserState?.user ?: return + val accountUser = remember (accountUserState) { accountUserState?.user } ?: return + + val profilePubHex = remember (accountUserState) { accountUserState?.user?.pubkeyHex } ?: return + + val profileBanner = remember (accountUserState) { accountUserState?.user?.info?.banner?.ifBlank { null } } + val profilePicture = remember (accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } } + val bestUserName = remember(accountUserState) { accountUserState?.user?.bestUsername() } + val bestDisplayName = remember(accountUserState) { accountUserState?.user?.bestDisplayName() } + val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags } + val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" } val accountUserFollowsState by baseAccountUser.live().follows.observeAsState() - val accountUserFollows = accountUserFollowsState?.user ?: return + val followingCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowCount()?.toString() ?: "--" } + val followerCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowerCount()?.toString() ?: "--"} Box { - val banner = accountUser.info?.banner - if (!banner.isNullOrBlank()) { + if (profileBanner != null) { AsyncImage( - model = banner, + model = profileBanner, contentDescription = stringResource(id = R.string.profile_image), contentScale = ContentScale.FillWidth, modifier = Modifier @@ -149,35 +158,34 @@ fun ProfileContent( } Column(modifier = modifier) { - RobohashAsyncImageProxy( - robot = accountUser.pubkeyHex, - model = ResizeImage(accountUser.profilePicture(), 100.dp), - contentDescription = stringResource(id = R.string.profile_image), - modifier = Modifier - .width(100.dp) - .height(100.dp) - .clip(shape = CircleShape) - .border(3.dp, MaterialTheme.colors.background, CircleShape) - .background(MaterialTheme.colors.background) - .clickable(onClick = { - accountUser.let { - navController.navigate("User/${it.pubkeyHex}") - } - coroutineScope.launch { - scaffoldState.drawerState.close() - } - }) - ) - if (accountUser.bestDisplayName() != null) { + profilePicture?.let { + RobohashAsyncImageProxy( + robot = profilePubHex, + model = profilePicture, + contentDescription = stringResource(id = R.string.profile_image), + modifier = Modifier + .width(100.dp) + .height(100.dp) + .clip(shape = CircleShape) + .border(3.dp, MaterialTheme.colors.background, CircleShape) + .background(MaterialTheme.colors.background) + .clickable(onClick = { + navController.navigate(route) + coroutineScope.launch { + scaffoldState.drawerState.close() + } + }) + ) + } + + if (bestDisplayName != null) { CreateTextWithEmoji( - text = accountUser.bestDisplayName() ?: "", - tags = accountUser.info?.latestMetadata?.tags, + text = bestDisplayName, + tags = tags, modifier = Modifier .padding(top = 7.dp) .clickable(onClick = { - accountUser.let { - navController.navigate("User/${it.pubkeyHex}") - } + navController.navigate(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -186,18 +194,16 @@ fun ProfileContent( fontSize = 18.sp ) } - if (accountUser.bestUsername() != null) { + if (bestUserName != null) { CreateTextWithEmoji( - text = " @${accountUser.bestUsername()}", + text = " @${bestUserName}", tags = accountUser.info?.latestMetadata?.tags, color = Color.LightGray, modifier = Modifier .padding(top = 15.dp) .clickable( onClick = { - accountUser.let { - navController.navigate("User/${it.pubkeyHex}") - } + navController.navigate(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -209,9 +215,7 @@ fun ProfileContent( modifier = Modifier .padding(top = 15.dp) .clickable(onClick = { - accountUser.let { - navController.navigate("User/${it.pubkeyHex}") - } + navController.navigate(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -219,14 +223,14 @@ fun ProfileContent( ) { Row() { Text( - "${accountUserFollows.cachedFollowCount() ?: "--"}", + text = followingCount, fontWeight = FontWeight.Bold ) Text(stringResource(R.string.following)) } Row(modifier = Modifier.padding(start = 10.dp)) { Text( - "${accountUserFollows.cachedFollowerCount() ?: "--"}", + text = followerCount, fontWeight = FontWeight.Bold ) Text(stringResource(R.string.followers)) @@ -239,7 +243,7 @@ fun ProfileContent( @OptIn(ExperimentalMaterialApi::class) @Composable fun ListContent( - accountUser: User?, + accountUserPubKey: String?, navController: NavHostController, scaffoldState: ScaffoldState, sheetState: ModalBottomSheetState, @@ -254,14 +258,14 @@ fun ListContent( var proxyPort = remember { mutableStateOf(account.proxyPort.toString()) } Column(modifier = modifier.fillMaxHeight().verticalScroll(rememberScrollState())) { - if (accountUser != null) { + if (accountUserPubKey != null) { NavigationRow( title = stringResource(R.string.profile), icon = Route.Profile.icon, tint = MaterialTheme.colors.primary, navController = navController, scaffoldState = scaffoldState, - route = "User/${accountUser.pubkeyHex}" + route = "User/$accountUserPubKey" ) NavigationRow( From 8223476cb372c52bc5900d85214e2436c709cdd2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 15:07:21 -0400 Subject: [PATCH 14/76] Formatting --- .../amethyst/ui/navigation/DrawerContent.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index dfa223532..40b37b3e3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -121,12 +121,12 @@ fun ProfileContent( val coroutineScope = rememberCoroutineScope() val accountUserState by baseAccountUser.live().metadata.observeAsState() - val accountUser = remember (accountUserState) { accountUserState?.user } ?: return + val accountUser = remember(accountUserState) { accountUserState?.user } ?: return - val profilePubHex = remember (accountUserState) { accountUserState?.user?.pubkeyHex } ?: return + val profilePubHex = remember(accountUserState) { accountUserState?.user?.pubkeyHex } ?: return - val profileBanner = remember (accountUserState) { accountUserState?.user?.info?.banner?.ifBlank { null } } - val profilePicture = remember (accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } } + val profileBanner = remember(accountUserState) { accountUserState?.user?.info?.banner?.ifBlank { null } } + val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } } val bestUserName = remember(accountUserState) { accountUserState?.user?.bestUsername() } val bestDisplayName = remember(accountUserState) { accountUserState?.user?.bestDisplayName() } val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags } @@ -134,7 +134,7 @@ fun ProfileContent( val accountUserFollowsState by baseAccountUser.live().follows.observeAsState() val followingCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowCount()?.toString() ?: "--" } - val followerCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowerCount()?.toString() ?: "--"} + val followerCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowerCount()?.toString() ?: "--" } Box { if (profileBanner != null) { @@ -196,7 +196,7 @@ fun ProfileContent( } if (bestUserName != null) { CreateTextWithEmoji( - text = " @${bestUserName}", + text = " @$bestUserName", tags = accountUser.info?.latestMetadata?.tags, color = Color.LightGray, modifier = Modifier From 93282b71aee4030a5b6532cc5583260c08b2253e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 15:07:47 -0400 Subject: [PATCH 15/76] Optimizes NIP-05 procedures --- .../ui/note/NIP05VerificationDisplay.kt | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 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 31dc03ec1..defb4810d 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 @@ -39,33 +39,48 @@ import java.util.Date @Composable fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State { - var nip05Verified = remember { mutableStateOf(null) } + val nip05Verified = remember(user) { + // starts with null if must verify or already filled in if verified in the last hour + val default = if ((user.nip05LastVerificationTime ?: 0) > (Date().time / 1000 - 60 * 60)) { // 1hour + user.nip05Verified + } else { + null + } + + mutableStateOf(default) + } LaunchedEffect(key1 = user) { - withContext(Dispatchers.IO) { - user.nip05?.ifBlank { null }?.let { nip05 -> - val now = Date().time / 1000 - if ((user.nip05LastVerificationTime ?: 0) > (now - 60 * 60)) { // 1hour - nip05Verified.value = user.nip05Verified - } else { + if (nip05Verified.value == null) { + withContext(Dispatchers.IO) { + user.nip05?.ifBlank { null }?.let { nip05 -> Nip05Verifier().verifyNip05( nip05, onSuccess = { // Marks user as verified if (it == pubkeyHex) { user.nip05Verified = true - user.nip05LastVerificationTime = now - nip05Verified.value = true + user.nip05LastVerificationTime = Date().time / 1000 + + if (nip05Verified.value != true) { + nip05Verified.value = true + } } else { user.nip05Verified = false user.nip05LastVerificationTime = 0 - nip05Verified.value = false + + if (nip05Verified.value != false) { + nip05Verified.value = false + } } }, onError = { user.nip05LastVerificationTime = 0 user.nip05Verified = false - nip05Verified.value = false + + if (nip05Verified.value != false) { + nip05Verified.value = false + } } ) } @@ -90,16 +105,14 @@ fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifie @Composable fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) { val userState by baseUser.live().metadata.observeAsState() - val user = userState?.user ?: return + val user = remember(userState) { userState?.user } ?: return + val parts = remember(userState) { userState?.user?.nip05()?.split("@") } ?: return - user.nip05()?.let { nip05 -> - val parts = nip05.split("@") - if (parts.size == 2) { - val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex) + if (parts.size == 2) { + val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex) - Column(modifier = columnModifier) { - DisplayNIP05(parts[0], parts[1], nip05Verified) - } + Column(modifier = columnModifier) { + DisplayNIP05(parts[0], parts[1], nip05Verified) } } } From e96a50b4545e452b6c4d72c17e29e260f0e57c58 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 15:08:14 -0400 Subject: [PATCH 16/76] Optimizes recomposition of bottom items. --- .../amethyst/ui/navigation/AppBottomBar.kt | 84 +++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) 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 0d715b80e..bae0bb5af 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 @@ -5,6 +5,7 @@ import android.view.ViewTreeObserver import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -36,6 +37,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -82,11 +84,7 @@ fun keyboardAsState(): State { @Composable fun AppBottomBar(navController: NavHostController, accountViewModel: AccountViewModel) { - val currentRoute = currentRoute(navController) - val currentRouteBase = currentRoute?.substringBefore("?") - val coroutineScope = rememberCoroutineScope() val isKeyboardOpen by keyboardAsState() - if (isKeyboardOpen == Keyboard.Closed) { Column() { Divider( @@ -98,43 +96,59 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView backgroundColor = MaterialTheme.colors.background ) { bottomNavigationItems.forEach { item -> - val selected = currentRouteBase == item.base - - BottomNavigationItem( - icon = { NotifiableIcon(item, selected, accountViewModel) }, - selected = selected, - onClick = { - coroutineScope.launch { - if (currentRouteBase != item.base) { - navController.navigate(item.base) { - navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) - restoreState = true - } - launchSingleTop = true - restoreState = true - } - } else { - val route = currentRoute.replace("{scrollToTop}", "true") - navController.navigate(route) { - navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) { inclusive = item.route == Route.Home.route } - restoreState = true - } - - launchSingleTop = true - restoreState = true - } - } - } - } - ) + BottomIcon(item, accountViewModel, navController) } } } } } +@Composable +private fun RowScope.BottomIcon( + item: Route, + accountViewModel: AccountViewModel, + navController: NavHostController +) { + val navBackStackEntry by navController.currentBackStackEntryAsState() + + navBackStackEntry?.let { navBackStackEntry -> + val currentRoute = remember(navBackStackEntry) { navBackStackEntry.destination.route } + val currentRouteBase = remember(navBackStackEntry) { navBackStackEntry.destination.route?.substringBefore("?") } + val selected = remember(navBackStackEntry) { currentRouteBase == item.base } + val coroutineScope = rememberCoroutineScope() + + BottomNavigationItem( + icon = { NotifiableIcon(item, selected, accountViewModel) }, + selected = selected, + onClick = { + coroutineScope.launch { + if (!selected) { + navController.navigate(item.base) { + navController.graph.startDestinationRoute?.let { start -> + popUpTo(start) + restoreState = true + } + launchSingleTop = true + restoreState = true + } + } else if (currentRoute != null) { + val route = currentRoute.replace("{scrollToTop}", "true") + navController.navigate(route) { + navController.graph.startDestinationRoute?.let { start -> + popUpTo(start) { inclusive = item.route == Route.Home.route } + restoreState = true + } + + launchSingleTop = true + restoreState = true + } + } + } + } + ) + } +} + @Composable private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) { val scope = rememberCoroutineScope() From 3b816559e4c0fa32237b14fea9ba4bd529db0a14 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 15:10:54 -0400 Subject: [PATCH 17/76] Optimizes MultiSetCompose --- .../amethyst/ui/note/MultiSetCompose.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 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 86dd8e055..c63ad607b 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 @@ -53,17 +53,19 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { - val noteState by multiSetCard.note.live().metadata.observeAsState() - val note = remember(noteState) { noteState?.note } + val baseNote = remember { multiSetCard.note } + + val noteState by baseNote.live().metadata.observeAsState() + val note = remember(noteState) { noteState?.note } ?: return val accountState by accountViewModel.accountLiveData.observeAsState() - val account = remember(accountState) { accountState?.account } + val account = remember(accountState) { accountState?.account } ?: return var popupExpanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() - if (note == null || account == null) { + if (note.event == null) { BlankNote(Modifier, false) } else { var isNew by remember { mutableStateOf(false) } @@ -98,7 +100,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun onClick = { scope.launch { routeFor( - note, + baseNote, account.userProfile() )?.let { navController.navigate(it) } } @@ -129,7 +131,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun Spacer(modifier = Modifier.width(65.dp)) NoteCompose( - baseNote = multiSetCard.note, + baseNote = baseNote, routeForLastRead = null, modifier = Modifier.padding(top = 5.dp), isBoostedNote = true, From d99711d3e64fc92921b8e7dc238259648d13361a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 17:51:12 -0400 Subject: [PATCH 18/76] Remembering Background color and Text elements for posts. --- .../amethyst/ui/note/NoteCompose.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 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 dd67d57e6..ffc3d80b3 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 @@ -246,15 +246,19 @@ fun NoteCompose( } } - val backgroundColor = if (isNew) { - val newColor = MaterialTheme.colors.primary.copy(0.12f) - if (parentBackgroundColor != null) { - newColor.compositeOver(parentBackgroundColor) + val primaryColor = MaterialTheme.colors.primary.copy(0.12f) + val defaultBackgroundColor = MaterialTheme.colors.background + + val backgroundColor = remember(isNew, parentBackgroundColor) { + if (isNew) { + if (parentBackgroundColor != null) { + primaryColor.compositeOver(parentBackgroundColor) + } else { + primaryColor.compositeOver(defaultBackgroundColor) + } } else { - newColor.compositeOver(MaterialTheme.colors.background) + parentBackgroundColor ?: defaultBackgroundColor } - } else { - parentBackgroundColor ?: MaterialTheme.colors.background } val columnModifier = remember(backgroundColor) { @@ -436,11 +440,11 @@ private fun RenderTextEvent( accountViewModel: AccountViewModel, navController: NavController ) { - val tags = remember { note.event?.tags() } - val hashtags = remember { note.event?.hashtags() ?: emptyList() } - val eventContent = remember { accountViewModel.decrypt(note) } - val modifier = remember { Modifier.fillMaxWidth() } - val isAuthorTheLoggedUser = remember { accountViewModel.isLoggedUser(note.author) } + val tags = remember(note.event?.id()) { note.event?.tags() } + val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() } + val eventContent = remember(note.event?.id()) { accountViewModel.decrypt(note) } + val modifier = remember(note.event?.id()) { Modifier.fillMaxWidth() } + val isAuthorTheLoggedUser = remember(note.event?.id()) { accountViewModel.isLoggedUser(note.author) } if (eventContent != null) { if (makeItShort && isAuthorTheLoggedUser) { From 278091e6efce09f03262779bfcb5ce052665acad Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 17:51:37 -0400 Subject: [PATCH 19/76] Moves default state of url previews to the remember function --- .../amethyst/ui/components/UrlPreview.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 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 14fb7bd9f..b4d914a40 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 @@ -19,16 +19,19 @@ import kotlinx.coroutines.launch @Composable fun UrlPreview(url: String, urlText: String) { - val default = UrlCachedPreviewer.cache[url]?.let { - if (it.allFetchComplete() && it.url == url) { - UrlPreviewState.Loaded(it) - } else { - UrlPreviewState.Empty - } - } ?: UrlPreviewState.Loading val context = LocalContext.current - var urlPreviewState by remember { mutableStateOf(default) } + var urlPreviewState by remember { + val default = UrlCachedPreviewer.cache[url]?.let { + if (it.allFetchComplete() && it.url == url) { + UrlPreviewState.Loaded(it) + } else { + UrlPreviewState.Empty + } + } ?: UrlPreviewState.Loading + + mutableStateOf(default) + } val scope = rememberCoroutineScope() // Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created). From 22dbaaf95a6f51247acc434c05aeb67235fc5d07 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 17:51:59 -0400 Subject: [PATCH 20/76] Adjusts bottombar to not recalculate hasNewItems in every recomposition. --- .../amethyst/ui/navigation/AppBottomBar.kt | 161 +++++++++++------- 1 file changed, 98 insertions(+), 63 deletions(-) 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 bae0bb5af..d392d85a2 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 @@ -34,8 +34,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.navigation.NavController import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import com.vitorpamplona.amethyst.NotificationCache @@ -96,7 +98,77 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView backgroundColor = MaterialTheme.colors.background ) { bottomNavigationItems.forEach { item -> - BottomIcon(item, accountViewModel, navController) + HasNewItemsIcon(item, accountViewModel, navController) + } + } + } + } +} + +@Composable +private fun RowScope.HasNewItemsIcon( + route: Route, + accountViewModel: AccountViewModel, + navController: NavHostController +) { + val scope = rememberCoroutineScope() + + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = remember(accountState) { accountState?.account } ?: return + + val notifState = NotificationCache.live.observeAsState() + val notif = remember(notifState) { notifState.value } ?: return + + var hasNewItems by remember { mutableStateOf(false) } + + LaunchedEffect(key1 = notif) { + scope.launch(Dispatchers.IO) { + val newHasNewItems = route.hasNewItems(account, notif.cache, emptySet()) + if (newHasNewItems != hasNewItems) { + hasNewItems = newHasNewItems + } + } + } + + LaunchedEffect(Unit) { + scope.launch(Dispatchers.IO) { + LocalCache.live.newEventBundles.collect { + val newHasNewItems = route.hasNewItems(account, notif.cache, it) + if (newHasNewItems != hasNewItems) { + hasNewItems = newHasNewItems + } + } + } + } + + BottomIcon( + icon = route.icon, + size = if ("Home" == route.base) 25.dp else 23.dp, + iconSize = if ("Home" == route.base) 24.dp else 20.dp, + base = route.base, + hasNewItems = hasNewItems, + navController + ) { selected -> + scope.launch { + if (!selected) { + navController.navigate(route.base) { + navController.graph.startDestinationRoute?.let { start -> + popUpTo(start) + restoreState = true + } + launchSingleTop = true + restoreState = true + } + } else { + val newRoute = route.route.replace("{scrollToTop}", "true") + navController.navigate(newRoute) { + navController.graph.startDestinationRoute?.let { start -> + popUpTo(start) { inclusive = newRoute == Route.Home.route } + restoreState = true + } + + launchSingleTop = true + restoreState = true } } } @@ -105,84 +177,47 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView @Composable private fun RowScope.BottomIcon( - item: Route, - accountViewModel: AccountViewModel, - navController: NavHostController + icon: Int, + size: Dp, + iconSize: Dp, + base: String, + hasNewItems: Boolean, + navController: NavController, + onClick: (Boolean) -> Unit ) { val navBackStackEntry by navController.currentBackStackEntryAsState() - navBackStackEntry?.let { navBackStackEntry -> - val currentRoute = remember(navBackStackEntry) { navBackStackEntry.destination.route } - val currentRouteBase = remember(navBackStackEntry) { navBackStackEntry.destination.route?.substringBefore("?") } - val selected = remember(navBackStackEntry) { currentRouteBase == item.base } - val coroutineScope = rememberCoroutineScope() + navBackStackEntry?.let { + val selected = remember(it) { + it.destination.route?.substringBefore("?") == base + } BottomNavigationItem( - icon = { NotifiableIcon(item, selected, accountViewModel) }, + icon = { + NotifiableIcon( + icon, + size, + iconSize, + selected, + hasNewItems + ) + }, selected = selected, - onClick = { - coroutineScope.launch { - if (!selected) { - navController.navigate(item.base) { - navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) - restoreState = true - } - launchSingleTop = true - restoreState = true - } - } else if (currentRoute != null) { - val route = currentRoute.replace("{scrollToTop}", "true") - navController.navigate(route) { - navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) { inclusive = item.route == Route.Home.route } - restoreState = true - } - - launchSingleTop = true - restoreState = true - } - } - } - } + onClick = { onClick(selected) } ) } } @Composable -private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) { - val scope = rememberCoroutineScope() - - Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) { +private fun NotifiableIcon(icon: Int, size: Dp, iconSize: Dp, selected: Boolean, hasNewItems: Boolean) { + Box(Modifier.size(size)) { Icon( - painter = painterResource(id = route.icon), + painter = painterResource(id = icon), contentDescription = null, - modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp), + modifier = Modifier.size(iconSize), tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified ) - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - - val notifState = NotificationCache.live.observeAsState() - val notif = notifState.value ?: return - - var hasNewItems by remember { mutableStateOf(false) } - - LaunchedEffect(key1 = notif) { - scope.launch(Dispatchers.IO) { - hasNewItems = route.hasNewItems(account, notif.cache, emptySet()) - } - } - - LaunchedEffect(Unit) { - scope.launch(Dispatchers.IO) { - LocalCache.live.newEventBundles.collect { - hasNewItems = route.hasNewItems(account, notif.cache, it) - } - } - } - if (hasNewItems) { Box( Modifier From 0a8c79dacd1431219b69005a9a124f20625987e5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 17:52:18 -0400 Subject: [PATCH 21/76] Stops recomposing the drawer at every new route. --- .../vitorpamplona/amethyst/ui/navigation/DrawerContent.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 40b37b3e3..d5f483769 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -407,11 +407,8 @@ fun NavigationRow( route: String ) { val coroutineScope = rememberCoroutineScope() - val currentRoute = currentRoute(navController) IconRow(title, icon, tint, onClick = { - if (currentRoute != route) { - navController.navigate(route) - } + navController.navigate(route) coroutineScope.launch { scaffoldState.drawerState.close() } From 6437a2106a8e0ac80322bc86f1d481de78e726e7 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 17:52:46 -0400 Subject: [PATCH 22/76] Moves zapProgress to inside the IO update to make sure it only updates when needed. --- .../com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 36891d3b5..1f8b86ab7 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 @@ -331,15 +331,15 @@ fun ZapReaction( animationSize: Dp = 14.dp ) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return + val account = remember(accountState) { accountState?.account } ?: return val zapsState by baseNote.live().zaps.observeAsState() - val zappedNote = zapsState?.note ?: return - val zapMessage = "" + val zappedNote = remember(zapsState) { zapsState?.note } ?: return var wantsToZap by remember { mutableStateOf(false) } var wantsToChangeZapAmount by remember { mutableStateOf(false) } var wantsToSetCustomZap by remember { mutableStateOf(false) } + val context = LocalContext.current val scope = rememberCoroutineScope() @@ -352,6 +352,7 @@ fun ZapReaction( scope.launch(Dispatchers.IO) { if (!wasZappedByLoggedInUser) { wasZappedByLoggedInUser = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote) + zappingProgress = 1f } zapAmountTxt = showAmount(account.calculateZappedAmount(zappedNote)) @@ -393,7 +394,7 @@ fun ZapReaction( baseNote, account.zapAmountChoices.first() * 1000, null, - zapMessage, + "", context, onError = { scope.launch { @@ -457,7 +458,6 @@ fun ZapReaction( } if (wasZappedByLoggedInUser) { - zappingProgress = 1f Icon( imageVector = Icons.Default.Bolt, contentDescription = stringResource(R.string.zaps), From a3fc22a04a67777b80c08ac327de6942a562c291 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 18:24:50 -0400 Subject: [PATCH 23/76] v0.50.5 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bf17bcf25..5c8b925af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 172 - versionName "0.50.4" + versionCode 173 + versionName "0.50.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 0bf94f316f6b21d4eb132e89e661036cccd715c2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 20:04:23 -0400 Subject: [PATCH 24/76] Moving more variables to remember clauses --- .../amethyst/ui/components/ClickableRoute.kt | 8 ++++---- .../amethyst/ui/note/MultiSetCompose.kt | 13 +++++++++---- .../amethyst/ui/note/NIP05VerificationDisplay.kt | 7 +++++-- .../vitorpamplona/amethyst/ui/note/NoteCompose.kt | 6 ++++-- .../vitorpamplona/amethyst/ui/note/ReactionsRow.kt | 5 ++++- 5 files changed, 26 insertions(+), 13 deletions(-) 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 9bd2753fb..acbc4b65f 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 @@ -84,8 +84,8 @@ private fun DisplayEvent( noteBase?.let { val noteState by it.live().metadata.observeAsState() - val note = noteState?.note ?: return - val channel = note.channel() + val note = remember(noteState) { noteState?.note } ?: return + val channel = remember(noteState) { note.channel() } if (note.event is ChannelCreateEvent) { CreateClickableText( @@ -105,7 +105,7 @@ private fun DisplayEvent( CreateClickableText( clickablePart = channel.toBestDisplayName(), suffix = "${nip19.additionalChars} ", - route = "Channel/${note.channel()?.idHex}", + route = "Channel/${channel.idHex}", navController = navController ) } else { @@ -196,7 +196,7 @@ private fun DisplayAddress( noteBase?.let { val noteState by it.live().metadata.observeAsState() - val note = noteState?.note ?: return + val note = remember(noteState) { noteState?.note } ?: return CreateClickableText( clickablePart = "@${note.idDisplayNote()}", 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 c63ad607b..d45180015 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 @@ -82,10 +82,15 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun } } - val backgroundColor = if (isNew) { - MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) - } else { - MaterialTheme.colors.background + val primaryColor = MaterialTheme.colors.primary.copy(0.12f) + val defaultBackgroundColor = MaterialTheme.colors.background + + val backgroundColor = remember(isNew) { + if (isNew) { + primaryColor.compositeOver(defaultBackgroundColor) + } else { + defaultBackgroundColor + } } val columnModifier = remember(isNew) { 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 defb4810d..4940819b7 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 @@ -19,6 +19,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.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -34,7 +35,7 @@ import com.vitorpamplona.amethyst.model.UserMetadata import com.vitorpamplona.amethyst.service.Nip05Verifier import com.vitorpamplona.amethyst.ui.theme.Nip05 import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.launch import java.util.Date @Composable @@ -50,9 +51,11 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State Nip05Verifier().verifyNip05( nip05, 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 ffc3d80b3..82d9bdf27 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 @@ -1169,12 +1169,14 @@ 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) + val newTimeStr = timeAgo(time, context = context) + if (newTimeStr != timeStr) { + timeStr = newTimeStr + } } } 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 1f8b86ab7..f4fbf8f41 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 @@ -352,10 +352,13 @@ fun ZapReaction( scope.launch(Dispatchers.IO) { if (!wasZappedByLoggedInUser) { wasZappedByLoggedInUser = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote) - zappingProgress = 1f } zapAmountTxt = showAmount(account.calculateZappedAmount(zappedNote)) + + if (wasZappedByLoggedInUser) { + zappingProgress = 1f + } } } From a1da4affeb5ef65cee66b7b94d4bfc7fd24699e6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 20:04:33 -0400 Subject: [PATCH 25/76] Updating dependencies --- app/build.gradle | 6 +++--- build.gradle | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5c8b925af..4e859781f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,7 +128,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11' // Json Serialization TODO: We might need to converge between gson and Jackson (we are usin both) - implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.15.0' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.15.1' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // link preview @@ -138,7 +138,7 @@ dependencies { implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha06' // view videos - implementation 'com.google.android.exoplayer:exoplayer:2.18.6' + implementation 'com.google.android.exoplayer:exoplayer:2.18.7' // Load images from the web. implementation "io.coil-kt:coil-compose:$coil_version" @@ -184,7 +184,7 @@ dependencies { implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}" // Automatic memory leak detection - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11' testImplementation 'junit:junit:4.13.2' testImplementation 'io.mockk:mockk:1.13.5' diff --git a/build.gradle b/build.gradle index 4d3a662e0..0a999c817 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ buildscript { nav_version = "2.5.3" room_version = "2.4.3" accompanist_version = '0.30.0' - coil_version = '2.3.0' - vico_version = '1.6.5' + coil_version = '2.4.0' + vico_version = '1.6.6' } dependencies { classpath 'com.google.gms:google-services:4.3.15' From 93f8847544a5c4d5904da509b7c4e12fb56a0d6d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 20:41:42 -0400 Subject: [PATCH 26/76] moving plugin to the latest --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0a999c817..07c24fd4e 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,8 @@ buildscript { plugins { id 'com.android.application' version '8.0.1' apply false id 'com.android.library' version '8.0.1' apply false - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false - id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false + id 'org.jetbrains.kotlin.android' version '1.8.21' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.21' apply false } task installGitHook(type: Copy) { From 538e61b38eb568c463ca6d1685dd0bd84a6a2e3b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 20:47:13 -0400 Subject: [PATCH 27/76] rollback vm version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 07c24fd4e..0a999c817 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,8 @@ buildscript { plugins { id 'com.android.application' version '8.0.1' apply false id 'com.android.library' version '8.0.1' apply false - id 'org.jetbrains.kotlin.android' version '1.8.21' apply false - id 'org.jetbrains.kotlin.jvm' version '1.8.21' apply false + id 'org.jetbrains.kotlin.android' version '1.8.10' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false } task installGitHook(type: Copy) { From 4b49cc1aab15322df67bf774f6073b056928fbe6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 20:47:34 -0400 Subject: [PATCH 28/76] better translations to unfollow in portuguese --- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2db0e85dc..d662b8a8a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -149,7 +149,7 @@ descrição para e foto para Sair - Remover seguidor + Desseguir Canal criado Informação do canal mudou para Chat público From 2082c6cb28bab865edbde715faeb29c1f8e1ecbe Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 May 2023 21:01:25 -0400 Subject: [PATCH 29/76] Fixes size for following symbol --- .../vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt | 3 +-- .../java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 2 +- 2 files changed, 2 insertions(+), 3 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 e59508bff..e799500d1 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 @@ -9,7 +9,6 @@ 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 @@ -557,7 +556,7 @@ private fun RenderRelay(dirtyUrl: String) { val iconModifier = remember { Modifier - .fillMaxSize(1f) + .size(15.dp) .clip(shape = CircleShape) } 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 82d9bdf27..7c23d1623 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 @@ -2132,7 +2132,7 @@ fun UserPicture( } val myIconModifier = remember { - Modifier.fillMaxSize() + Modifier.width(size.div(3.5f)).height(size.div(3.5f)) } Box(myIconBoxModifier, contentAlignment = Alignment.Center) { From d355f22938b765acefca8cfb8452f711e38071b1 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 10:35:56 -0400 Subject: [PATCH 30/76] Fixes click on the Home to update the feed --- .../com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d392d85a2..f6c9c8823 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 @@ -163,7 +163,7 @@ private fun RowScope.HasNewItemsIcon( val newRoute = route.route.replace("{scrollToTop}", "true") navController.navigate(newRoute) { navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) { inclusive = newRoute == Route.Home.route } + popUpTo(start) { inclusive = route.route == Route.Home.route } restoreState = true } From a29d9ad433afccbd04f8b9c0c51234336135845e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 10:37:18 -0400 Subject: [PATCH 31/76] Adds a cache layer for citations in Notes --- .../amethyst/service/model/BaseTextNoteEvent.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 93f576f05..4f9e98fce 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 @@ -19,6 +19,7 @@ open class BaseTextNoteEvent( open fun replyTos() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } private var citedUsersCache: Set? = null + private var citedNotesCache: Set? = null fun citedUsers(): Set { citedUsersCache?.let { return it } @@ -61,8 +62,10 @@ open class BaseTextNoteEvent( return returningList } - fun findCitations(): Set { - var citations = mutableSetOf() + fun findCitations(): Set { + citedNotesCache?.let { return it } + + val citations = mutableSetOf() // Removes citations from replies: val matcher = tagSearch.matcher(content) while (matcher.find()) { @@ -102,6 +105,7 @@ open class BaseTextNoteEvent( } } + citedNotesCache = citations return citations } From 15621f16a2e6f945c5aab7aa7b896190b89c6c0b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 10:37:34 -0400 Subject: [PATCH 32/76] Improves the speed of updating Notification notes --- .../amethyst/ui/screen/CardFeedViewModel.kt | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) 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 075419deb..cdcac191e 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 @@ -37,7 +37,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { val feedContent = _feedContent.asStateFlow() private var lastAccount: Account? = null - private var lastNotes: List? = null + private var lastNotes: Set? = null fun refresh() { val scope = CoroutineScope(Job() + Dispatchers.Default) @@ -57,13 +57,13 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { val newCards = convertToCard(notes.minus(lastNotesCopy)) if (newCards.isNotEmpty()) { - lastNotes = notes + lastNotes = notes.toSet() lastAccount = (localFilter as? NotificationFeedFilter)?.account updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed()) } } else { val cards = convertToCard(notes) - lastNotes = notes + lastNotes = notes.toSet() lastAccount = (localFilter as? NotificationFeedFilter)?.account updateFeed(cards) } @@ -184,13 +184,26 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { if (lastNotesCopy != null && localFilter is AdditiveFeedFilter && oldNotesState is CardFeedState.Loaded) { val filteredNewList = localFilter.applyFilter(newItems) + + if (filteredNewList.isEmpty()) return + val actuallyNew = filteredNewList.minus(lastNotesCopy) + if (actuallyNew.isEmpty()) return + val newCards = convertToCard(actuallyNew) + if (newCards.isNotEmpty()) { - lastNotes = lastNotesCopy + newItems + lastNotes = lastNotesCopy + actuallyNew lastAccount = (localFilter as? NotificationFeedFilter)?.account - updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed()) + + val updatedCards = (oldNotesState.feed.value + newCards) + .distinctBy { it.id() } + .sortedWith(compareBy({ it.createdAt() }, { it.id() })) + .reversed() + .take(1000) + + updateFeed(updatedCards) } } else { // Refresh Everything @@ -216,10 +229,13 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { @OptIn(ExperimentalTime::class) fun invalidateInsertData(newItems: Set) { bundlerInsert.invalidateList(newItems) { + val newObjects = it.flatten().toSet() val (value, elapsed) = measureTimedValue { - refreshFromOldState(it.flatten().toSet()) + if (newObjects.isNotEmpty()) { + refreshFromOldState(newObjects) + } } - Log.d("Time", "${this.javaClass.simpleName} Card additive update $elapsed") + Log.d("Time", "${this.javaClass.simpleName} Card additive update $elapsed. ${newObjects.size}") } } From 8898e5d10bf7797a92e08a6ca98ad4a5e173ca44 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 12:02:34 -0400 Subject: [PATCH 33/76] Adds Immutable and Stable annotations to avoid recomposing --- .../main/java/com/vitorpamplona/amethyst/model/Note.kt | 3 +++ .../main/java/com/vitorpamplona/amethyst/model/User.kt | 3 +++ .../com/vitorpamplona/amethyst/service/model/ATag.kt | 2 ++ .../amethyst/service/model/AudioTrackEvent.kt | 2 ++ .../amethyst/service/model/BadgeAwardEvent.kt | 2 ++ .../amethyst/service/model/BadgeDefinitionEvent.kt | 2 ++ .../amethyst/service/model/BadgeProfilesEvent.kt | 2 ++ .../amethyst/service/model/BaseTextNoteEvent.kt | 2 ++ .../amethyst/service/model/BookmarkListEvent.kt | 2 ++ .../amethyst/service/model/ChannelCreateEvent.kt | 2 ++ .../amethyst/service/model/ChannelHideMessageEvent.kt | 2 ++ .../amethyst/service/model/ChannelMessageEvent.kt | 2 ++ .../amethyst/service/model/ChannelMetadataEvent.kt | 2 ++ .../amethyst/service/model/ChannelMuteUserEvent.kt | 2 ++ .../amethyst/service/model/ContactListEvent.kt | 2 ++ .../amethyst/service/model/DeletionEvent.kt | 2 ++ .../com/vitorpamplona/amethyst/service/model/Event.kt | 3 +++ .../amethyst/service/model/EventInterface.kt | 2 ++ .../amethyst/service/model/FileHeaderEvent.kt | 2 ++ .../amethyst/service/model/FileStorageEvent.kt | 2 ++ .../amethyst/service/model/FileStorageHeaderEvent.kt | 2 ++ .../amethyst/service/model/GeneralListEvent.kt | 2 ++ .../amethyst/service/model/HighlightEvent.kt | 2 ++ .../vitorpamplona/amethyst/service/model/LnZapEvent.kt | 2 ++ .../amethyst/service/model/LnZapEventInterface.kt | 2 ++ .../amethyst/service/model/LnZapPaymentRequestEvent.kt | 2 ++ .../amethyst/service/model/LnZapPaymentResponseEvent.kt | 2 ++ .../amethyst/service/model/LnZapRequestEvent.kt | 2 ++ .../amethyst/service/model/LongTextNoteEvent.kt | 2 ++ .../amethyst/service/model/MetadataEvent.kt | 2 ++ .../amethyst/service/model/MuteListEvent.kt | 2 ++ .../amethyst/service/model/PeopleListEvent.kt | 2 ++ .../vitorpamplona/amethyst/service/model/PinListEvent.kt | 2 ++ .../amethyst/service/model/PollNoteEvent.kt | 2 ++ .../amethyst/service/model/PrivateDmEvent.kt | 2 ++ .../amethyst/service/model/ReactionEvent.kt | 2 ++ .../amethyst/service/model/RecommendRelayEvent.kt | 2 ++ .../amethyst/service/model/RelayAuthEvent.kt | 2 ++ .../vitorpamplona/amethyst/service/model/ReportEvent.kt | 3 +++ .../vitorpamplona/amethyst/service/model/RepostEvent.kt | 2 ++ .../amethyst/service/model/TextNoteEvent.kt | 2 ++ .../vitorpamplona/amethyst/ui/screen/CardFeedState.kt | 9 +++++++++ .../com/vitorpamplona/amethyst/ui/screen/FeedView.kt | 7 ++++++- 43 files changed, 101 insertions(+), 1 deletion(-) 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 e8920e69f..b012aabc2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil @@ -20,6 +21,7 @@ import java.util.regex.Pattern val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]") +@Stable class AddressableNote(val address: ATag) : Note(address.toTag()) { override fun idNote() = address.toNAddr() override fun toNEvent() = address.toNAddr() @@ -28,6 +30,7 @@ class AddressableNote(val address: ATag) : Note(address.toTag()) { override fun createdAt() = (event as? LongTextNoteEvent)?.publishedAt() ?: event?.createdAt() } +@Stable open class Note(val idHex: String) { // These fields are only available after the Text Note event is received. // They are immutable after that. diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 7d5877952..afc446004 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.model.BookmarkListEvent @@ -20,6 +21,7 @@ import java.util.regex.Pattern val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)") +@Stable class User(val pubkeyHex: String) { var info: UserMetadata? = null @@ -358,6 +360,7 @@ data class RelayInfo( data class Chatroom(var roomMessages: Set) +@Stable class UserMetadata { var name: String? = null var username: String? = null 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 24d705835..3b23481f6 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,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.hexToByteArray import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.nip19.Tlv @@ -9,6 +10,7 @@ import nostr.postr.Bech32 import nostr.postr.bechToBytes import nostr.postr.toByteArray +@Immutable data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) { fun toTag() = "$kind:$pubKeyHex:$dTag" diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt index 82dc30aa0..980dbe00d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class AudioTrackEvent( id: HexKey, pubKey: HexKey, 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 c16abc20e..71f374a39 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 @@ -1,7 +1,9 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey +@Immutable class BadgeAwardEvent( id: HexKey, pubKey: HexKey, 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 5e8be5350..f31cd3a42 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 @@ -1,7 +1,9 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey +@Immutable class BadgeDefinitionEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt index f616bb7a4..a81723fe9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt @@ -1,7 +1,9 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey +@Immutable class BadgeProfilesEvent( id: HexKey, pubKey: HexKey, 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 4f9e98fce..84d173277 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,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.tagSearch import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19.nip19regex +@Immutable open class BaseTextNoteEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt index 71f1ad7e6..5209d4d9e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class BookmarkListEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt index f9205b61a..aecf4d697 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ChannelCreateEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt index 5de80f0b6..896f8d462 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ChannelHideMessageEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt index 5fb98d1e3..32635e1ea 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ChannelMessageEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt index 6382e588c..b15365f91 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ChannelMetadataEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt index 79988b4de..c0b86ee3b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ChannelMuteUserEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt index d141c3fd8..56cb3212e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.decodePublicKey @@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable data class Contact(val pubKeyHex: String, val relayUri: String?) class ContactListEvent( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt index 4d7170f61..9b959e884 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class DeletionEvent( id: HexKey, pubKey: HexKey, 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 91b825e8d..2d633ee50 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.* import com.google.gson.annotations.SerializedName import com.vitorpamplona.amethyst.model.HexKey @@ -14,6 +15,7 @@ import java.math.BigDecimal import java.security.MessageDigest import java.util.* +@Immutable open class Event( val id: HexKey, @SerializedName("pubkey") val pubKey: HexKey, @@ -285,6 +287,7 @@ open class Event( } } +@Immutable interface AddressableEvent { fun dTag(): String fun address(): ATag diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt index 5a4e1db57..3528cfedd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt @@ -1,8 +1,10 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import java.math.BigDecimal +@Immutable interface EventInterface { fun id(): HexKey diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt index a33344af6..f9832f24d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class FileHeaderEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt index af25b4152..13087fb35 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt @@ -1,12 +1,14 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Base64 import java.util.Date +@Immutable class FileStorageEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt index f2ba729bb..b27d3bee5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class FileStorageHeaderEvent( id: HexKey, pubKey: HexKey, 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 57cadcd66..859e1381b 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 @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.hexToByteArray import nostr.postr.Utils +@Immutable abstract class GeneralListEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt index 1d3d1acf6..427cf7ddb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class HighlightEvent( id: HexKey, pubKey: HexKey, 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 a326f5795..7826700c5 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 @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil import com.vitorpamplona.amethyst.service.relays.Client +@Immutable class LnZapEvent( id: HexKey, pubKey: HexKey, 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 934dba55b..853fcbab5 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 @@ -1,7 +1,9 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import java.math.BigDecimal +@Immutable interface LnZapEventInterface : EventInterface { fun zappedPost(): List 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 4946fb4c9..24ec91882 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializer import com.google.gson.JsonElement @@ -12,6 +13,7 @@ import nostr.postr.Utils import java.lang.reflect.Type import java.util.Date +@Immutable class LnZapPaymentRequestEvent( id: HexKey, pubKey: HexKey, 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 5d8ec2e95..ccebb3cce 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializer import com.google.gson.JsonElement @@ -10,6 +11,7 @@ import com.vitorpamplona.amethyst.model.HexKey import nostr.postr.Utils import java.lang.reflect.Type +@Immutable class LnZapPaymentResponseEvent( id: HexKey, pubKey: HexKey, 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 eeb2bb81a..87f00b1f1 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 @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.* import nostr.postr.Bech32 import nostr.postr.Utils @@ -11,6 +12,7 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +@Immutable class LnZapRequestEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt index 529c96fd3..43d1a7ab9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class LongTextNoteEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt index 7cec19bca..e21e257d0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.Gson import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.HexKey @@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable data class ContactMetaData( val name: String, val picture: String, 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 977488836..aed9a91fe 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.hexToByteArray @@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class MuteListEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt index 583f534b5..26b490be0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class PeopleListEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt index 100aaf981..1007c0bea 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class PinListEvent( id: HexKey, pubKey: HexKey, 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 0cb71f805..3a425ecd7 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 @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils @@ -11,6 +12,7 @@ const val VALUE_MINIMUM = "value_minimum" const val CONSENSUS_THRESHOLD = "consensus_threshold" const val CLOSED_AT = "closed_at" +@Immutable class PollNoteEvent( id: HexKey, pubKey: HexKey, 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 20e4bc696..9bb2385ec 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import fr.acinq.secp256k1.Hex @@ -8,6 +9,7 @@ import nostr.postr.Utils import nostr.postr.toHex import java.util.Date +@Immutable class PrivateDmEvent( id: HexKey, pubKey: HexKey, 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 91b34d4e7..3e44207e8 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 @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class ReactionEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt index 117931b99..93639bb10 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.net.URI import java.util.Date +@Immutable class RecommendRelayEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt index 6a7e04228..55522a04f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable class RelayAuthEvent( id: HexKey, pubKey: HexKey, 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 b05e55870..c41a9f578 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 @@ -1,13 +1,16 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils import java.util.Date +@Immutable data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType) // NIP 56 event. +@Immutable class ReportEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt index c00c67617..930e49cb7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt @@ -1,11 +1,13 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.relays.Client import nostr.postr.Utils import java.util.Date +@Immutable class RepostEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt index aa80ac016..169b121a3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.model +import com.google.errorprone.annotations.Immutable import com.linkedin.urls.detection.UrlDetector import com.linkedin.urls.detection.UrlDetectorOptions import com.vitorpamplona.amethyst.model.HexKey @@ -8,6 +9,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.findHashtags import nostr.postr.Utils import java.util.Date +@Immutable class TextNoteEvent( id: HexKey, pubKey: HexKey, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt index 3f374730c..df080b9f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.screen +import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User @@ -9,6 +10,7 @@ abstract class Card() { abstract fun id(): String } +@Immutable class BadgeCard(val note: Note) : Card() { override fun createdAt(): Long { return note.createdAt() ?: 0 @@ -17,6 +19,7 @@ class BadgeCard(val note: Note) : Card() { override fun id() = note.idHex } +@Immutable class NoteCard(val note: Note) : Card() { override fun createdAt(): Long { return note.createdAt() ?: 0 @@ -25,6 +28,7 @@ class NoteCard(val note: Note) : Card() { override fun id() = note.idHex } +@Immutable class LikeSetCard(val note: Note, val likeEvents: List) : Card() { val createdAt = likeEvents.maxOf { it.createdAt() ?: 0 } override fun createdAt(): Long { @@ -33,6 +37,7 @@ class LikeSetCard(val note: Note, val likeEvents: List) : Card() { override fun id() = note.idHex + "L" + createdAt } +@Immutable class ZapSetCard(val note: Note, val zapEvents: Map) : Card() { val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 } override fun createdAt(): Long { @@ -41,6 +46,7 @@ class ZapSetCard(val note: Note, val zapEvents: Map) : Card() { override fun id() = note.idHex + "Z" + createdAt } +@Immutable class ZapUserSetCard(val user: User, val zapEvents: Map) : Card() { val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 } override fun createdAt(): Long { @@ -49,6 +55,7 @@ class ZapUserSetCard(val user: User, val zapEvents: Map) : Card() { override fun id() = user.pubkeyHex + "U" + createdAt } +@Immutable class MultiSetCard(val note: Note, val boostEvents: List, val likeEvents: List, val zapEvents: Map) : Card() { val createdAt = maxOf( zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0, @@ -62,6 +69,7 @@ class MultiSetCard(val note: Note, val boostEvents: List, val likeEvents: override fun id() = note.idHex + "X" + createdAt } +@Immutable class BoostSetCard(val note: Note, val boostEvents: List) : Card() { val createdAt = boostEvents.maxOf { it.createdAt() ?: 0 } @@ -72,6 +80,7 @@ class BoostSetCard(val note: Note, val boostEvents: List) : Card() { override fun id() = note.idHex + "B" + createdAt } +@Immutable class MessageSetCard(val note: Note) : Card() { override fun createdAt(): Long { return note.createdAt() ?: 0 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 02f670801..e5faa3fdc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -124,6 +124,10 @@ private fun FeedLoaded( } } + val baseModifier = remember { + Modifier + } + LazyColumn( contentPadding = PaddingValues( top = 10.dp, @@ -134,8 +138,9 @@ private fun FeedLoaded( itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> NoteCompose( item, - isBoostedNote = false, routeForLastRead = routeForLastRead, + modifier = baseModifier, + isBoostedNote = false, accountViewModel = accountViewModel, navController = navController ) From 0015100dea7b97573a216e450b489c10c10e1aa0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 12:12:43 -0400 Subject: [PATCH 34/76] Fixing Immutable imports --- .../main/java/com/vitorpamplona/amethyst/service/model/ATag.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/BadgeAwardEvent.kt | 2 +- .../amethyst/service/model/BadgeDefinitionEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/BaseTextNoteEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/BookmarkListEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt | 2 +- .../amethyst/service/model/ChannelHideMessageEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt | 2 +- .../amethyst/service/model/ChannelMetadataEvent.kt | 2 +- .../amethyst/service/model/ChannelMuteUserEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/ContactListEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/DeletionEvent.kt | 2 +- .../main/java/com/vitorpamplona/amethyst/service/model/Event.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/EventInterface.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/FileStorageEvent.kt | 2 +- .../amethyst/service/model/FileStorageHeaderEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/GeneralListEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/HighlightEvent.kt | 2 +- .../java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/LnZapEventInterface.kt | 2 +- .../amethyst/service/model/LnZapPaymentRequestEvent.kt | 2 +- .../amethyst/service/model/LnZapPaymentResponseEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/MetadataEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/MuteListEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/PinListEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/ReactionEvent.kt | 2 +- .../vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/ReportEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/RepostEvent.kt | 2 +- .../com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) 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 3b23481f6..10749bc4c 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.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.hexToByteArray import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.nip19.Tlv diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt index 980dbe00d..e7c543dd2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/AudioTrackEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 71f374a39..082322b11 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey @Immutable 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 f31cd3a42..5a4d25c16 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey @Immutable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt index a81723fe9..3acb994f7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BadgeProfilesEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey @Immutable 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 84d173277..201622544 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,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.tagSearch import com.vitorpamplona.amethyst.service.nip19.Nip19 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt index 5209d4d9e..d11b079a1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/BookmarkListEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt index aecf4d697..8123144b7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelCreateEvent.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt index 896f8d462..b2ed4c4b4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelHideMessageEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt index 32635e1ea..471419f22 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt index b15365f91..b3db7553e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMetadataEvent.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt index c0b86ee3b..626dbf529 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMuteUserEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt index 56cb3212e..b10099073 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ContactListEvent.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.decodePublicKey diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt index 9b959e884..9c3613569 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/DeletionEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 2d633ee50..dcc0ad35d 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.* import com.google.gson.annotations.SerializedName import com.vitorpamplona.amethyst.model.HexKey diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt index 3528cfedd..de9275d7b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import java.math.BigDecimal diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt index f9832f24d..ca72864f9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileHeaderEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt index 13087fb35..13fc5d03d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageEvent.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt index b27d3bee5..c84de54ad 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/FileStorageHeaderEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 859e1381b..2594c74f5 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.hexToByteArray diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt index 427cf7ddb..6bb49cb08 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/HighlightEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 7826700c5..59e61c309 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil import com.vitorpamplona.amethyst.service.relays.Client 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 853fcbab5..13921deee 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import java.math.BigDecimal @Immutable 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 24ec91882..9975ae814 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializer import com.google.gson.JsonElement 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 ccebb3cce..4e4c41a90 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializer import com.google.gson.JsonElement 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 87f00b1f1..aa60dcb6f 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.* import nostr.postr.Bech32 import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt index 43d1a7ab9..f2fe3de38 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt index e21e257d0..766d1e79d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/MetadataEvent.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.Gson import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.HexKey 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 aed9a91fe..afd65cfb7 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.hexToByteArray diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt index 26b490be0..51d9fa305 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PeopleListEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt index 1007c0bea..e5f6e69a2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PinListEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 3a425ecd7..10f154193 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 9bb2385ec..435e3b302 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 @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.service.model import android.util.Log -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import fr.acinq.secp256k1.Hex 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 3e44207e8..e2a23ecb7 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt index 93639bb10..8cdc833dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RecommendRelayEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt index 55522a04f..388daefdc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RelayAuthEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils 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 c41a9f578..cdb075f3a 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 @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Utils diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt index 930e49cb7..6b2053afd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.toHexKey import com.vitorpamplona.amethyst.service.relays.Client diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt index 169b121a3..8f7b5bb56 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.service.model -import com.google.errorprone.annotations.Immutable +import androidx.compose.runtime.Immutable import com.linkedin.urls.detection.UrlDetector import com.linkedin.urls.detection.UrlDetectorOptions import com.vitorpamplona.amethyst.model.HexKey From dcbd4148e2a4cd4f5c27bd11600942c763b0a9aa Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 12:14:47 -0400 Subject: [PATCH 35/76] v0.50.6 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4e859781f..8ba1e9907 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 173 - versionName "0.50.5" + versionCode 174 + versionName "0.50.6" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 5a375b94d6be096da9459bcb6bb2b55bd0551e34 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 15:14:00 -0400 Subject: [PATCH 36/76] Adds Zap amounts to Notification bubbles. --- .../amethyst/ui/note/MultiSetCompose.kt | 65 ++++++++++++++----- .../amethyst/ui/note/UserReactionsRow.kt | 3 +- 2 files changed, 49 insertions(+), 19 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 d45180015..49bd4cffc 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 @@ -31,8 +31,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache @@ -42,13 +44,16 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState +import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.math.BigDecimal @OptIn(ExperimentalFoundationApi::class) @Composable @@ -253,14 +258,10 @@ fun AuthorGalleryZaps( ) { val accountState = account.userProfile().live().follows.observeAsState() - val listToRender = remember { - authorNotes.keys.take(50) - } - Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { - listToRender.forEach { - AuthorPictureAndComment(it, navController, accountState, accountViewModel) + authorNotes.forEach { + AuthorPictureAndComment(it.key, it.value, navController, accountState, accountViewModel) } } } @@ -269,38 +270,48 @@ fun AuthorGalleryZaps( @Composable private fun AuthorPictureAndComment( zapRequest: Note, + zapEvent: Note?, navController: NavController, accountUser: State, accountViewModel: AccountViewModel ) { val author = zapRequest.author ?: return - var content by remember { mutableStateOf>(Pair(author, null)) } + var content by remember { mutableStateOf>(Triple(author, null, null)) } val scope = rememberCoroutineScope() - LaunchedEffect(key1 = zapRequest.idHex) { + LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) { scope.launch(Dispatchers.IO) { (zapRequest.event as? LnZapRequestEvent)?.let { val decryptedContent = accountViewModel.decryptZap(zapRequest) + val amount = (zapEvent?.event as? LnZapEvent)?.amount if (decryptedContent != null) { val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) - content = Pair(newAuthor, decryptedContent.content) + content = Triple(newAuthor, decryptedContent.content, amount) } else { - if (!zapRequest.event?.content().isNullOrBlank()) { - content = Pair(author, zapRequest.event?.content()) + if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { + content = Triple(author, zapRequest.event?.content(), amount) } } } } } - AuthorPictureAndComment(content.first, content.second, navController, accountUser, accountViewModel) + AuthorPictureAndComment( + author = content.first, + comment = content.second, + amount = showAmountAxis(content.third), + navController = navController, + accountUser = accountUser, + accountViewModel = accountViewModel + ) } @Composable private fun AuthorPictureAndComment( author: User, comment: String?, + amount: String?, navController: NavController, accountUser: State, accountViewModel: AccountViewModel @@ -323,11 +334,29 @@ private fun AuthorPictureAndComment( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { - FastNoteAuthorPicture( - author = author, - userAccount = accountUser, - size = 35.dp - ) + Box(modifier = remember { Modifier.size(35.dp) }, contentAlignment = Alignment.BottomCenter) { + FastNoteAuthorPicture( + author = author, + userAccount = accountUser, + size = 35.dp + ) + + amount?.let { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colors.background.copy(0.52f)), + contentAlignment = Alignment.BottomCenter + ) { + Text( + text = it, + fontWeight = FontWeight.Bold, + color = BitcoinOrange, + fontSize = 12.sp + ) + } + } + } if (!comment.isNullOrBlank()) { Spacer(modifier = Modifier.width(5.dp)) @@ -362,7 +391,7 @@ fun AuthorGallery( Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { listToRender.first.forEach { author -> - AuthorPictureAndComment(author, null, navController, accountState, accountViewModel) + AuthorPictureAndComment(author, null, null, navController, accountState, accountViewModel) } if (listToRender.second > 50) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 93ca64ab4..b6d243aa5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -345,7 +346,7 @@ fun UserZapReaction( Spacer(modifier = Modifier.width(8.dp)) Text( - showAmount(amount), + showAmountAxis(amount), fontWeight = FontWeight.Bold, fontSize = 18.sp ) From f0ba68e4677890617dd22ec72ec841f7c5babfaa Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 23 May 2023 16:04:29 -0400 Subject: [PATCH 37/76] Fixing missing some notifications. --- .../amethyst/ui/screen/CardFeedViewModel.kt | 9 +++++++-- .../amethyst/ui/screen/loggedIn/NotificationScreen.kt | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) 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 cdcac191e..40ed18d80 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 @@ -48,7 +48,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { @Synchronized private fun refreshSuspended() { - val notes = localFilter.loadTop() + val notes = localFilter.feed() val thisAccount = (localFilter as? NotificationFeedFilter)?.account val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null @@ -59,7 +59,12 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { if (newCards.isNotEmpty()) { lastNotes = notes.toSet() lastAccount = (localFilter as? NotificationFeedFilter)?.account - updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed()) + val singleList = (oldNotesState.feed.value + newCards) + .distinctBy { it.id() } + .sortedWith(compareBy({ it.createdAt() }, { it.id() })) + .reversed() + .take(1000) + updateFeed(singleList) } } else { val cards = convertToCard(notes) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 298955136..79843b121 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -75,7 +75,7 @@ fun NotificationScreen( NostrAccountDataSource.resetFilters() NotificationFeedFilter.account = account notifFeedViewModel.clear() - notifFeedViewModel.refresh() + notifFeedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current From 75d56214d2cdfb5cbc4af0d0c5b84da0ffbafec8 Mon Sep 17 00:00:00 2001 From: KotlinGeek Date: Tue, 23 May 2023 23:58:59 +0100 Subject: [PATCH 38/76] Use manifest to avoid Activity recreation. --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7ba5daf74..a386157e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ android:name=".ui.MainActivity" android:exported="true" android:windowSoftInputMode="adjustResize" + android:configChanges="orientation|screenSize|screenLayout" android:theme="@style/Theme.Amethyst"> From 4841d4004e83f9485295589725a5a29dfa800ec5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 May 2023 10:38:25 -0400 Subject: [PATCH 39/76] Fixes https://github.com/vitorpamplona/amethyst/issues/425 --- app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 9a20166c6..f758617e8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -20,6 +20,7 @@ import coil.decode.SvgDecoder import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.VideoCache +import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent @@ -60,6 +61,7 @@ class MainActivity : FragmentActivity() { } add(SvgDecoder.Factory()) } // .logger(DebugLogger()) + .okHttpClient { HttpClient.getHttpClient() } .respectCacheHeaders(false) .build() } From f1ecaf1ee5f1b60e1d137fc11920c0e75fd622d8 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 May 2023 12:34:09 -0400 Subject: [PATCH 40/76] - Adds Immutable Lists to avoid recompositions - Moves NavController to a lambda to avoid recompositions - Moves AccountViewModel builder to a ViewModel Factory to avoid recompositions - Reduces notifications invalidation requests when starting the screen. --- app/build.gradle | 4 +- .../components/TranslatableRichTextViewer.kt | 5 +- .../amethyst/NotificationCache.kt | 1 + .../vitorpamplona/amethyst/model/Account.kt | 4 + .../com/vitorpamplona/amethyst/model/Note.kt | 2 + .../com/vitorpamplona/amethyst/model/User.kt | 2 + .../amethyst/service/relays/Relay.kt | 1 + .../ui/actions/JoinUserOrChannelView.kt | 17 +- .../amethyst/ui/actions/NewMediaView.kt | 3 +- .../amethyst/ui/actions/NewPostView.kt | 5 +- .../amethyst/ui/buttons/ChannelFabColumn.kt | 5 +- .../amethyst/ui/buttons/NewNoteButton.kt | 5 +- .../amethyst/ui/components/BundledUpdate.kt | 6 +- .../ui/components/ClickableNoteTag.kt | 5 +- .../amethyst/ui/components/ClickableRoute.kt | 49 ++- .../ui/components/ClickableUserTag.kt | 5 +- .../ui/components/ExpandableRichTextViewer.kt | 5 +- .../amethyst/ui/components/RichTextViewer.kt | 290 +++++++++--------- .../ui/components/RobohashAsyncImage.kt | 1 + .../amethyst/ui/navigation/AppBottomBar.kt | 17 +- .../amethyst/ui/navigation/AppNavigation.kt | 34 +- .../amethyst/ui/navigation/AppTopBar.kt | 2 +- .../amethyst/ui/navigation/DrawerContent.kt | 36 +-- .../amethyst/ui/navigation/Routes.kt | 47 +++ .../amethyst/ui/note/BadgeCompose.kt | 7 +- .../amethyst/ui/note/BlankNote.kt | 5 +- .../amethyst/ui/note/BoostSetCompose.kt | 9 +- .../amethyst/ui/note/ChatroomCompose.kt | 7 +- .../ui/note/ChatroomMessageCompose.kt | 49 ++- .../amethyst/ui/note/LikeSetCompose.kt | 9 +- .../amethyst/ui/note/MessageSetCompose.kt | 7 +- .../amethyst/ui/note/MultiSetCompose.kt | 182 ++++++----- .../amethyst/ui/note/NoteCompose.kt | 189 ++++++------ .../amethyst/ui/note/PollNote.kt | 15 +- .../amethyst/ui/note/ReactionsRow.kt | 7 +- .../amethyst/ui/note/ReplyInformation.kt | 17 +- .../amethyst/ui/note/UserCompose.kt | 7 +- .../amethyst/ui/note/UserReactionsRow.kt | 3 +- .../amethyst/ui/note/ZapNoteCompose.kt | 7 +- .../amethyst/ui/note/ZapSetCompose.kt | 9 +- .../amethyst/ui/note/ZapUserSetCompose.kt | 9 +- .../amethyst/ui/screen/AccountScreen.kt | 15 +- .../amethyst/ui/screen/CardFeedState.kt | 9 +- .../amethyst/ui/screen/CardFeedView.kt | 122 ++++---- .../amethyst/ui/screen/CardFeedViewModel.kt | 50 ++- .../amethyst/ui/screen/ChatroomFeedView.kt | 5 +- .../ui/screen/ChatroomListFeedView.kt | 9 +- .../amethyst/ui/screen/FeedView.kt | 9 +- .../amethyst/ui/screen/LnZapFeedView.kt | 9 +- .../amethyst/ui/screen/ThreadFeedView.kt | 35 +-- .../amethyst/ui/screen/UserFeedView.kt | 9 +- .../ui/screen/loggedIn/AccountViewModel.kt | 9 + .../ui/screen/loggedIn/BookmarkListScreen.kt | 7 +- .../ui/screen/loggedIn/ChannelScreen.kt | 23 +- .../ui/screen/loggedIn/ChatroomListScreen.kt | 5 +- .../ui/screen/loggedIn/ChatroomScreen.kt | 13 +- .../ui/screen/loggedIn/HashtagScreen.kt | 5 +- .../ui/screen/loggedIn/HiddenUsersScreen.kt | 5 +- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 5 +- .../amethyst/ui/screen/loggedIn/MainScreen.kt | 24 +- .../ui/screen/loggedIn/NotificationScreen.kt | 15 +- .../ui/screen/loggedIn/ProfileScreen.kt | 77 +++-- .../ui/screen/loggedIn/SearchScreen.kt | 17 +- .../ui/screen/loggedIn/ThreadScreen.kt | 5 +- .../ui/screen/loggedIn/VideoScreen.kt | 39 +-- .../components/TranslatableRichTextViewer.kt | 5 +- 66 files changed, 847 insertions(+), 768 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8ba1e9907..7b4be8263 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -82,7 +82,6 @@ android { lint { disable 'MissingTranslation' } - } dependencies { @@ -183,6 +182,9 @@ dependencies { implementation "com.patrykandpatrick.vico:views:${vico_version}" implementation "com.patrykandpatrick.vico:compose-m2:${vico_version}" + // immutable collections to avoid recomposition + implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5") + // Automatic memory leak detection debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11' diff --git a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index 5f4659d44..a8cb1e8d9 100644 --- a/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/app/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.navigation.NavController import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable @@ -14,7 +13,7 @@ fun TranslatableRichTextViewer( tags: List>?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) = ExpandableRichTextViewer( content, canPreview, @@ -22,5 +21,5 @@ fun TranslatableRichTextViewer( tags, backgroundColor, accountViewModel, - navController + nav ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt index 2bb193fba..40e815cfe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch object NotificationCache { + // TODO: This must be account-based val lastReadByRoute = mutableMapOf() fun markAsRead(route: String, timestampInSecs: Long) { 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 89c2e96a8..0bc3449ee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.model import android.content.res.Resources import android.util.Log +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import androidx.core.os.ConfigurationCompat import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.FileHeader @@ -43,6 +45,7 @@ val GLOBAL_FOLLOWS = " Global " val KIND3_FOLLOWS = " All Follows " @OptIn(DelicateCoroutinesApi::class) +@Stable class Account( val loggedIn: Persona, var followingChannels: Set = DefaultChannels, @@ -1166,4 +1169,5 @@ class AccountLiveData(private val account: Account) : LiveData(Acc } } +@Immutable class AccountState(val account: Account) 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 b012aabc2..a850c01b6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource @@ -442,4 +443,5 @@ class NoteLiveData(val note: Note) : LiveData(NoteState(note)) { } } +@Immutable class NoteState(val note: 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 afc446004..3992607da 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource @@ -420,4 +421,5 @@ class UserLiveData(val user: User) : LiveData(UserState(user)) { } } +@Immutable class UserState(val user: User) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index b4e0a9341..41fe2823b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -30,6 +30,7 @@ class Relay( ) { val seconds = if (proxy != null) 20L else 10L val duration = Duration.ofSeconds(seconds) + private val httpClient = OkHttpClient.Builder() .proxy(proxy) .readTimeout(duration) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index eda028bf0..08ff4090e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -51,7 +51,6 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -73,7 +72,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable -fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController: NavController) { +fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, nav: (String) -> Unit) { val searchBarViewModel: SearchBarViewModel = viewModel() searchBarViewModel.account = account @@ -116,7 +115,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController: Spacer(modifier = Modifier.height(15.dp)) - RenderSeach(searchBarViewModel, account, navController) + RenderSeach(searchBarViewModel, account, nav) } } } @@ -127,7 +126,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, navController: private fun RenderSeach( searchBarViewModel: SearchBarViewModel, account: Account, - navController: NavController + nav: (String) -> Unit ) { val scope = rememberCoroutineScope() val listState = rememberLazyListState() @@ -275,7 +274,7 @@ private fun RenderSeach( UserComposeForChat( item, account = account, - navController = navController + nav = nav ) } @@ -295,7 +294,7 @@ private fun RenderSeach( channelLastTime = null, channelLastContent = item.info.about, false, - onClick = { navController.navigate("Channel/${item.idHex}") } + onClick = { nav("Channel/${item.idHex}") } ) } } @@ -307,12 +306,12 @@ private fun RenderSeach( fun UserComposeForChat( baseUser: User, account: Account, - navController: NavController + nav: (String) -> Unit ) { Column( modifier = Modifier.clickable( - onClick = { navController.navigate("Room/${baseUser.pubkeyHex}") } + onClick = { nav("Room/${baseUser.pubkeyHex}") } ) ) { Row( @@ -324,7 +323,7 @@ fun UserComposeForChat( ), verticalAlignment = Alignment.CenterVertically ) { - UserPicture(baseUser, navController, account.userProfile(), 55.dp) + UserPicture(baseUser, nav, account.userProfile(), 55.dp) Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { 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 cd24d89d5..efd74fb55 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 @@ -28,7 +28,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.navigation.NavController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account @@ -39,7 +38,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable -fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, accountViewModel: AccountViewModel, navController: NavController) { +fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val account = accountViewModel.accountLiveData.value?.account ?: return val resolver = LocalContext.current.contentResolver val context = LocalContext.current 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 b96e0c61c..ea205a851 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 @@ -57,7 +57,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account @@ -75,7 +74,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalComposeUiApi::class) @Composable -fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account, accountViewModel: AccountViewModel, navController: NavController) { +fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val postViewModel: NewPostViewModel = viewModel() val context = LocalContext.current @@ -292,7 +291,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n true, MaterialTheme.colors.background, accountViewModel, - navController + nav ) } else if (noProtocolUrlValidator.matcher(myUrlPreview).matches()) { UrlPreview("https://$myUrlPreview", myUrlPreview) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt index 7a4d3dad8..84e292087 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/ChannelFabColumn.kt @@ -15,14 +15,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.ui.actions.JoinUserOrChannelView import com.vitorpamplona.amethyst.ui.actions.NewChannelView @Composable -fun ChannelFabColumn(account: Account, navController: NavController) { +fun ChannelFabColumn(account: Account, nav: (String) -> Unit) { var isOpen by remember { mutableStateOf(false) } @@ -40,7 +39,7 @@ fun ChannelFabColumn(account: Account, navController: NavController) { } if (wantsToJoinChannelOrUser) { - JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, account = account, navController = navController) + JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, account = account, nav = nav) } Column() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt index 0fe83a465..d03c1d01c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewNoteButton.kt @@ -16,20 +16,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun NewNoteButton(account: Account, accountViewModel: AccountViewModel, navController: NavController) { +fun NewNoteButton(account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var wantsToPost by remember { mutableStateOf(false) } if (wantsToPost) { - NewPostView({ wantsToPost = false }, account = account, accountViewModel = accountViewModel, navController = navController) + NewPostView({ wantsToPost = false }, account = account, accountViewModel = accountViewModel, nav = nav) } OutlinedButton( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt index 2a1036fb9..9b5f60f9f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt @@ -22,9 +22,11 @@ class BundledUpdate( private var onlyOneInBlock = AtomicBoolean() private var invalidatesAgain = false - fun invalidate() { + fun invalidate(ignoreIfDoing: Boolean = false) { if (onlyOneInBlock.getAndSet(true)) { - invalidatesAgain = true + if (!ignoreIfDoing) { + invalidatesAgain = true + } return } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt index 8962fc219..cd48515fa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableNoteTag.kt @@ -5,18 +5,17 @@ import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.text.AnnotatedString -import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.toShortenHex @Composable fun ClickableNoteTag( baseNote: Note, - navController: NavController + nav: (String) -> Unit ) { ClickableText( text = AnnotatedString("@${baseNote.idNote().toShortenHex()}"), - onClick = { navController.navigate("Note/${baseNote.idHex}") }, + onClick = { nav("Note/${baseNote.idHex}") }, style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) ) } 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 acbc4b65f..32bb3c997 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 @@ -37,7 +37,6 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -52,16 +51,16 @@ import kotlinx.coroutines.withContext @Composable fun ClickableRoute( nip19: Nip19.Return, - navController: NavController + nav: (String) -> Unit ) { if (nip19.type == Nip19.Type.USER) { - DisplayUser(nip19, navController) + DisplayUser(nip19, nav) } else if (nip19.type == Nip19.Type.ADDRESS) { - DisplayAddress(nip19, navController) + DisplayAddress(nip19, nav) } else if (nip19.type == Nip19.Type.NOTE) { - DisplayNote(nip19, navController) + DisplayNote(nip19, nav) } else if (nip19.type == Nip19.Type.EVENT) { - DisplayEvent(nip19, navController) + DisplayEvent(nip19, nav) } else { Text( "@${nip19.hex}${nip19.additionalChars} " @@ -72,7 +71,7 @@ fun ClickableRoute( @Composable private fun DisplayEvent( nip19: Nip19.Return, - navController: NavController + nav: (String) -> Unit ) { var noteBase by remember { mutableStateOf(null) } @@ -92,28 +91,28 @@ private fun DisplayEvent( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Channel/${nip19.hex}", - navController = navController + nav = nav ) } else if (note.event is PrivateDmEvent) { CreateClickableText( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Room/${note.author?.pubkeyHex}", - navController = navController + nav = nav ) } else if (channel != null) { CreateClickableText( clickablePart = channel.toBestDisplayName(), suffix = "${nip19.additionalChars} ", route = "Channel/${channel.idHex}", - navController = navController + nav = nav ) } else { CreateClickableText( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Event/${nip19.hex}", - navController = navController + nav = nav ) } } @@ -128,7 +127,7 @@ private fun DisplayEvent( @Composable private fun DisplayNote( nip19: Nip19.Return, - navController: NavController + nav: (String) -> Unit ) { var noteBase by remember { mutableStateOf(null) } @@ -148,28 +147,28 @@ private fun DisplayNote( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Channel/${nip19.hex}", - navController = navController + nav = nav ) } else if (note.event is PrivateDmEvent) { CreateClickableText( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Room/${note.author?.pubkeyHex}", - navController = navController + nav = nav ) } else if (channel != null) { CreateClickableText( clickablePart = channel.toBestDisplayName(), suffix = "${nip19.additionalChars} ", route = "Channel/${note.channel()?.idHex}", - navController = navController + nav = nav ) } else { CreateClickableText( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Note/${nip19.hex}", - navController = navController + nav = nav ) } } @@ -184,7 +183,7 @@ private fun DisplayNote( @Composable private fun DisplayAddress( nip19: Nip19.Return, - navController: NavController + nav: (String) -> Unit ) { var noteBase by remember { mutableStateOf(null) } @@ -202,7 +201,7 @@ private fun DisplayAddress( clickablePart = "@${note.idDisplayNote()}", suffix = "${nip19.additionalChars} ", route = "Note/${nip19.hex}", - navController = navController + nav = nav ) } @@ -216,7 +215,7 @@ private fun DisplayAddress( @Composable private fun DisplayUser( nip19: Nip19.Return, - navController: NavController + nav: (String) -> Unit ) { var userBase by remember { mutableStateOf(null) } @@ -238,7 +237,7 @@ private fun DisplayUser( suffix = "${nip19.additionalChars} ", tags = userTags, route = route, - navController = navController + nav = nav ) } } @@ -257,7 +256,7 @@ fun CreateClickableText( overrideColor: Color? = null, fontWeight: FontWeight = FontWeight.Normal, route: String, - navController: NavController + nav: (String) -> Unit ) { ClickableText( text = buildAnnotatedString { @@ -272,7 +271,7 @@ fun CreateClickableText( append(suffix) } }, - onClick = { navController.navigate(route) } + onClick = { nav(route) } ) } @@ -378,21 +377,21 @@ fun CreateClickableTextWithEmoji( overrideColor: Color? = null, fontWeight: FontWeight = FontWeight.Normal, route: String, - navController: NavController + nav: (String) -> Unit ) { val emojis = remember(tags) { tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap() } if (emojis.isEmpty()) { - CreateClickableText(clickablePart, suffix, overrideColor, fontWeight, route, navController) + CreateClickableText(clickablePart, suffix, overrideColor, fontWeight, route, nav) } else { val myList = remember { assembleAnnotatedList(clickablePart, emojis) } ClickableInLineIconRenderer(myList, LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()) { - navController.navigate(route) + nav(route) } val myList2 = remember { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt index c56c654cc..8b8ea0ff4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt @@ -7,18 +7,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.text.AnnotatedString -import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.User @Composable fun ClickableUserTag( user: User, - navController: NavController + nav: (String) -> Unit ) { val innerUserState by user.live().metadata.observeAsState() ClickableText( text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()}"), - onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") }, + onClick = { nav("User/${innerUserState?.user?.pubkeyHex}") }, style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) ) } 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 8d622093f..b0f948fb3 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -39,7 +38,7 @@ fun ExpandableRichTextViewer( tags: List>?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { var showFullText by remember { mutableStateOf(false) } @@ -69,7 +68,7 @@ fun ExpandableRichTextViewer( tags, backgroundColor, accountViewModel, - navController + nav ) if (content.length > whereToCut && !showFullText) { 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 d745aeecf..552c1fd0b 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 @@ -26,7 +26,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.MarkdownParseOptions @@ -94,19 +93,20 @@ fun RichTextViewer( tags: List>?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val isMarkdown = remember { isMarkdown(content) } Column(modifier = modifier) { if (isMarkdown) { - RenderContentAsMarkdown(content, backgroundColor, tags, navController) + RenderContentAsMarkdown(content, backgroundColor, tags, nav) } else { - RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController) + RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, nav) } } } +@Stable class RichTextViewerState( val content: String, val urlSet: Set, @@ -122,10 +122,10 @@ private fun RenderRegular( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { - var processedState by remember { - mutableStateOf(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap())) + var state by remember { + mutableStateOf(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap())) } val scope = rememberCoroutineScope() @@ -149,80 +149,73 @@ private fun RenderRegular( val emojiMap = tags?.filter { it.size > 2 && it[0] == "emoji" }?.associate { ":${it[1]}:" to it[2] } ?: emptyMap() if (urlSet.isNotEmpty() || emojiMap.isNotEmpty()) { - processedState = RichTextViewerState(content, urlSet, imagesForPager, imageList, emojiMap) + state = RichTextViewerState(content, urlSet, imagesForPager, imageList, emojiMap) } } } // 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 (state.customEmoji.any { word.contains(it.key) }) { - RenderCustomEmoji(word, state.customEmoji) - } 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( + 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 (state.customEmoji.any { word.contains(it.key) }) { + RenderCustomEmoji(word, state.customEmoji) + } 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, + nav + ) + } else if (word.startsWith("#")) { + if (tagIndex.matcher(word).matches() && tags != null) { + TagLink( word, + tags, canPreview, backgroundColor, accountViewModel, - navController + nav ) - } 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, nav) + } 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 ", @@ -230,71 +223,76 @@ private fun RenderRegular( ) } } 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 (state.customEmoji.any { word.contains(it.key) }) { - RenderCustomEmoji(word, state.customEmoji) - } 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 (state.customEmoji.any { word.contains(it.key) }) { + RenderCustomEmoji(word, state.customEmoji) + } 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, + nav + ) + } else if (word.startsWith("#")) { + if (tagIndex.matcher(word).matches() && tags != null) { + TagLink( + word, + tags, + canPreview, + backgroundColor, + accountViewModel, + nav + ) + } else if (hashTagsPattern.matcher(word).matches()) { + HashTag(word, nav) + } 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) + ) } } } @@ -311,7 +309,7 @@ fun RenderCustomEmoji(word: String, customEmoji: Map) { } @Composable -private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List>?, navController: NavController) { +private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List>?, nav: (String) -> Unit) { val myMarkDownStyle = richTextDefaults.copy( codeBlockStyle = richTextDefaults.codeBlockStyle?.copy( textStyle = TextStyle( @@ -416,7 +414,7 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag ) { link -> val route = uriToRoute(link) if (route != null) { - navController.navigate(route) + nav(route) } else { runCatching { uri.openUri(link) } } @@ -572,12 +570,14 @@ fun isBechLink(word: String): Boolean { } @Composable -fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, navController: NavController) { +fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var nip19Route by remember { mutableStateOf(null) } var baseNotePair by remember { mutableStateOf?>(null) } + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = word) { - withContext(Dispatchers.IO) { + scope.launch(Dispatchers.IO) { Nip19.uriToRoute(word)?.let { if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) { LocalCache.checkGetOrCreateNote(it.hex)?.let { note -> @@ -606,7 +606,7 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV ), parentBackgroundColor = backgroundColor, isQuotedNote = true, - navController = navController + nav = nav ) if (!it.second.isNullOrEmpty()) { Text( @@ -614,21 +614,23 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV ) } } ?: nip19Route?.let { - ClickableRoute(it, navController) + ClickableRoute(it, nav) } ?: Text(text = "$word ") } else { nip19Route?.let { - ClickableRoute(it, navController) + ClickableRoute(it, nav) } ?: Text(text = "$word ") } } @Composable -fun HashTag(word: String, navController: NavController) { +fun HashTag(word: String, nav: (String) -> Unit) { var tagSuffixPair by remember { mutableStateOf?>(null) } + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = word) { - withContext(Dispatchers.IO) { + scope.launch(Dispatchers.IO) { val hashtagMatcher = hashTagsPattern.matcher(word) val (myTag, mySuffix) = try { @@ -646,7 +648,7 @@ fun HashTag(word: String, navController: NavController) { } tagSuffixPair?.let { tagPair -> - val hashtagIcon = checkForHashtagWithIcon(tagPair.first) + val hashtagIcon = remember(tagPair.first) { checkForHashtagWithIcon(tagPair.first) } ClickableText( text = buildAnnotatedString { withStyle( @@ -655,7 +657,7 @@ fun HashTag(word: String, navController: NavController) { append("#${tagPair.first}") } }, - onClick = { navController.navigate("Hashtag/${tagPair.first}") } + onClick = { nav("Hashtag/${tagPair.first}") } ) if (hashtagIcon != null) { @@ -701,7 +703,7 @@ fun HashTag(word: String, navController: NavController) { } @Composable -fun TagLink(word: String, tags: List>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, navController: NavController) { +fun TagLink(word: String, tags: List>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var baseUserPair by remember { mutableStateOf?>(null) } var baseNotePair by remember { mutableStateOf?>(null) } @@ -753,7 +755,7 @@ fun TagLink(word: String, tags: List>, canPreview: Boolean, backgro suffix = "${it.second} ", tags = userTags, route = route, - navController = navController + nav = nav ) } @@ -773,13 +775,13 @@ fun TagLink(word: String, tags: List>, canPreview: Boolean, backgro ), parentBackgroundColor = backgroundColor, isQuotedNote = true, - navController = navController + nav = nav ) it.second?.ifBlank { null }?.let { Text(text = "$it ") } } else { - ClickableNoteTag(it.first, navController) + ClickableNoteTag(it.first, nav) Text(text = "${it.second} ") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index c387bfa5d..272f77c1f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -132,6 +132,7 @@ fun RobohashAsyncImageProxy( filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { val proxy = remember(model) { model.proxyUrl() } + if (proxy == null) { RobohashAsyncImage( robot = robot, 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 f6c9c8823..8dd993fb0 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 @@ -37,7 +37,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import com.vitorpamplona.amethyst.NotificationCache @@ -116,24 +115,26 @@ private fun RowScope.HasNewItemsIcon( val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return - val notifState = NotificationCache.live.observeAsState() - val notif = remember(notifState) { notifState.value } ?: return + val notifState by NotificationCache.live.observeAsState() + val notif = remember(notifState) { notifState?.cache } ?: return var hasNewItems by remember { mutableStateOf(false) } - LaunchedEffect(key1 = notif) { + LaunchedEffect(key1 = notifState, key2 = accountState) { scope.launch(Dispatchers.IO) { - val newHasNewItems = route.hasNewItems(account, notif.cache, emptySet()) + val newHasNewItems = route.hasNewItems(account, notif, emptySet()) + println("Notification Change ${route.route} $hasNewItems -> $newHasNewItems") if (newHasNewItems != hasNewItems) { hasNewItems = newHasNewItems } } } - LaunchedEffect(Unit) { + LaunchedEffect(accountState) { scope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { - val newHasNewItems = route.hasNewItems(account, notif.cache, it) + val newHasNewItems = route.hasNewItems(account, notif, it) + println("Notification From Base ${route.route} $hasNewItems -> $newHasNewItems") if (newHasNewItems != hasNewItems) { hasNewItems = newHasNewItems } @@ -182,7 +183,7 @@ private fun RowScope.BottomIcon( iconSize: Dp, base: String, hasNewItems: Boolean, - navController: NavController, + navController: NavHostController, onClick: (Boolean) -> Unit ) { val navBackStackEntry by navController.currentBackStackEntryAsState() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index d1005265b..492f05648 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -82,6 +82,14 @@ fun AppNavigation( } } + val nav = remember { + { route: String -> + if (getRouteWithArguments(navController) != route) { + navController.navigate(route) + } + } + } + NavHost(navController, startDestination = Route.Home.route) { Route.Video.let { route -> composable(route.route, route.arguments, content = { @@ -90,7 +98,7 @@ fun AppNavigation( VideoScreen( videoFeedView = videoFeedViewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, scrollToTop = scrollToTop ) @@ -108,7 +116,7 @@ fun AppNavigation( SearchScreen( searchFeedViewModel = searchFeedViewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, scrollToTop = scrollToTop ) @@ -128,7 +136,7 @@ fun AppNavigation( homeFeedViewModel = homeFeedViewModel, repliesFeedViewModel = repliesFeedViewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, pagerState = homePagerState, scrollToTop = scrollToTop, nip47 = nip47 @@ -152,7 +160,7 @@ fun AppNavigation( notifFeedViewModel = notifFeedViewModel, userReactionsStatsModel = userReactionsStatsModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, scrollToTop = scrollToTop ) @@ -163,16 +171,16 @@ fun AppNavigation( }) } - composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) - composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) }) - composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) + composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, nav) }) + composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, nav) }) + composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, nav) }) Route.Profile.let { route -> composable(route.route, route.arguments, content = { ProfileScreen( userId = it.arguments?.getString("id"), accountViewModel = accountViewModel, - navController = navController + nav = nav ) }) } @@ -182,7 +190,7 @@ fun AppNavigation( ThreadScreen( noteId = it.arguments?.getString("id"), accountViewModel = accountViewModel, - navController = navController + nav = nav ) }) } @@ -192,7 +200,7 @@ fun AppNavigation( HashtagScreen( tag = it.arguments?.getString("id"), accountViewModel = accountViewModel, - navController = navController + nav = nav ) }) } @@ -202,7 +210,7 @@ fun AppNavigation( ChatroomScreen( userId = it.arguments?.getString("id"), accountViewModel = accountViewModel, - navController = navController + nav = nav ) }) } @@ -212,7 +220,7 @@ fun AppNavigation( ChannelScreen( channelId = it.arguments?.getString("id"), accountViewModel = accountViewModel, - navController = navController + nav = nav ) }) } @@ -230,7 +238,7 @@ fun AppNavigation( actionableNextPage?.let { LaunchedEffect(it) { - navController.navigate(it) + nav(it) } actionableNextPage = null } 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 cecad69ec..2c98f68e1 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 @@ -83,7 +83,7 @@ import kotlinx.coroutines.withContext @Composable fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { when (currentRoute(navController)?.substringBefore("?")) { - // Route.Profile.route -> TopBarWithBackButton(navController) + // Route.Profile.route -> TopBarWithBackButton(nav) Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel) Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel) Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index d5f483769..ac088468b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -48,8 +48,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import androidx.navigation.NavHostController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.LocalPreferences @@ -69,7 +67,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalMaterialApi::class) @Composable fun DrawerContent( - navController: NavHostController, + nav: (String) -> Unit, scaffoldState: ScaffoldState, sheetState: ModalBottomSheetState, accountViewModel: AccountViewModel @@ -89,7 +87,7 @@ fun DrawerContent( .padding(horizontal = 25.dp) .padding(top = 100.dp), scaffoldState, - navController + nav ) Divider( thickness = 0.25.dp, @@ -97,7 +95,7 @@ fun DrawerContent( ) ListContent( account.userProfile().pubkeyHex, - navController, + nav, scaffoldState, sheetState, modifier = Modifier @@ -106,7 +104,7 @@ fun DrawerContent( account ) - BottomContent(account.userProfile(), scaffoldState, navController) + BottomContent(account.userProfile(), scaffoldState, nav) } } } @@ -116,7 +114,7 @@ fun ProfileContent( baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, - navController: NavController + nav: (String) -> Unit ) { val coroutineScope = rememberCoroutineScope() @@ -170,7 +168,7 @@ fun ProfileContent( .border(3.dp, MaterialTheme.colors.background, CircleShape) .background(MaterialTheme.colors.background) .clickable(onClick = { - navController.navigate(route) + nav(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -185,7 +183,7 @@ fun ProfileContent( modifier = Modifier .padding(top = 7.dp) .clickable(onClick = { - navController.navigate(route) + nav(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -203,7 +201,7 @@ fun ProfileContent( .padding(top = 15.dp) .clickable( onClick = { - navController.navigate(route) + nav(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -215,7 +213,7 @@ fun ProfileContent( modifier = Modifier .padding(top = 15.dp) .clickable(onClick = { - navController.navigate(route) + nav(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -244,7 +242,7 @@ fun ProfileContent( @Composable fun ListContent( accountUserPubKey: String?, - navController: NavHostController, + nav: (String) -> Unit, scaffoldState: ScaffoldState, sheetState: ModalBottomSheetState, modifier: Modifier, @@ -263,7 +261,7 @@ fun ListContent( title = stringResource(R.string.profile), icon = Route.Profile.icon, tint = MaterialTheme.colors.primary, - navController = navController, + nav = nav, scaffoldState = scaffoldState, route = "User/$accountUserPubKey" ) @@ -272,7 +270,7 @@ fun ListContent( title = stringResource(R.string.bookmarks), icon = Route.Bookmarks.icon, tint = MaterialTheme.colors.onBackground, - navController = navController, + nav = nav, scaffoldState = scaffoldState, route = Route.Bookmarks.route ) @@ -282,7 +280,7 @@ fun ListContent( title = stringResource(R.string.security_filters), icon = Route.BlockedUsers.icon, tint = MaterialTheme.colors.onBackground, - navController = navController, + nav = nav, scaffoldState = scaffoldState, route = Route.BlockedUsers.route ) @@ -402,13 +400,13 @@ fun NavigationRow( title: String, icon: Int, tint: Color, - navController: NavHostController, + nav: (String) -> Unit, scaffoldState: ScaffoldState, route: String ) { val coroutineScope = rememberCoroutineScope() IconRow(title, icon, tint, onClick = { - navController.navigate(route) + nav(route) coroutineScope.launch { scaffoldState.drawerState.close() } @@ -448,7 +446,7 @@ fun IconRow(title: String, icon: Int, tint: Color, onClick: () -> Unit, onLongCl } @Composable -fun BottomContent(user: User, scaffoldState: ScaffoldState, navController: NavController) { +fun BottomContent(user: User, scaffoldState: ScaffoldState, nav: (String) -> Unit) { val coroutineScope = rememberCoroutineScope() // store the dialog open or close state @@ -515,7 +513,7 @@ fun BottomContent(user: User, scaffoldState: ScaffoldState, navController: NavCo coroutineScope.launch { scaffoldState.drawerState.close() } - navController.navigate(it) + nav(it) }, onClose = { dialogOpen = false } ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index a123dc0c4..ee631624c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -1,8 +1,10 @@ package com.vitorpamplona.amethyst.ui.navigation +import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.navigation.NamedNavArgument +import androidx.navigation.NavDestination import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.currentBackStackEntryAsState @@ -185,3 +187,48 @@ object MessagesLatestItem { return (note.createdAt() ?: 0) > lastTime } } + +fun getRouteWithArguments(navController: NavHostController): String? { + val currentEntry = navController.currentBackStackEntry ?: return null + return getRouteWithArguments(currentEntry.destination, currentEntry.arguments) +} + +private fun getRouteWithArguments( + destination: NavDestination, + arguments: Bundle? +): String? { + var route = destination.route ?: return null + arguments?.let { bundle -> + destination.arguments.keys.forEach { key -> + val value = destination.arguments[key]?.type?.get(bundle, key)?.toString() + if (value == null) { + val keyStart = route.indexOf("{$key}") + // if it is a parameter, removes the complete segment `var={key}` and adjust connectors `#`, `&` or `&` + if (keyStart > 0 && route[keyStart - 1] == '=') { + val end = keyStart + "{$key}".length + var start = keyStart + for (i in keyStart downTo 0) { + if (route[i] == '#' || route[i] == '?' || route[i] == '&') { + start = i + 1 + break + } + } + if (end < route.length && route[end] == '&') { + route = route.removeRange(start, end + 1) + } else if (end < route.length && route[end] == '#') { + route = route.removeRange(start - 1, end) + } else if (end == route.length) { + route = route.removeRange(start - 1, end) + } else { + route = route.removeRange(start, end) + } + } else { + route = route.replaceFirst("{$key}", "") + } + } else { + route = route.replaceFirst("{$key}", value) + } + } + } + return route +} 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 066777618..6623105b0 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 @@ -32,7 +32,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.BadgeCard @@ -42,7 +41,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteState by likeSetCard.note.live().metadata.observeAsState() val note = noteState?.note @@ -79,7 +78,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL routeFor( note, accountViewModel.userProfile() - )?.let { navController.navigate(it) } + )?.let { nav(it) } } }, onLongClick = { popupExpanded = true } @@ -149,7 +148,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL isBoostedNote = true, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } 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 a333a2b6c..a9a95fe6b 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 @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note @@ -55,7 +54,7 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: St } @Composable -fun HiddenNote(reports: Set, loggedIn: User, modifier: Modifier = Modifier, isQuote: Boolean = false, navController: NavController, onClick: () -> Unit) { +fun HiddenNote(reports: Set, loggedIn: User, modifier: Modifier = Modifier, isQuote: Boolean = false, nav: (String) -> Unit, onClick: () -> Unit) { Column(modifier = modifier) { Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) { Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) { @@ -75,7 +74,7 @@ fun HiddenNote(reports: Set, loggedIn: User, modifier: Modifier = Modifier reports.forEach { NoteAuthorPicture( baseNote = it, - navController = navController, + nav = nav, 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 10b61d0d6..4e437ea87 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalFoundationApi::class) @Composable -fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteState by boostSetCard.note.live().metadata.observeAsState() val note = noteState?.note @@ -77,7 +76,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro routeFor( note, account.userProfile() - )?.let { navController.navigate(it) } + )?.let { nav(it) } } }, onLongClick = { popupExpanded = true } @@ -114,7 +113,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro boostSetCard.boostEvents.forEach { NoteAuthorPicture( baseNote = it, - navController = navController, + nav = nav, userAccount = account.userProfile(), size = 35.dp ) @@ -128,7 +127,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro isBoostedNote = true, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) 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 b4711e887..549617784 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 @@ -39,7 +39,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache @@ -57,7 +56,7 @@ import kotlinx.coroutines.launch fun ChatroomCompose( baseNote: Note, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteState by baseNote.live().metadata.observeAsState() val note = noteState?.note @@ -128,7 +127,7 @@ fun ChatroomCompose( channelLastTime = note.createdAt(), channelLastContent = "${author?.toBestDisplayName()}: " + description, hasNewMessages = hasNewMessages, - onClick = { navController.navigate("Channel/${chan.idHex}") } + onClick = { nav("Channel/${chan.idHex}") } ) } } else { @@ -172,7 +171,7 @@ fun ChatroomCompose( channelLastTime = note.createdAt(), channelLastContent = accountViewModel.decrypt(note), hasNewMessages = hasNewMessages, - onClick = { navController.navigate("Room/${user.pubkeyHex}") } + onClick = { nav("Room/${user.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 e799500d1..f2f89b0f9 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 @@ -50,7 +50,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -81,7 +80,7 @@ fun ChatroomMessageCompose( innerQuote: Boolean = false, parentBackgroundColor: Color? = null, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, onWantsToReply: (Note) -> Unit ) { val accountState by accountViewModel.accountLiveData.observeAsState() @@ -137,7 +136,7 @@ fun ChatroomMessageCompose( account.userProfile(), Modifier, innerQuote, - navController, + nav, onClick = { showHiddenNote = true } ) } else { @@ -210,7 +209,7 @@ fun ChatroomMessageCompose( .combinedClickable( onClick = { if (noteEvent is ChannelCreateEvent) { - navController.navigate("Channel/${note.idHex}") + nav("Channel/${note.idHex}") } }, onLongClick = { popupExpanded = true } @@ -229,7 +228,7 @@ fun ChatroomMessageCompose( DrawAuthorInfo( baseNote, alignment, - navController + nav ) } else { Spacer(modifier = Modifier.height(5.dp)) @@ -245,7 +244,7 @@ fun ChatroomMessageCompose( innerQuote = true, parentBackgroundColor = backgroundBubbleColor, accountViewModel = accountViewModel, - navController = navController, + nav = nav, onWantsToReply = onWantsToReply ) } @@ -269,7 +268,7 @@ fun ChatroomMessageCompose( isAcceptableAndCanPreview.second, backgroundBubbleColor, accountViewModel, - navController + nav ) } } @@ -363,7 +362,7 @@ private fun RenderRegularTextNote( canPreview: Boolean, backgroundBubbleColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val tags = remember { note.event?.tags() } val eventContent = remember { accountViewModel.decrypt(note) } @@ -371,23 +370,23 @@ private fun RenderRegularTextNote( if (eventContent != null) { TranslatableRichTextViewer( - eventContent, - canPreview, - modifier, - tags, - backgroundBubbleColor, - accountViewModel, - navController + content = eventContent, + canPreview = canPreview, + modifier = modifier, + tags = tags, + backgroundColor = backgroundBubbleColor, + accountViewModel = accountViewModel, + nav = nav ) } else { TranslatableRichTextViewer( - stringResource(R.string.could_not_decrypt_the_message), - true, - modifier, - tags, - backgroundBubbleColor, - accountViewModel, - navController + content = stringResource(id = R.string.could_not_decrypt_the_message), + canPreview = true, + modifier = modifier, + tags = tags, + backgroundColor = backgroundBubbleColor, + accountViewModel = accountViewModel, + nav = nav ) } } @@ -444,7 +443,7 @@ private fun RenderCreateChannelNote(note: Note) { private fun DrawAuthorInfo( baseNote: Note, alignment: Arrangement.Horizontal, - navController: NavController + nav: (String) -> Unit ) { val userState by baseNote.author!!.live().metadata.observeAsState() @@ -468,7 +467,7 @@ private fun DrawAuthorInfo( .height(25.dp) .clip(shape = CircleShape) .clickable(onClick = { - navController.navigate(route) + nav(route) }) ) @@ -479,7 +478,7 @@ private fun DrawAuthorInfo( fontWeight = FontWeight.Bold, overrideColor = MaterialTheme.colors.onBackground, route = route, - navController = navController + nav = nav ) } } 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 eb22f8631..ee5a90b93 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalFoundationApi::class) @Composable -fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteState by likeSetCard.note.live().metadata.observeAsState() val note = noteState?.note @@ -76,7 +75,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route routeFor( note, account.userProfile() - )?.let { navController.navigate(it) } + )?.let { nav(it) } } }, onLongClick = { popupExpanded = true } @@ -113,7 +112,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route likeSetCard.likeEvents.forEach { NoteAuthorPicture( baseNote = it, - navController = navController, + nav = nav, userAccount = account.userProfile(), size = 35.dp ) @@ -127,7 +126,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route isBoostedNote = true, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) 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 c893e0439..1e1b8c0a3 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.screen.MessageSetCard @@ -35,7 +34,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteState by messageSetCard.note.live().metadata.observeAsState() val note = remember(noteState) { noteState?.note } @@ -81,7 +80,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, routeFor( note, accountViewModel.userProfile() - )?.let { navController.navigate(it) } + )?.let { nav(it) } } }, onLongClick = { popupExpanded = true } @@ -108,7 +107,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, addMarginTop = false, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) 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 49bd4cffc..339afcf93 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 @@ -20,6 +20,7 @@ import androidx.compose.material.icons.filled.Bolt import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -35,7 +36,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -51,13 +51,15 @@ import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.math.BigDecimal @OptIn(ExperimentalFoundationApi::class) @Composable -fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val baseNote = remember { multiSetCard.note } val noteState by baseNote.live().metadata.observeAsState() @@ -90,34 +92,38 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val primaryColor = MaterialTheme.colors.primary.copy(0.12f) val defaultBackgroundColor = MaterialTheme.colors.background - val backgroundColor = remember(isNew) { - if (isNew) { - primaryColor.compositeOver(defaultBackgroundColor) - } else { - defaultBackgroundColor + val backgroundColor by remember(isNew) { + derivedStateOf { + if (isNew) { + primaryColor.compositeOver(defaultBackgroundColor) + } else { + defaultBackgroundColor + } } } - val columnModifier = remember(isNew) { - Modifier - .background(backgroundColor) - .padding( - start = 12.dp, - end = 12.dp, - top = 10.dp - ) - .combinedClickable( - onClick = { - scope.launch { - routeFor( - baseNote, - account.userProfile() - )?.let { navController.navigate(it) } - } - }, - onLongClick = { popupExpanded = true } - ) - .fillMaxWidth() + val columnModifier by remember(isNew, backgroundColor) { + derivedStateOf { + Modifier + .background(backgroundColor) + .padding( + start = 12.dp, + end = 12.dp, + top = 10.dp + ) + .combinedClickable( + onClick = { + scope.launch { + routeFor( + baseNote, + account.userProfile() + )?.let { nav(it) } + } + }, + onLongClick = { popupExpanded = true } + ) + .fillMaxWidth() + } } val zapEvents = remember { multiSetCard.zapEvents } @@ -126,15 +132,15 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun Column(modifier = columnModifier) { if (zapEvents.isNotEmpty()) { - RenderZapGallery(zapEvents, navController, account, accountViewModel) + RenderZapGallery(zapEvents, backgroundColor, nav, account, accountViewModel) } if (boostEvents.isNotEmpty()) { - RenderBoostGallery(boostEvents, navController, account, accountViewModel) + RenderBoostGallery(boostEvents, backgroundColor, nav, account, accountViewModel) } if (likeEvents.isNotEmpty()) { - RenderLikeGallery(likeEvents, navController, account, accountViewModel) + RenderLikeGallery(likeEvents, backgroundColor, nav, account, accountViewModel) } Row(Modifier.fillMaxWidth()) { @@ -147,7 +153,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun isBoostedNote = true, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) @@ -158,8 +164,9 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun @Composable private fun RenderLikeGallery( - likeEvents: List, - navController: NavController, + likeEvents: ImmutableList, + backgroundColor: Color, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { @@ -183,14 +190,15 @@ private fun RenderLikeGallery( ) } - AuthorGallery(likeEvents, navController, account, accountViewModel) + AuthorGallery(likeEvents, backgroundColor, nav, account, accountViewModel) } } @Composable private fun RenderZapGallery( - zapEvents: Map, - navController: NavController, + zapEvents: ImmutableMap, + backgroundColor: Color, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { @@ -214,18 +222,23 @@ private fun RenderZapGallery( ) } - AuthorGalleryZaps(zapEvents, navController, account, accountViewModel) + AuthorGalleryZaps(zapEvents, backgroundColor, nav, account, accountViewModel) } } @Composable private fun RenderBoostGallery( - boostEvents: List, - navController: NavController, + boostEvents: ImmutableList, + backgroundColor: Color, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { - Row(Modifier.fillMaxWidth()) { + Row( + modifier = remember { + Modifier.fillMaxWidth() + } + ) { Box( modifier = remember { Modifier @@ -245,14 +258,15 @@ private fun RenderBoostGallery( ) } - AuthorGallery(boostEvents, navController, account, accountViewModel) + AuthorGallery(boostEvents, backgroundColor, nav, account, accountViewModel) } } @Composable fun AuthorGalleryZaps( - authorNotes: Map, - navController: NavController, + authorNotes: ImmutableMap, + backgroundColor: Color, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { @@ -261,7 +275,7 @@ fun AuthorGalleryZaps( Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { - AuthorPictureAndComment(it.key, it.value, navController, accountState, accountViewModel) + AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountState, accountViewModel) } } } @@ -271,7 +285,8 @@ fun AuthorGalleryZaps( private fun AuthorPictureAndComment( zapRequest: Note, zapEvent: Note?, - navController: NavController, + backgroundColor: Color, + nav: (String) -> Unit, accountUser: State, accountViewModel: AccountViewModel ) { @@ -287,23 +302,39 @@ private fun AuthorPictureAndComment( val amount = (zapEvent?.event as? LnZapEvent)?.amount if (decryptedContent != null) { val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) - content = Triple(newAuthor, decryptedContent.content, amount) + content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, amount) } else { if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { - content = Triple(author, zapRequest.event?.content(), amount) + content = Triple(author, zapRequest.event?.content()?.ifBlank { null }, amount) } } } } } + val modifier = remember(content.second) { + if (!content.second.isNullOrBlank()) { + Modifier + .fillMaxWidth() + .clickable { + nav("User/${author.pubkeyHex}") + } + } else { + Modifier.clickable { + nav("User/${author.pubkeyHex}") + } + } + } + AuthorPictureAndComment( author = content.first, comment = content.second, amount = showAmountAxis(content.third), - navController = navController, + backgroundColor = backgroundColor, + nav = nav, accountUser = accountUser, - accountViewModel = accountViewModel + accountViewModel = accountViewModel, + modifier = modifier ) } @@ -312,24 +343,12 @@ private fun AuthorPictureAndComment( author: User, comment: String?, amount: String?, - navController: NavController, + backgroundColor: Color, + nav: (String) -> Unit, accountUser: State, - accountViewModel: AccountViewModel + accountViewModel: AccountViewModel, + modifier: Modifier ) { - val modifier = remember(comment) { - if (!comment.isNullOrBlank()) { - Modifier - .fillMaxWidth() - .clickable { - navController.navigate("User/${author.pubkeyHex}") - } - } else { - Modifier.clickable { - navController.navigate("User/${author.pubkeyHex}") - } - } - } - Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically @@ -358,16 +377,16 @@ private fun AuthorPictureAndComment( } } - if (!comment.isNullOrBlank()) { + comment?.let { Spacer(modifier = Modifier.width(5.dp)) TranslatableRichTextViewer( - content = comment, + content = it, canPreview = true, tags = null, modifier = Modifier.weight(1f), - backgroundColor = MaterialTheme.colors.background, + backgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -375,27 +394,26 @@ private fun AuthorPictureAndComment( @Composable fun AuthorGallery( - authorNotes: Collection, - navController: NavController, + authorNotes: ImmutableList, + backgroundColor: Color, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { val accountState = account.userProfile().live().follows.observeAsState() - val listToRender = remember { - Pair( - authorNotes.take(50).mapNotNull { it.author }, - authorNotes.size - ) - } Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { - listToRender.first.forEach { author -> - AuthorPictureAndComment(author, null, null, navController, accountState, accountViewModel) - } + authorNotes.forEach { note -> + note.author?.let { user -> + val modifier = remember { + Modifier.clickable { + nav("User/${user.pubkeyHex}") + } + } - if (listToRender.second > 50) { - Text(" and ${listToRender.second - 50} others") + AuthorPictureAndComment(user, null, null, backgroundColor, nav, accountState, accountViewModel, modifier) + } } } } 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 7c23d1623..540985d0e 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 @@ -77,7 +77,6 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat 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 @@ -153,7 +152,7 @@ fun NoteCompose( addMarginTop: Boolean = true, parentBackgroundColor: Color? = null, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return @@ -212,11 +211,11 @@ fun NoteCompose( account.userProfile(), modifier, isBoostedNote, - navController, + nav, onClick = { showHiddenNote = true } ) } else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) { - ChannelHeader(baseChannel = baseChannel, account = account, navController = navController) + ChannelHeader(baseChannel = baseChannel, account = account, nav = nav) } else if (noteEvent is BadgeDefinitionEvent) { BadgeDisplay(baseNote = note) } else if (noteEvent is FileHeaderEvent) { @@ -267,11 +266,7 @@ fun NoteCompose( onClick = { scope.launch { routeFor(note, loggedIn)?.let { - if (note.idHex != navController.currentBackStackEntry?.arguments?.getString("id")) { - navController.navigate(it) - } else { - Log.d("Amethyst-Navigation", "Note already exists in the backstack!") - } + nav(it) } } }, @@ -292,7 +287,7 @@ fun NoteCompose( } ) { if (!isBoostedNote && !isQuotedNote) { - DrawAuthorImages(baseNote, loggedIn, navController) + DrawAuthorImages(baseNote, loggedIn, nav) } Column( @@ -306,14 +301,14 @@ fun NoteCompose( showAuthorPicture = isQuotedNote, account = account, accountViewModel = accountViewModel, - navController = navController + nav = nav ) if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { SecondUserInfoRow( note, account, - navController + nav ) } @@ -326,17 +321,17 @@ fun NoteCompose( backgroundColor, account, accountViewModel, - navController + nav ) } when (noteEvent) { is ReactionEvent -> { - RenderReaction(note, backgroundColor, accountViewModel, navController) + RenderReaction(note, backgroundColor, accountViewModel, nav) } is RepostEvent -> { - RenderRepost(note, backgroundColor, accountViewModel, navController) + RenderRepost(note, backgroundColor, accountViewModel, nav) } is ReportEvent -> { @@ -344,31 +339,31 @@ fun NoteCompose( } is LongTextNoteEvent -> { - RenderLongFormContent(note, loggedIn, accountViewModel, navController) + RenderLongFormContent(note, loggedIn, accountViewModel, nav) } is BadgeAwardEvent -> { - RenderBadgeAward(note, backgroundColor, accountViewModel, navController) + RenderBadgeAward(note, backgroundColor, accountViewModel, nav) } is PeopleListEvent -> { - RenderPeopleList(noteState, backgroundColor, accountViewModel, navController) + RenderPeopleList(noteState, backgroundColor, accountViewModel, nav) } is AudioTrackEvent -> { - RenderAudioTrack(note, loggedIn, accountViewModel, navController) + RenderAudioTrack(note, loggedIn, accountViewModel, nav) } is PinListEvent -> { - RenderPinListEvent(noteState, backgroundColor, accountViewModel, navController) + RenderPinListEvent(noteState, backgroundColor, accountViewModel, nav) } is PrivateDmEvent -> { - RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) + RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, nav) } is HighlightEvent -> { - RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) + RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, nav) } is PollNoteEvent -> { @@ -378,7 +373,7 @@ fun NoteCompose( isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, - navController + nav ) } @@ -389,7 +384,7 @@ fun NoteCompose( isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, - navController + nav ) } } @@ -438,7 +433,7 @@ private fun RenderTextEvent( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val tags = remember(note.event?.id()) { note.event?.tags() } val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() } @@ -462,15 +457,15 @@ private fun RenderTextEvent( tags = tags, backgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) - DisplayUncitedHashtags(hashtags, eventContent, navController) + DisplayUncitedHashtags(hashtags, eventContent, nav) } } if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) } Divider( @@ -486,7 +481,7 @@ private fun RenderPoll( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event as? PollNoteEvent ?: return val eventContent = noteEvent.content() @@ -506,22 +501,22 @@ private fun RenderPoll( noteEvent.tags(), backgroundColor, accountViewModel, - navController + nav ) - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) PollNote( note, canPreview = canPreview && !makeItShort, backgroundColor, accountViewModel, - navController + nav ) } if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) } Divider( @@ -537,7 +532,7 @@ private fun RenderHighlight( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event as? HighlightEvent ?: return @@ -549,11 +544,11 @@ private fun RenderHighlight( canPreview, backgroundColor, accountViewModel, - navController + nav ) if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) } Divider( @@ -569,7 +564,7 @@ private fun RenderPrivateMessage( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event as? PrivateDmEvent ?: return val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) } @@ -593,10 +588,10 @@ private fun RenderPrivateMessage( tags = noteEvent.tags(), backgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) } } } else { @@ -613,12 +608,12 @@ private fun RenderPrivateMessage( noteEvent.tags(), backgroundColor, accountViewModel, - navController + nav ) } if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) } Divider( @@ -632,12 +627,12 @@ fun RenderPeopleList( noteState: NoteState?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { - DisplayPeopleList(noteState, backgroundColor, accountViewModel, navController) + DisplayPeopleList(noteState, backgroundColor, accountViewModel, nav) noteState?.note?.let { - ReactionsRow(it, accountViewModel, navController) + ReactionsRow(it, accountViewModel, nav) } Divider( @@ -651,7 +646,7 @@ fun DisplayPeopleList( noteState: NoteState?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val note = remember(noteState) { noteState?.note } ?: return val noteEvent = note.event as? PeopleListEvent ?: return @@ -696,7 +691,7 @@ fun DisplayPeopleList( user, overallModifier = Modifier, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -740,7 +735,7 @@ private fun RenderBadgeAward( note: Note, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { if (note.replyTo.isNullOrEmpty()) return @@ -765,7 +760,7 @@ private fun RenderBadgeAward( modifier = Modifier .size(size = 35.dp) .clickable { - navController.navigate("User/${user.pubkeyHex}") + nav("User/${user.pubkeyHex}") }, verticalAlignment = Alignment.CenterVertically ) { @@ -791,11 +786,11 @@ private fun RenderBadgeAward( unPackReply = false, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) Divider( modifier = Modifier.padding(top = 10.dp), @@ -808,7 +803,7 @@ private fun RenderReaction( note: Note, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { note.replyTo?.lastOrNull()?.let { NoteCompose( @@ -818,7 +813,7 @@ private fun RenderReaction( unPackReply = false, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } @@ -836,7 +831,7 @@ private fun RenderRepost( note: Note, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val boostedNote = remember { note.replyTo?.lastOrNull() @@ -850,7 +845,7 @@ private fun RenderRepost( unPackReply = false, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -860,13 +855,13 @@ private fun RenderPinListEvent( noteState: NoteState?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = noteState?.note?.event as? PinListEvent ?: return - PinListHeader(noteState, backgroundColor, accountViewModel, navController) + PinListHeader(noteState, backgroundColor, accountViewModel, nav) - ReactionsRow(noteState?.note, accountViewModel, navController) + ReactionsRow(noteState?.note, accountViewModel, nav) Divider( modifier = Modifier.padding(top = 10.dp), @@ -879,7 +874,7 @@ fun PinListHeader( noteState: NoteState?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val note = remember(noteState) { noteState?.note } ?: return val noteEvent = note.event as? PinListEvent ?: return @@ -926,7 +921,7 @@ fun PinListHeader( tags = emptyList(), backgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -970,13 +965,13 @@ private fun RenderAudioTrack( note: Note, loggedIn: User, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event as? AudioTrackEvent ?: return - AudioTrackHeader(noteEvent, note, loggedIn, navController) + AudioTrackHeader(noteEvent, note, loggedIn, nav) - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) Divider( modifier = Modifier.padding(top = 10.dp), @@ -989,13 +984,13 @@ private fun RenderLongFormContent( note: Note, loggedIn: User, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event as? LongTextNoteEvent ?: return LongFormHeader(noteEvent, note, loggedIn) - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) Divider( modifier = Modifier.padding(top = 10.dp), @@ -1042,7 +1037,7 @@ private fun ReplyRow( backgroundColor: Color, account: Account, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event @@ -1068,16 +1063,16 @@ private fun ReplyRow( parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f) .compositeOver(backgroundColor), accountViewModel = accountViewModel, - navController = navController + nav = nav ) } else { - ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController) + ReplyInformation(note.replyTo, noteEvent.mentions(), account, nav) } Spacer(modifier = Modifier.height(5.dp)) } else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) { note.channel()?.let { - ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, navController) + ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, nav) } Spacer(modifier = Modifier.height(5.dp)) @@ -1088,7 +1083,7 @@ private fun ReplyRow( private fun SecondUserInfoRow( note: Note, account: Account, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = remember { note.event } ?: return val noteAuthor = remember { note.author } ?: return @@ -1098,7 +1093,7 @@ private fun SecondUserInfoRow( val baseReward = remember { noteEvent.getReward() } if (baseReward != null) { - DisplayReward(baseReward, note, account, navController) + DisplayReward(baseReward, note, account, nav) } val pow = remember { noteEvent.getPoWRank() } @@ -1114,7 +1109,7 @@ private fun FirstUserInfoRow( showAuthorPicture: Boolean, account: Account, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { var moreActionsExpanded by remember { mutableStateOf(false) } val eventNote = remember { baseNote.event } ?: return @@ -1126,7 +1121,7 @@ private fun FirstUserInfoRow( Row(verticalAlignment = Alignment.CenterVertically) { if (showAuthorPicture) { - NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp) + NoteAuthorPicture(baseNote, nav, loggedIn, 25.dp) Spacer(padding) NoteUsernameDisplay(baseNote, Modifier.weight(1f)) } else { @@ -1140,7 +1135,7 @@ private fun FirstUserInfoRow( color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) } else { - DisplayFollowingHashtagsInPost(eventNote, account, navController) + DisplayFollowingHashtagsInPost(eventNote, account, nav) } TimeAgo(time) @@ -1188,17 +1183,17 @@ fun TimeAgo(time: Long) { } @Composable -private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { +private fun DrawAuthorImages(baseNote: Note, loggedIn: User, nav: (String) -> Unit) { val baseChannel = remember { baseNote.channel() } val modifier = remember { Modifier.width(55.dp) } Column(modifier) { // Draws the boosted picture outside the boosted card. Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) { - NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp) + NoteAuthorPicture(baseNote, nav, loggedIn, 55.dp) if (baseNote.event is RepostEvent) { - RepostNoteAuthorPicture(baseNote, navController, loggedIn) + RepostNoteAuthorPicture(baseNote, nav, loggedIn) } if (baseNote.event is ChannelMessageEvent && baseChannel != null) { @@ -1262,7 +1257,7 @@ private fun ChannelNotePicture(baseChannel: Channel) { @Composable private fun RepostNoteAuthorPicture( baseNote: Note, - navController: NavController, + nav: (String) -> Unit, loggedIn: User ) { val baseRepost = remember { baseNote.replyTo?.lastOrNull() } @@ -1277,7 +1272,7 @@ private fun RepostNoteAuthorPicture( Box(modifier) { NoteAuthorPicture( it, - navController, + nav, loggedIn, 35.dp, pictureModifier = Modifier.border( @@ -1299,7 +1294,7 @@ fun DisplayHighlight( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val quote = remember { @@ -1316,7 +1311,7 @@ fun DisplayHighlight( emptyList(), backgroundColor, accountViewModel, - navController + nav ) var userBase by remember { mutableStateOf(null) } @@ -1343,7 +1338,7 @@ fun DisplayHighlight( suffix = " ", tags = userTags, route = route, - navController = navController + nav = nav ) } } @@ -1371,7 +1366,7 @@ fun DisplayHighlight( fun DisplayFollowingHashtagsInPost( noteEvent: EventInterface, account: Account, - navController: NavController + nav: (String) -> Unit ) { var firstTag by remember { mutableStateOf(null) } @@ -1386,7 +1381,7 @@ fun DisplayFollowingHashtagsInPost( firstTag?.let { ClickableText( text = AnnotatedString(" #$firstTag"), - onClick = { navController.navigate("Hashtag/$firstTag") }, + onClick = { nav("Hashtag/$firstTag") }, style = LocalTextStyle.current.copy( color = MaterialTheme.colors.primary.copy( alpha = 0.52f @@ -1402,7 +1397,7 @@ fun DisplayFollowingHashtagsInPost( fun DisplayUncitedHashtags( hashtags: List, eventContent: String, - navController: NavController + nav: (String) -> Unit ) { if (hashtags.isNotEmpty()) { FlowRow( @@ -1412,7 +1407,7 @@ fun DisplayUncitedHashtags( if (!eventContent.contains(hashtag, true)) { ClickableText( text = AnnotatedString("#$hashtag "), - onClick = { navController.navigate("Hashtag/$hashtag") }, + onClick = { nav("Hashtag/$hashtag") }, style = LocalTextStyle.current.copy( color = MaterialTheme.colors.primary.copy( alpha = 0.52f @@ -1444,7 +1439,7 @@ fun DisplayReward( baseReward: BigDecimal, baseNote: Note, account: Account, - navController: NavController + nav: (String) -> Unit ) { var popupExpanded by remember { mutableStateOf(false) } @@ -1455,7 +1450,7 @@ fun DisplayReward( ) { ClickableText( text = AnnotatedString("#bounty"), - onClick = { navController.navigate("Hashtag/bounty") }, + onClick = { nav("Hashtag/bounty") }, style = LocalTextStyle.current.copy( color = MaterialTheme.colors.primary.copy( alpha = 0.52f @@ -1676,7 +1671,7 @@ fun FileStorageHeaderDisplay(baseNote: Note) { } @Composable -fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, navController: NavController) { +fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav: (String) -> Unit) { val media = remember { noteEvent.media() } val cover = remember { noteEvent.cover() } val subject = remember { noteEvent.subject() } @@ -1713,7 +1708,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav modifier = Modifier .padding(top = 5.dp, start = 10.dp, end = 10.dp) .clickable { - navController.navigate("User/${it.second.pubkeyHex}") + nav("User/${it.second.pubkeyHex}") } ) { UserPicture(it.second, loggedIn, 25.dp) @@ -1962,17 +1957,13 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) { @Composable fun NoteAuthorPicture( baseNote: Note, - navController: NavController, + nav: (String) -> Unit, userAccount: User, size: Dp, pictureModifier: Modifier = Modifier ) { NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) { - if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { - navController.navigate("User/${it.pubkeyHex}") - } else { - Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!") - } + nav("User/${it.pubkeyHex}") } } @@ -2019,17 +2010,13 @@ fun NoteAuthorPicture( @Composable fun UserPicture( user: User, - navController: NavController, + nav: (String) -> Unit, userAccount: User, size: Dp, pictureModifier: Modifier = Modifier ) { UserPicture(user, userAccount, size, pictureModifier) { - if (it.pubkeyHex != navController.currentBackStackEntry?.arguments?.getString("id")) { - navController.navigate("User/${it.pubkeyHex}") - } else { - Log.d("Amethyst-Navigation", "Profile destination already exists in the backstack!") - } + nav("User/${it.pubkeyHex}") } } 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 12342c292..596b9f228 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 @@ -29,7 +29,6 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.Popup import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -47,7 +46,7 @@ fun PollNote( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return @@ -64,7 +63,7 @@ fun PollNote( canPreview = canPreview, backgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } @@ -75,7 +74,7 @@ fun PollNote( canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val zapsState by baseNote.live().zaps.observeAsState() @@ -93,7 +92,7 @@ fun PollNote( accountViewModel, canPreview, backgroundColor, - navController + nav ) } } @@ -106,7 +105,7 @@ private fun OptionNote( accountViewModel: AccountViewModel, canPreview: Boolean, backgroundColor: Color, - navController: NavController + nav: (String) -> Unit ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -168,7 +167,7 @@ private fun OptionNote( pollViewModel.pollEvent?.tags(), backgroundColor, accountViewModel, - navController + nav ) } } @@ -202,7 +201,7 @@ private fun OptionNote( pollViewModel.pollEvent?.tags(), backgroundColor, accountViewModel, - navController + nav ) } } 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 f4fbf8f41..3c0fb6278 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 @@ -50,7 +50,6 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup -import androidx.navigation.NavController import coil.compose.AsyncImage import coil.request.CachePolicy import coil.request.ImageRequest @@ -66,7 +65,7 @@ import java.math.RoundingMode import kotlin.math.roundToInt @Composable -fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { +fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return @@ -81,11 +80,11 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navControll } if (wantsToReplyTo != null) { - NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, navController) + NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, nav) } if (wantsToQuote != null) { - NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, navController) + NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, nav) } Spacer(modifier = Modifier.height(8.dp)) 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 2d7ab31a4..19512a828 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 @@ -14,7 +14,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.* @@ -23,7 +22,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable -fun ReplyInformation(replyTo: List?, mentions: List, account: Account, navController: NavController) { +fun ReplyInformation(replyTo: List?, mentions: List, account: Account, nav: (String) -> Unit) { var dupMentions by remember { mutableStateOf?>(null) } LaunchedEffect(Unit) { @@ -34,7 +33,7 @@ fun ReplyInformation(replyTo: List?, mentions: List, account: Acco if (dupMentions != null) { ReplyInformation(replyTo, dupMentions, account) { - navController.navigate("User/${it.pubkeyHex}") + nav("User/${it.pubkeyHex}") } } } @@ -116,7 +115,7 @@ fun ReplyInformation(replyTo: List?, dupMentions: List?, account: Ac } @Composable -fun ReplyInformationChannel(replyTo: List?, mentions: List, channel: Channel, account: Account, navController: NavController) { +fun ReplyInformationChannel(replyTo: List?, mentions: List, channel: Channel, account: Account, nav: (String) -> Unit) { var sortedMentions by remember { mutableStateOf?>(null) } LaunchedEffect(Unit) { @@ -134,26 +133,26 @@ fun ReplyInformationChannel(replyTo: List?, mentions: List, channe sortedMentions, channel, onUserTagClick = { - navController.navigate("User/${it.pubkeyHex}") + nav("User/${it.pubkeyHex}") }, onChannelTagClick = { - navController.navigate("Channel/${it.idHex}") + nav("Channel/${it.idHex}") } ) } } @Composable -fun ReplyInformationChannel(replyTo: List?, mentions: List?, channel: Channel, navController: NavController) { +fun ReplyInformationChannel(replyTo: List?, mentions: List?, channel: Channel, nav: (String) -> Unit) { ReplyInformationChannel( replyTo, mentions, channel, onUserTagClick = { - navController.navigate("User/${it.pubkeyHex}") + nav("User/${it.pubkeyHex}") }, onChannelTagClick = { - navController.navigate("Channel/${it.idHex}") + nav("Channel/${it.idHex}") } ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 1164d9067..5c366380f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton @@ -34,7 +33,7 @@ fun UserCompose( top = 10.dp ), accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -47,14 +46,14 @@ fun UserCompose( Column( modifier = Modifier.clickable( - onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } + onClick = { nav("User/${baseUser.pubkeyHex}") } ) ) { Row( modifier = overallModifier, verticalAlignment = Alignment.CenterVertically ) { - UserPicture(baseUser, navController, account.userProfile(), 55.dp) + UserPicture(baseUser, nav, account.userProfile(), 55.dp) Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index b6d243aa5..7ed0e9a09 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.NavController import com.patrykandpatrick.vico.core.chart.composed.ComposedChartEntryModel import com.patrykandpatrick.vico.core.entry.ChartEntryModel import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer @@ -54,7 +53,7 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter @Composable -fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountViewModel, navController: NavController, onClick: () -> Unit) { +fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, onClick: () -> Unit) { Row( verticalAlignment = CenterVertically, modifier = Modifier 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 aa881b1f0..de185a436 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 @@ -23,7 +23,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.LnZapEvent @@ -37,7 +36,7 @@ import kotlinx.coroutines.launch import java.math.BigDecimal @Composable -fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewModel, navController: NavController) { +fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -60,7 +59,7 @@ fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewMode Column( modifier = Modifier.clickable( - onClick = { navController.navigate("User/${baseAuthor.pubkeyHex}") } + onClick = { nav("User/${baseAuthor.pubkeyHex}") } ), verticalArrangement = Arrangement.Center ) { @@ -73,7 +72,7 @@ fun ZapNoteCompose(baseNote: Pair, accountViewModel: AccountViewMode ), verticalAlignment = Alignment.CenterVertically ) { - UserPicture(baseAuthor, navController, account.userProfile(), 55.dp) + UserPicture(baseAuthor, nav, account.userProfile(), 55.dp) Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { 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 b99f3007c..a67a18f5a 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 @@ -26,7 +26,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -39,7 +38,7 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalFoundationApi::class) @Composable -fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteState by zapSetCard.note.live().metadata.observeAsState() val note = noteState?.note @@ -78,7 +77,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor routeFor( note, account.userProfile() - )?.let { navController.navigate(it) } + )?.let { nav(it) } } }, onLongClick = { popupExpanded = true } @@ -115,7 +114,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor zapSetCard.zapEvents.forEach { NoteAuthorPicture( baseNote = it.key, - navController = navController, + nav = nav, userAccount = account.userProfile(), size = 35.dp ) @@ -129,7 +128,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor isBoostedNote = true, parentBackgroundColor = backgroundColor, accountViewModel = accountViewModel, - navController = navController + nav = nav ) NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) 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 70aa994f7..5958fdb91 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 @@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R @@ -37,7 +36,7 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalFoundationApi::class) @Composable -fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -61,7 +60,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, modifier = Modifier .background(backgroundColor) .clickable { - navController.navigate("User/${zapSetCard.user.pubkeyHex}") + nav("User/${zapSetCard.user.pubkeyHex}") } ) { Row( @@ -95,14 +94,14 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, zapSetCard.zapEvents.forEach { NoteAuthorPicture( baseNote = it.key, - navController = navController, + nav = nav, userAccount = account.userProfile(), size = 35.dp ) } } - UserCompose(baseUser = zapSetCard.user, accountViewModel = accountViewModel, navController = navController) + UserCompose(baseUser = zapSetCard.user, accountViewModel = accountViewModel, nav = nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index f96914f78..f3c923a68 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage @@ -21,10 +22,20 @@ fun AccountScreen(accountStateViewModel: AccountStateViewModel, startingPage: St LoginPage(accountStateViewModel, isFirstLogin = true) } is AccountState.LoggedIn -> { - MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) + val accountViewModel: AccountViewModel = viewModel( + key = state.account.userProfile().pubkeyHex, + factory = AccountViewModel.Factory(state.account) + ) + + MainScreen(accountViewModel, accountStateViewModel, startingPage) } is AccountState.LoggedInViewOnly -> { - MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) + val accountViewModel: AccountViewModel = viewModel( + key = state.account.userProfile().pubkeyHex, + factory = AccountViewModel.Factory(state.account) + ) + + MainScreen(accountViewModel, accountStateViewModel, startingPage) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt index df080b9f6..31de49a55 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt @@ -4,7 +4,10 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +@Immutable abstract class Card() { abstract fun createdAt(): Long abstract fun id(): String @@ -47,7 +50,7 @@ class ZapSetCard(val note: Note, val zapEvents: Map) : Card() { } @Immutable -class ZapUserSetCard(val user: User, val zapEvents: Map) : Card() { +class ZapUserSetCard(val user: User, val zapEvents: ImmutableMap) : Card() { val createdAt = zapEvents.maxOf { it.value.createdAt() ?: 0 } override fun createdAt(): Long { return createdAt @@ -56,7 +59,7 @@ class ZapUserSetCard(val user: User, val zapEvents: Map) : Card() { } @Immutable -class MultiSetCard(val note: Note, val boostEvents: List, val likeEvents: List, val zapEvents: Map) : Card() { +class MultiSetCard(val note: Note, val boostEvents: ImmutableList, val likeEvents: ImmutableList, val zapEvents: ImmutableMap) : Card() { val createdAt = maxOf( zapEvents.maxOfOrNull { it.value.createdAt() ?: 0 } ?: 0, likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0, @@ -91,7 +94,7 @@ class MessageSetCard(val note: Note) : Card() { sealed class CardFeedState { object Loading : CardFeedState() - class Loaded(val feed: MutableState>) : CardFeedState() + class Loaded(val feed: MutableState>) : CardFeedState() object Empty : CardFeedState() class FeedError(val errorMessage: String) : CardFeedState() } 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 6b1d28edc..341f93f91 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,6 +1,5 @@ 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 @@ -23,7 +22,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.ui.note.BadgeCompose import com.vitorpamplona.amethyst.ui.note.BoostSetCompose import com.vitorpamplona.amethyst.ui.note.LikeSetCompose @@ -33,15 +31,13 @@ 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 fun CardFeedView( viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, routeForLastRead: String, scrollStateKey: String? = null, scrollToTop: Boolean = false @@ -74,7 +70,7 @@ fun CardFeedView( FeedLoaded( state = state, accountViewModel = accountViewModel, - navController = navController, + nav = nav, routeForLastRead = routeForLastRead, scrollStateKey = scrollStateKey, scrollToTop = scrollToTop @@ -91,12 +87,11 @@ fun CardFeedView( } } -@OptIn(ExperimentalTime::class) @Composable private fun FeedLoaded( state: CardFeedState.Loaded, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, routeForLastRead: String, scrollStateKey: String?, scrollToTop: Boolean = false @@ -121,64 +116,61 @@ private fun FeedLoaded( state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item -> - 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 - ) - } + when (item) { + is NoteCard -> NoteCompose( + item.note, + isBoostedNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is ZapSetCard -> ZapSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is ZapUserSetCard -> ZapUserSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is LikeSetCard -> LikeSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is BoostSetCard -> BoostSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is MultiSetCard -> MultiSetCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is BadgeCard -> BadgeCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + is MessageSetCard -> MessageSetCompose( + messageSetCard = item, + routeForLastRead = routeForLastRead, + accountViewModel = accountViewModel, + nav = nav + ) } - Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed ${item.id()}") } } } 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 40ed18d80..5f7567338 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 @@ -20,6 +20,9 @@ import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -59,17 +62,28 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { if (newCards.isNotEmpty()) { lastNotes = notes.toSet() lastAccount = (localFilter as? NotificationFeedFilter)?.account - val singleList = (oldNotesState.feed.value + newCards) + + val updatedCards = (oldNotesState.feed.value + newCards) .distinctBy { it.id() } .sortedWith(compareBy({ it.createdAt() }, { it.id() })) .reversed() .take(1000) - updateFeed(singleList) + .toImmutableList() + + if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) { + updateFeed(updatedCards) + } } } else { - val cards = convertToCard(notes) lastNotes = notes.toSet() lastAccount = (localFilter as? NotificationFeedFilter)?.account + + val cards = convertToCard(notes) + .sortedWith(compareBy({ it.createdAt() }, { it.id() })) + .reversed() + .take(1000) + .toImmutableList() + updateFeed(cards) } } @@ -138,9 +152,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { singleList.chunked(50).map { chunk -> MultiSetCard( baseNote, - boostsInCard.filter { it in chunk }, - reactionsInCard.filter { it in chunk }, - zapsInCard.filter { it.value in chunk } + boostsInCard.filter { it in chunk }.toImmutableList(), + reactionsInCard.filter { it in chunk }.toImmutableList(), + zapsInCard.filter { it.value in chunk }.toImmutableMap() ) } }.flatten() @@ -148,7 +162,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { val userZaps = zapsPerUser.map { ZapUserSetCard( it.key, - it.value + it.value.toImmutableMap() ) } @@ -165,7 +179,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { return (multiCards + textNoteCards + userZaps).sortedWith(compareBy({ it.createdAt() }, { it.id() })).reversed() } - private fun updateFeed(notes: List) { + private fun updateFeed(notes: ImmutableList) { val scope = CoroutineScope(Job() + Dispatchers.Main) scope.launch { val currentState = _feedContent.value @@ -173,7 +187,6 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { if (notes.isEmpty()) { _feedContent.update { CardFeedState.Empty } } else if (currentState is CardFeedState.Loaded) { - // updates the current list currentState.feed.value = notes } else { _feedContent.update { CardFeedState.Loaded(mutableStateOf(notes)) } @@ -207,8 +220,11 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { .sortedWith(compareBy({ it.createdAt() }, { it.id() })) .reversed() .take(1000) + .toImmutableList() - updateFeed(updatedCards) + if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) { + updateFeed(updatedCards) + } } } else { // Refresh Everything @@ -227,8 +243,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } private val bundlerInsert = BundledInsert>(250, Dispatchers.IO) - fun invalidateData() { - bundler.invalidate() + fun invalidateData(ignoreIfDoing: Boolean = false) { + bundler.invalidate(ignoreIfDoing) } @OptIn(ExperimentalTime::class) @@ -270,3 +286,13 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { super.onCleared() } } + +fun equalImmutableLists(list1: ImmutableList, list2: ImmutableList): Boolean { + if (list1.size != list2.size) return false + for (i in 0 until list1.size) { + if (list1[i] !== list2[i]) { + return false + } + } + return true +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt index 1f67a61f3..ff929707e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt @@ -14,13 +14,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String, onWantsToReply: (Note) -> Unit) { +fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, routeForLastRead: String, onWantsToReply: (Note) -> Unit) { val feedState by viewModel.feedContent.collectAsState() var isRefreshing by remember { mutableStateOf(false) } @@ -63,7 +62,7 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> - ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController, onWantsToReply = onWantsToReply) + ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, nav = nav, onWantsToReply = onWantsToReply) } } } 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 60905dbc3..736976132 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 @@ -24,7 +24,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.model.PrivateDmEvent @@ -36,7 +35,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun ChatroomListFeedView( viewModel: FeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, markAsRead: MutableState ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() @@ -68,7 +67,7 @@ fun ChatroomListFeedView( if (refreshing) { refreshing = false } - FeedLoaded(state, accountViewModel, navController, markAsRead) + FeedLoaded(state, accountViewModel, nav, markAsRead) } FeedState.Loading -> { @@ -86,7 +85,7 @@ fun ChatroomListFeedView( private fun FeedLoaded( state: FeedState.Loaded, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, markAsRead: MutableState ) { val listState = rememberLazyListState() @@ -139,7 +138,7 @@ private fun FeedLoaded( ChatroomCompose( item, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index e5faa3fdc..72a7b3194 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -39,7 +38,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun FeedView( viewModel: FeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, routeForLastRead: String?, scrollStateKey: String? = null, scrollToTop: Boolean = false, @@ -84,7 +83,7 @@ fun FeedView( state, routeForLastRead, accountViewModel, - navController, + nav, scrollStateKey, scrollToTop ) @@ -108,7 +107,7 @@ private fun FeedLoaded( state: FeedState.Loaded, routeForLastRead: String?, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollStateKey: String?, scrollToTop: Boolean = false ) { @@ -142,7 +141,7 @@ private fun FeedLoaded( modifier = baseModifier, isBoostedNote = false, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index f6811611b..e32473ad1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -30,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun LnZapFeedView( viewModel: LnZapFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, enablePullRefresh: Boolean = true ) { val feedState by viewModel.feedContent.collectAsState() @@ -64,7 +63,7 @@ fun LnZapFeedView( refreshing = false } - LnZapFeedLoaded(state, accountViewModel, navController) + LnZapFeedLoaded(state, accountViewModel, nav) } is LnZapFeedState.Loading -> { LoadingFeed() @@ -83,7 +82,7 @@ fun LnZapFeedView( private fun LnZapFeedLoaded( state: LnZapFeedState.Loaded, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val listState = rememberLazyListState() @@ -95,7 +94,7 @@ private fun LnZapFeedLoaded( state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { _, item -> - ZapNoteCompose(item, accountViewModel = accountViewModel, navController = navController) + ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav) } } } 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 cc1d47a35..86f85c37f 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 @@ -49,7 +49,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note @@ -82,7 +81,7 @@ import kotlinx.coroutines.delay @OptIn(ExperimentalMaterialApi::class) @Composable -fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { +fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedState by viewModel.feedContent.collectAsState() val listState = rememberLazyListState() @@ -139,7 +138,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A if (item.idHex == noteId) MaterialTheme.colors.primary.copy(alpha = 0.52f) else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ), accountViewModel = accountViewModel, - navController = navController + nav = nav ) } else { Column() { @@ -155,7 +154,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A isBoostedNote = false, unPackReply = false, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -204,7 +203,7 @@ fun NoteMaster( baseNote: Note, modifier: Modifier = Modifier, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteState by baseNote.live().metadata.observeAsState() val note = noteState?.note @@ -233,7 +232,7 @@ fun NoteMaster( account.userProfile(), Modifier, false, - navController, + nav, onClick = { showHiddenNote = true } ) } else { @@ -247,13 +246,13 @@ fun NoteMaster( .padding(start = 12.dp, end = 12.dp) .clickable(onClick = { note.author?.let { - navController.navigate("User/${it.pubkeyHex}") + nav("User/${it.pubkeyHex}") } }) ) { NoteAuthorPicture( baseNote = baseNote, - navController = navController, + nav = nav, userAccount = account.userProfile(), size = 55.dp ) @@ -262,7 +261,7 @@ fun NoteMaster( Row(verticalAlignment = Alignment.CenterVertically) { NoteUsernameDisplay(baseNote, Modifier.weight(1f)) - DisplayFollowingHashtagsInPost(noteEvent, account, navController) + DisplayFollowingHashtagsInPost(noteEvent, account, nav) Text( timeAgo(note.createdAt(), context = context), @@ -290,7 +289,7 @@ fun NoteMaster( val baseReward = noteEvent.getReward() if (baseReward != null) { - DisplayReward(baseReward, baseNote, account, navController) + DisplayReward(baseReward, baseNote, account, nav) } val pow = noteEvent.getPoWRank() @@ -353,11 +352,11 @@ fun NoteMaster( ) { Column() { if (noteEvent is PeopleListEvent) { - DisplayPeopleList(noteState, MaterialTheme.colors.background, accountViewModel, navController) + DisplayPeopleList(noteState, MaterialTheme.colors.background, accountViewModel, nav) } else if (noteEvent is AudioTrackEvent) { - AudioTrackHeader(noteEvent, note, account.userProfile(), navController) + AudioTrackHeader(noteEvent, note, account.userProfile(), nav) } else if (noteEvent is PinListEvent) { - PinListHeader(noteState, MaterialTheme.colors.background, accountViewModel, navController) + PinListHeader(noteState, MaterialTheme.colors.background, accountViewModel, nav) } else if (noteEvent is HighlightEvent) { DisplayHighlight( noteEvent.quote(), @@ -367,7 +366,7 @@ fun NoteMaster( true, backgroundColor, accountViewModel, - navController + nav ) } else { val eventContent = note.event?.content() @@ -384,10 +383,10 @@ fun NoteMaster( note.event?.tags(), MaterialTheme.colors.background, accountViewModel, - navController + nav ) - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) if (noteEvent is PollNoteEvent) { PollNote( @@ -395,13 +394,13 @@ fun NoteMaster( canPreview, backgroundColor, accountViewModel, - navController + nav ) } } } - ReactionsRow(note, accountViewModel, navController) + ReactionsRow(note, accountViewModel, nav) Divider( modifier = Modifier.padding(top = 10.dp), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index d2f8078cd..4c3946e6d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.navigation.NavController import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -30,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun UserFeedView( viewModel: UserFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, enablePullRefresh: Boolean = true ) { val feedState by viewModel.feedContent.collectAsState() @@ -61,7 +60,7 @@ fun UserFeedView( } is UserFeedState.Loaded -> { refreshing = false - FeedLoaded(state, accountViewModel, navController) + FeedLoaded(state, accountViewModel, nav) } is UserFeedState.Loading -> { LoadingFeed() @@ -80,7 +79,7 @@ fun UserFeedView( private fun FeedLoaded( state: UserFeedState.Loaded, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val listState = rememberLazyListState() @@ -92,7 +91,7 @@ private fun FeedLoaded( state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item -> - UserCompose(item, accountViewModel = accountViewModel, navController = navController) + UserCompose(item, accountViewModel = accountViewModel, nav = nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 05e5c7fa6..90dfbb5d4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -3,9 +3,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import android.content.Context import android.content.Intent import android.net.Uri +import androidx.compose.runtime.Immutable import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.R @@ -23,6 +25,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Locale +@Immutable class AccountViewModel(private val account: Account) : ViewModel() { val accountLiveData: LiveData = account.live.map { it } val accountLanguagesLiveData: LiveData = account.liveLanguages.map { it } @@ -228,4 +231,10 @@ class AccountViewModel(private val account: Account) : ViewModel() { fun dontShowBlockAlertDialog() { account.setHideBlockAlertDialog() } + + class Factory(val account: Account) : ViewModelProvider.Factory { + override fun create(modelClass: Class): AccountViewModel { + return AccountViewModel(account) as AccountViewModel + } + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt index b9e79bef6..c81b6ea74 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BookmarkListScreen.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter @@ -30,7 +29,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun BookmarkListScreen(accountViewModel: AccountViewModel, navController: NavController) { +fun BookmarkListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account @@ -74,8 +73,8 @@ fun BookmarkListScreen(accountViewModel: AccountViewModel, navController: NavCon } HorizontalPager(pageCount = 2, state = pagerState) { page -> when (page) { - 0 -> FeedView(privateFeedViewModel, accountViewModel, navController, null) - 1 -> FeedView(publicFeedViewModel, accountViewModel, navController, null) + 0 -> FeedView(privateFeedViewModel, accountViewModel, nav, null) + 1 -> FeedView(publicFeedViewModel, accountViewModel, nav, null) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 1af7f02c3..2155f2e5d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Channel @@ -83,7 +82,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel fun ChannelScreen( channelId: String?, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account @@ -143,7 +142,7 @@ fun ChannelScreen( ChannelHeader( channel, account, - navController = navController + nav = nav ) Column( @@ -152,7 +151,7 @@ fun ChannelScreen( .padding(vertical = 0.dp) .weight(1f, true) ) { - ChatroomFeedView(feedViewModel, accountViewModel, navController, "Channel/$channelId") { + ChatroomFeedView(feedViewModel, accountViewModel, nav, "Channel/$channelId") { replyTo.value = it } } @@ -168,7 +167,7 @@ fun ChannelScreen( null, innerQuote = true, accountViewModel = accountViewModel, - navController = navController, + nav = nav, onWantsToReply = { replyTo.value = it } @@ -248,7 +247,7 @@ fun ChannelScreen( } @Composable -fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavController) { +fun ChannelHeader(baseChannel: Channel, account: Account, nav: (String) -> Unit) { val channelState by baseChannel.live.observeAsState() val channel = channelState?.channel ?: return @@ -256,7 +255,7 @@ fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavCont Column( Modifier.clickable { - navController.navigate("Channel/${baseChannel.idHex}") + nav("Channel/${baseChannel.idHex}") } ) { Column(modifier = Modifier.padding(12.dp)) { @@ -306,9 +305,9 @@ fun ChannelHeader(baseChannel: Channel, account: Account, navController: NavCont } if (account.followingChannels.contains(channel.idHex)) { - LeaveButton(account, channel, navController) + LeaveButton(account, channel, nav) } else { - JoinButton(account, channel, navController) + JoinButton(account, channel, nav) } } } @@ -386,7 +385,7 @@ private fun EditButton(account: Account, channel: Channel) { } @Composable -private fun JoinButton(account: Account, channel: Channel, navController: NavController) { +private fun JoinButton(account: Account, channel: Channel, nav: (String) -> Unit) { Button( modifier = Modifier.padding(horizontal = 3.dp), onClick = { @@ -404,12 +403,12 @@ private fun JoinButton(account: Account, channel: Channel, navController: NavCon } @Composable -private fun LeaveButton(account: Account, channel: Channel, navController: NavController) { +private fun LeaveButton(account: Account, channel: Channel, nav: (String) -> Unit) { Button( modifier = Modifier.padding(horizontal = 3.dp), onClick = { account.leaveChannel(channel.idHex) - navController.navigate(Route.Message.route) + nav(Route.Message.route) }, shape = RoundedCornerShape(20.dp), colors = ButtonDefaults diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt index 8c7c26e02..880006ecd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt @@ -39,7 +39,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter @@ -52,7 +51,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavController) { +fun ChatroomListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val pagerState = rememberPagerState() val coroutineScope = rememberCoroutineScope() @@ -151,7 +150,7 @@ fun ChatroomListScreen(accountViewModel: AccountViewModel, navController: NavCon ChatroomListFeedView( viewModel = tabs[page].viewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, markAsRead = tabs[page].markAsRead ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index cf75e066a..c853709de 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User @@ -61,7 +60,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel @Composable -fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { +fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account val context = LocalContext.current @@ -106,7 +105,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr Column(Modifier.fillMaxHeight()) { NostrChatroomDataSource.withUser?.let { - ChatroomHeader(it, account.userProfile(), navController = navController) + ChatroomHeader(it, account.userProfile(), nav = nav) } Column( @@ -115,7 +114,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr .padding(vertical = 0.dp) .weight(1f, true) ) { - ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/$userId") { + ChatroomFeedView(feedViewModel, accountViewModel, nav, "Room/$userId") { replyTo.value = it } } @@ -131,7 +130,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr null, innerQuote = true, accountViewModel = accountViewModel, - navController = navController, + nav = nav, onWantsToReply = { replyTo.value = it } @@ -208,10 +207,10 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr } @Composable -fun ChatroomHeader(baseUser: User, accountUser: User, navController: NavController) { +fun ChatroomHeader(baseUser: User, accountUser: User, nav: (String) -> Unit) { Column( modifier = Modifier.clickable( - onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } + onClick = { nav("User/${baseUser.pubkeyHex}") } ) ) { Column(modifier = Modifier.padding(12.dp)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt index b2cd429e8..4d8489073 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.NostrHashtagDataSource import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter @@ -32,7 +31,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable -fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navController: NavController) { +fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -76,7 +75,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navControlle modifier = Modifier.padding(vertical = 0.dp) ) { HashtagHeader(tag, account) - FeedView(feedViewModel, accountViewModel, navController, null) + FeedView(feedViewModel, accountViewModel, nav, null) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index 243e57289..fa8a42c76 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel @@ -27,7 +26,7 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun HiddenUsersScreen(accountViewModel: AccountViewModel, navController: NavController) { +fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account @@ -55,7 +54,7 @@ fun HiddenUsersScreen(accountViewModel: AccountViewModel, navController: NavCont } HorizontalPager(pageCount = 1, state = pagerState) { page -> when (page) { - 0 -> UserFeedView(feedViewModel, accountViewModel, navController) + 0 -> UserFeedView(feedViewModel, accountViewModel, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 713cafd32..da87f72ec 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter @@ -45,7 +44,7 @@ fun HomeScreen( homeFeedViewModel: NostrHomeFeedViewModel, repliesFeedViewModel: NostrHomeRepliesFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, pagerState: PagerState, scrollToTop: Boolean = false, nip47: String? = null @@ -120,7 +119,7 @@ fun HomeScreen( FeedView( viewModel = tabs[page].viewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, routeForLastRead = tabs[page].routeForLastRead, scrollStateKey = tabs[page].scrollStateKey, scrollToTop = scrollToTop 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 e230e9d42..035034165 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 @@ -53,6 +53,14 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun skipHalfExpanded = true ) + val nav = remember { + { route: String -> + if (getRouteWithArguments(navController) != route) { + navController.navigate(route) + } + } + } + val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } @@ -76,7 +84,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun AppTopBar(followLists, navController, scaffoldState, accountViewModel) }, drawerContent = { - DrawerContent(navController, scaffoldState, sheetState, accountViewModel) + DrawerContent(nav, scaffoldState, sheetState, accountViewModel) BackHandler(enabled = scaffoldState.drawerState.isOpen) { scope.launch { scaffoldState.drawerState.close() } } @@ -97,6 +105,14 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun fun FloatingButtons(navController: NavHostController, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) { val accountState by accountStateViewModel.accountContent.collectAsState() + val nav = remember { + { route: String -> + if (getRouteWithArguments(navController) != route) { + navController.navigate(route) + } + } + } + Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state -> when (state) { is AccountState.LoggedInViewOnly -> { @@ -107,13 +123,13 @@ fun FloatingButtons(navController: NavHostController, accountViewModel: AccountV } is AccountState.LoggedIn -> { if (currentRoute(navController)?.substringBefore("?") == Route.Home.base) { - NewNoteButton(state.account, accountViewModel, navController) + NewNoteButton(state.account, accountViewModel, nav) } if (currentRoute(navController) == Route.Message.base) { - ChannelFabColumn(state.account, navController) + ChannelFabColumn(state.account, nav) } if (currentRoute(navController)?.substringBefore("?") == Route.Video.base) { - NewImageButton(accountViewModel, navController) + NewImageButton(accountViewModel, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 79843b121..61ae8598e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis import com.patrykandpatrick.vico.compose.axis.vertical.endAxis import com.patrykandpatrick.vico.compose.axis.vertical.startAxis @@ -61,7 +60,7 @@ fun NotificationScreen( notifFeedViewModel: NotificationViewModel, userReactionsStatsModel: UserReactionsViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollToTop: Boolean = false ) { val accountState by accountViewModel.accountLiveData.observeAsState() @@ -74,8 +73,6 @@ fun NotificationScreen( LaunchedEffect(account.userProfile().pubkeyHex, account.defaultNotificationFollowList) { NostrAccountDataSource.resetFilters() NotificationFeedFilter.account = account - notifFeedViewModel.clear() - notifFeedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current @@ -83,7 +80,7 @@ fun NotificationScreen( val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { NotificationFeedFilter.account = account - notifFeedViewModel.invalidateData() + notifFeedViewModel.invalidateData(true) } } @@ -97,11 +94,11 @@ fun NotificationScreen( Column( modifier = Modifier.padding(vertical = 0.dp) ) { - SummaryBar(userReactionsStatsModel, accountViewModel, navController) + SummaryBar(userReactionsStatsModel, accountViewModel, nav) CardFeedView( viewModel = notifFeedViewModel, accountViewModel = accountViewModel, - navController = navController, + nav = nav, routeForLastRead = Route.Notification.base, scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN, scrollToTop = scrollToTop @@ -111,12 +108,12 @@ fun NotificationScreen( } @Composable -fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, navController: NavController) { +fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var showChart by remember { mutableStateOf(false) } - UserReactionsRow(model, accountViewModel, navController) { + UserReactionsRow(model, accountViewModel, nav) { showChart = !showChart } 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 485892a66..1fb9482eb 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 @@ -49,7 +49,6 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account @@ -103,7 +102,7 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal @Composable -fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { +fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) { if (userId == null) return var userBase by remember { mutableStateOf(null) } @@ -118,14 +117,14 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro ProfileScreen( user = it, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @OptIn(ExperimentalFoundationApi::class) @Composable -fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: NavController) { +fun ProfileScreen(user: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return @@ -210,7 +209,7 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: .fillMaxHeight() ) { Column(modifier = Modifier.padding()) { - ProfileHeader(baseUser, navController, account, accountViewModel) + ProfileHeader(baseUser, nav, account, accountViewModel) ScrollableTabRow( backgroundColor = MaterialTheme.colors.background, selectedTabIndex = pagerState.currentPage, @@ -246,13 +245,13 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: } ) { page -> when (page) { - 0 -> TabNotesNewThreads(accountViewModel, navController) - 1 -> TabNotesConversations(accountViewModel, navController) - 2 -> TabFollows(baseUser, accountViewModel, navController) - 3 -> TabFollowers(baseUser, accountViewModel, navController) - 4 -> TabReceivedZaps(baseUser, accountViewModel, navController) - 5 -> TabBookmarks(baseUser, accountViewModel, navController) - 6 -> TabReports(baseUser, accountViewModel, navController) + 0 -> TabNotesNewThreads(accountViewModel, nav) + 1 -> TabNotesConversations(accountViewModel, nav) + 2 -> TabFollows(baseUser, accountViewModel, nav) + 3 -> TabFollowers(baseUser, accountViewModel, nav) + 4 -> TabReceivedZaps(baseUser, accountViewModel, nav) + 5 -> TabBookmarks(baseUser, accountViewModel, nav) + 6 -> TabReports(baseUser, accountViewModel, nav) 7 -> TabRelays(baseUser, accountViewModel) } } @@ -331,7 +330,7 @@ private fun FollowTabHeader(baseUser: User) { @Composable private fun ProfileHeader( baseUser: User, - navController: NavController, + nav: (String) -> Unit, account: Account, accountViewModel: AccountViewModel ) { @@ -412,7 +411,7 @@ private fun ProfileHeader( .height(35.dp) .padding(bottom = 3.dp) ) { - MessageButton(baseUser, navController) + MessageButton(baseUser, nav) // No need for this button anymore // NPubCopyButton(baseUser) @@ -421,7 +420,7 @@ private fun ProfileHeader( } } - DrawAdditionalInfo(baseUser, account, accountViewModel, navController) + DrawAdditionalInfo(baseUser, account, accountViewModel, nav) Divider(modifier = Modifier.padding(top = 6.dp)) } @@ -468,7 +467,7 @@ private fun ProfileActions( } @Composable -private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewModel: AccountViewModel, navController: NavController) { +private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val userState by baseUser.live().metadata.observeAsState() val user = remember(userState) { userState?.user } ?: return val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags } @@ -534,7 +533,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode user, onScan = { dialogOpen = false - navController.navigate(it) + nav(it) }, onClose = { dialogOpen = false } ) @@ -553,7 +552,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode } } - DisplayBadges(baseUser, navController) + DisplayBadges(baseUser, nav) DisplayNip05ProfileStatus(user) @@ -613,7 +612,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode tags = null, backgroundColor = MaterialTheme.colors.background, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } } @@ -701,7 +700,7 @@ private fun DisplayLNAddress( @OptIn(ExperimentalLayoutApi::class) private fun DisplayBadges( baseUser: User, - navController: NavController + nav: (String) -> Unit ) { val userBadgeState by baseUser.live().badges.observeAsState() val userBadge = remember(userBadgeState) { userBadgeState?.user } ?: return @@ -716,7 +715,7 @@ private fun DisplayBadges( val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull() if (baseBadgeDefinition != null) { - BadgeThumb(baseBadgeDefinition, navController, 35.dp) + BadgeThumb(baseBadgeDefinition, nav, 35.dp) } } } @@ -728,12 +727,12 @@ private fun DisplayBadges( @Composable fun BadgeThumb( note: Note, - navController: NavController, + nav: (String) -> Unit, size: Dp, pictureModifier: Modifier = Modifier ) { BadgeThumb(note, size, pictureModifier) { - navController.navigate("Note/${it.idHex}") + nav("Note/${it.idHex}") } } @@ -831,7 +830,7 @@ private fun DrawBanner(baseUser: User) { } @Composable -fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavController) { +fun TabNotesNewThreads(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel() LaunchedEffect(Unit) { @@ -842,13 +841,13 @@ fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavCon Column( modifier = Modifier.padding(vertical = 0.dp) ) { - FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) + FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false) } } } @Composable -fun TabNotesConversations(accountViewModel: AccountViewModel, navController: NavController) { +fun TabNotesConversations(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel() LaunchedEffect(Unit) { @@ -859,13 +858,13 @@ fun TabNotesConversations(accountViewModel: AccountViewModel, navController: Nav Column( modifier = Modifier.padding(vertical = 0.dp) ) { - FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) + FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false) } } } @Composable -fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { +fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileBookmarksFeedViewModel = viewModel() LaunchedEffect(Unit) { @@ -876,13 +875,13 @@ fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, navControll Column( modifier = Modifier.padding(vertical = 0.dp) ) { - FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) + FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false) } } } @Composable -fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { +fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel() val userState by baseUser.live().follows.observeAsState() @@ -895,13 +894,13 @@ fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, navController Column( modifier = Modifier.padding(vertical = 0.dp) ) { - UserFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) + UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false) } } } @Composable -fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { +fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel() val userState by baseUser.live().follows.observeAsState() @@ -914,13 +913,13 @@ fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, navControll Column( modifier = Modifier.padding(vertical = 0.dp) ) { - UserFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) + UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false) } } } @Composable -fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { +fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel() val userState by baseUser.live().zaps.observeAsState() @@ -933,13 +932,13 @@ fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, navContr Column( modifier = Modifier.padding(vertical = 0.dp) ) { - LnZapFeedView(feedViewModel, accountViewModel, navController, enablePullRefresh = false) + LnZapFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false) } } } @Composable -fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { +fun TabReports(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val feedViewModel: NostrUserProfileReportFeedViewModel = viewModel() val userState by baseUser.live().reports.observeAsState() @@ -952,7 +951,7 @@ fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController Column( modifier = Modifier.padding(vertical = 0.dp) ) { - FeedView(feedViewModel, accountViewModel, navController, null, enablePullRefresh = false) + FeedView(feedViewModel, accountViewModel, nav, null, enablePullRefresh = false) } } } @@ -993,12 +992,12 @@ fun TabRelays(user: User, accountViewModel: AccountViewModel) { } @Composable -private fun MessageButton(user: User, navController: NavController) { +private fun MessageButton(user: User, nav: (String) -> Unit) { Button( modifier = Modifier .padding(horizontal = 3.dp) .width(50.dp), - onClick = { navController.navigate("Room/${user.pubkeyHex}") }, + onClick = { nav("Room/${user.pubkeyHex}") }, shape = RoundedCornerShape(20.dp), colors = ButtonDefaults .buttonColors( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 320e913c0..2bdce2350 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -48,7 +48,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Channel @@ -83,7 +82,7 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel fun SearchScreen( searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollToTop: Boolean = false ) { val lifeCycleOwner = LocalLifecycleOwner.current @@ -124,8 +123,8 @@ fun SearchScreen( Column( modifier = Modifier.padding(vertical = 0.dp) ) { - SearchBar(accountViewModel, navController) - FeedView(searchFeedViewModel, accountViewModel, navController, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) + SearchBar(accountViewModel, nav) + FeedView(searchFeedViewModel, accountViewModel, nav, null, ScrollStateKeys.GLOBAL_SCREEN, scrollToTop) } } } @@ -185,7 +184,7 @@ class SearchBarViewModel : ViewModel() { @OptIn(FlowPreview::class) @Composable -private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) { +private fun SearchBar(accountViewModel: AccountViewModel, nav: (String) -> Unit) { val searchBarViewModel: SearchBarViewModel = viewModel() searchBarViewModel.account = accountViewModel.accountLiveData.value?.account @@ -306,12 +305,12 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont ) { itemsIndexed(searchBarViewModel.hashtagResults.value, key = { _, item -> "#" + item }) { _, item -> HashtagLine(item) { - navController.navigate("Hashtag/$item") + nav("Hashtag/$item") } } itemsIndexed(searchBarViewModel.searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item -> - UserCompose(item, accountViewModel = accountViewModel, navController = navController) + UserCompose(item, accountViewModel = accountViewModel, nav = nav) } itemsIndexed(searchBarViewModel.searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item -> @@ -327,12 +326,12 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont channelLastTime = null, channelLastContent = item.info.about, false, - onClick = { navController.navigate("Channel/${item.idHex}") } + onClick = { nav("Channel/${item.idHex}") } ) } itemsIndexed(searchBarViewModel.searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item -> - NoteCompose(item, accountViewModel = accountViewModel, navController = navController) + NoteCompose(item, accountViewModel = accountViewModel, nav = nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt index 2d5d6aa53..524711e09 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt @@ -14,14 +14,13 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.screen.NostrThreadFeedViewModel import com.vitorpamplona.amethyst.ui.screen.ThreadFeedView @Composable -fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navController: NavController) { +fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val account by accountViewModel.accountLiveData.observeAsState() val lifeCycleOwner = LocalLifecycleOwner.current @@ -63,7 +62,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navControl Column( modifier = Modifier.padding(vertical = 0.dp) ) { - ThreadFeedView(noteId, feedViewModel, accountViewModel, navController) + ThreadFeedView(noteId, feedViewModel, accountViewModel, nav) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 7dc3d86dc..e6b9185dc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -59,7 +59,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted @@ -99,7 +98,7 @@ import kotlinx.coroutines.launch fun VideoScreen( videoFeedView: NostrVideoFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollToTop: Boolean = false ) { val lifeCycleOwner = LocalLifecycleOwner.current @@ -142,7 +141,7 @@ fun VideoScreen( Column( modifier = Modifier.padding(vertical = 0.dp) ) { - FeedView(videoFeedView, accountViewModel, navController, ScrollStateKeys.VIDEO_SCREEN, scrollToTop) + FeedView(videoFeedView, accountViewModel, nav, ScrollStateKeys.VIDEO_SCREEN, scrollToTop) } } } @@ -151,7 +150,7 @@ fun VideoScreen( fun FeedView( videoFeedView: NostrVideoFeedViewModel, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollStateKey: String? = null, scrollToTop: Boolean = false ) { @@ -176,7 +175,7 @@ fun FeedView( SlidingCarousel( state.feed, accountViewModel, - navController, + nav, scrollStateKey, scrollToTop ) @@ -196,7 +195,7 @@ fun FeedView( fun SlidingCarousel( feed: MutableState>, accountViewModel: AccountViewModel, - navController: NavController, + nav: (String) -> Unit, scrollStateKey: String? = null, scrollToTop: Boolean = false ) { @@ -222,7 +221,7 @@ fun SlidingCarousel( } ) { index -> feed.value.getOrNull(index)?.let { note -> - RenderVideoOrPictureNote(note, accountViewModel, navController) + RenderVideoOrPictureNote(note, accountViewModel, nav) } } } @@ -231,7 +230,7 @@ fun SlidingCarousel( private fun RenderVideoOrPictureNote( note: Note, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { val noteEvent = note.event @@ -255,7 +254,7 @@ private fun RenderVideoOrPictureNote( Column(Modifier.weight(1f)) { Row(Modifier.padding(10.dp), verticalAlignment = Alignment.Bottom) { Column(Modifier.size(55.dp), verticalArrangement = Arrangement.Center) { - NoteAuthorPicture(note, navController, loggedIn, 55.dp) + NoteAuthorPicture(note, nav, loggedIn, 55.dp) } Column( @@ -299,7 +298,7 @@ private fun RenderVideoOrPictureNote( verticalArrangement = Arrangement.Center ) { Row(horizontalArrangement = Arrangement.Center) { - ReactionsColumn(note, accountViewModel, navController) + ReactionsColumn(note, accountViewModel, nav) } } } @@ -342,7 +341,7 @@ private fun RelayBadges(baseNote: Note) { } @Composable -fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { +fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -355,11 +354,11 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navContr } if (wantsToReplyTo != null) { - NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, navController) + NewPostView({ wantsToReplyTo = null }, wantsToReplyTo, null, account, accountViewModel, nav) } if (wantsToQuote != null) { - NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, navController) + NewPostView({ wantsToQuote = null }, null, wantsToQuote, account, accountViewModel, nav) } Spacer(modifier = Modifier.height(8.dp)) @@ -380,7 +379,7 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, navContr @OptIn(ExperimentalPermissionsApi::class) @Composable -fun NewImageButton(accountViewModel: AccountViewModel, navController: NavController) { +fun NewImageButton(accountViewModel: AccountViewModel, nav: (String) -> Unit) { var wantsToPost by remember { mutableStateOf(false) } @@ -397,15 +396,7 @@ fun NewImageButton(accountViewModel: AccountViewModel, navController: NavControl // awaits an refresh on the list delay(250) val route = Route.Video.route.replace("{scrollToTop}", "true") - navController.navigate(route) { - navController.graph.startDestinationRoute?.let { start -> - popUpTo(start) { inclusive = false } - restoreState = true - } - - launchSingleTop = true - restoreState = true - } + nav(route) } } @@ -445,7 +436,7 @@ fun NewImageButton(accountViewModel: AccountViewModel, navController: NavControl onClose = { pickedURI = null }, postViewModel = postViewModel, accountViewModel = accountViewModel, - navController = navController + nav = nav ) } 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 8e55bfcca..4825ac45b 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 @@ -30,7 +30,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.core.os.ConfigurationCompat -import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.service.lang.ResultOrError @@ -47,7 +46,7 @@ fun TranslatableRichTextViewer( tags: List>?, backgroundColor: Color, accountViewModel: AccountViewModel, - navController: NavController + nav: (String) -> Unit ) { var translatedTextState by remember { mutableStateOf(ResultOrError(content, null, null, null)) @@ -93,7 +92,7 @@ fun TranslatableRichTextViewer( tags, backgroundColor, accountViewModel, - navController + nav ) val target = translatedTextState.targetLang From 2c0bfe2118e26fb32b1816985b2cbef543f89496 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 May 2023 17:01:30 -0400 Subject: [PATCH 41/76] v0.51.0 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7b4be8263..8322d9d3b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 174 - versionName "0.50.6" + versionCode 175 + versionName "0.51.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From e5aba62fddbbc3e62a80535c4823d708bf6a796b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 May 2023 18:11:51 -0400 Subject: [PATCH 42/76] Imports BahaUrlPreview library to add the proxy connection into it. --- app/build.gradle | 3 +- .../amethyst/model/UrlCachedPreviewer.kt | 6 +- .../service/previews/BahaUrlPreview.kt | 48 ++++++++ .../service/previews/IUrlPreviewCallback.kt | 6 + .../amethyst/service/previews/UrlInfoItem.kt | 12 ++ .../service/previews/UrlPreviewUtils.kt | 116 ++++++++++++++++++ .../amethyst/ui/components/UrlPreview.kt | 4 +- .../amethyst/ui/components/UrlPreviewCard.kt | 2 +- .../amethyst/ui/components/UrlPreviewState.kt | 2 +- 9 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/previews/IUrlPreviewCallback.kt create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreviewUtils.kt diff --git a/app/build.gradle b/app/build.gradle index 8322d9d3b..86f4d18f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,7 +131,8 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // link preview - implementation 'tw.com.oneup.www:Baha-UrlPreview:1.0.1' + implementation 'org.jsoup:jsoup:1.13.1' + //implementation 'tw.com.oneup.www:Baha-UrlPreview:1.0.1' // Encrypted Key Storage implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha06' diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt index 833d1155b..e13a06396 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt @@ -1,8 +1,8 @@ package com.vitorpamplona.amethyst.model -import com.baha.url.preview.BahaUrlPreview -import com.baha.url.preview.IUrlPreviewCallback -import com.baha.url.preview.UrlInfoItem +import com.vitorpamplona.amethyst.service.previews.BahaUrlPreview +import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback +import com.vitorpamplona.amethyst.service.previews.UrlInfoItem import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt new file mode 100644 index 000000000..e56493ceb --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/BahaUrlPreview.kt @@ -0,0 +1,48 @@ +package com.vitorpamplona.amethyst.service.previews + +import android.net.Uri +import kotlinx.coroutines.* +import java.util.* + +class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) { + val scope = CoroutineScope(Job() + Dispatchers.Main) + private val imageExtensionArray = arrayOf(".gif", ".png", ".jpg", ".jpeg", ".bmp", ".webp") + + fun fetchUrlPreview(timeOut: Int = 30000) { + val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -> + callback?.onFailed(throwable) + } + scope.launch(exceptionHandler) { + fetch(timeOut) + } + } + + private suspend fun fetch(timeOut: Int = 30000) { + lateinit var urlInfoItem: UrlInfoItem + if (checkIsImageUrl()) { + urlInfoItem = UrlInfoItem(url = url, image = url) + } else { + val document = getDocument(url, timeOut) + urlInfoItem = parseHtml(document) + urlInfoItem.url = url + } + callback?.onComplete(urlInfoItem) + } + + private fun checkIsImageUrl(): Boolean { + val uri = Uri.parse(url) + var isImage = false + for (imageExtension in imageExtensionArray) { + if (uri.path != null && uri.path!!.toLowerCase(Locale.getDefault()).endsWith(imageExtension)) { + isImage = true + break + } + } + return isImage + } + + fun cleanUp() { + scope.cancel() + callback = null + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/IUrlPreviewCallback.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/IUrlPreviewCallback.kt new file mode 100644 index 000000000..6d9778312 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/IUrlPreviewCallback.kt @@ -0,0 +1,6 @@ +package com.vitorpamplona.amethyst.service.previews + +interface IUrlPreviewCallback { + fun onComplete(urlInfo: UrlInfoItem) + fun onFailed(throwable: Throwable) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt new file mode 100644 index 000000000..553f9b1c1 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlInfoItem.kt @@ -0,0 +1,12 @@ +package com.vitorpamplona.amethyst.service.previews + +data class UrlInfoItem( + var url: String = "", + var title: String = "", + var description: String = "", + var image: String = "" +) { + fun allFetchComplete(): Boolean { + return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty() + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreviewUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreviewUtils.kt new file mode 100644 index 000000000..62c6e83e9 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/previews/UrlPreviewUtils.kt @@ -0,0 +1,116 @@ +package com.vitorpamplona.amethyst.service.previews + +import com.vitorpamplona.amethyst.service.HttpClient +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Document + +private const val ELEMENT_TAG_META = "meta" +private const val ATTRIBUTE_VALUE_PROPERTY = "property" +private const val ATTRIBUTE_VALUE_NAME = "name" +private const val ATTRIBUTE_VALUE_ITEMPROP = "itemprop" + +/* for if (urlInfo.title.isEmpty()) urlInfo.title = it.attr(CONTENT) + in META_OG_DESCRIPTION -> if (urlInfo.description.isEmpty()) { + urlInfo.description = + it.attr(CONTENT) + } + in META_OG_IMAGE -> if (urlInfo.image.isEmpty()) urlInfo.image = it.attr(CONTENT) + } + + when (it.attr(ATTRIBUTE_VALUE_NAME)) { + in META_NAME_TITLE -> if (urlInfo.title.isEmpty()) urlInfo.title = it.attr(CONTENT) + in META_NAME_DESCRIPTION -> if (urlInfo.description.isEmpty()) { + urlInfo.description = + it.attr(CONTENT) + } + in META_OG_IMAGE -> if (urlInfo.image.isEmpty()) urlInfo.image = it.attr(CONTENT) + } + + when (it.attr(ATTRIBUTE_VALUE_ITEMPROP)) { + in META_ITEMPROP_TITLE -> if (urlInfo.title.isEmpty()) { + urlInfo.title = + it.attr(CONTENT) + } + in META_ITEMPROP_DESCRIPTION -> if (urlInfo.description.isEmpty()) { + urlInfo.description = + it.attr( + CONTENT + ) + } + in META_ITEMPROP_IMAGE -> if (urlInfo.image.isEmpty()) { + urlInfo.image = it.attr( + CONTENT + ) + } + } + if (urlInfo.allFetchComplete()) { + return@withContext urlInfo + } + } + return@withContext urlInfo + } 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 b4d914a40..a1d8e97ca 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 @@ -10,10 +10,10 @@ 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 -import com.baha.url.preview.UrlInfoItem import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.UrlCachedPreviewer +import com.vitorpamplona.amethyst.service.previews.IUrlPreviewCallback +import com.vitorpamplona.amethyst.service.previews.UrlInfoItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt index 88de0195b..ef5673f21 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil.compose.AsyncImage -import com.baha.url.preview.UrlInfoItem import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.service.previews.UrlInfoItem import java.net.URL @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewState.kt index ee0089e56..e992924e1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewState.kt @@ -1,6 +1,6 @@ package com.vitorpamplona.amethyst.ui.components -import com.baha.url.preview.UrlInfoItem +import com.vitorpamplona.amethyst.service.previews.UrlInfoItem sealed class UrlPreviewState { object Loading : UrlPreviewState() From 493888d121cf86c5e18271add914f5acc77bfbe9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 May 2023 18:12:46 -0400 Subject: [PATCH 43/76] v0.51.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 86f4d18f9..caf7b6b51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 175 - versionName "0.51.0" + versionCode 176 + versionName "0.51.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 1ccaa1a028a9e32cb366e0955e12e7a8f642219a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 08:56:04 -0400 Subject: [PATCH 44/76] Removing the start of relays before logging in. --- .../main/java/com/vitorpamplona/amethyst/ServiceManager.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index f283fa812..3f0eab359 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -15,7 +15,6 @@ import com.vitorpamplona.amethyst.service.NostrThreadDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource import com.vitorpamplona.amethyst.service.NostrVideoDataSource import com.vitorpamplona.amethyst.service.relays.Client -import com.vitorpamplona.amethyst.service.relays.Constants object ServiceManager { private var account: Account? = null @@ -46,9 +45,6 @@ object ServiceManager { NostrSingleEventDataSource.start() NostrSingleChannelDataSource.start() NostrSingleUserDataSource.start() - } else { - // if not logged in yet, start a basic service wit default relays - Client.connect(Constants.convertDefaultRelays()) } } From 310a93ef29419c437e4452e178effb96b1b18240 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 16:24:16 -0400 Subject: [PATCH 45/76] removes logs --- .../com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt | 2 -- 1 file changed, 2 deletions(-) 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 8dd993fb0..50fe73049 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 @@ -123,7 +123,6 @@ private fun RowScope.HasNewItemsIcon( LaunchedEffect(key1 = notifState, key2 = accountState) { scope.launch(Dispatchers.IO) { val newHasNewItems = route.hasNewItems(account, notif, emptySet()) - println("Notification Change ${route.route} $hasNewItems -> $newHasNewItems") if (newHasNewItems != hasNewItems) { hasNewItems = newHasNewItems } @@ -134,7 +133,6 @@ private fun RowScope.HasNewItemsIcon( scope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { val newHasNewItems = route.hasNewItems(account, notif, it) - println("Notification From Base ${route.route} $hasNewItems -> $newHasNewItems") if (newHasNewItems != hasNewItems) { hasNewItems = newHasNewItems } From aed7f46ca031d5431f65c978b0627b855c473967 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:25:47 -0400 Subject: [PATCH 46/76] remembering size of the robohash picture --- .../amethyst/ui/components/RobohashAsyncImage.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index 272f77c1f..fdf9c3099 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -34,7 +34,9 @@ fun RobohashAsyncImage( ) { val context = LocalContext.current val size = with(LocalDensity.current) { - robotSize.roundToPx() + remember { + robotSize.roundToPx() + } } val imageRequest = remember(robotSize, robot) { From e9bdc045ba67178c403b3f2625d121708be805a3 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:27:05 -0400 Subject: [PATCH 47/76] Adds @Stable tag to Notification Card models --- .../vitorpamplona/amethyst/ui/note/UserReactionsRow.kt | 2 ++ .../vitorpamplona/amethyst/ui/screen/CardFeedState.kt | 9 +++++++++ .../amethyst/ui/screen/CardFeedViewModel.kt | 2 ++ 3 files changed, 13 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 7ed0e9a09..d70e44b3d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -13,6 +13,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -92,6 +93,7 @@ fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountVie } } +@Stable class UserReactionsViewModel : ViewModel() { var user: User? = null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt index 31de49a55..2cd9757a4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedState.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import kotlinx.collections.immutable.ImmutableList @@ -92,9 +93,17 @@ class MessageSetCard(val note: Note) : Card() { override fun id() = note.idHex } +@Immutable sealed class CardFeedState { + @Immutable object Loading : CardFeedState() + + @Stable class Loaded(val feed: MutableState>) : CardFeedState() + + @Immutable object Empty : CardFeedState() + + @Immutable class FeedError(val errorMessage: String) : CardFeedState() } 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 5f7567338..f2bb269e0 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 @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.ui.screen import android.util.Log +import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -33,6 +34,7 @@ import kotlinx.coroutines.launch import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue +@Stable class NotificationViewModel : CardFeedViewModel(NotificationFeedFilter) open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { From 5ce19474ae887a351440009b413daf0cf04e5afe Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:28:19 -0400 Subject: [PATCH 48/76] Moves Home and Video screen invalidations to follow the scrollTop parameter --- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 18 ++++++++++++------ .../amethyst/ui/screen/loggedIn/VideoScreen.kt | 14 +++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index da87f72ec..62959250a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.ui.screen.FeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @@ -58,9 +59,6 @@ fun HomeScreen( LaunchedEffect(accountViewModel, account.defaultHomeFollowList) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account - NostrHomeDataSource.resetFilters() - homeFeedViewModel.invalidateData() - repliesFeedViewModel.invalidateData() } if (wantsToAddNip47 != null) { @@ -73,9 +71,6 @@ fun HomeScreen( if (event == Lifecycle.Event.ON_RESUME) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account - NostrHomeDataSource.resetFilters() - homeFeedViewModel.invalidateData() - repliesFeedViewModel.invalidateData() } } @@ -85,6 +80,17 @@ fun HomeScreen( } } + if (scrollToTop) { + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + NostrHomeDataSource.resetFilters() + homeFeedViewModel.invalidateData(true) + repliesFeedViewModel.invalidateData(true) + } + } + } + val tabs by remember(homeFeedViewModel, repliesFeedViewModel) { mutableStateOf( listOf( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index e6b9185dc..0e417fa86 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -91,6 +91,7 @@ import com.vitorpamplona.amethyst.ui.screen.LoadingFeed import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -109,11 +110,19 @@ fun VideoScreen( NostrVideoDataSource.account = account VideoFeedFilter.account = account + if (scrollToTop) { + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + NostrVideoDataSource.resetFilters() + videoFeedView.invalidateData() + } + } + } + LaunchedEffect(accountViewModel, accountState.value?.account?.defaultStoriesFollowList) { VideoFeedFilter.account = account NostrVideoDataSource.account = account - NostrVideoDataSource.resetFilters() - videoFeedView.invalidateData() } DisposableEffect(accountViewModel) { @@ -123,7 +132,6 @@ fun VideoScreen( VideoFeedFilter.account = account NostrVideoDataSource.account = account NostrVideoDataSource.start() - videoFeedView.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) { println("Video Stop") From b40c6f1361ff60ce1471624ccdc457f2b6f0b195 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:28:34 -0400 Subject: [PATCH 49/76] remember modifiers to avoid recomposition --- .../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 540985d0e..b662e2487 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 @@ -2026,7 +2026,7 @@ fun UserPicture( baseUser: User, baseUserAccount: User, size: Dp, - modifier: Modifier = Modifier, + modifier: Modifier = remember { Modifier }, onClick: ((User) -> Unit)? = null, onLongClick: ((User) -> Unit)? = null ) { From 6a3600fd732e69ba806d9cbb132ae503d9d15633 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:28:51 -0400 Subject: [PATCH 50/76] Updates Notification lists in intervals of 1 second. --- .../com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f2bb269e0..bae39a5fe 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 @@ -235,7 +235,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } @OptIn(ExperimentalTime::class) - private val bundler = BundledUpdate(250, Dispatchers.IO) { + private val bundler = BundledUpdate(1000, Dispatchers.IO) { // adds the time to perform the refresh into this delay // holding off new updates in case of heavy refresh routines. val (value, elapsed) = measureTimedValue { @@ -243,7 +243,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } Log.d("Time", "${this.javaClass.simpleName} Card update $elapsed") } - private val bundlerInsert = BundledInsert>(250, Dispatchers.IO) + private val bundlerInsert = BundledInsert>(1000, Dispatchers.IO) fun invalidateData(ignoreIfDoing: Boolean = false) { bundler.invalidate(ignoreIfDoing) From 4f2c78e73a4f98e91a53c5b2d1acdb3f53ad13ec Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:29:41 -0400 Subject: [PATCH 51/76] Remembers formatting for the Notification Reaction Row --- .../amethyst/ui/note/UserReactionsRow.kt | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index d70e44b3d..189cbbc7a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier @@ -41,7 +42,6 @@ import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import kotlinx.coroutines.Dispatchers @@ -54,7 +54,10 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter @Composable -fun UserReactionsRow(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, onClick: () -> Unit) { +fun UserReactionsRow( + model: UserReactionsViewModel, + onClick: () -> Unit +) { Row( verticalAlignment = CenterVertically, modifier = Modifier @@ -102,13 +105,12 @@ class UserReactionsViewModel : ViewModel() { var zaps by mutableStateOf>(emptyMap()) var replies by mutableStateOf>(emptyMap()) - var takenIntoAccount = setOf() - - val sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd") // SimpleDateFormat() - var chartModel by mutableStateOf?>(null) var axisLabels by mutableStateOf>(emptyList()) + private var takenIntoAccount = setOf() + private val sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd") // SimpleDateFormat() + fun load(baseUser: User) { user = baseUser reactions = emptyMap() @@ -277,6 +279,8 @@ class UserReactionsViewModel : ViewModel() { fun UserReplyReaction( replyCount: Int? ) { + val showCounts = remember { showCount(replyCount) } + Icon( painter = painterResource(R.drawable.ic_comment), null, @@ -287,7 +291,7 @@ fun UserReplyReaction( Spacer(modifier = Modifier.width(10.dp)) Text( - showCount(replyCount), + showCounts, fontWeight = FontWeight.Bold, fontSize = 18.sp ) @@ -297,6 +301,8 @@ fun UserReplyReaction( fun UserBoostReaction( boostCount: Int? ) { + val showCounts = remember { showCount(boostCount) } + Icon( painter = painterResource(R.drawable.ic_retweeted), null, @@ -307,7 +313,7 @@ fun UserBoostReaction( Spacer(modifier = Modifier.width(10.dp)) Text( - showCount(boostCount), + showCounts, fontWeight = FontWeight.Bold, fontSize = 18.sp ) @@ -317,6 +323,8 @@ fun UserBoostReaction( fun UserLikeReaction( likeCount: Int? ) { + val showCounts = remember { showCount(likeCount) } + Icon( painter = painterResource(R.drawable.ic_liked), null, @@ -327,7 +335,7 @@ fun UserLikeReaction( Spacer(modifier = Modifier.width(10.dp)) Text( - showCount(likeCount), + showCounts, fontWeight = FontWeight.Bold, fontSize = 18.sp ) @@ -337,6 +345,8 @@ fun UserLikeReaction( fun UserZapReaction( amount: BigDecimal? ) { + val showAmounts = remember { showAmountAxis(amount) } + Icon( imageVector = Icons.Default.Bolt, contentDescription = stringResource(R.string.zaps), @@ -347,7 +357,7 @@ fun UserZapReaction( Spacer(modifier = Modifier.width(8.dp)) Text( - showAmountAxis(amount), + showAmounts, fontWeight = FontWeight.Bold, fontSize = 18.sp ) From 9f394900a725c74034079b0f0676a2085a0f1944 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:30:15 -0400 Subject: [PATCH 52/76] Only scrolls to the top if not at the top --- .../java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt | 4 +++- .../java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 341f93f91..fe85aaa2d 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 @@ -104,7 +104,9 @@ private fun FeedLoaded( if (scrollToTop) { LaunchedEffect(Unit) { - listState.scrollToItem(index = 0) + if (listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0) { + listState.scrollToItem(index = 0) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 72a7b3194..08d0fe524 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -119,7 +119,9 @@ private fun FeedLoaded( if (scrollToTop) { LaunchedEffect(Unit) { - listState.scrollToItem(index = 0) + if (listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0) { + listState.scrollToItem(index = 0) + } } } From 3bcda33fa618d6e5334ae793bc3faa819c5f1c01 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:30:42 -0400 Subject: [PATCH 53/76] Activates invalidations that can be ignored if another invalidation is already running. --- .../com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index a3f3da1a5..1686410bf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -115,8 +115,8 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel() { } private val bundlerInsert = BundledInsert>(250, Dispatchers.IO) - fun invalidateData() { - bundler.invalidate() + fun invalidateData(ignoreIfDoing: Boolean = false) { + bundler.invalidate(ignoreIfDoing) } fun invalidateInsertData(newItems: Set) { From 1693455d0dc80a9f1c5c7a708dad4d1488550820 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:33:22 -0400 Subject: [PATCH 54/76] Only removes scrollTop from Uri after 1 second and to avoid changing before composition finishes. --- .../amethyst/ui/navigation/AppNavigation.kt | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 492f05648..2d5b94007 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.VideoScreen import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @@ -103,8 +104,14 @@ fun AppNavigation( ) // Avoids running scroll to top when back button is pressed + // Changes this on a thread to avoid changing before it finishes the composition if (scrollToTop) { - it.arguments?.remove("scrollToTop") + LaunchedEffect(key1 = Unit) { + scope.launch { + delay(1000) + it.arguments?.remove("scrollToTop") + } + } } }) } @@ -121,8 +128,14 @@ fun AppNavigation( ) // Avoids running scroll to top when back button is pressed + // Changes this on a thread to avoid changing before it finishes the composition if (scrollToTop) { - it.arguments?.remove("scrollToTop") + LaunchedEffect(key1 = Unit) { + scope.launch { + delay(1000) + it.arguments?.remove("scrollToTop") + } + } } }) } @@ -144,10 +157,20 @@ fun AppNavigation( // Avoids running scroll to top when back button is pressed if (scrollToTop) { - it.arguments?.remove("scrollToTop") + LaunchedEffect(key1 = Unit) { + scope.launch { + delay(1000) + it.arguments?.remove("scrollToTop") + } + } } if (nip47 != null) { - it.arguments?.remove("nip47") + LaunchedEffect(key1 = Unit) { + scope.launch { + delay(1000) + it.arguments?.remove("nip47") + } + } } }) } @@ -165,8 +188,14 @@ fun AppNavigation( ) // Avoids running scroll to top when back button is pressed + // Changes this on a thread to avoid changing before it finishes the composition if (scrollToTop) { - it.arguments?.remove("scrollToTop") + LaunchedEffect(key1 = Unit) { + scope.launch { + delay(1000) + it.arguments?.remove("scrollToTop") + } + } } }) } From 2ed5eae029c6e2fb42b58d124582bfa1e2440256 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:34:40 -0400 Subject: [PATCH 55/76] Preserves invalidations of DataSources for Home, Videos and Notifications --- .../vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt | 4 +++- .../amethyst/ui/screen/loggedIn/NotificationScreen.kt | 6 +++--- .../amethyst/ui/screen/loggedIn/VideoScreen.kt | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 62959250a..e859f23d2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -59,6 +59,7 @@ fun HomeScreen( LaunchedEffect(accountViewModel, account.defaultHomeFollowList) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account + NostrHomeDataSource.invalidateFilters() } if (wantsToAddNip47 != null) { @@ -71,6 +72,7 @@ fun HomeScreen( if (event == Lifecycle.Event.ON_RESUME) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account + NostrHomeDataSource.invalidateFilters() } } @@ -84,7 +86,7 @@ fun HomeScreen( val scope = rememberCoroutineScope() LaunchedEffect(key1 = Unit) { scope.launch(Dispatchers.IO) { - NostrHomeDataSource.resetFilters() + NostrHomeDataSource.invalidateFilters() homeFeedViewModel.invalidateData(true) repliesFeedViewModel.invalidateData(true) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 61ae8598e..e37ba81c9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -70,8 +70,8 @@ fun NotificationScreen( notifFeedViewModel.clear() } - LaunchedEffect(account.userProfile().pubkeyHex, account.defaultNotificationFollowList) { - NostrAccountDataSource.resetFilters() + LaunchedEffect(accountViewModel, account.defaultNotificationFollowList) { + NostrAccountDataSource.invalidateFilters() NotificationFeedFilter.account = account } @@ -79,8 +79,8 @@ fun NotificationScreen( DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { + NostrAccountDataSource.invalidateFilters() NotificationFeedFilter.account = account - notifFeedViewModel.invalidateData(true) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 0e417fa86..b12ebcb66 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -123,6 +123,7 @@ fun VideoScreen( LaunchedEffect(accountViewModel, accountState.value?.account?.defaultStoriesFollowList) { VideoFeedFilter.account = account NostrVideoDataSource.account = account + NostrVideoDataSource.resetFilters() } DisposableEffect(accountViewModel) { From 6461c3e498ef7bd4408c48c3d2393cd36cfd892e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:35:23 -0400 Subject: [PATCH 56/76] Changes the structure of the notification view to increase speed. --- .../amethyst/ui/note/MultiSetCompose.kt | 217 +++++++++--------- .../amethyst/ui/screen/CardFeedView.kt | 155 ++++++++----- .../ui/screen/loggedIn/NotificationScreen.kt | 25 +- 3 files changed, 226 insertions(+), 171 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 339afcf93..7b847cdc7 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 @@ -19,7 +19,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -39,11 +38,9 @@ import androidx.compose.ui.unit.sp import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -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.model.UserState import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer @@ -55,7 +52,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.math.BigDecimal @OptIn(ExperimentalFoundationApi::class) @Composable @@ -66,7 +62,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val note = remember(noteState) { noteState?.note } ?: return val accountState by accountViewModel.accountLiveData.observeAsState() - val account = remember(accountState) { accountState?.account } ?: return + val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return var popupExpanded by remember { mutableStateOf(false) } @@ -92,55 +88,48 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val primaryColor = MaterialTheme.colors.primary.copy(0.12f) val defaultBackgroundColor = MaterialTheme.colors.background - val backgroundColor by remember(isNew) { - derivedStateOf { - if (isNew) { - primaryColor.compositeOver(defaultBackgroundColor) - } else { - defaultBackgroundColor - } - } + val backgroundColor = if (isNew) { + primaryColor.compositeOver(defaultBackgroundColor) + } else { + defaultBackgroundColor } - val columnModifier by remember(isNew, backgroundColor) { - derivedStateOf { - Modifier - .background(backgroundColor) - .padding( - start = 12.dp, - end = 12.dp, - top = 10.dp - ) - .combinedClickable( - onClick = { - scope.launch { - routeFor( - baseNote, - account.userProfile() - )?.let { nav(it) } - } - }, - onLongClick = { popupExpanded = true } - ) - .fillMaxWidth() - } - } + val columnModifier = Modifier + .background(backgroundColor) + .padding( + start = 12.dp, + end = 12.dp, + top = 10.dp + ) + .combinedClickable( + onClick = { + scope.launch { + routeFor(baseNote, loggedIn)?.let { nav(it) } + } + }, + onLongClick = { popupExpanded = true } + ) + .fillMaxWidth() - val zapEvents = remember { multiSetCard.zapEvents } - val boostEvents = remember { multiSetCard.boostEvents } - val likeEvents = remember { multiSetCard.likeEvents } + val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } } + val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } } + val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } } + + val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } } + val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } } + val hasLikeEvents by remember { derivedStateOf { multiSetCard.likeEvents.isNotEmpty() } } Column(modifier = columnModifier) { - if (zapEvents.isNotEmpty()) { - RenderZapGallery(zapEvents, backgroundColor, nav, account, accountViewModel) + if (hasZapEvents) { + RenderZapGallery(zapEvents, backgroundColor, nav, accountViewModel) } - if (boostEvents.isNotEmpty()) { - RenderBoostGallery(boostEvents, backgroundColor, nav, account, accountViewModel) + if (hasBoostEvents) { + RenderBoostGallery(boostEvents, backgroundColor, nav, accountViewModel) } - if (likeEvents.isNotEmpty()) { - RenderLikeGallery(likeEvents, backgroundColor, nav, account, accountViewModel) + if (hasLikeEvents) { + RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel) } Row(Modifier.fillMaxWidth()) { @@ -167,7 +156,6 @@ private fun RenderLikeGallery( likeEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row(Modifier.fillMaxWidth()) { @@ -190,7 +178,7 @@ private fun RenderLikeGallery( ) } - AuthorGallery(likeEvents, backgroundColor, nav, account, accountViewModel) + AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel) } } @@ -199,7 +187,6 @@ private fun RenderZapGallery( zapEvents: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row(Modifier.fillMaxWidth()) { @@ -222,7 +209,7 @@ private fun RenderZapGallery( ) } - AuthorGalleryZaps(zapEvents, backgroundColor, nav, account, accountViewModel) + AuthorGalleryZaps(zapEvents, backgroundColor, nav, accountViewModel) } } @@ -231,7 +218,6 @@ private fun RenderBoostGallery( boostEvents: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { Row( @@ -258,7 +244,7 @@ private fun RenderBoostGallery( ) } - AuthorGallery(boostEvents, backgroundColor, nav, account, accountViewModel) + AuthorGallery(boostEvents, backgroundColor, nav, accountViewModel) } } @@ -267,15 +253,14 @@ fun AuthorGalleryZaps( authorNotes: ImmutableMap, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { - val accountState = account.userProfile().live().follows.observeAsState() - Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { - AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountState, accountViewModel) + Box() { + AuthorPictureAndComment(it.key, it.value, backgroundColor, nav, accountViewModel) + } } } } @@ -287,55 +272,45 @@ private fun AuthorPictureAndComment( zapEvent: Note?, backgroundColor: Color, nav: (String) -> Unit, - accountUser: State, accountViewModel: AccountViewModel ) { - val author = zapRequest.author ?: return - - var content by remember { mutableStateOf>(Triple(author, null, null)) } + var content by remember { mutableStateOf>(Triple(zapRequest.author, null, null)) } val scope = rememberCoroutineScope() LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) { - scope.launch(Dispatchers.IO) { + scope.launch(Dispatchers.Default) { (zapRequest.event as? LnZapRequestEvent)?.let { val decryptedContent = accountViewModel.decryptZap(zapRequest) val amount = (zapEvent?.event as? LnZapEvent)?.amount if (decryptedContent != null) { val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey) - content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, amount) + content = Triple(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount)) } else { if (!zapRequest.event?.content().isNullOrBlank() || amount != null) { - content = Triple(author, zapRequest.event?.content()?.ifBlank { null }, amount) + content = Triple(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount)) } } } } } - val modifier = remember(content.second) { - if (!content.second.isNullOrBlank()) { - Modifier - .fillMaxWidth() - .clickable { - nav("User/${author.pubkeyHex}") - } - } else { - Modifier.clickable { - nav("User/${author.pubkeyHex}") + content.first?.let { + val route by remember { + derivedStateOf { + "User/${it.pubkeyHex}" } } - } - AuthorPictureAndComment( - author = content.first, - comment = content.second, - amount = showAmountAxis(content.third), - backgroundColor = backgroundColor, - nav = nav, - accountUser = accountUser, - accountViewModel = accountViewModel, - modifier = modifier - ) + AuthorPictureAndComment( + author = it, + comment = content.second, + amount = content.third, + route = route, + backgroundColor = backgroundColor, + nav = nav, + accountViewModel = accountViewModel + ) + } } @Composable @@ -343,12 +318,27 @@ private fun AuthorPictureAndComment( author: User, comment: String?, amount: String?, + route: String, backgroundColor: Color, nav: (String) -> Unit, - accountUser: State, - accountViewModel: AccountViewModel, - modifier: Modifier + accountViewModel: AccountViewModel ) { + val authorPictureModifier = remember { Modifier } + + val modifier = remember(comment) { + if (comment != null) { + Modifier + .fillMaxWidth() + .clickable { + nav(route) + } + } else { + Modifier.clickable { + nav(route) + } + } + } + Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically @@ -356,8 +346,9 @@ private fun AuthorPictureAndComment( Box(modifier = remember { Modifier.size(35.dp) }, contentAlignment = Alignment.BottomCenter) { FastNoteAuthorPicture( author = author, - userAccount = accountUser, - size = 35.dp + size = 35.dp, + accountViewModel = accountViewModel, + pictureModifier = authorPictureModifier ) amount?.let { @@ -397,34 +388,47 @@ fun AuthorGallery( authorNotes: ImmutableList, backgroundColor: Color, nav: (String) -> Unit, - account: Account, accountViewModel: AccountViewModel ) { - val accountState = account.userProfile().live().follows.observeAsState() - Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { note -> - note.author?.let { user -> - val modifier = remember { - Modifier.clickable { - nav("User/${user.pubkeyHex}") - } - } - - AuthorPictureAndComment(user, null, null, backgroundColor, nav, accountState, accountViewModel, modifier) + Box(Modifier.size(35.dp)) { + NotePictureAndComment(note, backgroundColor, nav, accountViewModel) } } } } } +@Composable +private fun NotePictureAndComment( + baseNote: Note, + backgroundColor: Color, + nav: (String) -> Unit, + accountViewModel: AccountViewModel +) { + val author by remember(baseNote) { + derivedStateOf { + baseNote.author + } + } + + val route by remember(baseNote) { + derivedStateOf { + "User/${baseNote.author?.pubkeyHex}" + } + } + + author?.let { AuthorPictureAndComment(it, null, null, route, backgroundColor, nav, accountViewModel) } +} + @Composable fun FastNoteAuthorPicture( author: User, - userAccount: State, size: Dp, - pictureModifier: Modifier = Modifier + pictureModifier: Modifier = Modifier, + accountViewModel: AccountViewModel ) { val userState by author.live().metadata.observeAsState() val profilePicture = remember(userState) { @@ -435,8 +439,15 @@ fun FastNoteAuthorPicture( author.pubkeyHex } - val showFollowingMark = remember(userAccount.value) { - userAccount.value?.user?.isFollowingCached(author) == true || (author === userAccount.value?.user) + val accountState by accountViewModel.accountLiveData.observeAsState() + val loggedInLiveFollows = remember(accountState) { accountState?.account?.userProfile()?.live()?.follows } ?: return + + val accountFollowsState by loggedInLiveFollows.observeAsState() + + val showFollowingMark by remember(accountFollowsState) { + derivedStateOf { + accountFollowsState?.user?.isFollowingCached(author) == true || (author === accountFollowsState?.user) + } } UserPicture( 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 fe85aaa2d..7bd6b749f 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 @@ -5,7 +5,12 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi @@ -48,9 +53,14 @@ fun CardFeedView( val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) - Box(Modifier.pullRefresh(pullRefreshState)) { - Column() { - Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state -> + Box(Modifier.fillMaxSize().pullRefresh(pullRefreshState)) { + Column(modifier = Modifier.fillMaxSize()) { + Crossfade( + modifier = Modifier.fillMaxSize(), + targetState = feedState, + animationSpec = tween(durationMillis = 100) + ) { state -> + when (state) { is CardFeedState.Empty -> { FeedEmpty { @@ -110,68 +120,91 @@ private fun FeedLoaded( } } + LazyNotificationList(listState, state, routeForLastRead, accountViewModel, nav) +} + +@Composable +private fun LazyNotificationList( + listState: LazyListState, + state: CardFeedState.Loaded, + routeForLastRead: String, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { LazyColumn( - contentPadding = PaddingValues( - top = 10.dp, - bottom = 10.dp - ), + modifier = remember { Modifier.fillMaxSize() }, + contentPadding = remember { + PaddingValues( + top = 10.dp, + bottom = 10.dp + ) + }, state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item -> - when (item) { - is NoteCard -> NoteCompose( - item.note, - isBoostedNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is ZapSetCard -> ZapSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is ZapUserSetCard -> ZapUserSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is LikeSetCard -> LikeSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is BoostSetCard -> BoostSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is MultiSetCard -> MultiSetCompose( - item, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is BadgeCard -> BadgeCompose( - item, - accountViewModel = accountViewModel, - nav = nav, - routeForLastRead = routeForLastRead - ) - is MessageSetCard -> MessageSetCompose( - messageSetCard = item, - routeForLastRead = routeForLastRead, - accountViewModel = accountViewModel, - nav = nav - ) + Row(Modifier.fillMaxWidth().defaultMinSize(minHeight = 100.dp)) { + when (item) { + is NoteCard -> NoteCompose( + item.note, + isBoostedNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is ZapSetCard -> ZapSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is ZapUserSetCard -> ZapUserSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is LikeSetCard -> LikeSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is BoostSetCard -> BoostSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is MultiSetCard -> MultiSetCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is BadgeCard -> BadgeCompose( + item, + accountViewModel = accountViewModel, + nav = nav, + routeForLastRead = routeForLastRead + ) + + is MessageSetCard -> MessageSetCompose( + messageSetCard = item, + routeForLastRead = routeForLastRead, + accountViewModel = accountViewModel, + nav = nav + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index e37ba81c9..90089e97c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -13,6 +13,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.Brush @@ -51,6 +52,8 @@ import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode import kotlin.math.roundToInt @@ -64,10 +67,16 @@ fun NotificationScreen( scrollToTop: Boolean = false ) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return + val account = remember(accountState) { accountState?.account } ?: return if (scrollToTop) { - notifFeedViewModel.clear() + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + notifFeedViewModel.clear() + notifFeedViewModel.invalidateData(true) + } + } } LaunchedEffect(accountViewModel, account.defaultNotificationFollowList) { @@ -94,7 +103,9 @@ fun NotificationScreen( Column( modifier = Modifier.padding(vertical = 0.dp) ) { - SummaryBar(userReactionsStatsModel, accountViewModel, nav) + SummaryBar( + model = userReactionsStatsModel + ) CardFeedView( viewModel = notifFeedViewModel, accountViewModel = accountViewModel, @@ -108,12 +119,12 @@ fun NotificationScreen( } @Composable -fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) { +fun SummaryBar(model: UserReactionsViewModel) { var showChart by remember { mutableStateOf(false) } - UserReactionsRow(model, accountViewModel, nav) { + UserReactionsRow(model) { showChart = !showChart } @@ -153,8 +164,8 @@ fun SummaryBar(model: UserReactionsViewModel, accountViewModel: AccountViewModel targetVerticalAxisPosition = AxisPosition.Vertical.End ) - model.chartModel?.let { - if (showChart) { + if (showChart) { + model.chartModel?.let { Row( modifier = Modifier .padding(vertical = 10.dp, horizontal = 20.dp) From ff927d4830a7c78d8ccaea6e796c0776a0f880f6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:38:34 -0400 Subject: [PATCH 57/76] v0.51.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index caf7b6b51..40bca5d2c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 176 - versionName "0.51.1" + versionCode 177 + versionName "0.51.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 07ccf75d4473be81d3b8a7a82ecc74a34a129ee0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 18:43:01 -0400 Subject: [PATCH 58/76] updates jsoup and activity compose to the latest versions --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 40bca5d2c..444f18238 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,7 +86,7 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.activity:activity-compose:1.7.1' + implementation 'androidx.activity:activity-compose:1.7.2' implementation "androidx.compose.ui:ui:$compose_ui_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" @@ -131,7 +131,7 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // link preview - implementation 'org.jsoup:jsoup:1.13.1' + implementation 'org.jsoup:jsoup:1.16.1' //implementation 'tw.com.oneup.www:Baha-UrlPreview:1.0.1' // Encrypted Key Storage From fe317964b8e0074806c09766790f53b0d141d9e9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 20:39:15 -0400 Subject: [PATCH 59/76] Simplifying Private message rendering scheme --- .../amethyst/service/model/PrivateDmEvent.kt | 2 +- .../vitorpamplona/amethyst/ui/note/NoteCompose.kt | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 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 435e3b302..ef2b2478d 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 @@ -39,7 +39,7 @@ class PrivateDmEvent( fun with(pubkeyHex: String): Boolean { return pubkeyHex == pubKey || - tags.firstOrNull { it.size > 1 && it[0] == "p" }?.getOrNull(1) == pubkeyHex + tags.any { it.size > 1 && it[0] == "p" && it[1] == pubkeyHex } } fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? { 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 b662e2487..1aa8e6796 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 @@ -567,13 +567,18 @@ private fun RenderPrivateMessage( nav: (String) -> Unit ) { val noteEvent = note.event as? PrivateDmEvent ?: return + val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) } + val tags = remember(note.event?.id()) { note.event?.tags() } + val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() } + val modifier = remember(note.event?.id()) { Modifier.fillMaxWidth() } + val isAuthorTheLoggedUser = remember(note.event?.id()) { accountViewModel.isLoggedUser(note.author) } if (withMe) { val eventContent = remember { accountViewModel.decrypt(note) } if (eventContent != null) { - if (makeItShort && accountViewModel.isLoggedUser(note.author)) { + if (makeItShort && isAuthorTheLoggedUser) { Text( text = eventContent, color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), @@ -584,14 +589,14 @@ private fun RenderPrivateMessage( TranslatableRichTextViewer( content = eventContent, canPreview = canPreview && !makeItShort, - modifier = Modifier.fillMaxWidth(), - tags = noteEvent.tags(), + modifier = modifier, + tags = tags, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav ) - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) + DisplayUncitedHashtags(hashtags, eventContent, nav) } } } else { From 56a68b91004c816dc39140923288a103a091ed0a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 20:40:04 -0400 Subject: [PATCH 60/76] Feed invalidation is necessary for lists --- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 2 ++ .../ui/screen/loggedIn/NotificationScreen.kt | 21 ++++++++++--------- .../ui/screen/loggedIn/VideoScreen.kt | 21 ++++++++++--------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index e859f23d2..10130e4c6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -60,6 +60,8 @@ fun HomeScreen( HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account NostrHomeDataSource.invalidateFilters() + homeFeedViewModel.invalidateData(true) + repliesFeedViewModel.invalidateData(true) } if (wantsToAddNip47 != null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 90089e97c..c2e639701 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -69,19 +69,10 @@ fun NotificationScreen( val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return - if (scrollToTop) { - val scope = rememberCoroutineScope() - LaunchedEffect(key1 = Unit) { - scope.launch(Dispatchers.IO) { - notifFeedViewModel.clear() - notifFeedViewModel.invalidateData(true) - } - } - } - LaunchedEffect(accountViewModel, account.defaultNotificationFollowList) { NostrAccountDataSource.invalidateFilters() NotificationFeedFilter.account = account + notifFeedViewModel.invalidateData(true) } val lifeCycleOwner = LocalLifecycleOwner.current @@ -99,6 +90,16 @@ fun NotificationScreen( } } + if (scrollToTop) { + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + notifFeedViewModel.clear() + notifFeedViewModel.invalidateData(true) + } + } + } + Column(Modifier.fillMaxHeight()) { Column( modifier = Modifier.padding(vertical = 0.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index b12ebcb66..3c8541185 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -110,20 +110,11 @@ fun VideoScreen( NostrVideoDataSource.account = account VideoFeedFilter.account = account - if (scrollToTop) { - val scope = rememberCoroutineScope() - LaunchedEffect(key1 = Unit) { - scope.launch(Dispatchers.IO) { - NostrVideoDataSource.resetFilters() - videoFeedView.invalidateData() - } - } - } - LaunchedEffect(accountViewModel, accountState.value?.account?.defaultStoriesFollowList) { VideoFeedFilter.account = account NostrVideoDataSource.account = account NostrVideoDataSource.resetFilters() + videoFeedView.invalidateData() } DisposableEffect(accountViewModel) { @@ -146,6 +137,16 @@ fun VideoScreen( } } + if (scrollToTop) { + val scope = rememberCoroutineScope() + LaunchedEffect(key1 = Unit) { + scope.launch(Dispatchers.IO) { + NostrVideoDataSource.resetFilters() + videoFeedView.invalidateData() + } + } + } + Column(Modifier.fillMaxHeight()) { Column( modifier = Modifier.padding(vertical = 0.dp) From fa4463f1d7bf8f29821f58b6a520b0053a4a328a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 21:08:20 -0400 Subject: [PATCH 61/76] Improves rendering of messages in the Notification feed --- .../amethyst/ui/note/MessageSetCompose.kt | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 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 1e1b8c0a3..0cb22db4c 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 @@ -35,9 +35,14 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - val noteState by messageSetCard.note.live().metadata.observeAsState() + val baseNote = remember { messageSetCard.note } + + val noteState by baseNote.live().metadata.observeAsState() val note = remember(noteState) { noteState?.note } + val accountState by accountViewModel.accountLiveData.observeAsState() + val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return + var popupExpanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() @@ -78,8 +83,8 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, onClick = { scope.launch { routeFor( - note, - accountViewModel.userProfile() + baseNote, + loggedIn )?.let { nav(it) } } }, @@ -90,18 +95,11 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, Column(columnModifier) { Row(Modifier.fillMaxWidth()) { - Box(modifier = remember { Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp) }) { - Icon( - painter = painterResource(R.drawable.ic_dm), - null, - modifier = remember { Modifier.size(16.dp).align(Alignment.TopEnd) }, - tint = MaterialTheme.colors.primary - ) - } + MessageIcon() Column(modifier = remember { Modifier.padding(start = 10.dp) }) { NoteCompose( - baseNote = messageSetCard.note, + baseNote = baseNote, routeForLastRead = null, isBoostedNote = true, addMarginTop = false, @@ -116,3 +114,25 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, } } } + +@Composable +private fun MessageIcon() { + Box( + modifier = remember { + Modifier + .width(55.dp) + .padding(top = 5.dp, end = 5.dp) + } + ) { + Icon( + painter = painterResource(R.drawable.ic_dm), + null, + modifier = remember { + Modifier + .size(16.dp) + .align(Alignment.TopEnd) + }, + tint = MaterialTheme.colors.primary + ) + } +} From 755d89dc79dcc0a69c2364ffc3bf0e67fb4afbab Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 21:09:14 -0400 Subject: [PATCH 62/76] Only changing stats for the Reaction Row if the data actually changed. --- .../amethyst/ui/note/ReactionsRow.kt | 69 ++++++++++++++++--- 1 file changed, 58 insertions(+), 11 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 3c0fb6278..8d671a9f2 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 @@ -28,6 +28,7 @@ import androidx.compose.material.icons.outlined.Bolt import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -62,6 +63,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode +import kotlin.math.abs import kotlin.math.roundToInt @Composable @@ -124,6 +126,14 @@ fun ReplyReaction( val repliesState by baseNote.live().replies.observeAsState() val replies = remember(repliesState) { repliesState?.note?.replies } ?: emptySet() + val isWriteable = remember { accountViewModel.isWriteable() } + + val replyCount by remember(repliesState) { + derivedStateOf { + showCount(replies.size) + } + } + val context = LocalContext.current val scope = rememberCoroutineScope() @@ -138,7 +148,7 @@ fun ReplyReaction( IconButton( modifier = iconButtonModifier, onClick = { - if (accountViewModel.isWriteable()) { + if (isWriteable) { onPress() } else { scope.launch { @@ -161,7 +171,7 @@ fun ReplyReaction( if (showCounter) { Text( - " ${showCount(replies.size)}", + " $replyCount", fontSize = 14.sp, color = grayTint ) @@ -179,11 +189,25 @@ fun BoostReaction( val boostsState by baseNote.live().boosts.observeAsState() val boostedNote = remember(boostsState) { boostsState?.note } ?: return - val hasBoosted = remember(boostsState) { accountViewModel.hasBoosted(baseNote) } - val wasBoostedByLoggedIn = remember(boostsState) { boostedNote.isBoostedBy(accountViewModel.userProfile()) } + val hasBoosted by remember(boostsState) { + derivedStateOf { + accountViewModel.hasBoosted(baseNote) + } + } + + val wasBoostedByLoggedIn by remember(boostsState) { + derivedStateOf { + boostedNote.isBoostedBy(accountViewModel.userProfile()) + } + } + val isWriteable = remember { accountViewModel.isWriteable() } - val boostCount = remember(boostsState) { showCount(boostedNote.boosts.size) } + val boostCount by remember(boostsState) { + derivedStateOf { + showCount(boostedNote.boosts.size) + } + } val context = LocalContext.current val scope = rememberCoroutineScope() @@ -258,11 +282,25 @@ fun LikeReaction( val reactionsState by baseNote.live().reactions.observeAsState() val reactedNote = remember(reactionsState) { reactionsState?.note } ?: return - val hasReacted = remember(reactionsState) { accountViewModel.hasReactedTo(baseNote) } - val wasReactedByLoggedIn = remember(reactionsState) { reactedNote.isReactedBy(accountViewModel.userProfile()) } + val hasReacted by remember(reactionsState) { + derivedStateOf { + accountViewModel.hasReactedTo(baseNote) + } + } + + val wasReactedByLoggedIn by remember(reactionsState) { + derivedStateOf { + reactedNote.isReactedBy(accountViewModel.userProfile()) + } + } + val isWriteable = remember { accountViewModel.isWriteable() } - val reactionCount = remember(reactionsState) { showCount(reactedNote.reactions.size) } + val reactionCount by remember(reactionsState) { + derivedStateOf { + showCount(reactedNote.reactions.size) + } + } val context = LocalContext.current val scope = rememberCoroutineScope() @@ -350,13 +388,22 @@ fun ZapReaction( LaunchedEffect(key1 = zapsState) { scope.launch(Dispatchers.IO) { if (!wasZappedByLoggedInUser) { - wasZappedByLoggedInUser = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote) + val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(zappedNote) + + if (wasZappedByLoggedInUser != newWasZapped) { + wasZappedByLoggedInUser = newWasZapped + } } - zapAmountTxt = showAmount(account.calculateZappedAmount(zappedNote)) + val newZapAmount = showAmount(account.calculateZappedAmount(zappedNote)) + if (newZapAmount != zapAmountTxt) { + zapAmountTxt = newZapAmount + } if (wasZappedByLoggedInUser) { - zappingProgress = 1f + if (abs(zappingProgress - 1) < 0.001) { + zappingProgress = 1f + } } } } From a3adadb25134cf5cb6a2cd94d03c0bb5d3f6e086 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 May 2023 21:09:47 -0400 Subject: [PATCH 63/76] 0.51.3 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 444f18238..6a8831672 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 177 - versionName "0.51.2" + versionCode 178 + versionName "0.51.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 860f3bd09651aaadbcf642d6f6698775e657ee9d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 09:00:05 -0400 Subject: [PATCH 64/76] Improves color contrast for Zap Amount notifications in light mode --- .../amethyst/ui/note/MultiSetCompose.kt | 32 +++++++++++-------- .../amethyst/ui/note/UserReactionsRow.kt | 3 +- .../ui/screen/loggedIn/NotificationScreen.kt | 3 +- .../vitorpamplona/amethyst/ui/theme/Color.kt | 1 + .../vitorpamplona/amethyst/ui/theme/Theme.kt | 7 ++-- 5 files changed, 29 insertions(+), 17 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 7b847cdc7..63011188c 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 @@ -8,10 +8,12 @@ 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.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -28,6 +30,7 @@ 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 import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.res.painterResource @@ -237,7 +240,7 @@ private fun RenderBoostGallery( null, modifier = remember { Modifier - .size(18.dp) + .size(19.dp) .align(Alignment.TopEnd) }, tint = Color.Unspecified @@ -352,18 +355,21 @@ private fun AuthorPictureAndComment( ) amount?.let { - Box( - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colors.background.copy(0.52f)), - contentAlignment = Alignment.BottomCenter - ) { - Text( - text = it, - fontWeight = FontWeight.Bold, - color = BitcoinOrange, - fontSize = 12.sp - ) + Box(modifier = Modifier.fillMaxSize().clip(shape = CircleShape), contentAlignment = Alignment.BottomCenter) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colors.background.copy(0.62f)), + contentAlignment = Alignment.BottomCenter + ) { + Text( + text = it, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colors.secondaryVariant, + fontSize = 12.sp, + modifier = Modifier.padding(bottom = 1.dp) + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 189cbbc7a..ad06c950e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -44,6 +44,7 @@ import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import com.vitorpamplona.amethyst.ui.theme.RoyalBlue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -285,7 +286,7 @@ fun UserReplyReaction( painter = painterResource(R.drawable.ic_comment), null, modifier = Modifier.size(20.dp), - tint = Color.Cyan + tint = RoyalBlue ) Spacer(modifier = Modifier.width(10.dp)) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index c2e639701..b3090afec 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -52,6 +52,7 @@ import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import com.vitorpamplona.amethyst.ui.theme.RoyalBlue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.math.BigDecimal @@ -131,7 +132,7 @@ fun SummaryBar(model: UserReactionsViewModel) { val lineChartCount = lineChart( - lines = listOf(Color.Cyan, Color.Green, Color.Red).map { lineChartColor -> + lines = listOf(RoyalBlue, Color.Green, Color.Red).map { lineChartColor -> LineChart.LineSpec( lineColor = lineChartColor.toArgb(), lineBackgroundShader = DynamicShaders.fromBrush( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt index f2609a161..c21e0dfdd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt @@ -7,6 +7,7 @@ val Purple500 = Color(0xFF6200EE) val Purple700 = Color(0xFF3700B3) val Teal200 = Color(0xFF03DAC5) val BitcoinOrange = Color(0xFFF7931A) +val RoyalBlue = Color(0xFF4169E1) val Following = Color(0xFF03DAC5) val Nip05 = Color(0xFF01BAFF) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index ed7b30ae4..7fcedea44 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -7,19 +7,22 @@ import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView private val DarkColorPalette = darkColors( primary = Purple200, primaryVariant = Purple700, - secondary = Teal200 + secondary = Teal200, + secondaryVariant = Color(0xFFF7931A) ) private val LightColorPalette = lightColors( primary = Purple500, primaryVariant = Purple700, - secondary = Teal200 + secondary = Teal200, + secondaryVariant = Color(0xFFB66605) /* Other default colors to override background = Color.White, From 8dc9c2ed2faaad4e8253d92dd9a0b12b66a252c3 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 09:37:51 -0400 Subject: [PATCH 65/76] 0.51.4 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6a8831672..60376f023 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 178 - versionName "0.51.3" + versionCode 179 + versionName "0.51.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From d26b83a8514d9329151e54c3f0e818a3e96f7769 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 12:19:38 -0400 Subject: [PATCH 66/76] Updating kotlin dependencies. --- .idea/kotlinc.xml | 2 +- app/build.gradle | 3 ++- build.gradle | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0fc311313..217e5c51f 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 60376f023..0fb95ea62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ android { buildTypes { release { + // TODO: Make sure all of JSON parsers work when activating these. //minifyEnabled true //proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' resValue "string", "app_name", "@string/app_name_release" @@ -71,7 +72,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion "1.4.3" + kotlinCompilerExtensionVersion "1.4.7" } packagingOptions { diff --git a/build.gradle b/build.gradle index 0a999c817..e8f7522c3 100644 --- a/build.gradle +++ b/build.gradle @@ -14,10 +14,10 @@ buildscript { } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.0.1' apply false - id 'com.android.library' version '8.0.1' apply false - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false - id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false + id 'com.android.application' version '8.0.2' apply false + id 'com.android.library' version '8.0.2' apply false + id 'org.jetbrains.kotlin.android' version '1.8.21' apply false + id 'org.jetbrains.kotlin.jvm' version '1.8.21' apply false } task installGitHook(type: Copy) { From 39818268ab10694e085b85d99897f1b5e0257e7f Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 12:20:17 -0400 Subject: [PATCH 67/76] Support for NIP-36, declaration of sensitive content. --- .../amethyst/LocalPreferences.kt | 16 +- .../vitorpamplona/amethyst/model/Account.kt | 31 +++- .../service/model/ChannelMessageEvent.kt | 14 +- .../amethyst/service/model/Event.kt | 6 + .../amethyst/service/model/EventInterface.kt | 1 + .../amethyst/service/model/PollNoteEvent.kt | 6 +- .../amethyst/service/model/PrivateDmEvent.kt | 6 +- .../amethyst/service/model/TextNoteEvent.kt | 4 + .../amethyst/ui/actions/NewPostView.kt | 71 ++++++++- .../amethyst/ui/actions/NewPostViewModel.kt | 13 +- .../ui/components/SensitivityWarning.kt | 127 +++++++++++++++ .../amethyst/ui/note/AddBountyAmountDialog.kt | 9 +- .../ui/note/ChatroomMessageCompose.kt | 25 +-- .../amethyst/ui/note/NoteCompose.kt | 145 ++++++++++++------ .../amethyst/ui/note/PollNote.kt | 4 +- .../amethyst/ui/screen/ThreadFeedView.kt | 26 ++-- .../ui/screen/loggedIn/AccountViewModel.kt | 12 ++ .../ui/screen/loggedIn/ChannelScreen.kt | 2 +- .../ui/screen/loggedIn/ChatroomScreen.kt | 2 +- app/src/main/res/values/strings.xml | 6 + 20 files changed, 434 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 6984ff9a2..68547124e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -58,6 +58,7 @@ private object PrefKeys { const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog" const val USE_PROXY = "use_proxy" const val PROXY_PORT = "proxy_port" + const val SHOW_SENSITIVE_CONTENT = "show_sensitive_content" val LAST_READ: (String) -> String = { route -> "last_read_route_$route" } } @@ -214,6 +215,12 @@ object LocalPreferences { putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog) putBoolean(PrefKeys.USE_PROXY, account.proxy != null) putInt(PrefKeys.PROXY_PORT, account.proxyPort) + + if (account.showSensitiveContent == null) { + remove(PrefKeys.SHOW_SENSITIVE_CONTENT) + } else { + putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, account.showSensitiveContent!!) + } }.apply() } @@ -292,6 +299,12 @@ object LocalPreferences { val proxyPort = getInt(PrefKeys.PROXY_PORT, 9050) val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort) + val showSensitiveContent = if (contains(PrefKeys.SHOW_SENSITIVE_CONTENT)) { + getBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, false) + } else { + null + } + val a = Account( Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()), followingChannels, @@ -311,7 +324,8 @@ object LocalPreferences { hideBlockAlertDialog, latestContactList, proxy, - proxyPort + proxyPort, + showSensitiveContent ) return a 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 0bc3449ee..91ac9fb5f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -65,7 +65,8 @@ class Account( var hideBlockAlertDialog: Boolean = false, var backupContactList: ContactListEvent? = null, var proxy: Proxy?, - var proxyPort: Int + var proxyPort: Int, + var showSensitiveContent: Boolean? = null ) { var transientHiddenUsers: Set = setOf() @@ -480,7 +481,14 @@ class Account( return LocalCache.notes[signedEvent.id] } - fun sendPost(message: String, replyTo: List?, mentions: List?, tags: List? = null, zapReceiver: String? = null) { + fun sendPost( + message: String, + replyTo: List?, + mentions: List?, + tags: List? = null, + zapReceiver: String? = null, + wantsToMarkAsSensitive: Boolean + ) { if (!isWriteable()) return val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex } @@ -494,6 +502,7 @@ class Account( addresses = addresses, extraTags = tags, zapReceiver = zapReceiver, + markAsSensitive = wantsToMarkAsSensitive, privateKey = loggedIn.privKey!! ) @@ -510,7 +519,8 @@ class Account( valueMinimum: Int?, consensusThreshold: Int?, closedAt: Int?, - zapReceiver: String? = null + zapReceiver: String? = null, + wantsToMarkAsSensitive: Boolean ) { if (!isWriteable()) return @@ -529,14 +539,15 @@ class Account( valueMinimum = valueMinimum, consensusThreshold = consensusThreshold, closedAt = closedAt, - zapReceiver = zapReceiver + zapReceiver = zapReceiver, + markAsSensitive = wantsToMarkAsSensitive ) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) Client.send(signedEvent) LocalCache.consume(signedEvent) } - fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, zapReceiver: String? = null) { + fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) { if (!isWriteable()) return // val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } @@ -549,13 +560,14 @@ class Account( replyTos = repliesToHex, mentions = mentionsHex, zapReceiver = zapReceiver, + markAsSensitive = wantsToMarkAsSensitive, privateKey = loggedIn.privKey!! ) Client.send(signedEvent) LocalCache.consume(signedEvent, null) } - fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List?, zapReceiver: String? = null) { + fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) { if (!isWriteable()) return val user = LocalCache.users[toUser] ?: return @@ -569,6 +581,7 @@ class Account( replyTos = repliesToHex, mentions = mentionsHex, zapReceiver = zapReceiver, + markAsSensitive = wantsToMarkAsSensitive, privateKey = loggedIn.privKey!!, advertiseNip18 = false ) @@ -1114,6 +1127,12 @@ class Account( saveable.invalidateData() } + fun updateShowSensitiveContent(show: Boolean?) { + showSensitiveContent = show + saveable.invalidateData() + live.invalidateData() + } + fun registerObservers() { // Observes relays to restart connections userProfile().live().relays.observeForever { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt index 471419f22..f953c3045 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt @@ -22,7 +22,16 @@ class ChannelMessageEvent( companion object { const val kind = 42 - fun create(message: String, channel: String, replyTos: List? = null, mentions: List? = null, zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent { + fun create( + message: String, + channel: String, + replyTos: List? = null, + mentions: List? = null, + zapReceiver: String?, + privateKey: ByteArray, + createdAt: Long = Date().time / 1000, + markAsSensitive: Boolean + ): ChannelMessageEvent { val content = message val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = mutableListOf( @@ -37,6 +46,9 @@ class ChannelMessageEvent( zapReceiver?.let { tags.add(listOf("zap", it)) } + if (markAsSensitive) { + tags.add(listOf("content-warning", "")) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) 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 dcc0ad35d..946f0bcba 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 @@ -48,6 +48,12 @@ open class Event( fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } + override fun isSensitive() = tags.any { + (it.size > 0 && it[0].equals("content-warning", true)) || + (it.size > 1 && it[0] == "t" && it[1].equals("nsfw", true)) || + (it.size > 1 && it[0] == "t" && it[1].equals("nude", true)) + } + override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1) fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt index de9275d7b..75e49d3c5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt @@ -37,4 +37,5 @@ interface EventInterface { fun getPoWRank(): Int fun zapAddress(): String? + fun isSensitive(): Boolean } 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 10f154193..cd0dfb67d 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 @@ -51,7 +51,8 @@ class PollNoteEvent( valueMinimum: Int?, consensusThreshold: Int?, closedAt: Int?, - zapReceiver: String? + zapReceiver: String?, + markAsSensitive: Boolean ): PollNoteEvent { val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = mutableListOf>() @@ -75,6 +76,9 @@ class PollNoteEvent( if (zapReceiver != null) { tags.add(listOf("zap", zapReceiver)) } + if (markAsSensitive) { + tags.add(listOf("content-warning", "")) + } val id = generateId(pubKey, createdAt, kind, tags, msg) val sig = Utils.sign(id, privateKey) 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 ef2b2478d..837747f8e 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 @@ -73,7 +73,8 @@ class PrivateDmEvent( privateKey: ByteArray, createdAt: Long = Date().time / 1000, publishedRecipientPubKey: ByteArray? = null, - advertiseNip18: Boolean = true + advertiseNip18: Boolean = true, + markAsSensitive: Boolean ): PrivateDmEvent { val content = Utils.encrypt( if (advertiseNip18) { nip18Advertisement } else { "" } + msg, @@ -94,6 +95,9 @@ class PrivateDmEvent( zapReceiver?.let { tags.add(listOf("zap", it)) } + if (markAsSensitive) { + tags.add(listOf("content-warning", "")) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt index 8f7b5bb56..616f40d7c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -29,6 +29,7 @@ class TextNoteEvent( addresses: List?, extraTags: List?, zapReceiver: String?, + markAsSensitive: Boolean, privateKey: ByteArray, createdAt: Long = Date().time / 1000 ): TextNoteEvent { @@ -56,6 +57,9 @@ class TextNoteEvent( findURLs(msg).forEach { tags.add(listOf("r", it)) } + if (markAsSensitive) { + tags.add(listOf("content-warning", "")) + } val id = generateId(pubKey, createdAt, kind, tags, msg) val sig = Utils.sign(id, privateKey) 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 ea205a851..73dd8ab3a 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 @@ -22,8 +22,12 @@ import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.CurrencyBitcoin +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.outlined.ArrowForwardIos import androidx.compose.material.icons.outlined.Bolt +import androidx.compose.material.icons.rounded.Warning import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -83,7 +87,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current - val scroolState = rememberScrollState() + val scrollState = rememberScrollState() val scope = rememberCoroutineScope() LaunchedEffect(Unit) { @@ -153,7 +157,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n Column( modifier = Modifier .fillMaxWidth() - .verticalScroll(scroolState) + .verticalScroll(scrollState) ) { Notifying(postViewModel.mentions) { postViewModel.removeFromReplyList(it) @@ -335,10 +339,11 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } if (postViewModel.canUsePoll) { - val hashtag = stringResource(R.string.poll_hashtag) + // These should be hashtag recommendations the user selects in the future. + // val hashtag = stringResource(R.string.poll_hashtag) + // postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag) AddPollButton(postViewModel.wantsPoll) { postViewModel.wantsPoll = !postViewModel.wantsPoll - postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag) } } @@ -348,6 +353,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } } + MarkAsSensitive(postViewModel) { + postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive + } + ForwardZapTo(postViewModel) { postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo } @@ -537,6 +546,60 @@ private fun ForwardZapTo( } } +@Composable +private fun MarkAsSensitive( + postViewModel: NewPostViewModel, + onClick: () -> Unit +) { + IconButton( + onClick = { + onClick() + } + ) { + Box( + Modifier + .height(20.dp) + .width(23.dp) + ) { + if (!postViewModel.wantsToMarkAsSensitive) { + Icon( + imageVector = Icons.Default.Visibility, + contentDescription = stringResource(R.string.content_warning), + modifier = Modifier + .size(18.dp) + .align(Alignment.BottomStart), + tint = MaterialTheme.colors.onBackground + ) + Icon( + imageVector = Icons.Rounded.Warning, + contentDescription = stringResource(R.string.content_warning), + modifier = Modifier + .size(10.dp) + .align(Alignment.TopEnd), + tint = MaterialTheme.colors.onBackground + ) + } else { + Icon( + imageVector = Icons.Default.VisibilityOff, + contentDescription = stringResource(id = R.string.content_warning), + modifier = Modifier + .size(18.dp) + .align(Alignment.BottomStart), + tint = Color.Red + ) + Icon( + imageVector = Icons.Rounded.Warning, + contentDescription = stringResource(id = R.string.content_warning), + modifier = Modifier + .size(10.dp) + .align(Alignment.TopEnd), + tint = Color.Yellow + ) + } + } + } +} + @Composable fun CloseButton(onCancel: () -> Unit) { Button( 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 c22115261..327843b60 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 @@ -67,6 +67,9 @@ open class NewPostViewModel : ViewModel() { var forwardZapTo by mutableStateOf(null) var forwardZapToEditting by mutableStateOf(TextFieldValue("")) + // NSFW, Sensitive + var wantsToMarkAsSensitive by mutableStateOf(false) + open fun load(account: Account, replyingTo: Note?, quote: Note?) { originalNote = replyingTo replyingTo?.let { replyNote -> @@ -97,6 +100,7 @@ open class NewPostViewModel : ViewModel() { contentToAddUrl = null wantsForwardZapTo = false + wantsToMarkAsSensitive = false forwardZapTo = null forwardZapToEditting = TextFieldValue("") @@ -118,13 +122,13 @@ open class NewPostViewModel : ViewModel() { } if (wantsPoll) { - account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver) + account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive) } else if (originalNote?.channel() != null) { - account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver) + account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive) } else if (originalNote?.event is PrivateDmEvent) { - account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver) + account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive) } else { - account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver) + account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver, wantsToMarkAsSensitive) } cancel() @@ -183,6 +187,7 @@ open class NewPostViewModel : ViewModel() { wantsInvoice = false wantsForwardZapTo = false + wantsToMarkAsSensitive = false forwardZapTo = null forwardZapToEditting = TextFieldValue("") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt new file mode 100644 index 000000000..f0951a307 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -0,0 +1,127 @@ +package com.vitorpamplona.amethyst.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +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.Visibility +import androidx.compose.material.icons.rounded.Warning +import androidx.compose.runtime.Composable +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel + +@Composable +fun SensitivityWarning( + hasSensitiveContent: Boolean, + accountViewModel: AccountViewModel, + content: @Composable () -> Unit +) { + val accountState by accountViewModel.accountLiveData.observeAsState() + + var showContentWarningNote by remember(accountState) { + mutableStateOf(accountState?.account?.showSensitiveContent != true && hasSensitiveContent) + } + + if (showContentWarningNote) { + ContentWarningNote() { + showContentWarningNote = false + } + } else { + content() + } +} + +@Composable +fun ContentWarningNote(onDismiss: () -> Unit) { + Column() { + Row(modifier = Modifier.padding(horizontal = 12.dp)) { + Column(modifier = Modifier.padding(start = 10.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Box( + Modifier + .height(80.dp) + .width(90.dp) + ) { + Icon( + imageVector = Icons.Default.Visibility, + contentDescription = stringResource(R.string.content_warning), + modifier = Modifier + .size(70.dp) + .align(Alignment.BottomStart), + tint = MaterialTheme.colors.onBackground + ) + Icon( + imageVector = Icons.Rounded.Warning, + contentDescription = stringResource(R.string.content_warning), + modifier = Modifier + .size(30.dp) + .align(Alignment.TopEnd), + tint = MaterialTheme.colors.onBackground + ) + } + } + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Text( + text = stringResource(R.string.content_warning), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + } + + Row() { + Text( + text = stringResource(R.string.content_warning_explanation), + color = Color.Gray, + modifier = Modifier.padding(top = 10.dp), + textAlign = TextAlign.Center + ) + } + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Button( + modifier = Modifier.padding(top = 10.dp), + onClick = onDismiss, + shape = RoundedCornerShape(20.dp), + colors = ButtonDefaults + .buttonColors( + backgroundColor = MaterialTheme.colors.primary + ), + contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp) + ) { + Text( + text = stringResource(R.string.show_anyway), + color = Color.White + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt index 4791d00b4..9d94699e6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/AddBountyAmountDialog.kt @@ -55,10 +55,11 @@ class AddBountyAmountViewModel : ViewModel() { if (newValue != null) { account?.sendPost( - newValue.toString(), - listOfNotNull(bounty), - listOfNotNull(bounty?.author), - listOf("bounty-added-reward") + message = newValue.toString(), + replyTo = listOfNotNull(bounty), + mentions = listOfNotNull(bounty?.author), + tags = listOf("bounty-added-reward"), + wantsToMarkAsSensitive = false ) nextAmount = TextFieldValue("") 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 f2f89b0f9..a800c9fd8 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 @@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers @@ -369,15 +370,21 @@ private fun RenderRegularTextNote( val modifier = remember { Modifier.padding(top = 5.dp) } if (eventContent != null) { - TranslatableRichTextViewer( - content = eventContent, - canPreview = canPreview, - modifier = modifier, - tags = tags, - backgroundColor = backgroundBubbleColor, - accountViewModel = accountViewModel, - nav = nav - ) + val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } + SensitivityWarning( + hasSensitiveContent = hasSensitiveContent, + accountViewModel = accountViewModel + ) { + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview, + modifier = modifier, + tags = tags, + backgroundColor = backgroundBubbleColor, + accountViewModel = accountViewModel, + nav = nav + ) + } } else { TranslatableRichTextViewer( content = stringResource(id = R.string.could_not_decrypt_the_message), 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 1aa8e6796..721b28320 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 @@ -116,6 +116,7 @@ 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.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.components.ZoomableContent @@ -166,6 +167,7 @@ fun NoteCompose( val noteEvent = remember(noteState) { note.event } val baseChannel = remember(noteState) { note.channel() } + val isSensitive = remember(noteState) { note.event?.isSensitive() ?: false } var popupExpanded by remember { mutableStateOf(false) } @@ -183,7 +185,7 @@ fun NoteCompose( note.let { NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel) } - } else if (account.isHidden(noteForReports.author!!)) { + } else if (account.isHidden(noteForReports.author!!) || (isSensitive && account.showSensitiveContent == false)) { // Does nothing } else { var showHiddenNote by remember { mutableStateOf(false) } @@ -435,13 +437,10 @@ private fun RenderTextEvent( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val tags = remember(note.event?.id()) { note.event?.tags() } - val hashtags = remember(note.event?.id()) { note.event?.hashtags() ?: emptyList() } - val eventContent = remember(note.event?.id()) { accountViewModel.decrypt(note) } - val modifier = remember(note.event?.id()) { Modifier.fillMaxWidth() } - val isAuthorTheLoggedUser = remember(note.event?.id()) { accountViewModel.isLoggedUser(note.author) } + val eventContent = remember(note.event) { accountViewModel.decrypt(note) } if (eventContent != null) { + val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) } if (makeItShort && isAuthorTheLoggedUser) { Text( text = eventContent, @@ -450,16 +449,27 @@ private fun RenderTextEvent( overflow = TextOverflow.Ellipsis ) } else { - TranslatableRichTextViewer( - content = eventContent, - canPreview = canPreview && !makeItShort, - modifier = modifier, - tags = tags, - backgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav - ) + val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } + SensitivityWarning( + hasSensitiveContent = hasSensitiveContent, + accountViewModel = accountViewModel + ) { + val modifier = remember(note.event) { Modifier.fillMaxWidth() } + val tags = remember(note.event) { note.event?.tags() } + + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview && !makeItShort, + modifier = modifier, + tags = tags, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav + ) + } + + val hashtags = remember(note.event) { note.event?.hashtags() ?: emptyList() } DisplayUncitedHashtags(hashtags, eventContent, nav) } } @@ -494,25 +504,31 @@ private fun RenderPoll( overflow = TextOverflow.Ellipsis ) } else { - TranslatableRichTextViewer( - eventContent, - canPreview = canPreview && !makeItShort, - Modifier.fillMaxWidth(), - noteEvent.tags(), - backgroundColor, - accountViewModel, - nav - ) + val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } + SensitivityWarning( + hasSensitiveContent = hasSensitiveContent, + accountViewModel = accountViewModel + ) { + TranslatableRichTextViewer( + eventContent, + canPreview = canPreview && !makeItShort, + Modifier.fillMaxWidth(), + noteEvent.tags(), + backgroundColor, + accountViewModel, + nav + ) + + PollNote( + note, + canPreview = canPreview && !makeItShort, + backgroundColor, + accountViewModel, + nav + ) + } DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) - - PollNote( - note, - canPreview = canPreview && !makeItShort, - backgroundColor, - accountViewModel, - nav - ) } if (!makeItShort) { @@ -586,15 +602,21 @@ private fun RenderPrivateMessage( overflow = TextOverflow.Ellipsis ) } else { - TranslatableRichTextViewer( - content = eventContent, - canPreview = canPreview && !makeItShort, - modifier = modifier, - tags = tags, - backgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav - ) + val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } + SensitivityWarning( + hasSensitiveContent = hasSensitiveContent, + accountViewModel = accountViewModel + ) { + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview && !makeItShort, + modifier = modifier, + tags = tags, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav + ) + } DisplayUncitedHashtags(hashtags, eventContent, nav) } @@ -2124,7 +2146,9 @@ fun UserPicture( } val myIconModifier = remember { - Modifier.width(size.div(3.5f)).height(size.div(3.5f)) + Modifier + .width(size.div(3.5f)) + .height(size.div(3.5f)) } Box(myIconBoxModifier, contentAlignment = Alignment.Center) { @@ -2147,7 +2171,9 @@ data class DropDownParams( val isFollowingAuthor: Boolean, val isPrivateBookmarkNote: Boolean, val isPublicBookmarkNote: Boolean, - val isLoggedUser: Boolean + val isLoggedUser: Boolean, + val isSensitive: Boolean, + val showSensitiveContent: Boolean? ) @Composable @@ -2158,20 +2184,23 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, var reportDialogShowing by remember { mutableStateOf(false) } val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState() + val accountState by accountViewModel.accountLiveData.observeAsState() var state by remember { mutableStateOf( - DropDownParams(false, false, false, false) + DropDownParams(false, false, false, false, false, null) ) } - LaunchedEffect(key1 = note, key2 = bookmarkState) { + LaunchedEffect(key1 = note, key2 = bookmarkState, key3 = accountState) { withContext(Dispatchers.IO) { state = DropDownParams( - accountViewModel.isFollowing(note.author), - accountViewModel.isInPrivateBookmarks(note), - accountViewModel.isInPublicBookmarks(note), - accountViewModel.isLoggedUser(note.author) + isFollowingAuthor = accountViewModel.isFollowing(note.author), + isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note), + isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note), + isLoggedUser = accountViewModel.isLoggedUser(note.author), + isSensitive = note.event?.isSensitive() ?: false, + showSensitiveContent = accountState?.account?.showSensitiveContent ) } } @@ -2249,6 +2278,24 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, Text("Block / Report") } } + // if (state.isSensitive) { + Divider() + if (state.showSensitiveContent == null || state.showSensitiveContent == true) { + DropdownMenuItem(onClick = { accountViewModel.hideSensitiveContent(); onDismiss() }) { + Text(stringResource(R.string.content_warning_hide_all_sensitive_content)) + } + } + if (state.showSensitiveContent == null || state.showSensitiveContent == false) { + DropdownMenuItem(onClick = { accountViewModel.disableContentWarnings(); onDismiss() }) { + Text(stringResource(R.string.content_warning_show_all_sensitive_content)) + } + } + if (state.showSensitiveContent != null) { + DropdownMenuItem(onClick = { accountViewModel.seeContentWarnings(); onDismiss() }) { + Text(stringResource(R.string.content_warning_see_warnings)) + } + } + // } } if (reportDialogShowing) { 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 596b9f228..ca545bb3f 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 @@ -51,7 +51,9 @@ fun PollNote( val accountState by accountViewModel.accountLiveData.observeAsState() val account = remember(accountState) { accountState?.account } ?: return - val pollViewModel: PollNoteViewModel = viewModel() + val pollViewModel: PollNoteViewModel = viewModel( + key = baseNote.idHex + ) LaunchedEffect(key1 = baseNote) { pollViewModel.load(account, baseNote) 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 86f85c37f..25840d8ac 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 @@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.service.model.PeopleListEvent import com.vitorpamplona.amethyst.service.model.PinListEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status +import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.note.* import com.vitorpamplona.amethyst.ui.note.BadgeDisplay @@ -376,15 +377,22 @@ fun NoteMaster( !noteForReports.hasAnyReports() if (eventContent != null) { - TranslatableRichTextViewer( - eventContent, - canPreview, - Modifier.fillMaxWidth(), - note.event?.tags(), - MaterialTheme.colors.background, - accountViewModel, - nav - ) + val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } + + SensitivityWarning( + hasSensitiveContent = hasSensitiveContent, + accountViewModel = accountViewModel + ) { + TranslatableRichTextViewer( + eventContent, + canPreview, + Modifier.fillMaxWidth(), + note.event?.tags(), + MaterialTheme.colors.background, + accountViewModel, + nav + ) + } DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, nav) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 90dfbb5d4..d3fec5be0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -232,6 +232,18 @@ class AccountViewModel(private val account: Account) : ViewModel() { account.setHideBlockAlertDialog() } + fun hideSensitiveContent() { + account.updateShowSensitiveContent(false) + } + + fun disableContentWarnings() { + account.updateShowSensitiveContent(true) + } + + fun seeContentWarnings() { + account.updateShowSensitiveContent(null) + } + class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): AccountViewModel { return AccountViewModel(account) as AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 2155f2e5d..52f082482 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -218,7 +218,7 @@ fun ChannelScreen( onPost = { val tagger = NewMessageTagger(channel, listOfNotNull(replyTo.value?.author), listOfNotNull(replyTo.value), channelScreenModel.message.text) tagger.run() - account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions) + account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions, wantsToMarkAsSensitive = false) channelScreenModel.message = TextFieldValue("") replyTo.value = null feedViewModel.invalidateData() // Don't wait a full second before updating diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index c853709de..a64bf071d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -178,7 +178,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St trailingIcon = { PostButton( onPost = { - account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null) + account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null, wantsToMarkAsSensitive = false) chatRoomScreenModel.message = TextFieldValue("") replyTo.value = null feedViewModel.invalidateData() // Don't wait a full second before updating diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b10e09631..5a2b1bef5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -404,4 +404,10 @@ Join Today + + Content warning + This post contains sensitive content which some people may find offensive or disturbing + Always hide sensitive content + Always show sensitive content + Always show content warnings From bd4a4503360c1544a1f663aeb3c85e43d1ad8432 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 12:21:10 -0400 Subject: [PATCH 68/76] v0.52.0 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0fb95ea62..4852b3e89 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 179 - versionName "0.51.4" + versionCode 180 + versionName "0.52.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From a8ffbb922d0790bdd6d73f5b785d9b5bc2b9714f Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 14:03:57 -0400 Subject: [PATCH 69/76] Moves relay color filters to a singleton --- .../ui/note/ChatroomMessageCompose.kt | 11 ++---- .../amethyst/ui/note/NoteCompose.kt | 8 +--- .../ui/screen/loggedIn/VideoScreen.kt | 39 +++---------------- .../vitorpamplona/amethyst/ui/theme/Color.kt | 4 ++ 4 files changed, 15 insertions(+), 47 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 a800c9fd8..5560f7d8a 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 @@ -37,8 +37,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.onSizeChanged @@ -66,6 +64,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -538,7 +537,7 @@ private fun RelayBadges(baseNote: Note) { } @Composable -private fun RenderRelay(dirtyUrl: String) { +fun RenderRelay(dirtyUrl: String) { val uri = LocalUriHandler.current val website = remember { val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://") @@ -556,10 +555,6 @@ private fun RenderRelay(dirtyUrl: String) { .clickable(onClick = { uri.openUri(website) }) } - val colorFilter = remember { - ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) }) - } - val iconModifier = remember { Modifier .size(15.dp) @@ -574,7 +569,7 @@ private fun RenderRelay(dirtyUrl: String) { robotSize = 15.dp, model = iconUrl, contentDescription = stringResource(id = R.string.relay_icon), - colorFilter = colorFilter, + colorFilter = RelayIconFilter, modifier = iconModifier.background(MaterialTheme.colors.background) ) } 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 721b28320..2ede5871f 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 @@ -57,8 +57,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale @@ -131,6 +129,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.Following +import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -1936,9 +1935,6 @@ private fun RelayIconCompose(url: String) { .clip(shape = CircleShape) .clickable(onClick = { uri.openUri("https://$url") }) } - val colorFilter = remember { - ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) }) - } Box(boxModifier) { RobohashFallbackAsyncImage( @@ -1946,7 +1942,7 @@ private fun RelayIconCompose(url: String) { robotSize = 15.dp, model = model, contentDescription = stringResource(R.string.relay_icon), - colorFilter = colorFilter, + colorFilter = RelayIconFilter, modifier = iconModifier.background(MaterialTheme.colors.background) ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 3c8541185..7dd638332 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -47,12 +46,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -73,7 +68,6 @@ import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status -import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.FileHeaderDisplay @@ -82,6 +76,7 @@ import com.vitorpamplona.amethyst.ui.note.LikeReaction import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay +import com.vitorpamplona.amethyst.ui.note.RenderRelay import com.vitorpamplona.amethyst.ui.note.ViewCountReaction import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.FeedEmpty @@ -317,35 +312,13 @@ private fun RenderVideoOrPictureNote( @Composable private fun RelayBadges(baseNote: Note) { val noteRelaysState by baseNote.live().relays.observeAsState() - val noteRelays = noteRelaysState?.note?.relays ?: emptySet() - - var expanded by remember { mutableStateOf(false) } - - val relaysToDisplay = noteRelays - - val uri = LocalUriHandler.current + val noteRelays = remember(noteRelaysState) { + noteRelaysState?.note?.relays ?: emptySet() + } FlowRow() { - relaysToDisplay.forEach { - val url = it.removePrefix("wss://").removePrefix("ws://") - Box( - Modifier - .size(15.dp) - .padding(1.dp) - ) { - RobohashFallbackAsyncImage( - robot = "https://$url/favicon.ico", - robotSize = 15.dp, - model = "https://$url/favicon.ico", - contentDescription = stringResource(id = R.string.relay_icon), - colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }), - modifier = Modifier - .fillMaxSize(1f) - .clip(shape = CircleShape) - .background(MaterialTheme.colors.background) - .clickable(onClick = { uri.openUri("https://$url") }) - ) - } + noteRelays.forEach { dirtyUrl -> + RenderRelay(dirtyUrl) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt index c21e0dfdd..c97fb88c6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt @@ -1,6 +1,8 @@ package com.vitorpamplona.amethyst.ui.theme import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix val Purple200 = Color(0xFFBB86FC) val Purple500 = Color(0xFF6200EE) @@ -15,3 +17,5 @@ val FollowsFollow = Color.Yellow val NIP05Verified = Color.Blue val WarningColor = Color(0xFFC62828) + +val RelayIconFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0.5f) }) From 69be28ec98a6172e7ce1cb59b2133ce9d1456a2c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 26 May 2023 18:05:35 -0400 Subject: [PATCH 70/76] Making sure the UserReactonsRow in notifications get updated as new events come in. --- README.md | 2 +- .../amethyst/ui/components/ClickableRoute.kt | 10 +++++----- .../amethyst/ui/components/RichTextViewer.kt | 14 +++++++------- .../amethyst/ui/components/UrlPreview.kt | 2 +- .../amethyst/ui/components/UrlPreviewCard.kt | 4 ++-- .../amethyst/ui/note/NoteQuickActionMenu.kt | 8 ++++---- .../amethyst/ui/note/UserReactionsRow.kt | 8 ++++---- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index f5374fa5e..6030feb40 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ Or get the latest APK from the [Releases Section](https://github.com/vitorpamplo - [x] Audio Tracks (zapstr.live) (NIP-TBD) - [x] Push Notifications (Zaps and Messages) - [x] Generic Tags (NIP-12) +- [x] Sensitive Content (NIP-36) - [ ] Marketplace (NIP-15) - [ ] Image/Video Capture in the app - [ ] Local Database - [ ] View Individual Reactions (Like, Boost, Zaps, Reports) per Post - [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51) -- [ ] Sensitive Content (NIP-36) - [ ] Relay Pages (NIP-11) - [ ] Proof of Work in the Phone (NIP-13, NIP-20) - [ ] Events with a Subject (NIP-14) 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 32bb3c997..0da6a33d7 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 @@ -73,7 +73,7 @@ private fun DisplayEvent( nip19: Nip19.Return, nav: (String) -> Unit ) { - var noteBase by remember { mutableStateOf(null) } + var noteBase by remember(nip19) { mutableStateOf(null) } LaunchedEffect(key1 = nip19.hex) { withContext(Dispatchers.IO) { @@ -129,7 +129,7 @@ private fun DisplayNote( nip19: Nip19.Return, nav: (String) -> Unit ) { - var noteBase by remember { mutableStateOf(null) } + var noteBase by remember(nip19) { mutableStateOf(null) } LaunchedEffect(key1 = nip19.hex) { withContext(Dispatchers.IO) { @@ -185,7 +185,7 @@ private fun DisplayAddress( nip19: Nip19.Return, nav: (String) -> Unit ) { - var noteBase by remember { mutableStateOf(null) } + var noteBase by remember(nip19) { mutableStateOf(null) } LaunchedEffect(key1 = nip19.hex) { withContext(Dispatchers.IO) { @@ -217,7 +217,7 @@ private fun DisplayUser( nip19: Nip19.Return, nav: (String) -> Unit ) { - var userBase by remember { mutableStateOf(null) } + var userBase by remember(nip19) { mutableStateOf(null) } LaunchedEffect(key1 = nip19.hex) { withContext(Dispatchers.IO) { @@ -227,7 +227,7 @@ private fun DisplayUser( userBase?.let { val userState by it.live().metadata.observeAsState() - val route = remember { "User/${it.pubkeyHex}" } + val route = remember(userState) { "User/${it.pubkeyHex}" } val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() } val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags } 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 552c1fd0b..52ad41e47 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 @@ -95,7 +95,7 @@ fun RichTextViewer( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val isMarkdown = remember { isMarkdown(content) } + val isMarkdown = remember(content) { isMarkdown(content) } Column(modifier = modifier) { if (isMarkdown) { @@ -124,7 +124,7 @@ private fun RenderRegular( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - var state by remember { + var state by remember(content) { mutableStateOf(RichTextViewerState(content, emptySet(), emptyMap(), emptyList(), emptyMap())) } @@ -344,9 +344,9 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag ) ) - var markdownWithSpecialContent by remember { mutableStateOf(null) } - var nip19References by remember { mutableStateOf>(emptyList()) } - var refresh by remember { mutableStateOf(0) } + var markdownWithSpecialContent by remember(content) { mutableStateOf(null) } + var nip19References by remember(content) { mutableStateOf>(emptyList()) } + var refresh by remember(content) { mutableStateOf(0) } LaunchedEffect(key1 = content) { withContext(Dispatchers.IO) { @@ -365,8 +365,8 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tag } nip19References.forEach { - var baseUser by remember { mutableStateOf(null) } - var baseNote by remember { mutableStateOf(null) } + var baseUser by remember(it) { mutableStateOf(null) } + var baseNote by remember(it) { mutableStateOf(null) } LaunchedEffect(key1 = it.hex) { withContext(Dispatchers.IO) { 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 a1d8e97ca..30bdb93fe 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 @@ -21,7 +21,7 @@ import kotlinx.coroutines.launch fun UrlPreview(url: String, urlText: String) { val context = LocalContext.current - var urlPreviewState by remember { + var urlPreviewState by remember(url) { val default = UrlCachedPreviewer.cache[url]?.let { if (it.allFetchComplete() && it.url == url) { UrlPreviewState.Loaded(it) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt index ef5673f21..54046bde9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -42,10 +42,10 @@ fun UrlPreviewCard( ) ) { Column { - val validatedUrl = remember { URL(previewInfo.url) } + val validatedUrl = remember(url) { URL(previewInfo.url) } // correctly treating relative images - val imageUrl = remember { + val imageUrl = remember(url) { if (previewInfo.image.startsWith("/")) { URL(validatedUrl, previewInfo.image).toString() } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 73c75812c..2c9ab94bb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -116,10 +116,10 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni val cardShape = RoundedCornerShape(5.dp) val clipboardManager = LocalClipboardManager.current val scope = rememberCoroutineScope() - var showSelectTextDialog by remember { mutableStateOf(false) } - var showDeleteAlertDialog by remember { mutableStateOf(false) } - var showBlockAlertDialog by remember { mutableStateOf(false) } - var showReportDialog by remember { mutableStateOf(false) } + var showSelectTextDialog by remember(note) { mutableStateOf(false) } + var showDeleteAlertDialog by remember(note) { mutableStateOf(false) } + var showBlockAlertDialog by remember(note) { mutableStateOf(false) } + var showReportDialog by remember(note) { mutableStateOf(false) } val backgroundColor = if (MaterialTheme.colors.isLight) { MaterialTheme.colors.primary diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index ad06c950e..26cebaa48 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -280,7 +280,7 @@ class UserReactionsViewModel : ViewModel() { fun UserReplyReaction( replyCount: Int? ) { - val showCounts = remember { showCount(replyCount) } + val showCounts = remember(replyCount) { showCount(replyCount) } Icon( painter = painterResource(R.drawable.ic_comment), @@ -302,7 +302,7 @@ fun UserReplyReaction( fun UserBoostReaction( boostCount: Int? ) { - val showCounts = remember { showCount(boostCount) } + val showCounts = remember(boostCount) { showCount(boostCount) } Icon( painter = painterResource(R.drawable.ic_retweeted), @@ -324,7 +324,7 @@ fun UserBoostReaction( fun UserLikeReaction( likeCount: Int? ) { - val showCounts = remember { showCount(likeCount) } + val showCounts = remember(likeCount) { showCount(likeCount) } Icon( painter = painterResource(R.drawable.ic_liked), @@ -346,7 +346,7 @@ fun UserLikeReaction( fun UserZapReaction( amount: BigDecimal? ) { - val showAmounts = remember { showAmountAxis(amount) } + val showAmounts = remember(amount) { showAmountAxis(amount) } Icon( imageVector = Icons.Default.Bolt, From 359e22ae42c1c9bb0f6824bdb034724b283ddce9 Mon Sep 17 00:00:00 2001 From: Zoltan <34719275+ZsZolee@users.noreply.github.com> Date: Sat, 27 May 2023 17:02:15 +0200 Subject: [PATCH 71/76] Sensitive content 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 86d2c42c4..649cf45ae 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -385,4 +385,10 @@ Csatlakozás Ma + + Tartalomra vonatkozó figyelmeztetés + Ez a bejegyzés érzékeny tartalmat tartalmaz, amelyet egyesek sértőnek vagy zavarónak találhatnak + Az érzékeny tartalmat mindig rejtse el + Az érzékeny tartalmat mindig jelenítse meg + A tartalomra vonatkozó figyelmeztetéseket mindig jelenítse meg From 545643d165c24220c514c360586e9d4026334b33 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 May 2023 16:04:20 -0400 Subject: [PATCH 72/76] Fixes Relay Icon rendering. --- .../ui/note/ChatroomMessageCompose.kt | 18 ++++----- .../amethyst/ui/note/NoteCompose.kt | 37 +------------------ 2 files changed, 10 insertions(+), 45 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 5560f7d8a..68895ce5e 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 @@ -539,25 +539,25 @@ private fun RelayBadges(baseNote: Note) { @Composable fun RenderRelay(dirtyUrl: String) { val uri = LocalUriHandler.current - val website = remember { - val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://") + val website = remember(dirtyUrl) { + val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://").removeSuffix("/") "https://$cleanUrl" } - val iconUrl = remember { - val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://") + val iconUrl = remember(dirtyUrl) { + val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://").removeSuffix("/") "https://$cleanUrl/favicon.ico" } - val clickableModifier = remember { + val clickableModifier = remember(dirtyUrl) { Modifier - .size(15.dp) .padding(1.dp) + .size(15.dp) .clickable(onClick = { uri.openUri(website) }) } - val iconModifier = remember { + val iconModifier = remember(dirtyUrl) { Modifier - .size(15.dp) + .size(13.dp) .clip(shape = CircleShape) } @@ -566,7 +566,7 @@ fun RenderRelay(dirtyUrl: String) { ) { RobohashFallbackAsyncImage( robot = iconUrl, - robotSize = 15.dp, + robotSize = 13.dp, model = iconUrl, contentDescription = stringResource(id = R.string.relay_icon), colorFilter = RelayIconFilter, 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 2ede5871f..92c9a9a15 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 @@ -62,7 +62,6 @@ import androidx.compose.ui.graphics.luminance import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -113,7 +112,6 @@ 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.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.VideoView @@ -129,7 +127,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.Following -import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -1911,43 +1908,11 @@ private fun VerticalRelayPanelWithFlow( // FlowRow Seems to be a lot faster than LazyVerticalGrid FlowRow() { relays.forEach { url -> - RelayIconCompose(url) + RenderRelay(url) } } } -@Composable -@Stable -private fun RelayIconCompose(url: String) { - val uri = LocalUriHandler.current - - val model = remember(url) { "https://$url/favicon.ico" } - - val boxModifier = remember { - Modifier - .padding(1.dp) - .size(15.dp) - } - val iconModifier = remember(url) { - Modifier - .width(13.dp) - .height(13.dp) - .clip(shape = CircleShape) - .clickable(onClick = { uri.openUri("https://$url") }) - } - - Box(boxModifier) { - RobohashFallbackAsyncImage( - robot = model, - robotSize = 15.dp, - model = model, - contentDescription = stringResource(R.string.relay_icon), - colorFilter = RelayIconFilter, - modifier = iconModifier.background(MaterialTheme.colors.background) - ) - } -} - @Composable private fun ShowMoreRelaysButton(onClick: () -> Unit) { val boxModifier = remember { From 1146b8ad549dfee3dadffc40fdd5144acc0ff3dd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 May 2023 16:06:15 -0400 Subject: [PATCH 73/76] v0.52.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4852b3e89..489b072e9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 180 - versionName "0.52.0" + versionCode 181 + versionName "0.52.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 4517ca9fdd0548b93664dc400740bfac148bbcd9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 May 2023 16:16:52 -0400 Subject: [PATCH 74/76] trimming urls as well --- .../vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 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 68895ce5e..f441f53df 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 @@ -540,11 +540,11 @@ private fun RelayBadges(baseNote: Note) { fun RenderRelay(dirtyUrl: String) { val uri = LocalUriHandler.current val website = remember(dirtyUrl) { - val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://").removeSuffix("/") + val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/") "https://$cleanUrl" } val iconUrl = remember(dirtyUrl) { - val cleanUrl = dirtyUrl.removePrefix("wss://").removePrefix("ws://").removeSuffix("/") + val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/") "https://$cleanUrl/favicon.ico" } From c5a586ec5625706915e745c99546a593fa860831 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 May 2023 16:36:27 -0400 Subject: [PATCH 75/76] Fixes Missing Notifications --- .../com/vitorpamplona/amethyst/model/Note.kt | 6 +++--- .../com/vitorpamplona/amethyst/model/User.kt | 2 +- .../amethyst/ui/dal/NotificationFeedFilter.kt | 16 ++++++++-------- .../amethyst/ui/note/ChatroomMessageCompose.kt | 2 +- .../amethyst/ui/note/MultiSetCompose.kt | 2 +- .../amethyst/ui/note/NoteCompose.kt | 4 ++-- .../ui/screen/loggedIn/AccountViewModel.kt | 2 +- 7 files changed, 17 insertions(+), 17 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 a850c01b6..c6c691b15 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -232,7 +232,7 @@ open class Note(val idHex: String) { fun isZappedBy(user: User, account: Account): Boolean { // Zaps who the requester was the user return zaps.any { - it.key.author === user || account.decryptZapContentAuthor(it.key)?.pubKey == user.pubkeyHex + it.key.author?.pubkeyHex == user.pubkeyHex || account.decryptZapContentAuthor(it.key)?.pubKey == user.pubkeyHex } || zapPayments.any { val zapResponseEvent = it.value?.event as? LnZapPaymentResponseEvent val response = if (zapResponseEvent != null) { @@ -245,11 +245,11 @@ open class Note(val idHex: String) { } fun isReactedBy(user: User): Boolean { - return reactions.any { it.author === user } + return reactions.any { it.author?.pubkeyHex == user.pubkeyHex } } fun isBoostedBy(user: User): Boolean { - return boosts.any { it.author === user } + return boosts.any { it.author?.pubkeyHex == user.pubkeyHex } } fun reportsBy(user: User): Set { 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 3992607da..e5ff8b1a7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -299,7 +299,7 @@ class User(val pubkeyHex: String) { fun hasSentMessagesTo(user: User?): Boolean { val messagesToUser = privateChatrooms[user] ?: return false - return messagesToUser.roomMessages.any { this === it.author } + return messagesToUser.roomMessages.any { this.pubkeyHex == it.author?.pubkeyHex } } fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 4aad1f481..3f8d98a35 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS +import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.* object NotificationFeedFilter : AdditiveFeedFilter() { @@ -36,7 +36,7 @@ object NotificationFeedFilter : AdditiveFeedFilter() { (isGlobal || it.author?.pubkeyHex in followingKeySet) && it.event?.isTaggedUser(loggedInUserHex) ?: false && (it.author == null || !account.isHidden(it.author!!.pubkeyHex)) && - tagsAnEventByUser(it, loggedInUser) + tagsAnEventByUser(it, loggedInUserHex) }.toSet() } @@ -44,27 +44,27 @@ object NotificationFeedFilter : AdditiveFeedFilter() { return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() } - fun tagsAnEventByUser(note: Note, author: User): Boolean { + fun tagsAnEventByUser(note: Note, authorHex: HexKey): Boolean { val event = note.event if (event is BaseTextNoteEvent) { val isAuthoredPostCited = event.findCitations().any { - LocalCache.notes[it]?.author === author || LocalCache.addressables[it]?.author === author + LocalCache.notes[it]?.author?.pubkeyHex == authorHex || LocalCache.addressables[it]?.author?.pubkeyHex == authorHex } return isAuthoredPostCited || ( - event.citedUsers().contains(author.pubkeyHex) || - note.replyTo?.any { it.author === author } == true + event.citedUsers().contains(authorHex) || + note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true ) } if (event is ReactionEvent) { - return note.replyTo?.lastOrNull()?.author === author + return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex } if (event is RepostEvent) { - return note.replyTo?.lastOrNull()?.author === author + return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex } return true 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 f441f53df..4452452ab 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 @@ -117,7 +117,7 @@ fun ChatroomMessageCompose( LaunchedEffect(key1 = noteReportsState, key2 = accountState) { withContext(Dispatchers.IO) { account.userProfile().let { loggedIn -> - val newCanPreview = note.author === loggedIn || + val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex || (note.author?.let { loggedIn.isFollowingCached(it) } ?: true) || !(noteForReports.hasAnyReports()) 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 63011188c..f6fd7b1a1 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 @@ -452,7 +452,7 @@ fun FastNoteAuthorPicture( val showFollowingMark by remember(accountFollowsState) { derivedStateOf { - accountFollowsState?.user?.isFollowingCached(author) == true || (author === accountFollowsState?.user) + accountFollowsState?.user?.isFollowingCached(author) == true || (author.pubkeyHex == accountFollowsState?.user?.pubkeyHex) } } 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 92c9a9a15..86ce4ff2b 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,7 +190,7 @@ fun NoteCompose( LaunchedEffect(key1 = noteReportsState, key2 = accountState) { withContext(Dispatchers.IO) { account.userProfile().let { loggedIn -> - val newCanPreview = note.author === loggedIn || + val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex || (note.author?.let { loggedIn.isFollowingCached(it) } ?: true) || !(noteForReports.hasAnyReports()) @@ -2030,7 +2030,7 @@ fun UserPicture( } val showFollowingMark = remember(accountState) { - accountState?.user?.isFollowingCached(baseUser) == true || baseUser === accountState?.user + accountState?.user?.isFollowingCached(baseUser) == true || baseUser.pubkeyHex == accountState?.user?.pubkeyHex } // BaseUser is the same reference as accountState.user diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index d3fec5be0..bbad681ff 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -210,7 +210,7 @@ class AccountViewModel(private val account: Account) : ViewModel() { } fun isLoggedUser(user: User?): Boolean { - return account.userProfile() === user + return account.userProfile().pubkeyHex == user?.pubkeyHex } fun isFollowing(user: User?): Boolean { From b3be4567e22918d6be8da02475d30f5c6acd9947 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 27 May 2023 16:36:36 -0400 Subject: [PATCH 76/76] v0.52.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 489b072e9..6f2fd2298 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 181 - versionName "0.52.1" + versionCode 182 + versionName "0.52.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables {