use alternative gallery using 1163 events instead of 10011 lists.

(This might still need some tuning but works in general)
This commit is contained in:
Believethehype
2024-07-06 00:11:35 +02:00
parent 06a1862d17
commit 511a7030b8
13 changed files with 291 additions and 156 deletions
@@ -71,7 +71,6 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileServersEvent
import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.GalleryListEvent
import com.vitorpamplona.quartz.events.GeneralListEvent import com.vitorpamplona.quartz.events.GeneralListEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GenericRepostEvent
import com.vitorpamplona.quartz.events.GiftWrapEvent import com.vitorpamplona.quartz.events.GiftWrapEvent
@@ -92,6 +91,7 @@ import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.Price import com.vitorpamplona.quartz.events.Price
import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.events.ReactionEvent import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.RelayAuthEvent import com.vitorpamplona.quartz.events.RelayAuthEvent
import com.vitorpamplona.quartz.events.ReportEvent import com.vitorpamplona.quartz.events.ReportEvent
@@ -2204,48 +2204,27 @@ class Account(
relay: String?, relay: String?,
) { ) {
if (!isWriteable()) return if (!isWriteable()) return
ProfileGalleryEntryEvent.create(
GalleryListEvent.addEvent( url = url,
userProfile().latestGalleryList, eventid = idHex,
idHex, /*magnetUri = magnetUri,
url, mimeType = headerInfo.mimeType,
relay, hash = headerInfo.hash,
signer, size = headerInfo.size.toString(),
) { dimensions = headerInfo.dim,
Client.send(it) blurhash = headerInfo.blurHash,
LocalCache.consume(it) alt = alt,
originalHash = originalHash,
sensitiveContent = sensitiveContent, */
signer = signer,
) { event ->
Client.send(event)
LocalCache.consume(event, null)
} }
} }
fun removeFromGallery( fun removeFromGallery(note: Note) {
note: Note, delete(note)
url: String,
) {
if (!isWriteable()) return
val galleryentries = userProfile().latestGalleryList ?: return
if (note is AddressableNote) {
GalleryListEvent.removeReplaceable(
galleryentries,
note.address,
url,
signer,
) {
Client.send(it)
LocalCache.consume(it)
}
} else {
GalleryListEvent.removeEvent(
galleryentries,
note.idHex,
url,
signer,
) {
Client.send(it)
LocalCache.consume(it)
}
}
} }
fun addBookmark( fun addBookmark(
@@ -75,7 +75,6 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileServersEvent
import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.GalleryListEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GenericRepostEvent
import com.vitorpamplona.quartz.events.GiftWrapEvent import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.GitIssueEvent import com.vitorpamplona.quartz.events.GitIssueEvent
@@ -104,6 +103,7 @@ import com.vitorpamplona.quartz.events.PinListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.events.ReactionEvent import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.RecommendRelayEvent import com.vitorpamplona.quartz.events.RecommendRelayEvent
import com.vitorpamplona.quartz.events.RelaySetEvent import com.vitorpamplona.quartz.events.RelaySetEvent
@@ -424,17 +424,6 @@ object LocalCache {
} }
} }
fun consume(event: GalleryListEvent) {
val user = getOrCreateUser(event.pubKey)
if (user.latestGalleryList == null || event.createdAt > user.latestGalleryList!!.createdAt) {
user.updateGallery(event)
// Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}")
} else {
// Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()}
// ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}")
}
}
fun formattedDateTime(timestamp: Long): String = fun formattedDateTime(timestamp: Long): String =
Instant Instant
.ofEpochSecond(timestamp) .ofEpochSecond(timestamp)
@@ -1680,6 +1669,26 @@ object LocalCache {
refreshObservers(note) refreshObservers(note)
} }
fun consume(
event: ProfileGalleryEntryEvent,
relay: Relay?,
) {
val note = getOrCreateNote(event.id)
val author = getOrCreateUser(event.pubKey)
if (relay != null) {
author.addRelayBeingUsed(relay, event.createdAt)
note.addRelay(relay)
}
// Already processed this event.
if (note.event != null) return
note.loadEvent(event, author, emptyList())
refreshObservers(note)
}
fun consume( fun consume(
event: FileStorageHeaderEvent, event: FileStorageHeaderEvent,
relay: Relay?, relay: Relay?,
@@ -2535,13 +2544,13 @@ object LocalCache {
is DraftEvent -> consume(event, relay) is DraftEvent -> consume(event, relay)
is EmojiPackEvent -> consume(event, relay) is EmojiPackEvent -> consume(event, relay)
is EmojiPackSelectionEvent -> consume(event, relay) is EmojiPackSelectionEvent -> consume(event, relay)
is GalleryListEvent -> consume(event)
is GenericRepostEvent -> { is GenericRepostEvent -> {
event.containedPost()?.let { verifyAndConsume(it, relay) } event.containedPost()?.let { verifyAndConsume(it, relay) }
consume(event) consume(event)
} }
is FhirResourceEvent -> consume(event, relay) is FhirResourceEvent -> consume(event, relay)
is FileHeaderEvent -> consume(event, relay) is FileHeaderEvent -> consume(event, relay)
is ProfileGalleryEntryEvent -> consume(event, relay)
is FileServersEvent -> consume(event, relay) is FileServersEvent -> consume(event, relay)
is FileStorageEvent -> consume(event, relay) is FileStorageEvent -> consume(event, relay)
is FileStorageHeaderEvent -> consume(event, relay) is FileStorageHeaderEvent -> consume(event, relay)
@@ -139,7 +139,7 @@ open class Note(
var relays = listOf<RelayBriefInfoCache.RelayBriefInfo>() var relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
private set private set
var headerImage: String? = null var associatedNote: Note? = null
var lastReactionsDownloadTime: Map<String, EOSETime> = emptyMap() var lastReactionsDownloadTime: Map<String, EOSETime> = emptyMap()
@@ -40,7 +40,6 @@ import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.events.BookmarkListEvent import com.vitorpamplona.quartz.events.BookmarkListEvent
import com.vitorpamplona.quartz.events.ChatroomKey import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.GalleryListEvent
import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.MetadataEvent import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.ReportEvent import com.vitorpamplona.quartz.events.ReportEvent
@@ -61,7 +60,6 @@ class User(
var latestMetadataRelay: String? = null var latestMetadataRelay: String? = null
var latestContactList: ContactListEvent? = null var latestContactList: ContactListEvent? = null
var latestBookmarkList: BookmarkListEvent? = null var latestBookmarkList: BookmarkListEvent? = null
var latestGalleryList: GalleryListEvent? = null
var reports = mapOf<User, Set<Note>>() var reports = mapOf<User, Set<Note>>()
private set private set
@@ -125,13 +123,6 @@ class User(
liveSet?.innerBookmarks?.invalidateData() liveSet?.innerBookmarks?.invalidateData()
} }
fun updateGallery(event: GalleryListEvent) {
if (event.id == latestGalleryList?.id) return
print("GALLERY " + event.id())
latestGalleryList = event
liveSet?.innerGallery?.invalidateData()
}
fun clearEOSE() { fun clearEOSE() {
latestEOSEs = emptyMap() latestEOSEs = emptyMap()
} }
@@ -31,7 +31,6 @@ import com.vitorpamplona.quartz.events.BadgeAwardEvent
import com.vitorpamplona.quartz.events.BadgeProfilesEvent import com.vitorpamplona.quartz.events.BadgeProfilesEvent
import com.vitorpamplona.quartz.events.BookmarkListEvent import com.vitorpamplona.quartz.events.BookmarkListEvent
import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.GalleryListEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GenericRepostEvent
import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.HighlightEvent
import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.LnZapEvent
@@ -40,6 +39,7 @@ import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PinListEvent import com.vitorpamplona.quartz.events.PinListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent import com.vitorpamplona.quartz.events.WikiNoteEvent
@@ -84,7 +84,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
WikiNoteEvent.KIND, WikiNoteEvent.KIND,
), ),
authors = listOf(it.pubkeyHex), authors = listOf(it.pubkeyHex),
limit = 200, limit = 500,
), ),
) )
} }
@@ -147,13 +147,27 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
filter = filter =
Filter( Filter(
kinds = kinds =
listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND, GalleryListEvent.KIND), listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND),
authors = listOf(it.pubkeyHex), authors = listOf(it.pubkeyHex),
limit = 100, limit = 100,
), ),
) )
} }
fun createProfileGalleryFilter() =
user?.let {
TypedFilter(
types = COMMON_FEED_TYPES,
filter =
Filter(
kinds =
listOf(ProfileGalleryEntryEvent.KIND),
authors = listOf(it.pubkeyHex),
limit = 1000,
),
)
}
fun createReceivedAwardsFilter() = fun createReceivedAwardsFilter() =
user?.let { user?.let {
TypedFilter( TypedFilter(
@@ -174,6 +188,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
listOfNotNull( listOfNotNull(
createUserInfoFilter(), createUserInfoFilter(),
createUserPostsFilter(), createUserPostsFilter(),
createProfileGalleryFilter(),
createFollowFilter(), createFollowFilter(),
createFollowersFilter(), createFollowersFilter(),
createUserReceivedZapsFilter(), createUserReceivedZapsFilter(),
@@ -24,47 +24,65 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.ammolite.relays.FeedType import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.ammolite.relays.Relay import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent
class UserProfileGalleryFeedFilter( class UserProfileGalleryFeedFilter(
val user: User, val user: User,
val account: Account, val account: Account,
) : FeedFilter<Note>() { ) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-Gallery-" + user.pubkeyHex override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultStoriesFollowList.value
override fun showHiddenKey(): Boolean =
account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
override fun feed(): List<Note> { override fun feed(): List<Note> {
val params = buildFilterParams(account)
val notes = val notes =
user.latestGalleryList LocalCache.notes.filterIntoSet { _, it ->
?.taggedGalleryEntries() acceptableEvent(it, params, user)
?.map { }
Triple(
// (
// if (ATag.isATag(it.id)) {
// ATag.parse(it.id, null)?.let { it1 -> LocalCache.getOrCreateAddressableNote(it1) }
// } else {
LocalCache.getOrCreateNote(it.id),
// }
// )!!
it.url,
it.relay,
)
}?.toSet()
?: emptySet()
var finalnotes = setOf<Note>() var finalnotes = setOf<Note>()
for (pair in notes) { for (item in notes) {
pair.first.headerImage = pair.second item.associatedNote = (item.event as ProfileGalleryEntryEvent).event()?.let { LocalCache.getOrCreateNote(it) }
if (pair.third != null) { finalnotes = finalnotes + item
val relay = Relay(pair.third!!, true, false, setOf(FeedType.GLOBAL))
pair.first.createdAt()?.let { user.addRelayBeingUsed(relay, it) }
pair.first.addRelay(relay)
}
finalnotes = finalnotes + pair.first
} }
println(finalnotes)
return (finalnotes) return sort(finalnotes)
.filter { account.isAcceptable(it) }
} }
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val params = buildFilterParams(account)
return collection.filterTo(HashSet()) { acceptableEvent(it, params, user) }
}
fun acceptableEvent(
it: Note,
params: FilterByListParams,
user: User,
): Boolean {
val noteEvent = it.event
return (
(it.event?.pubKey() == user.pubkeyHex && noteEvent is ProfileGalleryEntryEvent) && noteEvent.hasUrl() && noteEvent.hasEvent() // && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))
) &&
params.match(noteEvent) &&
account.isAcceptable(it)
}
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultStoriesFollowList.value,
followLists = account.liveStoriesFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
} }
@@ -671,11 +671,7 @@ fun DeleteFromGalleryDialog(
buttonIcon = Icons.Default.Delete, buttonIcon = Icons.Default.Delete,
buttonText = stringRes(R.string.quick_action_delete_dialog_btn), buttonText = stringRes(R.string.quick_action_delete_dialog_btn),
onClickDoOnce = { onClickDoOnce = {
note.headerImage.let { accountViewModel.removefromMediaGallery(note)
if (it != null) {
accountViewModel.removefromMediaGallery(note, it)
}
}
onDismiss() onDismiss()
}, },
onDismiss = onDismiss, onDismiss = onDismiss,
@@ -677,11 +677,8 @@ class AccountViewModel(
viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url, relay) } viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url, relay) }
} }
fun removefromMediaGallery( fun removefromMediaGallery(note: Note) {
note: Note, viewModelScope.launch(Dispatchers.IO) { account.removeFromGallery(note) }
url: String,
) {
viewModelScope.launch(Dispatchers.IO) { account.removeFromGallery(note, url) }
} }
fun addPrivateBookmark(note: Note) { fun addPrivateBookmark(note: Note) {
@@ -33,7 +33,6 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -74,7 +73,7 @@ import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent
@Composable @Composable
fun RenderGalleryFeed( fun RenderGalleryFeed(
@@ -134,27 +133,45 @@ private fun GalleryFeedLoaded(
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() }
Row(defaultModifier) { if (item.associatedNote != null) {
GalleryCardCompose( if (item.associatedNote!!.event != null) {
baseNote = item, if ((item.event as ProfileGalleryEntryEvent).hasUrl() &&
routeForLastRead = routeForLastRead, (item.event as ProfileGalleryEntryEvent).hasEvent()
modifier = Modifier, ) {
forceEventKind = forceEventKind, val image = (item.event as ProfileGalleryEntryEvent).url()
accountViewModel = accountViewModel,
nav = nav,
)
}
HorizontalDivider( Row(defaultModifier) {
thickness = DividerThickness, if (image != null) {
) GalleryCardCompose(
galleryNote = item,
image = image,
baseNote = item.associatedNote!!,
routeForLastRead = routeForLastRead,
modifier = Modifier,
forceEventKind = forceEventKind,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
HorizontalDivider(
thickness = DividerThickness,
)
} else {
accountViewModel.delete(item)
}
}
}
} }
} }
} }
@Composable @Composable
fun GalleryCardCompose( fun GalleryCardCompose(
galleryNote: Note,
baseNote: Note, baseNote: Note,
image: String,
routeForLastRead: String? = null, routeForLastRead: String? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
parentBackgroundColor: MutableState<Color>? = null, parentBackgroundColor: MutableState<Color>? = null,
@@ -172,8 +189,11 @@ fun GalleryCardCompose(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) { canPreview -> ) { canPreview ->
GalleryCard( GalleryCard(
galleryNote = galleryNote,
baseNote = baseNote, baseNote = baseNote,
image = image,
modifier = modifier, modifier = modifier,
parentBackgroundColor = parentBackgroundColor, parentBackgroundColor = parentBackgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
@@ -185,16 +205,19 @@ fun GalleryCardCompose(
@Composable @Composable
fun GalleryCard( fun GalleryCard(
galleryNote: Note,
baseNote: Note, baseNote: Note,
image: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
parentBackgroundColor: MutableState<Color>? = null, parentBackgroundColor: MutableState<Color>? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
// baseNote.event?.let { Text(text = it.pubKey()) } // baseNote.event?.let { Text(text = it.pubKey()) }
LongPressToQuickActionGallery(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> LongPressToQuickActionGallery(baseNote = galleryNote, accountViewModel = accountViewModel) { showPopup ->
CheckNewAndRenderChannelCard( CheckNewAndRenderChannelCard(
baseNote, baseNote,
image,
modifier, modifier,
parentBackgroundColor, parentBackgroundColor,
accountViewModel, accountViewModel,
@@ -207,6 +230,7 @@ fun GalleryCard(
@Composable @Composable
private fun CheckNewAndRenderChannelCard( private fun CheckNewAndRenderChannelCard(
baseNote: Note, baseNote: Note,
image: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
parentBackgroundColor: MutableState<Color>? = null, parentBackgroundColor: MutableState<Color>? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -228,26 +252,14 @@ private fun CheckNewAndRenderChannelCard(
showPopup = showPopup, showPopup = showPopup,
nav = nav, nav = nav,
) { ) {
InnerGalleryCardWithReactions( InnerGalleryCardBox(baseNote, image, accountViewModel, nav)
baseNote = baseNote,
accountViewModel = accountViewModel,
nav = nav,
)
} }
} }
@Composable
fun InnerGalleryCardWithReactions(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
InnerGalleryCardBox(baseNote, accountViewModel, nav)
}
@Composable @Composable
fun InnerGalleryCardBox( fun InnerGalleryCardBox(
baseNote: Note, baseNote: Note,
image: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
@@ -256,7 +268,7 @@ fun InnerGalleryCardBox(
note = baseNote, note = baseNote,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
) { ) {
RenderGalleryThumb(baseNote, accountViewModel, nav) RenderGalleryThumb(baseNote, image, accountViewModel, nav)
} }
} }
} }
@@ -272,21 +284,19 @@ data class GalleryThumb(
@Composable @Composable
fun RenderGalleryThumb( fun RenderGalleryThumb(
baseNote: Note, baseNote: Note,
image: String,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val noteEvent = baseNote.event as? TextNoteEvent ?: return
val card by val card by
baseNote baseNote
.live() .live()
.metadata .metadata
.map { .map {
val noteEvent = baseNote.event as TextNoteEvent
GalleryThumb( GalleryThumb(
id = "", id = "",
image = baseNote.headerImage, image = image,
title = noteEvent.content(), title = "",
// noteEvent?.title(), // noteEvent?.title(),
// price = noteEvent?.price(), // price = noteEvent?.price(),
) )
@@ -294,11 +304,8 @@ fun RenderGalleryThumb(
.observeAsState( .observeAsState(
GalleryThumb( GalleryThumb(
id = "", id = "",
image = baseNote.headerImage, image = image,
title = noteEvent.content(), title = "",
// image = noteEvent.image(),
// title = noteEvent.title(),
// price = noteEvent.price(),
), ),
) )
@@ -119,11 +119,6 @@ open class Event(
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] } override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
override fun taggedGalleryEntries() =
tags.filter { it.size > 2 && it[0] == GalleryListEvent.GALLERYTAGNAME }.map {
GalleryListEvent.GalleryUrl(it[1], it[2], it.getOrNull(3))
}
override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
override fun firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] } override fun firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] }
@@ -85,6 +85,7 @@ class EventFactory {
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent.KIND ->
EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig) EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig) FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig) FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent.KIND ->
@@ -97,7 +98,6 @@ class EventFactory {
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig) GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig) GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig) GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
GalleryListEvent.KIND -> GalleryListEvent(id, pubKey, createdAt, tags, content, sig)
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig) HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent.KIND ->
HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig) HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
@@ -145,8 +145,6 @@ interface EventInterface {
fun firstTaggedK(): Int? fun firstTaggedK(): Int?
fun taggedGalleryEntries(): List<GalleryListEvent.GalleryUrl>
fun taggedEmojis(): List<EmojiUrl> fun taggedEmojis(): List<EmojiUrl>
fun matchTag1With(text: String): Boolean fun matchTag1With(text: String): Boolean
@@ -0,0 +1,130 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
class ProfileGalleryEntryEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun url() = tags.firstOrNull { it.size > 1 && it[0] == URL }?.get(1)
fun urls() = tags.filter { it.size > 1 && it[0] == URL }.map { it[1] }
fun encryptionKey() = tags.firstOrNull { it.size > 2 && it[0] == ENCRYPTION_KEY }?.let { AESGCM(it[1], it[2]) }
fun mimeType() = tags.firstOrNull { it.size > 1 && it[0] == MIME_TYPE }?.get(1)
fun hash() = tags.firstOrNull { it.size > 1 && it[0] == HASH }?.get(1)
fun size() = tags.firstOrNull { it.size > 1 && it[0] == FILE_SIZE }?.get(1)
fun alt() = tags.firstOrNull { it.size > 1 && it[0] == ALT }?.get(1)
fun dimensions() = tags.firstOrNull { it.size > 1 && it[0] == DIMENSION }?.get(1)
fun magnetURI() = tags.firstOrNull { it.size > 1 && it[0] == MAGNET_URI }?.get(1)
fun torrentInfoHash() = tags.firstOrNull { it.size > 1 && it[0] == TORRENT_INFOHASH }?.get(1)
fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
fun event() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
fun hasEvent() = tags.any { it.size > 1 && it[0] == "e" }
fun isOneOf(mimeTypes: Set<String>) = tags.any { it.size > 1 && it[0] == MIME_TYPE && mimeTypes.contains(it[1]) }
companion object {
const val KIND = 1163
const val ALT_DESCRIPTION = "Profile Gallery Entry"
const val URL = "url"
const val ENCRYPTION_KEY = "aes-256-gcm"
const val MIME_TYPE = "m"
const val FILE_SIZE = "size"
const val DIMENSION = "dim"
const val HASH = "x"
const val MAGNET_URI = "magnet"
const val TORRENT_INFOHASH = "i"
const val BLUR_HASH = "blurhash"
const val ORIGINAL_HASH = "ox"
const val ALT = "alt"
fun create(
url: String,
eventid: String? = null,
magnetUri: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: String? = null,
dimensions: String? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
encryptionKey: AESGCM? = null,
sensitiveContent: Boolean? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (ProfileGalleryEntryEvent) -> Unit,
) {
val tags =
listOfNotNull(
arrayOf(URL, url),
eventid?.let { arrayOf("e", it) },
magnetUri?.let { arrayOf(MAGNET_URI, it) },
mimeType?.let { arrayOf(MIME_TYPE, it) },
alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION),
hash?.let { arrayOf(HASH, it) },
size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) },
blurhash?.let { arrayOf(BLUR_HASH, it) },
originalHash?.let { arrayOf(ORIGINAL_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, it) },
torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) },
encryptionKey?.let { arrayOf(ENCRYPTION_KEY, it.key, it.nonce) },
sensitiveContent?.let {
if (it) {
arrayOf("content-warning", "")
} else {
null
}
},
)
val content = alt ?: ""
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
}
}
}