From e7db6598d03f7b1b3701bc06d9fc184efcbadc77 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sun, 3 May 2026 19:03:50 +0200 Subject: [PATCH] Improve note rendering --- .../intermdiate/LocalQuotedNostrEvent.kt | 75 +++- .../ui/composable/NostrEventDetailScreen.kt | 2 +- .../ui/composable/WriteNewNoteScreen.kt | 6 +- .../widgets/content/QuotedAddressableNote.kt | 5 +- .../composable/widgets/content/QuotedNote.kt | 57 ++- .../composable/widgets/content/RichContent.kt | 22 +- .../widgets/detail/TextNoteEventDetail.kt | 31 +- .../composable/widgets/feed/EventListView.kt | 90 +++- .../widgets/feed/TextNoteFeedItem.kt | 396 ++++++++++++------ 9 files changed, 483 insertions(+), 201 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalQuotedNostrEvent.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalQuotedNostrEvent.kt index e6fdcc70..1499e063 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalQuotedNostrEvent.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/intermdiate/LocalQuotedNostrEvent.kt @@ -7,21 +7,24 @@ import ac.cord.auxiliary.compose.database.model.QuotedRelation import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString import ac.cord.auxiliary.compose.ui.composable.widgets.content.RichContent import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar +import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileColor +import ac.cord.auxiliary.compose.ui.theme.AuxTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.room3.Embedded import androidx.room3.Relation +import com.vitorpamplona.quartz.nip01Core.core.HexKey data class LocalQuotedNostrEvent( @Embedded val quotedRelation: QuotedRelation, @@ -46,37 +49,43 @@ data class LocalQuotedNostrEvent( val mentionedProfiles: List = emptyList(), ) { @Composable - fun RenderNotePreview() { + fun RenderNotePreview( + onNavigateToEvent: (HexKey) -> Unit + ) { Column { Row( - modifier = Modifier.fillMaxWidth().padding(12.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.fillMaxWidth().padding(10.dp) +// .clickable( +// onClick = { +// onNavigateToEvent.invoke(profile.nostrEventId) +// } +// ) + , + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start), verticalAlignment = Alignment.CenterVertically ) { - val profile = profile - ProfileAvatar( - profile = profile, - publicKey = nostrEvent.pubKey + publicKey = nostrEvent.pubKey, + profile = profile ) Column( modifier = Modifier.weight(1f) - ) { + ){ Text( - text = profile?.displayName ?: profile?.userName ?: nostrEvent.pubKey, - style = MaterialTheme.typography.labelLarge + text = profile?.humanReadableNameOrPubkey() ?: nostrEvent.pubKey, + style = MaterialTheme.typography.bodyMedium ) - profile?.nip05?.let { + profile?.staticIdentifier()?.let { staticIdentifier -> Text( - text = profile.nip05 ?: "", - style = MaterialTheme.typography.labelMedium + text = staticIdentifier, + style = MaterialTheme.typography.bodySmall, + color = ProfileColor.fromProfile(profile) ) } } } - Column( modifier = Modifier.fillMaxWidth().padding( horizontal = 10.dp @@ -86,6 +95,7 @@ data class LocalQuotedNostrEvent( nostrEvent = nostrEvent, localQuotedNostrEvent = null, mentionedProfiles = mentionedProfiles, + onNavigateToEvent = onNavigateToEvent ) } @@ -100,4 +110,37 @@ data class LocalQuotedNostrEvent( } } } +} + +@Preview +@Composable +private fun LocalQuotedNostrEventPreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + LocalQuotedNostrEvent( + nostrEvent = NostrEvent( + id = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6", + content = "God is not in the habit of coming down from heaven to solve people’s problems on earth.", + pubKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + quotedRelation = QuotedRelation( + quotingNostrEventId = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a520", + quotedProfilePublicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + quotedNostrEventId = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6" + ), + profile = Profile( + publicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + nostrEventId = "referenced", + displayName = "Steve Biko" + ), + ).RenderNotePreview( + onNavigateToEvent = {} + ) + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/NostrEventDetailScreen.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/NostrEventDetailScreen.kt index f0fc1867..cc2a048e 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/NostrEventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/NostrEventDetailScreen.kt @@ -78,7 +78,7 @@ fun NostrEventDetailScreen( feedListUIState.localNostrEvent, onNavigateBack = onNavigateBack, onNavigateToWriteAReply = onNavigateToWriteAReply, - onNavigateToQuoteNostrEvent = onNavigateToQuoteNostrEvent + onNavigateToNostrEvent = onNavigateToQuoteNostrEvent ) } RepostEvent.KIND -> { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt index 3f9703ca..04cc8f10 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/WriteNewNoteScreen.kt @@ -47,11 +47,7 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.key -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.text.SpanStyle @@ -60,7 +56,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel -import co.touchlab.kermit.Logger import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import kotlin.time.Clock @@ -368,6 +363,7 @@ fun WriteNewNoteScreen( ) }, style = MaterialTheme.typography.bodySmall, + onNavigateToEvent = {} // TODO: Add TextAlign.Center ) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedAddressableNote.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedAddressableNote.kt index e19e0f15..7a229737 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedAddressableNote.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedAddressableNote.kt @@ -1,6 +1,5 @@ package ac.cord.auxiliary.compose.ui.composable.widgets.content -import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalQuotedNostrEvent import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Row @@ -28,7 +27,7 @@ internal fun QuotedAddressableNote( dTag: String, author: String, relayHints: List, - onNoteClick: ((String) -> Unit)?, + onNoteClick: (String) -> Unit, onProfileClick: ((String) -> Unit)?, noteActions: NoteActions?, style: TextStyle @@ -39,7 +38,7 @@ internal fun QuotedAddressableNote( if (localQuotedNostrEvent != null) { QuotedNote( localQuotedNostrEvent = localQuotedNostrEvent, - onNoteClick = onNoteClick, + onNavigateToEvent = onNoteClick, noteActions = noteActions ) } else { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedNote.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedNote.kt index f8d11d26..a71a0443 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedNote.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/QuotedNote.kt @@ -1,8 +1,10 @@ package ac.cord.auxiliary.compose.ui.composable.widgets.content +import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.QuotedRelation import ac.cord.auxiliary.compose.database.model.intermdiate.LocalQuotedNostrEvent +import ac.cord.auxiliary.compose.ui.theme.AuxTheme import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row @@ -19,12 +21,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.vitorpamplona.quartz.nip01Core.core.HexKey @Composable fun QuotedNote( localQuotedNostrEvent: LocalQuotedNostrEvent? = null, - onNoteClick: ((String) -> Unit)? = null, + onNavigateToEvent:(HexKey) -> Unit, noteActions: NoteActions? = null ) { // Observe versions so we recompose when data arrives from relays @@ -36,7 +40,7 @@ fun QuotedNote( // } // } - val effectiveNoteClick = noteActions?.onNoteClick ?: onNoteClick + val effectiveNoteClick = noteActions?.onNoteClick ?: onNavigateToEvent // Fetch poll votes when quoted event is a poll // LaunchedEffect(event?.id, event?.kind) { @@ -59,7 +63,9 @@ fun QuotedNote( .fillMaxWidth() .padding(vertical = 6.dp) ) { - localQuotedNostrEvent.RenderNotePreview() + localQuotedNostrEvent.RenderNotePreview( + onNavigateToEvent = onNavigateToEvent + ) // quotedNostrEvent.RenderNotePreview() } } else if (localQuotedNostrEvent != null) { @@ -70,13 +76,13 @@ fun QuotedNote( color = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() - .padding(vertical = 6.dp) .then( - if (effectiveNoteClick != null) Modifier.clickable { effectiveNoteClick(localQuotedNostrEvent.nostrEvent.id) } - else Modifier + Modifier.clickable { effectiveNoteClick(localQuotedNostrEvent.nostrEvent.id) } ) ) { - localQuotedNostrEvent.RenderNotePreview() + localQuotedNostrEvent.RenderNotePreview( + onNavigateToEvent = onNavigateToEvent + ) } } else { // Loading state @@ -108,4 +114,41 @@ fun QuotedNote( } } } +} + +@Preview +@Composable +private fun QuotedNotePreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + + QuotedNote( + localQuotedNostrEvent = LocalQuotedNostrEvent( + nostrEvent = NostrEvent( + id = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6", + content = "God is not in the habit of coming down from heaven to solve people’s problems on earth.", + pubKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + quotedRelation = QuotedRelation( + quotingNostrEventId = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a520", + quotedProfilePublicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + quotedNostrEventId = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6" + ), + profile = Profile( + publicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + nostrEventId = "referenced", + displayName = "Steve Biko" + ), + ), + onNavigateToEvent = { }, + noteActions = null + ) + } + } + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt index 5fd3b6d1..d2baac33 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/content/RichContent.kt @@ -5,14 +5,14 @@ import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.QuotedRelation import ac.cord.auxiliary.compose.database.model.intermdiate.LocalMention -import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalQuotedNostrEvent import ac.cord.auxiliary.compose.ui.composable.widgets.content.Constants.INLINE_CONTENT_TAG -import ac.cord.auxiliary.compose.ui.composable.widgets.feed.ListView import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileColor import ac.cord.auxiliary.compose.ui.theme.AuxTheme import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.selection.SelectionContainer @@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import co.touchlab.kermit.Logger import coil3.compose.AsyncImage +import com.vitorpamplona.quartz.nip01Core.core.HexKey @Composable fun RichContent( @@ -54,7 +55,7 @@ fun RichContent( imetaMap: Map = emptyMap(), plainLinks: Boolean = false, onProfileClick: ((String) -> Unit)? = null, - onNoteClick: ((String) -> Unit)? = null, + onNavigateToEvent: (HexKey) -> Unit, onHashtagClick: ((String) -> Unit)? = null, onLiveStreamClick: ((String, String, String?) -> Unit)? = null, noteActions: NoteActions? = null, @@ -294,7 +295,7 @@ fun RichContent( if (localQuotedNostrEvent != null) { QuotedNote( localQuotedNostrEvent = localQuotedNostrEvent, - onNoteClick = onNoteClick, + onNavigateToEvent = onNavigateToEvent, noteActions = noteActions ) } else { @@ -316,7 +317,7 @@ fun RichContent( dTag = segment.dTag, author = segment.author, relayHints = segment.relays, - onNoteClick = onNoteClick, + onNoteClick = onNavigateToEvent, onProfileClick = onProfileClick, noteActions = noteActions, style = style @@ -423,7 +424,7 @@ private fun RichContentPreview() { modifier = Modifier.padding(10.dp), nostrEvent = NostrEvent( id = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a520", - content = "Something something something nostr:npub1uh366qnj68a7atvwgwdy4569xd6v5jtferftapqztwjwlzh9553q47pqsx\nnostr:note1uh366qnj68a7atvwgwdy4569xd6v5jtferftapqztwjwlzh955s53z8ee", + content = "Something something something nostr:npub1uh366qnj68a7atvwgwdy4569xd6v5jtferftapqztwjwlzh9553q47pqsx\nhttps://www.brainyquote.com/authors/steven-biko-quotes\nnostr:nevent1qqspcusnzxx5u97xht2udvg3yhcq2cvmc0jrxa90lwwdvjplve0atespzpmhxue69uhkummnw3ezuamfdejsyg8h5g639qhuv803hzwexfwhyud6t6suxfu364anddqvrmn9j4cahvxksr8q", pubKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", kind = 0, sig = "hexsig", @@ -431,8 +432,8 @@ private fun RichContentPreview() { ), localQuotedNostrEvent = LocalQuotedNostrEvent( nostrEvent = NostrEvent( - id = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a521", - content = "Quote me on this!", + id = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6", + content = "God is not in the habit of coming down from heaven to solve people’s problems on earth.", pubKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", kind = 0, sig = "hexsig", @@ -441,12 +442,12 @@ private fun RichContentPreview() { quotedRelation = QuotedRelation( quotingNostrEventId = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a520", quotedProfilePublicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", - quotedNostrEventId = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a521" + quotedNostrEventId = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6" ), profile = Profile( publicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", nostrEventId = "referenced", - displayName = "Frank Talk" + displayName = "Steve Biko" ), ), mentionedProfiles = listOf( @@ -462,6 +463,7 @@ private fun RichContentPreview() { ), ) ), + onNavigateToEvent = {} ) } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt index b7263113..9f7a2687 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt @@ -4,13 +4,10 @@ import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString -import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentParser -import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentSegment import ac.cord.auxiliary.compose.ui.composable.widgets.content.RichContent import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileColor import ac.cord.auxiliary.compose.ui.theme.AuxTheme -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -21,14 +18,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.InputTransformation -import androidx.compose.foundation.text.input.OutputTransformation -import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddReaction import androidx.compose.material.icons.filled.ArrowBackIosNew @@ -37,36 +28,25 @@ import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Repeat -import androidx.compose.material.icons.filled.Reply -import androidx.compose.material.icons.filled.Send import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FlexibleBottomAppBar -import androidx.compose.material3.FloatingToolbarDefaults import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.HorizontalFloatingToolbar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Shapes import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.graphics.shapes.CornerRounding +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @@ -75,7 +55,7 @@ fun TextNoteEventDetail( localNostrEvent: LocalNostrEvent, onNavigateBack: () -> Unit, onNavigateToWriteAReply: (String) -> Unit, - onNavigateToQuoteNostrEvent: (String) -> Unit, + onNavigateToNostrEvent: (HexKey) -> Unit, ) { Scaffold( modifier = Modifier.fillMaxSize(), @@ -259,7 +239,8 @@ fun TextNoteEventDetail( ), nostrEvent = localNostrEvent.nostrEvent, localQuotedNostrEvent = localNostrEvent.localQuotedNostrEvent, - mentionedProfiles = localNostrEvent.mentionedProfiles + mentionedProfiles = localNostrEvent.mentionedProfiles, + onNavigateToEvent = onNavigateToNostrEvent ) Row( @@ -302,7 +283,7 @@ fun TextNoteEventDetail( IconButton( onClick = { - onNavigateToQuoteNostrEvent.invoke( + onNavigateToNostrEvent.invoke( localNostrEvent.nostrEvent.id ) } @@ -373,7 +354,7 @@ It has survived not only five centuries, but also the leap into electronic types ), onNavigateBack = {}, onNavigateToWriteAReply = {}, - onNavigateToQuoteNostrEvent = {} + onNavigateToNostrEvent = {} ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/EventListView.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/EventListView.kt index be382749..662402e1 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/EventListView.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/EventListView.kt @@ -1,8 +1,12 @@ package ac.cord.auxiliary.compose.ui.composable.widgets.feed +import ac.cord.auxiliary.compose.database.model.InReplyToRelation import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile +import ac.cord.auxiliary.compose.database.model.RepostedRelation +import ac.cord.auxiliary.compose.database.model.intermdiate.LocalInReplyToNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent +import ac.cord.auxiliary.compose.database.model.intermdiate.LocalRepostedNostrEvent import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString import ac.cord.auxiliary.compose.ui.composable.widgets.content.RichContent import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar @@ -36,6 +40,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import kotlin.time.Instant @@ -135,7 +140,7 @@ fun LocalNostrEvent.ListView( @Preview @Composable -private fun EventListViewPreview() { +private fun EventListViewRepostedPreview() { AuxTheme { Surface( modifier = Modifier.padding(20.dp) @@ -143,17 +148,92 @@ private fun EventListViewPreview() { LocalNostrEvent( nostrEvent = NostrEvent( id = "hex", - content = "Something something seomthing", + content = "Reposting", pubKey = "hexpubkeypubkeypubkeypubkey", - kind = 0, + kind = RepostEvent.KIND, sig = "hexsig", tags = emptyArray() ), + localRepostedNostrEvent = LocalRepostedNostrEvent( + repostedRelation = RepostedRelation( + repostingNostrEventId = "hex", + repostedNostrEventId = "repostedhex", + repostedProfilePublicKey = "reposterHex" + ), + nostrEvent = NostrEvent( + id = "hex", + content = "Something something something", + pubKey = "hexpubkeypubkeypubkeypubkey", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "hesd", + nostrEventId = "referenced", + displayName = "Frank Talk", + nip05 = "frank@talk.at" + ), + mentionedProfiles = emptyList() + ), profile = Profile( publicKey = "", nostrEventId = "referenced", - displayName = "Frank Talk", - nip05 = "frank@talk.at" + displayName = "Steve Biko", + nip05 = "steve@bck.co.za" + ), + ).ListView( + onNavigateToEvent = {} + ) + } + } +} + +@Preview +@Composable +private fun EventListViewInReplyPreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + LocalNostrEvent( + nostrEvent = NostrEvent( + id = "hex", + content = "Reposting", + pubKey = "hexpubkeypubkeypubkeypubkey", + kind = TextNoteEvent.KIND, + sig = "hexsig", + tags = emptyArray() + ), + localInReplyToNostrEvent = LocalInReplyToNostrEvent( + inReplyToRelation = InReplyToRelation( + inReplyToNostrEventId = "nostrEvent", + inReplyToRootNostrEventId = "rootId", + inReplyToProfilePublicKey = "inReplyToProfileKey", + inReplyToRootProfilePublicKey = "rootPubKey", + replyingNostrEventId = "hex" + ), + nostrEvent = NostrEvent( + id = "hex", + content = "Something something something", + pubKey = "hexpubkeypubkeypubkeypubkey", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "hesd", + nostrEventId = "referenced", + displayName = "Frank Talk", + nip05 = "frank@talk.at" + ), + mentionedProfiles = emptyList() + ), + profile = Profile( + publicKey = "", + nostrEventId = "referenced", + displayName = "Steve Biko", + nip05 = "steve@bck.co.za" ), ).ListView( onNavigateToEvent = {} diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt index 70f933ff..ef238969 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/widgets/feed/TextNoteFeedItem.kt @@ -1,10 +1,12 @@ package ac.cord.auxiliary.compose.ui.composable.widgets.feed +import ac.cord.auxiliary.compose.database.model.InReplyToRelation +import ac.cord.auxiliary.compose.database.model.Mention import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.Profile +import ac.cord.auxiliary.compose.database.model.QuotedRelation import ac.cord.auxiliary.compose.database.model.intermdiate.LocalInReplyToNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalMention -import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalQuotedNostrEvent import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString import ac.cord.auxiliary.compose.ui.composable.widgets.content.RichContent @@ -27,6 +29,7 @@ import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign @@ -44,156 +47,150 @@ internal fun TextNoteFeedItem( mentionedProfiles: List, onNavigateToEvent: (HexKey) -> Unit ) { - if (localInReplyToNostrEvent != null) { - Row( - modifier = Modifier.fillMaxWidth().padding( - horizontal = 20.dp - ), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - "In reply to ", - style = MaterialTheme.typography.labelSmall - ) + val expended = mutableStateOf(false) - Text( - modifier = Modifier.clickable( - onClick = { - onNavigateToEvent.invoke(localInReplyToNostrEvent.inReplyToRelation.inReplyToNostrEventId) - } + Column { + + if (localInReplyToNostrEvent != null) { + Row( + modifier = Modifier.fillMaxWidth().padding( + horizontal = 20.dp ), - style = MaterialTheme.typography.labelLargeEmphasized, - text = localInReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "loading information..." - ) - } - } + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "In reply to ", + style = MaterialTheme.typography.labelSmall + ) - ElevatedCard( - modifier = Modifier.padding( - horizontal = 10.dp - ), - onClick = { - onNavigateToEvent.invoke(nostrEvent.id) - } - ) { - Column( - modifier = Modifier.padding(10.dp).fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(10.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (profile != null) { - Row( - modifier = Modifier.fillMaxWidth().clickable( + Text( + modifier = Modifier.clickable( onClick = { - onNavigateToEvent.invoke(profile.nostrEventId) + onNavigateToEvent.invoke(localInReplyToNostrEvent.inReplyToRelation.inReplyToNostrEventId) } ), - horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start), - verticalAlignment = Alignment.CenterVertically - ) { - ProfileAvatar( - publicKey = nostrEvent.pubKey, - profile = profile - ) + style = MaterialTheme.typography.labelLargeEmphasized, + color = ProfileColor.fromPublicKey(localInReplyToNostrEvent.nostrEvent.pubKey), + text = localInReplyToNostrEvent.profile?.humanReadableNameOrPubkey() ?: "loading information..." + ) + } + } - Column( - modifier = Modifier.weight(1f) - ){ - Text( - text = profile.humanReadableNameOrPubkey(), - style = MaterialTheme.typography.bodyMedium + ElevatedCard( + modifier = Modifier.padding( + horizontal = 10.dp + ), + onClick = { + onNavigateToEvent.invoke(nostrEvent.id) + } + ) { + Column( + modifier = Modifier.padding(10.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (profile != null) { + Row( + modifier = Modifier.fillMaxWidth().clickable( + onClick = { + onNavigateToEvent.invoke(profile.nostrEventId) + } + ), + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically + ) { + ProfileAvatar( + publicKey = nostrEvent.pubKey, + profile = profile ) - profile.staticIdentifier()?.let { staticIdentifier -> + Column( + modifier = Modifier.weight(1f) + ){ Text( - text = staticIdentifier, - style = MaterialTheme.typography.bodySmall, - color = ProfileColor.fromProfile(profile) + text = profile.humanReadableNameOrPubkey(), + style = MaterialTheme.typography.bodyMedium + ) + + profile.staticIdentifier()?.let { staticIdentifier -> + Text( + text = staticIdentifier, + style = MaterialTheme.typography.bodySmall, + color = ProfileColor.fromProfile(profile) + ) + } + } + } + } else { + Text( + text = "Loading author information" + ) + } + + RichContent( + modifier = Modifier.fillMaxWidth(), + nostrEvent = nostrEvent, + localQuotedNostrEvent = localQuotedNostrEvent, + mentionedProfiles = mentionedProfiles, + onNavigateToEvent = onNavigateToEvent + ) + + if (expended.value) { + LazyRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy( + 10.dp, + alignment = Alignment.Start + ) + ) { + item { + AssistChip( + label = { + Text( + text = nostrEvent.kind.toString() // TODO: Kind by name... + ) + }, + onClick = { + + } + ) + } + + items( + items = nostrEvent.tags, + ) { tag -> + SuggestionChip( + label = { + Text( + text = tag.joinToString(":"), + maxLines = 1 + ) + }, + onClick = { + + } ) } } } - } else { + + Text( - text = "Loading author information" + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.End, + text = nostrEvent.createdAt.toFormattedTimeAndDateString() ) } - - RichContent( - modifier = Modifier.fillMaxWidth(), - nostrEvent = nostrEvent, - localQuotedNostrEvent = localQuotedNostrEvent, - mentionedProfiles = mentionedProfiles - ) - - LazyRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy( - 10.dp, - alignment = Alignment.Start - ) - ) { - item { - AssistChip( - label = { - Text( - text = nostrEvent.kind.toString() // TODO: Kind by name... - ) - }, - onClick = { - - } - ) - } - -// item { -// SuggestionChip( -// label = { -// Text( -// text = if (localNostrEvent.mentionedProfiles.isNotEmpty()) { -// localNostrEvent.mentionedProfiles.joinToString(",") { it?.profile?.humanReadableNameOrPubkey() ?: "Find user information" } -// } else { -// ":-(" -// } -// ) -// }, -// onClick = { -// -// } -// ) -// } - - items( - items = nostrEvent.tags, - ) { tag -> - SuggestionChip( - label = { - Text( - text = tag.joinToString(":"), - maxLines = 1 - ) - }, - onClick = { - - } - ) - } - } - - Text( - modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.labelLarge, - textAlign = TextAlign.End, - text = nostrEvent.createdAt.toFormattedTimeAndDateString() - ) } } + } @Preview @Composable -private fun EventListViewPreview() { +private fun TextNoteFeedItemPreview() { AuxTheme { Surface( modifier = Modifier.padding(20.dp) @@ -201,7 +198,7 @@ private fun EventListViewPreview() { TextNoteFeedItem( nostrEvent = NostrEvent( id = "hex", - content = "Something something something", + content = "You are either alive and proud or you are dead, and when you are dead, you can't care anyway. https://www.brainyquote.com/authors/steven-biko-quotes", pubKey = "hexpubkeypubkeypubkeypubkey", kind = 0, sig = "hexsig", @@ -220,4 +217,145 @@ private fun EventListViewPreview() { ) } } +} + +@Preview +@Composable +private fun TextNoteFeedItemInReplyToPreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + TextNoteFeedItem( + nostrEvent = NostrEvent( + id = "hex", + content = "It is better to die for an idea that will live, than to live for an idea that will die", + pubKey = "hexpubkeypubkeypubkeypubkey", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "", + nostrEventId = "referenced", + displayName = "Frank Talk", + nip05 = "frank@talk.at" + ), + localQuotedNostrEvent = null, + localInReplyToNostrEvent = LocalInReplyToNostrEvent( + inReplyToRelation = InReplyToRelation( + inReplyToNostrEventId = "nostrEvent", + inReplyToRootNostrEventId = "rootId", + inReplyToProfilePublicKey = "inReplyToProfileKey", + inReplyToRootProfilePublicKey = "rootPubKey", + replyingNostrEventId = "hex" + ), + nostrEvent = NostrEvent( + id = "hex", + content = "Something something something", + pubKey = "hexie", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "hesd", + nostrEventId = "referenced", + displayName = "Steve Biko", + nip05 = "steve@bcm.co.za" + ), + mentionedProfiles = emptyList() + ), + mentionedProfiles = emptyList(), + onNavigateToEvent = {} + ) + } + } +} + +@Preview +@Composable +private fun TextNoteFeedItemQuoteNostrEventPreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + TextNoteFeedItem( + nostrEvent = NostrEvent( + id = "hex", + content = "The revolutionary sees his task as liberation not only of the oppressed but also of the oppressor. Happiness can never truly exist in a state of tension.\n" + + "Read more at nostr:npub1uh366qnj68a7atvwgwdy4569xd6v5jtferftapqztwjwlzh9553q47pqsx\nnostr:nevent1qqspcusnzxx5u97xht2udvg3yhcq2cvmc0jrxa90lwwdvjplve0atespzpmhxue69uhkummnw3ezuamfdejsyg8h5g639qhuv803hzwexfwhyud6t6suxfu364anddqvrmn9j4cahvxksr8q", + pubKey = "hexpubkeypubkeypubkeypubkey", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "", + nostrEventId = "referenced", + displayName = "Frank Talk", + nip05 = "frank@talk.at" + ), + localQuotedNostrEvent = LocalQuotedNostrEvent( + nostrEvent = NostrEvent( + id = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6", + content = "God is not in the habit of coming down from heaven to solve people’s problems on earth.", + pubKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + quotedRelation = QuotedRelation( + quotingNostrEventId = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a520", + quotedProfilePublicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + quotedNostrEventId = "1c7213118d4e17c6bad5c6b11125f005619bc3e43374affb9cd6483f665fd5e6" + ), + profile = Profile( + publicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + nostrEventId = "referenced", + displayName = "Steve Biko" + ), + ), + localInReplyToNostrEvent = LocalInReplyToNostrEvent( + inReplyToRelation = InReplyToRelation( + inReplyToNostrEventId = "nostrEvent", + inReplyToRootNostrEventId = "rootId", + inReplyToProfilePublicKey = "inReplyToProfileKey", + inReplyToRootProfilePublicKey = "rootPubKey", + replyingNostrEventId = "hex" + ), + nostrEvent = NostrEvent( + id = "hex", + content = "Something something something", + pubKey = "hexie", + kind = 0, + sig = "hexsig", + tags = emptyArray() + ), + profile = Profile( + publicKey = "hesd", + nostrEventId = "referenced", + displayName = "Steve Biko", + nip05 = "steve@bcm.co.za" + ), + mentionedProfiles = emptyList() + ), + mentionedProfiles = listOf( + LocalMention( + mention = Mention( + mentioningNostrEventId = "000067b3f15cac88ba066b28c370083a4fc80dacc054e554ab1dc0def974c89c", + mentionedPublicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522" + ), + profile = Profile( + publicKey = "e5e3ad0272d1fbeead8e439a4ad3453374ca4969c8d2be84025ba4ef8ae5a522", + nostrEventId = "referenced", + displayName = "Steve Biko", + nip05 = "steve@bcm.co.za" + ), + ) + ), + onNavigateToEvent = {} + ) + } + } } \ No newline at end of file