Setup NostrEventDetailScreen.kt
This commit is contained in:
@@ -20,7 +20,7 @@ interface NostrEventDao {
|
||||
fun getAllNostrEvents(kinds: Array<Kind>): List<NostrEvent>
|
||||
|
||||
@Query("SELECT * FROM NostrEvent WHERE id = :id")
|
||||
fun observeNostrEventById(id: String): Flow<NostrEvent?>
|
||||
fun observeNostrEventById(id: String): Flow<LocalNostrEvent?>
|
||||
|
||||
@Query("SELECT * FROM NostrEvent WHERE id = :id")
|
||||
fun getNostrEventById(id: String): NostrEvent?
|
||||
|
||||
@@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlin.collections.emptyMap
|
||||
import kotlin.collections.plus
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.Instant
|
||||
|
||||
class DatabaseNostrRepository(
|
||||
@@ -308,4 +307,8 @@ class DatabaseNostrRepository(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> {
|
||||
return database.nostrEventDao().observeNostrEventById(nostrEventId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ interface NostrRepository {
|
||||
|
||||
suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>>
|
||||
|
||||
suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?>
|
||||
|
||||
companion object {
|
||||
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
@@ -146,6 +148,10 @@ interface NostrRepository {
|
||||
override suspend fun observeNostrFeed(): Flow<List<LocalNostrEvent>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package ac.aux.compose.ui.composable
|
||||
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
|
||||
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.fillMaxSize
|
||||
@@ -9,27 +13,113 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
|
||||
@Composable
|
||||
fun NostrEventDetailScreen() {
|
||||
fun NostrEventDetailScreen(
|
||||
initialNostrEventDetailUIState: NostrEventDetailUIState,
|
||||
nostrEventId: HexKey,
|
||||
nostrRepository: NostrRepository
|
||||
) {
|
||||
val nostrEventDetailViewModel: NostrEventDetailViewModel = viewModel(
|
||||
factory = NostrEventDetailViewModel.factory(
|
||||
nostrEventId = nostrEventId,
|
||||
initialNostrEventDetailUIState = initialNostrEventDetailUIState,
|
||||
nostrRepository = nostrRepository
|
||||
),
|
||||
)
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
when(val feedListUIState = nostrEventDetailViewModel.nostrEventDetailUIState) {
|
||||
NostrEventDetailUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.padding(20.dp).fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
"\"Nostr Event Detail\" functionality coming soon",
|
||||
Text("Something went wrong")
|
||||
}
|
||||
}
|
||||
is NostrEventDetailUIState.Loaded -> {
|
||||
|
||||
)
|
||||
when(feedListUIState.localNostrEvent.nostrEvent.kind) {
|
||||
MetadataEvent.KIND -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(feedListUIState.localNostrEvent.nostrEvent.content)
|
||||
|
||||
Text("profile functionality coming soon.")
|
||||
}
|
||||
}
|
||||
TextNoteEvent.KIND -> {
|
||||
// TODO: Post...
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(feedListUIState.localNostrEvent.nostrEvent.content)
|
||||
|
||||
Text("post functionality coming soon.")
|
||||
}
|
||||
}
|
||||
RepostEvent.KIND -> {
|
||||
// TODO: RePost...
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(feedListUIState.localNostrEvent.nostrEvent.content)
|
||||
|
||||
Text("post functionality coming soon.")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Scaffold { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(feedListUIState.localNostrEvent.nostrEvent.content)
|
||||
|
||||
Text("functionality coming soon.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
NostrEventDetailUIState.Loading -> {
|
||||
LoadingDataIndicator()
|
||||
|
||||
LaunchedEffect(true) {
|
||||
nostrEventDetailViewModel.loadNostrFeed()
|
||||
}
|
||||
}
|
||||
NostrEventDetailUIState.NotFound -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text("We couldn't find your nostr event. Please try again later.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +132,11 @@ private fun NostrEventDetailScreenPreview() {
|
||||
Surface(
|
||||
modifier = Modifier.padding(20.dp)
|
||||
) {
|
||||
NostrEventDetailScreen()
|
||||
NostrEventDetailScreen(
|
||||
initialNostrEventDetailUIState = NostrEventDetailUIState.Loading,
|
||||
nostrEventId = "",
|
||||
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import ac.aux.compose.ui.composable.navigation.routes.WriteNewNoteRoute
|
||||
import ac.aux.compose.ui.view.model.NavigationViewModel
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
import ac.aux.compose.ui.view.state.NavigationUIState
|
||||
import ac.aux.compose.ui.view.state.NostrEventDetailUIState
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -334,7 +335,11 @@ fun AuxNavHost(
|
||||
composable<NostrEventDetailRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<NostrEventDetailRoute>()
|
||||
|
||||
NostrEventDetailScreen()
|
||||
NostrEventDetailScreen(
|
||||
initialNostrEventDetailUIState = NostrEventDetailUIState.Loading,
|
||||
nostrEventId = route.nostrEventId,
|
||||
nostrRepository = nostrRepository,
|
||||
)
|
||||
}
|
||||
composable<ImplementationPendingRoute> { backStackEntry ->
|
||||
val route: ImplementationPendingRoute = backStackEntry.toRoute()
|
||||
|
||||
@@ -7,6 +7,6 @@ import kotlin.time.Clock
|
||||
|
||||
@Serializable
|
||||
data class NostrEventDetailRoute(
|
||||
val nostrEventId: String? = null,
|
||||
val nostrEventId: String,
|
||||
): Route() {
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.aux.compose.database.model.types.SynchronizationFilter
|
||||
import ac.aux.compose.managers.SeedManager
|
||||
import ac.aux.compose.nostr.Relays
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.ui.view.state.NostrEventDetailUIState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NostrEventDetailViewModel(
|
||||
val nostrEventId: HexKey,
|
||||
initialNostrEventDetailUIState: NostrEventDetailUIState,
|
||||
val nostrRepository: NostrRepository
|
||||
): ViewModel() {
|
||||
var nostrEventDetailUIState: NostrEventDetailUIState by mutableStateOf(initialNostrEventDetailUIState)
|
||||
private set
|
||||
|
||||
fun retryLoad() {
|
||||
nostrEventDetailUIState = NostrEventDetailUIState.Loading
|
||||
loadNostrFeed()
|
||||
}
|
||||
|
||||
fun loadNostrFeed() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Sync Notifications... might want to also run this in the background
|
||||
nostrRepository.queueSynchronizeNostrEvent(
|
||||
Relays.eventPublishRelaySet.map { normalizedRelay ->
|
||||
SynchronizeNostrEventRequest(
|
||||
synchronizationFilters = arrayOf(
|
||||
SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
TextNoteEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(SeedManager.activePublicKey().toHexKey()))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
),
|
||||
relayURL = normalizedRelay.url
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// Get contact list and sync feed...
|
||||
nostrRepository.observeLocalNostrEventById(
|
||||
nostrEventId
|
||||
).collect { localNostrEvent ->
|
||||
nostrEventDetailUIState = if (localNostrEvent != null) {
|
||||
NostrEventDetailUIState.Loaded(
|
||||
localNostrEvent = localNostrEvent
|
||||
)
|
||||
} else {
|
||||
NostrEventDetailUIState.Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "NostrEventDetailViewModel"
|
||||
|
||||
fun factory(
|
||||
nostrEventId: HexKey,
|
||||
initialNostrEventDetailUIState: NostrEventDetailUIState = NostrEventDetailUIState.Loading,
|
||||
nostrRepository: NostrRepository,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
NostrEventDetailViewModel(
|
||||
nostrEventId = nostrEventId,
|
||||
initialNostrEventDetailUIState = initialNostrEventDetailUIState,
|
||||
nostrRepository = nostrRepository
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ac.aux.compose.ui.view.state
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
|
||||
|
||||
sealed interface NostrEventDetailUIState {
|
||||
data class Loaded(
|
||||
val localNostrEvent: LocalNostrEvent
|
||||
): NostrEventDetailUIState
|
||||
|
||||
data object Error: NostrEventDetailUIState
|
||||
data object NotFound: NostrEventDetailUIState
|
||||
data object Loading: NostrEventDetailUIState
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user