simplifying the readme
This commit is contained in:
+27
-76
@@ -9,6 +9,9 @@ one instance of this class.
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
object AppGraph {
|
object AppGraph {
|
||||||
|
// application-wide scope
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
// the local db
|
// the local db
|
||||||
val sqlite = EventStore(dbName = "demo-events.db")
|
val sqlite = EventStore(dbName = "demo-events.db")
|
||||||
|
|
||||||
@@ -30,6 +33,18 @@ object AppGraph {
|
|||||||
|
|
||||||
// update this variable when a user logs in, starts with a guest
|
// update this variable when a user logs in, starts with a guest
|
||||||
var signer: NostrSigner = NostrSignerInternal(KeyPair())
|
var signer: NostrSigner = NostrSignerInternal(KeyPair())
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Periodic NIP-40 sweep — drops expired events from SQLite and
|
||||||
|
// emits StoreChange.DeleteExpired so live projections drop them
|
||||||
|
// too. Without this the on-disk store grows monotonically.
|
||||||
|
scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
delay(15.minutes)
|
||||||
|
runCatching { db.deleteExpiredEvents() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -100,61 +115,14 @@ fun main() {
|
|||||||
val viewModel = remember {
|
val viewModel = remember {
|
||||||
FeedViewModel(AppGraph.db, AppGraph.client, AppGraph.signer)
|
FeedViewModel(AppGraph.db, AppGraph.client, AppGraph.signer)
|
||||||
}
|
}
|
||||||
val notes by viewModel.feed.collectAsState()
|
|
||||||
FeedScreen(state = notes, onSend = viewModel::send)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
val noteState by viewModel.feed.collectAsStateWithLifecycle()
|
||||||
@Composable
|
when (noteState) {
|
||||||
fun FeedScreen(
|
is ProjectionState.Loading -> LoadingFeed()
|
||||||
state: ProjectionState<TextNoteEvent>,
|
is ProjectionState.Loaded -> Feed(noteState.items)
|
||||||
onSend: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text("Nostr Kind 1 Demo") },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { padding ->
|
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(padding)) {
|
|
||||||
Composer(onSend = onSend)
|
|
||||||
HorizontalDivider()
|
|
||||||
when (state) {
|
|
||||||
is ProjectionState.Loading -> LoadingFeed()
|
|
||||||
is ProjectionState.Loaded -> Feed(state.items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun Composer(onSend: (String) -> Unit) {
|
|
||||||
var text by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(12.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = text,
|
|
||||||
onValueChange = { text = it },
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
placeholder = { Text("What's on your mind?") },
|
|
||||||
maxLines = 4,
|
|
||||||
)
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
if (text.isNotBlank()) {
|
|
||||||
onSend(text)
|
|
||||||
text = ""
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
enabled = text.isNotBlank(),
|
}
|
||||||
) { Text("Post") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +135,6 @@ private fun LoadingFeed() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Feed(items: List<MutableStateFlow<TextNoteEvent>>) {
|
private fun Feed(items: List<MutableStateFlow<TextNoteEvent>>) {
|
||||||
// The list reference is stable while membership is unchanged, so
|
|
||||||
// LazyColumn only invalidates structure on insert / removal.
|
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
items(items = items, key = { it.value.id }) { handle ->
|
items(items = items, key = { it.value.id }) { handle ->
|
||||||
NoteRow(handle)
|
NoteRow(handle)
|
||||||
@@ -179,27 +145,12 @@ private fun Feed(items: List<MutableStateFlow<TextNoteEvent>>) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoteRow(handle: MutableStateFlow<TextNoteEvent>) {
|
private fun NoteRow(handle: MutableStateFlow<TextNoteEvent>) {
|
||||||
// Only collectors of THIS handle re-render when the event mutates
|
val event by handle.collectAsStateWithLifecycle()
|
||||||
// in place (addressable supersession, etc.).
|
Text(
|
||||||
val note by handle.collectAsState()
|
text = event.content,
|
||||||
Column(modifier = Modifier.fillMaxWidth().padding(12.dp)) {
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
Text(
|
)
|
||||||
text = note.pubKey.take(12) + "…",
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = formatTime(note.createdAt),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = MaterialTheme.colorScheme.outline,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = note.content,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user