From 334b94890084644a357a315a0c80ead046e75b02 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 13 May 2024 17:27:34 +0200 Subject: [PATCH] app meta data, show content dvms --- .../ui/dal/DiscoverNIP89FeedFilter.kt | 1 - .../amethyst/ui/note/ChannelCardCompose.kt | 103 ++++++++++++++++++ .../vitorpamplona/amethyst/ui/note/Loaders.kt | 22 ++++ .../amethyst/ui/note/types/AppDefinition.kt | 4 +- .../amethyst/ui/screen/FeedViewModel.kt | 2 +- .../quartz/events/AppDefinitionEvent.kt | 87 ++++++++++++++- 6 files changed, 213 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt index ce0ef9513..306372b6b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt @@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.PeopleListEvent open class DiscoverNIP89FeedFilter( - val ktag: Int, val account: Account, ) : AdditiveFeedFilter() { override fun feedKey(): String { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index 85f8ab9ca..0176f33fb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -89,6 +89,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.quartz.events.AppDefinitionEvent import com.vitorpamplona.quartz.events.ChannelCreateEvent import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent @@ -213,6 +214,9 @@ fun InnerChannelCardWithReactions( is ClassifiedsEvent -> { InnerCardBox(baseNote, accountViewModel, nav) } + is AppDefinitionEvent -> { + InnerCardRow(baseNote, accountViewModel, nav) + } } } @@ -268,6 +272,9 @@ private fun RenderNoteRow( is ChannelCreateEvent -> { RenderChannelThumb(baseNote, accountViewModel, nav) } + is AppDefinitionEvent -> { + RenderContentDVMThumb(baseNote, accountViewModel, nav) + } } } @@ -516,6 +523,14 @@ data class CommunityCard( val moderators: ImmutableList, ) +@Immutable +data class DVMCard( + val name: String, + val description: String?, + val cover: String?, + val moderators: ImmutableList, +) + @Composable fun RenderCommunitiesThumb( baseNote: Note, @@ -715,6 +730,94 @@ private fun LoadParticipants( inner(participantUsers) } +@Composable +fun RenderContentDVMThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val noteEvent = baseNote.event as? AppDefinitionEvent ?: return + + 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 }, + moderators = persistentListOf(), + ) + } + .distinctUntilChanged() + .observeAsState( + DVMCard( + name = noteEvent.appMetaData()?.name ?: "", + description = noteEvent.appMetaData()?.about ?: "", + cover = noteEvent.appMetaData()?.image?.ifBlank { null }, + moderators = persistentListOf(), + ), + ) + + LeftPictureLayout( + onImage = { + card.cover?.let { + Box(contentAlignment = BottomStart) { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize().clip(QuoteBorder), + ) + } + } ?: run { DisplayAuthorBanner(baseNote) } + }, + onTitleRow = { + Text( + text = card.name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + + Spacer(modifier = StdHorzSpacer) + LikeReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + Spacer(modifier = StdHorzSpacer) + ZapReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + }, + onDescription = { + card.description?.let { + Spacer(modifier = StdVertSpacer) + Row { + Text( + text = it, + color = MaterialTheme.colorScheme.placeholderText, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + fontSize = 14.sp, + ) + } + } + }, + onBottomRow = { + }, + ) +} + @Composable fun RenderChannelThumb( baseNote: Note, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt index 3379d9025..f7bab1a9d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt @@ -234,3 +234,25 @@ fun LoadChannel( channel?.let { content(it) } } + +@Composable +fun LoadDVMNip89( + baseChannelHex: String, + accountViewModel: AccountViewModel, + content: @Composable (Channel) -> Unit, +) { + var channel by + remember(baseChannelHex) { + mutableStateOf(accountViewModel.getChannelIfExists(baseChannelHex)) + } + + if (channel == null) { + LaunchedEffect(key1 = baseChannelHex) { + accountViewModel.checkGetOrCreateChannel(baseChannelHex) { newChannel -> + launch(Dispatchers.Main) { channel = newChannel } + } + } + } + + channel?.let { content(it) } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt index 663bec70d..49d813789 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt @@ -70,8 +70,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size16Modifier import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.AppDefinitionEvent +import com.vitorpamplona.quartz.events.AppMetadata import com.vitorpamplona.quartz.events.EmptyTagList -import com.vitorpamplona.quartz.events.UserMetadata import com.vitorpamplona.quartz.events.toImmutableListOfLists import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -85,7 +85,7 @@ fun RenderAppDefinition( ) { val noteEvent = note.event as? AppDefinitionEvent ?: return - var metadata by remember { mutableStateOf(null) } + var metadata by remember { mutableStateOf(null) } LaunchedEffect(key1 = noteEvent) { withContext(Dispatchers.Default) { metadata = noteEvent.appMetaData() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 852705688..58d8dec22 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -112,7 +112,7 @@ class NostrDiscoverMarketplaceFeedViewModel(val account: Account) : class NostrDiscoverNIP89FeedViewModel(val account: Account) : FeedViewModel( - DiscoverNIP89FeedFilter(5003, account), + DiscoverNIP89FeedFilter(account), ) { class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): NostrDiscoverNIP89FeedViewModel { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/AppDefinitionEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/AppDefinitionEvent.kt index 8374c518d..f0cf4a7c3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/AppDefinitionEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/AppDefinitionEvent.kt @@ -22,11 +22,94 @@ package com.vitorpamplona.quartz.events import android.util.Log import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable +import com.fasterxml.jackson.annotation.JsonProperty import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.utils.TimeUtils import java.io.ByteArrayInputStream +@Stable +class AppMetadata { + var name: String? = null + var username: String? = null + + @JsonProperty("display_name") + var displayName: String? = null + var picture: String? = null + + var banner: String? = null + var image: String? = null + var website: String? = null + var about: String? = null + var subscription: Boolean? = false + var cashuAccepted: Boolean? = false + var encryptionSupported: Boolean? = false + var personalized: Boolean? = false + var amount: String? = null + + var nip05: String? = null + var domain: String? = null + var lud06: String? = null + var lud16: String? = null + + var twitter: String? = null + + @Transient + var tags: ImmutableListOfLists? = null + + fun anyName(): String? { + return displayName ?: name ?: username + } + + fun anyNameStartsWith(prefix: String): Boolean { + return listOfNotNull(name, username, displayName, nip05, lud06, lud16).any { + it.contains(prefix, true) + } + } + + fun lnAddress(): String? { + return lud16 ?: lud06 + } + + fun bestName(): String? { + return displayName ?: name ?: username + } + + fun nip05(): String? { + return nip05 + } + + fun profilePicture(): String? { + return picture + } + + fun cleanBlankNames() { + if (picture?.isNotEmpty() == true) picture = picture?.trim() + if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim() + + if (displayName?.isNotEmpty() == true) displayName = displayName?.trim() + if (name?.isNotEmpty() == true) name = name?.trim() + if (username?.isNotEmpty() == true) username = username?.trim() + if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim() + if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim() + + if (website?.isNotEmpty() == true) website = website?.trim() + if (domain?.isNotEmpty() == true) domain = domain?.trim() + + if (picture?.isBlank() == true) picture = null + if (nip05?.isBlank() == true) nip05 = null + if (displayName?.isBlank() == true) displayName = null + if (name?.isBlank() == true) name = null + if (username?.isBlank() == true) username = null + if (lud06?.isBlank() == true) lud06 = null + if (lud16?.isBlank() == true) lud16 = null + + if (website?.isBlank() == true) website = null + if (domain?.isBlank() == true) domain = null + } +} + @Immutable class AppDefinitionEvent( id: HexKey, @@ -36,7 +119,7 @@ class AppDefinitionEvent( content: String, sig: HexKey, ) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) { - @Transient private var cachedMetadata: UserMetadata? = null + @Transient private var cachedMetadata: AppMetadata? = null fun appMetaData() = if (cachedMetadata != null) { @@ -46,7 +129,7 @@ class AppDefinitionEvent( val newMetadata = mapper.readValue( ByteArrayInputStream(content.toByteArray(Charsets.UTF_8)), - UserMetadata::class.java, + AppMetadata::class.java, ) cachedMetadata = newMetadata