Migrates DVM route from pubkey to AppDefinition eventId and makes sure NFC reading will load the AppDefinition itself before rendering the rest of the screen.

This commit is contained in:
Vitor Pamplona
2024-05-17 18:06:20 -04:00
parent d6988ad4e1
commit 72c6e93524
6 changed files with 202 additions and 90 deletions
@@ -229,7 +229,7 @@ fun AppNavigation(
content = { content = {
it.arguments?.getString("id")?.let { id -> it.arguments?.getString("id")?.let { id ->
NIP90ContentDiscoveryScreen( NIP90ContentDiscoveryScreen(
dvmPublicKey = id, appDefinitionEventId = id,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
@@ -34,8 +34,10 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
@@ -60,11 +62,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@@ -73,6 +77,7 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
import coil.Coil import coil.Coil
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
@@ -97,6 +102,7 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.NostrVideoDataSource import com.vitorpamplona.amethyst.service.NostrVideoDataSource
import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.service.relays.RelayPool import com.vitorpamplona.amethyst.service.relays.RelayPool
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.note.AmethystIcon import com.vitorpamplona.amethyst.ui.note.AmethystIcon
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
@@ -106,6 +112,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadChannel
import com.vitorpamplona.amethyst.ui.note.LoadCityName import com.vitorpamplona.amethyst.ui.note.LoadCityName
import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.note.LoadUser
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.SearchIcon import com.vitorpamplona.amethyst.ui.note.SearchIcon
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
@@ -122,6 +129,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LongRoomHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.RoomNameOnlyDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.RoomNameOnlyDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShortChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShortChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.observeAppDefinition
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
@@ -297,26 +305,34 @@ private fun RoomTopBar(
@Composable @Composable
private fun DvmTopBar( private fun DvmTopBar(
id: String, appDefinitionId: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
navPopBack: () -> Unit, navPopBack: () -> Unit,
) { ) {
FlexibleTopBarWithBackButton( FlexibleTopBarWithBackButton(
title = { title = {
LoadUser(baseUserHex = id, accountViewModel) { baseUser -> LoadNote(baseNoteHex = appDefinitionId, accountViewModel = accountViewModel) { appDefinitionNote ->
if (baseUser != null) { if (appDefinitionNote != null) {
ClickableUserPicture( val card = observeAppDefinition(appDefinitionNote)
baseUser = baseUser,
accountViewModel = accountViewModel, card.cover?.let {
size = Size34dp, AsyncImage(
model = it,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(Size34dp).clip(shape = CircleShape),
) )
} ?: run { NoteAuthorPicture(baseNote = appDefinitionNote, size = Size34dp, accountViewModel = accountViewModel) }
Spacer(modifier = DoubleHorzSpacer) Spacer(modifier = DoubleHorzSpacer)
UsernameDisplay(baseUser, Modifier.weight(1f), fontWeight = FontWeight.Normal) Text(
} else { text = card.name,
Spacer(BottomTopHeight) fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} }
} }
}, },
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.EndedFlag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.OfflineFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.OfflineFlag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.observeAppDefinition
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding
@@ -342,7 +343,9 @@ fun InnerRenderClassifiedsThumb(
note: Note, note: Note,
) { ) {
Box( Box(
Modifier.fillMaxWidth().aspectRatio(1f), Modifier
.fillMaxWidth()
.aspectRatio(1f),
contentAlignment = BottomStart, contentAlignment = BottomStart,
) { ) {
card.image?.let { card.image?.let {
@@ -355,7 +358,10 @@ fun InnerRenderClassifiedsThumb(
} ?: run { DisplayAuthorBanner(note) } } ?: run { DisplayAuthorBanner(note) }
Row( Row(
Modifier.fillMaxWidth().background(Color.Black.copy(0.6f)).padding(Size5dp), Modifier
.fillMaxWidth()
.background(Color.Black.copy(0.6f))
.padding(Size5dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
card.title?.let { card.title?.let {
@@ -451,14 +457,20 @@ fun RenderLiveActivityThumb(
) { ) {
Box( Box(
contentAlignment = TopEnd, contentAlignment = TopEnd,
modifier = Modifier.aspectRatio(ratio = 16f / 9f).fillMaxWidth(), modifier =
Modifier
.aspectRatio(ratio = 16f / 9f)
.fillMaxWidth(),
) { ) {
card.cover?.let { card.cover?.let {
AsyncImage( AsyncImage(
model = it, model = it,
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier =
Modifier
.fillMaxSize()
.clip(QuoteBorder),
) )
} ?: run { DisplayAuthorBanner(baseNote) } } ?: run { DisplayAuthorBanner(baseNote) }
@@ -494,7 +506,9 @@ fun RenderLiveActivityThumb(
LoadParticipants(card.participants, baseNote, accountViewModel) { participantUsers -> LoadParticipants(card.participants, baseNote, accountViewModel) { participantUsers ->
Box( Box(
Modifier.padding(10.dp).align(BottomStart), Modifier
.padding(10.dp)
.align(BottomStart),
) { ) {
if (participantUsers.isNotEmpty()) { if (participantUsers.isNotEmpty()) {
Gallery(participantUsers, accountViewModel) Gallery(participantUsers, accountViewModel)
@@ -572,7 +586,10 @@ fun RenderCommunitiesThumb(
model = it, model = it,
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier =
Modifier
.fillMaxSize()
.clip(QuoteBorder),
) )
} }
} ?: run { DisplayAuthorBanner(baseNote) } } ?: run { DisplayAuthorBanner(baseNote) }
@@ -742,29 +759,7 @@ fun RenderContentDVMThumb(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val noteEvent = baseNote.event as? AppDefinitionEvent ?: return val card = observeAppDefinition(appDefinitionNote = baseNote)
val card by
baseNote
.live()
.metadata
.map {
val noteEvent = it.note.event as? AppDefinitionEvent
DVMCard(
name = noteEvent?.appMetaData()?.name ?: "",
description = noteEvent?.appMetaData()?.about ?: "",
cover = noteEvent?.appMetaData()?.image?.ifBlank { null },
)
}
.distinctUntilChanged()
.observeAsState(
DVMCard(
name = noteEvent.appMetaData()?.name ?: "",
description = noteEvent.appMetaData()?.about ?: "",
cover = noteEvent.appMetaData()?.image?.ifBlank { null },
),
)
LeftPictureLayout( LeftPictureLayout(
onImage = { onImage = {
@@ -774,7 +769,10 @@ fun RenderContentDVMThumb(
model = it, model = it,
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier =
Modifier
.fillMaxSize()
.clip(QuoteBorder),
) )
} }
} ?: run { DisplayAuthorBanner(baseNote) } } ?: run { DisplayAuthorBanner(baseNote) }
@@ -788,7 +786,7 @@ fun RenderContentDVMThumb(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdVertSpacer)
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = RowColSpacing, horizontalArrangement = RowColSpacing,
@@ -895,7 +893,10 @@ fun RenderChannelThumb(
model = it, model = it,
contentDescription = null, contentDescription = null,
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier =
Modifier
.fillMaxSize()
.clip(QuoteBorder),
) )
} ?: run { DisplayAuthorBanner(baseNote) } } ?: run { DisplayAuthorBanner(baseNote) }
}, },
@@ -970,6 +971,11 @@ fun Gallery(
@Composable @Composable
fun DisplayAuthorBanner(note: Note) { fun DisplayAuthorBanner(note: Note) {
WatchAuthor(note) { WatchAuthor(note) {
BannerImage(it, Modifier.fillMaxSize().clip(QuoteBorder)) BannerImage(
it,
Modifier
.fillMaxSize()
.clip(QuoteBorder),
)
} }
} }
@@ -39,16 +39,10 @@ fun WatchNoteEvent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onNoteEventFound: @Composable () -> Unit, onNoteEventFound: @Composable () -> Unit,
) { ) {
if (baseNote.event != null) { WatchNoteEvent(
onNoteEventFound() baseNote,
} else { onNoteEventFound,
// avoid observing costs if already has an event. onBlank = {
val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null)
Crossfade(targetState = hasEvent, label = "Event presence") {
if (it) {
onNoteEventFound()
} else {
LongPressToQuickAction( LongPressToQuickAction(
baseNote = baseNote, baseNote = baseNote,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
@@ -62,6 +56,27 @@ fun WatchNoteEvent(
}, },
) )
} }
},
)
}
@Composable
fun WatchNoteEvent(
baseNote: Note,
onNoteEventFound: @Composable () -> Unit,
onBlank: @Composable () -> Unit,
) {
if (baseNote.event != null) {
onNoteEventFound()
} else {
// avoid observing costs if already has an event.
val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null)
Crossfade(targetState = hasEvent, label = "Event presence") {
if (it) {
onNoteEventFound()
} else {
onBlank()
} }
} }
} }
@@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -36,45 +37,77 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.DVMCard
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
import com.vitorpamplona.amethyst.ui.screen.FeedEmpty import com.vitorpamplona.amethyst.ui.screen.FeedEmpty
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.Size75dp
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.events.AppDefinitionEvent
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.events.NIP90StatusEvent import com.vitorpamplona.quartz.events.NIP90StatusEvent
@Composable @Composable
fun NIP90ContentDiscoveryScreen( fun NIP90ContentDiscoveryScreen(
dvmPublicKey: String, appDefinitionEventId: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
LoadNote(baseNoteHex = appDefinitionEventId, accountViewModel = accountViewModel) {
it?.let { baseNote ->
WatchNoteEvent(
baseNote,
onNoteEventFound = {
NIP90ContentDiscoveryScreen(baseNote, accountViewModel, nav)
},
onBlank = {
FeedEmptywithStatus(baseNote, stringResource(R.string.dvm_looking_for_app), accountViewModel, nav)
},
)
}
}
}
@Composable
fun NIP90ContentDiscoveryScreen(
appDefinition: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteAuthor = appDefinition.author ?: return
var requestEventID by var requestEventID by
remember(dvmPublicKey) { remember(appDefinition) {
mutableStateOf<Note?>(null) mutableStateOf<Note?>(null)
} }
val onRefresh = { val onRefresh = {
accountViewModel.requestDVMContentDiscovery(dvmPublicKey) { accountViewModel.requestDVMContentDiscovery(noteAuthor.pubkeyHex) {
requestEventID = it requestEventID = it
} }
} }
LaunchedEffect(key1 = dvmPublicKey) { LaunchedEffect(key1 = appDefinition) {
onRefresh() onRefresh()
} }
@@ -84,7 +117,7 @@ fun NIP90ContentDiscoveryScreen(
val myRequestEventID = requestEventID val myRequestEventID = requestEventID
if (myRequestEventID != null) { if (myRequestEventID != null) {
ObserverContentDiscoveryResponse( ObserverContentDiscoveryResponse(
dvmPublicKey, appDefinition,
myRequestEventID, myRequestEventID,
onRefresh, onRefresh,
accountViewModel, accountViewModel,
@@ -92,19 +125,20 @@ fun NIP90ContentDiscoveryScreen(
) )
} else { } else {
// TODO: Make a good splash screen with loading animation for this DVM. // TODO: Make a good splash screen with loading animation for this DVM.
FeedEmptywithStatus(dvmPublicKey, stringResource(R.string.dvm_requesting_job), accountViewModel, nav) FeedEmptywithStatus(appDefinition, stringResource(R.string.dvm_requesting_job), accountViewModel, nav)
} }
} }
} }
@Composable @Composable
fun ObserverContentDiscoveryResponse( fun ObserverContentDiscoveryResponse(
dvmPublicKey: String, appDefinition: Note,
dvmRequestId: Note, dvmRequestId: Note,
onRefresh: () -> Unit, onRefresh: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val noteAuthor = appDefinition.author ?: return
val updateFiltersFromRelays = dvmRequestId.live().metadata.observeAsState() val updateFiltersFromRelays = dvmRequestId.live().metadata.observeAsState()
val resultFlow = val resultFlow =
@@ -116,7 +150,7 @@ fun ObserverContentDiscoveryResponse(
if (latestResponse != null) { if (latestResponse != null) {
PrepareViewContentDiscoveryModels( PrepareViewContentDiscoveryModels(
dvmPublicKey, noteAuthor,
dvmRequestId.idHex, dvmRequestId.idHex,
onRefresh, onRefresh,
accountViewModel, accountViewModel,
@@ -124,7 +158,7 @@ fun ObserverContentDiscoveryResponse(
) )
} else { } else {
ObserverDvmStatusResponse( ObserverDvmStatusResponse(
dvmPublicKey, appDefinition,
dvmRequestId.idHex, dvmRequestId.idHex,
accountViewModel, accountViewModel,
nav, nav,
@@ -134,7 +168,7 @@ fun ObserverContentDiscoveryResponse(
@Composable @Composable
fun ObserverDvmStatusResponse( fun ObserverDvmStatusResponse(
dvmPublicKey: String, appDefinition: Note,
dvmRequestId: String, dvmRequestId: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
@@ -149,17 +183,17 @@ fun ObserverDvmStatusResponse(
if (latestStatus != null) { if (latestStatus != null) {
// TODO: Make a good splash screen with loading animation for this DVM. // TODO: Make a good splash screen with loading animation for this DVM.
latestStatus?.let { latestStatus?.let {
FeedEmptywithStatus(dvmPublicKey, it.content(), accountViewModel, nav) FeedEmptywithStatus(appDefinition, it.content(), accountViewModel, nav)
} }
} else { } else {
// TODO: Make a good splash screen with loading animation for this DVM. // TODO: Make a good splash screen with loading animation for this DVM.
FeedEmptywithStatus(dvmPublicKey, stringResource(R.string.dvm_waiting_status), accountViewModel, nav) FeedEmptywithStatus(appDefinition, stringResource(R.string.dvm_waiting_status), accountViewModel, nav)
} }
} }
@Composable @Composable
fun PrepareViewContentDiscoveryModels( fun PrepareViewContentDiscoveryModels(
dvmPublicKey: String, dvm: User,
dvmRequestId: String, dvmRequestId: String,
onRefresh: () -> Unit, onRefresh: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -167,8 +201,8 @@ fun PrepareViewContentDiscoveryModels(
) { ) {
val resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel = val resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel =
viewModel( viewModel(
key = "NostrNIP90ContentDiscoveryFeedViewModel$dvmPublicKey$dvmRequestId", key = "NostrNIP90ContentDiscoveryFeedViewModel${dvm.pubkeyHex}$dvmRequestId",
factory = NostrNIP90ContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmkey = dvmPublicKey, requestid = dvmRequestId), factory = NostrNIP90ContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmkey = dvm.pubkeyHex, requestid = dvmRequestId),
) )
LaunchedEffect(key1 = dvmRequestId) { LaunchedEffect(key1 = dvmRequestId) {
@@ -210,7 +244,7 @@ fun RenderNostrNIP90ContentDiscoveryScreen(
@Composable @Composable
fun FeedEmptywithStatus( fun FeedEmptywithStatus(
pubkey: HexKey, appDefinitionNote: Note,
status: String, status: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
@@ -222,22 +256,62 @@ fun FeedEmptywithStatus(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
LoadUser(baseUserHex = pubkey, accountViewModel = accountViewModel) { baseUser -> val card = observeAppDefinition(appDefinitionNote)
if (baseUser != null) {
ClickableUserPicture( card.cover?.let {
baseUser = baseUser, AsyncImage(
accountViewModel = accountViewModel, model = it,
size = Size75dp, contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.size(Size75dp).clip(QuoteBorder),
)
} ?: run { NoteAuthorPicture(appDefinitionNote, nav, accountViewModel, Size75dp) }
Spacer(modifier = DoubleVertSpacer)
Text(
text = card.name,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
Spacer(modifier = DoubleVertSpacer) Spacer(modifier = DoubleVertSpacer)
UsernameDisplay(baseUser, Modifier, fontWeight = FontWeight.Normal)
Spacer(modifier = DoubleVertSpacer)
}
}
Text(status) Text(status)
} }
} }
@Composable
fun observeAppDefinition(appDefinitionNote: Note): DVMCard {
val noteEvent =
appDefinitionNote.event as? AppDefinitionEvent ?: return DVMCard(
name = "",
description = "",
cover = null,
)
val card by
appDefinitionNote
.live()
.metadata
.map {
val noteEvent = it.note.event as? AppDefinitionEvent
DVMCard(
name = noteEvent?.appMetaData()?.name ?: "",
description = noteEvent?.appMetaData()?.about ?: "",
cover = noteEvent?.appMetaData()?.image?.ifBlank { null },
)
}
.distinctUntilChanged()
.observeAsState(
DVMCard(
name = noteEvent.appMetaData()?.name ?: "",
description = noteEvent.appMetaData()?.about ?: "",
cover = noteEvent.appMetaData()?.image?.ifBlank { null },
),
)
return card
}
+1
View File
@@ -847,6 +847,7 @@
<string name="load_from_text">From Msg</string> <string name="load_from_text">From Msg</string>
<string name="dvm_looking_for_app">Looking for Application</string>
<string name="dvm_waiting_status">Job Requested, waiting for a reply</string> <string name="dvm_waiting_status">Job Requested, waiting for a reply</string>
<string name="dvm_requesting_job">Requesting Job from DVM</string> <string name="dvm_requesting_job">Requesting Job from DVM</string>
</resources> </resources>