Show feed of local nostr events.
This commit is contained in:
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface NostrEventDao {
|
||||
@Query("SELECT * FROM NostrEvent")
|
||||
@Query("SELECT * FROM NostrEvent ORDER BY createdAt DESC")
|
||||
fun getAllNostrEvents(): List<NostrEvent>
|
||||
|
||||
@Query("SELECT * FROM NostrEvent WHERE id = :id")
|
||||
|
||||
@@ -204,4 +204,8 @@ class DatabaseNostrRepository(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadNostrFeed(): List<NostrEvent> {
|
||||
return database.nostrEventDao().getAllNostrEvents()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,6 +64,8 @@ interface NostrRepository {
|
||||
synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>,
|
||||
)
|
||||
|
||||
suspend fun loadNostrFeed(): List<NostrEvent>
|
||||
|
||||
companion object {
|
||||
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
@@ -139,6 +141,10 @@ interface NostrRepository {
|
||||
override suspend fun queueSynchronizeNostrEvent(synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun loadNostrFeed(): List<NostrEvent> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package ac.aux.compose.ui.composable
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
|
||||
import ac.aux.compose.ui.composable.widgets.feed.FeedListItem
|
||||
import ac.aux.compose.ui.composable.widgets.feed.ListView
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import ac.aux.compose.ui.view.model.FeedListViewModel
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
@@ -14,8 +15,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Adb
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Create
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -30,6 +29,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -37,19 +37,19 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FeedScreen(
|
||||
initialFeedListUIState: FeedListUIState,
|
||||
onNavigateToEvent: (Event) -> Unit,
|
||||
onNavigateToEvent: (NostrEvent) -> Unit,
|
||||
onNavigateToWriteNewNote: () -> Unit,
|
||||
nostrRepository: NostrRepository,
|
||||
) {
|
||||
val feedListViewModel: FeedListViewModel = viewModel(
|
||||
factory = FeedListViewModel.factory(
|
||||
initialFeedListUIState = initialFeedListUIState,
|
||||
nostrRepository = nostrRepository
|
||||
),
|
||||
)
|
||||
|
||||
@@ -107,6 +107,10 @@ fun FeedScreen(
|
||||
when(val feedListUIState = feedListViewModel.feedListUIState) {
|
||||
is FeedListUIState.Loading -> {
|
||||
LoadingDataIndicator()
|
||||
|
||||
LaunchedEffect(true) {
|
||||
feedListViewModel.loadNostrFeed()
|
||||
}
|
||||
}
|
||||
FeedListUIState.Error -> {
|
||||
Text("Something went wrong")
|
||||
@@ -118,13 +122,13 @@ fun FeedScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(
|
||||
items = feedListUIState.events,
|
||||
items = feedListUIState.nostrEvents,
|
||||
key = { e -> e.id }
|
||||
) { event ->
|
||||
) { nostrEvent ->
|
||||
FeedListItem(
|
||||
event = event,
|
||||
nostrEvent = nostrEvent,
|
||||
onNavigateToEvent = {
|
||||
onNavigateToEvent.invoke(event)
|
||||
onNavigateToEvent.invoke(nostrEvent)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -145,7 +149,8 @@ private fun FeedScreenPreview() {
|
||||
FeedScreen(
|
||||
initialFeedListUIState = FeedListUIState.Loading,
|
||||
onNavigateToEvent = {},
|
||||
onNavigateToWriteNewNote = {}
|
||||
onNavigateToWriteNewNote = {},
|
||||
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import ac.aux.compose.ui.composable.navigation.routes.UnqueuedProfileSynchroniza
|
||||
import ac.aux.compose.ui.composable.navigation.routes.UnsignedProfileRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.UnsyncedProfileRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.WriteNewNoteRoute
|
||||
import ac.aux.compose.ui.composable.widgets.feed.DUMMY_EVENTS
|
||||
import ac.aux.compose.ui.view.model.NavigationViewModel
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
import ac.aux.compose.ui.view.state.NavigationUIState
|
||||
@@ -295,9 +294,7 @@ fun AuxNavHost(
|
||||
}
|
||||
composable<FeedRoute> {
|
||||
FeedScreen(
|
||||
initialFeedListUIState = FeedListUIState.Loaded(
|
||||
events = DUMMY_EVENTS,
|
||||
),
|
||||
initialFeedListUIState = FeedListUIState.Loading,
|
||||
onNavigateToEvent = { event ->
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute(
|
||||
@@ -309,7 +306,8 @@ fun AuxNavHost(
|
||||
navController.navigate(
|
||||
route = WriteNewNoteRoute()
|
||||
)
|
||||
}
|
||||
},
|
||||
nostrRepository = nostrRepository
|
||||
)
|
||||
}
|
||||
composable<BlankRoute> {
|
||||
|
||||
@@ -4,32 +4,3 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
|
||||
val DUMMY_EVENTS = listOf(
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is just a note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
),
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is another note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
),
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is a better note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -8,19 +9,23 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.BiasAbsoluteAlignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Composable
|
||||
fun Event.ListView() {
|
||||
fun NostrEvent.ListView() {
|
||||
when (kind) {
|
||||
// TODO: Support other kinds...
|
||||
else -> {
|
||||
@@ -29,8 +34,18 @@ fun Event.ListView() {
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = "Profile picture"
|
||||
)
|
||||
Text(
|
||||
text = pubKey
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = content,
|
||||
@@ -62,7 +77,7 @@ fun Event.ListView() {
|
||||
SuggestionChip(
|
||||
label = {
|
||||
Text(
|
||||
text = tag.toString()
|
||||
text = tag.joinToString(":")
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
@@ -71,6 +86,10 @@ fun Event.ListView() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = createdAt.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +102,14 @@ private fun EventListViewPreview() {
|
||||
Surface(
|
||||
modifier = Modifier.padding(20.dp)
|
||||
) {
|
||||
DUMMY_EVENTS.first().ListView()
|
||||
NostrEvent(
|
||||
id = "hex",
|
||||
content = "Something something seomthing",
|
||||
pubKey = "hexpubkeypubkeypubkeypubkey",
|
||||
kind = 0,
|
||||
sig = "hexsig",
|
||||
tags = emptyArray()
|
||||
).ListView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Composable
|
||||
fun FeedListItem(
|
||||
event: Event,
|
||||
onNavigateToEvent: (Event) -> Unit
|
||||
nostrEvent: NostrEvent,
|
||||
onNavigateToEvent: (NostrEvent) -> Unit
|
||||
) {
|
||||
ElevatedCard(
|
||||
onClick = {
|
||||
onNavigateToEvent.invoke(event)
|
||||
onNavigateToEvent.invoke(nostrEvent)
|
||||
}
|
||||
) {
|
||||
event.ListView()
|
||||
nostrEvent.ListView()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -9,35 +10,43 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FeedListViewModel(
|
||||
initialFeedListUIState: FeedListUIState,
|
||||
val nostrRepository: NostrRepository
|
||||
): ViewModel() {
|
||||
var feedListUIState: FeedListUIState by mutableStateOf(initialFeedListUIState)
|
||||
private set
|
||||
|
||||
fun retryLoad() {
|
||||
feedListUIState = FeedListUIState.Loading
|
||||
loadRecipientListData()
|
||||
loadNostrFeed()
|
||||
}
|
||||
|
||||
fun loadRecipientListData() {
|
||||
viewModelScope.launch {
|
||||
// TODO: Load Dummy data...
|
||||
fun loadNostrFeed() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val nostrFeed = nostrRepository.loadNostrFeed()
|
||||
|
||||
feedListUIState = FeedListUIState.Loaded(
|
||||
nostrEvents = nostrFeed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val TAG = "RecipientListViewModel"
|
||||
const val TAG = "FeedListViewModel"
|
||||
|
||||
fun factory(
|
||||
initialFeedListUIState: FeedListUIState = FeedListUIState.Loading,
|
||||
nostrRepository: NostrRepository,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
FeedListViewModel(
|
||||
initialFeedListUIState = initialFeedListUIState,
|
||||
nostrRepository = nostrRepository
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package ac.aux.compose.ui.view.state
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
|
||||
sealed interface FeedListUIState {
|
||||
data class Loaded(
|
||||
val events: List<Event>
|
||||
val nostrEvents: List<NostrEvent>
|
||||
): FeedListUIState
|
||||
|
||||
data object Error: FeedListUIState
|
||||
|
||||
Reference in New Issue
Block a user