TextNoteDetail screen

This commit is contained in:
Kgothatso Ngako
2026-04-01 01:11:23 +02:00
parent a910a95b1d
commit 9b6ae7159a
4 changed files with 288 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ package ac.aux.compose.extensions
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.format.MonthNames
import kotlinx.datetime.format.char
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant
@@ -10,15 +11,19 @@ import kotlin.time.Instant
fun Instant.toFormattedTimeAndDateString(): String {
return toLocalDateTime(TimeZone.currentSystemDefault()).format(
LocalDateTime.Format {
year()
char('-')
monthNumber()
char('-')
day()
char(' ')
hour()
char(':')
minute()
char(' ')
day()
char(' ')
monthName(
MonthNames.ENGLISH_ABBREVIATED
)
char(' ')
year()
}
)
}

View File

@@ -1,21 +1,52 @@
package ac.aux.compose.ui.composable
import ac.aux.compose.database.model.NostrEvent
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
import ac.aux.compose.extensions.toFormattedTimeAndDateString
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
import ac.aux.compose.ui.composable.widgets.detail.TextNoteDetail
import ac.aux.compose.ui.theme.AuxTheme
import ac.aux.compose.ui.view.model.NostrEventDetailViewModel
import ac.aux.compose.ui.view.state.NostrEventDetailUIState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddReaction
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.ChatBubble
import androidx.compose.material.icons.filled.CurrencyBitcoin
import androidx.compose.material.icons.filled.Cyclone
import androidx.compose.material.icons.filled.Loop
import androidx.compose.material.icons.filled.MonetizationOn
import androidx.compose.material.icons.filled.Money
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Repeat
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
@@ -24,11 +55,14 @@ import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NostrEventDetailScreen(
initialNostrEventDetailUIState: NostrEventDetailUIState,
nostrEventId: HexKey,
nostrRepository: NostrRepository
nostrRepository: NostrRepository,
onNavigateBack: () -> Unit,
onNavigateToWriteAReply: (String) -> Unit
) {
val nostrEventDetailViewModel: NostrEventDetailViewModel = viewModel(
factory = NostrEventDetailViewModel.factory(
@@ -63,16 +97,11 @@ fun NostrEventDetailScreen(
}
}
TextNoteEvent.KIND -> {
// TODO: Post...
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(feedListUIState.localNostrEvent.nostrEvent.content)
Text("post functionality coming soon.")
}
TextNoteDetail(
feedListUIState.localNostrEvent,
onNavigateBack = onNavigateBack,
onNavigateToWriteAReply = onNavigateToWriteAReply
)
}
RepostEvent.KIND -> {
// TODO: RePost...
@@ -102,7 +131,6 @@ fun NostrEventDetailScreen(
}
}
}
}
}
}
@@ -133,9 +161,31 @@ private fun NostrEventDetailScreenPreview() {
modifier = Modifier.padding(20.dp)
) {
NostrEventDetailScreen(
initialNostrEventDetailUIState = NostrEventDetailUIState.Loading,
initialNostrEventDetailUIState = NostrEventDetailUIState.Loaded(
localNostrEvent = LocalNostrEvent(
nostrEvent = NostrEvent(
id = "eventId",
pubKey = "publicKey",
content = """Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.""".trimIndent(),
tags = emptyArray(),
sig = "",
kind = TextNoteEvent.KIND
),
profile = Profile(
publicKey = "pubKey",
displayName = "John Doe",
nostrEventId = "nostrEventId"
)
)
),
nostrEventId = "",
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY,
onNavigateBack = {},
onNavigateToWriteAReply = {}
)
}
}

View File

@@ -339,6 +339,16 @@ fun AuxNavHost(
initialNostrEventDetailUIState = NostrEventDetailUIState.Loading,
nostrEventId = route.nostrEventId,
nostrRepository = nostrRepository,
onNavigateBack = {
navController.popBackStack()
},
onNavigateToWriteAReply = { nostrEventId ->
navController.navigate(
route = WriteNewNoteRoute(
inReplyToEventId = nostrEventId
)
)
}
)
}
composable<ImplementationPendingRoute> { backStackEntry ->

View File

@@ -0,0 +1,203 @@
package ac.aux.compose.ui.composable.widgets.detail
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
import ac.aux.compose.extensions.toFormattedTimeAndDateString
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddReaction
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.ChatBubble
import androidx.compose.material.icons.filled.CurrencyBitcoin
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Repeat
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
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.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TextNoteDetail(
localNostrEvent: LocalNostrEvent,
onNavigateBack: () -> Unit,
onNavigateToWriteAReply: (String) -> Unit
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
navigationIcon = {
IconButton(
onClick = onNavigateBack
) {
Icon(
Icons.Default.ArrowBackIosNew,
contentDescription = "Navigate back"
)
}
},
title = {
Text("Post")
},
)
}
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding).fillMaxSize()
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.fillMaxWidth().padding(20.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
val profile = localNostrEvent.profile
Icon(
Icons.Default.Person,
modifier = Modifier.size(50.dp),
contentDescription = profile.displayName ?: profile.userName ?: "Profile picture"
)
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = profile.displayName ?: profile.userName ?: profile.publicKey,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = profile.nip05 ?: "",
style = MaterialTheme.typography.labelMedium
)
}
Button(
onClick = {
}
) {
Text(
"Follow"
)
}
}
Text(
modifier = Modifier.fillMaxWidth().padding(
horizontal = 20.dp
),
textAlign = TextAlign.Start,
text = localNostrEvent.nostrEvent.content
)
Row(
modifier = Modifier.fillMaxWidth().padding(20.dp),
horizontalArrangement = Arrangement.Start
) {
Text(
text = localNostrEvent.nostrEvent.createdAt.toFormattedTimeAndDateString()
)
}
HorizontalDivider()
BottomAppBar(
actions = {
IconButton(
onClick = {
}
) {
Icon(
Icons.Default.AddReaction,
contentDescription = "Add Reaction"
)
}
IconButton(
onClick = {
}
) {
Icon(
Icons.Default.CurrencyBitcoin,
contentDescription = "Tip"
)
}
IconButton(
onClick = {
// TODO: Repost...
}
) {
Icon(
Icons.Default.Repeat,
contentDescription = "Repost"
)
}
},
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = {
onNavigateToWriteAReply.invoke(
localNostrEvent.nostrEvent.id
)
},
icon = {
Icon(
Icons.Default.ChatBubble,
contentDescription = "Reply"
)
},
text = {
Text("Reply")
}
)
},
)
HorizontalDivider()
Text("Replies coming soon...")
}
}
}
}
}
}