From 511a7030b82c472fbc92ceed7120aedea2b800ae Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Sat, 6 Jul 2024 00:11:35 +0200 Subject: [PATCH] use alternative gallery using 1163 events instead of 10011 lists. (This might still need some tuning but works in general) --- .../vitorpamplona/amethyst/model/Account.kt | 59 +++----- .../amethyst/model/LocalCache.kt | 35 +++-- .../com/vitorpamplona/amethyst/model/Note.kt | 2 +- .../com/vitorpamplona/amethyst/model/User.kt | 9 -- .../service/NostrUserProfileDataSource.kt | 21 ++- .../ui/dal/UserProfileGalleryFeedFilter.kt | 80 ++++++----- .../amethyst/ui/note/NoteQuickActionMenu.kt | 6 +- .../ui/screen/loggedIn/AccountViewModel.kt | 7 +- .../ui/screen/loggedIn/ProfileGallery.kt | 89 ++++++------ .../com/vitorpamplona/quartz/events/Event.kt | 5 - .../quartz/events/EventFactory.kt | 2 +- .../quartz/events/EventInterface.kt | 2 - .../quartz/events/ProfileGalleryEntryEvent.kt | 130 ++++++++++++++++++ 13 files changed, 291 insertions(+), 156 deletions(-) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/ProfileGalleryEntryEvent.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 20b750ebe..1995916c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -71,7 +71,6 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent -import com.vitorpamplona.quartz.events.GalleryListEvent import com.vitorpamplona.quartz.events.GeneralListEvent import com.vitorpamplona.quartz.events.GenericRepostEvent 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.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent +import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent import com.vitorpamplona.quartz.events.ReactionEvent import com.vitorpamplona.quartz.events.RelayAuthEvent import com.vitorpamplona.quartz.events.ReportEvent @@ -2204,48 +2204,27 @@ class Account( relay: String?, ) { if (!isWriteable()) return - - GalleryListEvent.addEvent( - userProfile().latestGalleryList, - idHex, - url, - relay, - signer, - ) { - Client.send(it) - LocalCache.consume(it) + ProfileGalleryEntryEvent.create( + url = url, + eventid = idHex, + /*magnetUri = magnetUri, + mimeType = headerInfo.mimeType, + hash = headerInfo.hash, + size = headerInfo.size.toString(), + dimensions = headerInfo.dim, + blurhash = headerInfo.blurHash, + alt = alt, + originalHash = originalHash, + sensitiveContent = sensitiveContent, */ + signer = signer, + ) { event -> + Client.send(event) + LocalCache.consume(event, null) } } - fun removeFromGallery( - note: 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 removeFromGallery(note: Note) { + delete(note) } fun addBookmark( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 2d7f5133a..c74002477 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -75,7 +75,6 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent -import com.vitorpamplona.quartz.events.GalleryListEvent import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GiftWrapEvent 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.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent +import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent import com.vitorpamplona.quartz.events.ReactionEvent import com.vitorpamplona.quartz.events.RecommendRelayEvent 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 = Instant .ofEpochSecond(timestamp) @@ -1680,6 +1669,26 @@ object LocalCache { 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( event: FileStorageHeaderEvent, relay: Relay?, @@ -2535,13 +2544,13 @@ object LocalCache { is DraftEvent -> consume(event, relay) is EmojiPackEvent -> consume(event, relay) is EmojiPackSelectionEvent -> consume(event, relay) - is GalleryListEvent -> consume(event) is GenericRepostEvent -> { event.containedPost()?.let { verifyAndConsume(it, relay) } consume(event) } is FhirResourceEvent -> consume(event, relay) is FileHeaderEvent -> consume(event, relay) + is ProfileGalleryEntryEvent -> consume(event, relay) is FileServersEvent -> consume(event, relay) is FileStorageEvent -> consume(event, relay) is FileStorageHeaderEvent -> consume(event, relay) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index acc2ae0ad..a5118c559 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -139,7 +139,7 @@ open class Note( var relays = listOf() private set - var headerImage: String? = null + var associatedNote: Note? = null var lastReactionsDownloadTime: Map = emptyMap() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 43d0fe6fa..50964cd07 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -40,7 +40,6 @@ import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent import com.vitorpamplona.quartz.events.BookmarkListEvent import com.vitorpamplona.quartz.events.ChatroomKey import com.vitorpamplona.quartz.events.ContactListEvent -import com.vitorpamplona.quartz.events.GalleryListEvent import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.MetadataEvent import com.vitorpamplona.quartz.events.ReportEvent @@ -61,7 +60,6 @@ class User( var latestMetadataRelay: String? = null var latestContactList: ContactListEvent? = null var latestBookmarkList: BookmarkListEvent? = null - var latestGalleryList: GalleryListEvent? = null var reports = mapOf>() private set @@ -125,13 +123,6 @@ class User( liveSet?.innerBookmarks?.invalidateData() } - fun updateGallery(event: GalleryListEvent) { - if (event.id == latestGalleryList?.id) return - print("GALLERY " + event.id()) - latestGalleryList = event - liveSet?.innerGallery?.invalidateData() - } - fun clearEOSE() { latestEOSEs = emptyMap() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index fc0144b2f..3f7139549 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -31,7 +31,6 @@ import com.vitorpamplona.quartz.events.BadgeAwardEvent import com.vitorpamplona.quartz.events.BadgeProfilesEvent import com.vitorpamplona.quartz.events.BookmarkListEvent import com.vitorpamplona.quartz.events.ContactListEvent -import com.vitorpamplona.quartz.events.GalleryListEvent import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.HighlightEvent 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.PinListEvent import com.vitorpamplona.quartz.events.PollNoteEvent +import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.WikiNoteEvent @@ -84,7 +84,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") { WikiNoteEvent.KIND, ), authors = listOf(it.pubkeyHex), - limit = 200, + limit = 500, ), ) } @@ -147,13 +147,27 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") { filter = Filter( kinds = - listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND, GalleryListEvent.KIND), + listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND), authors = listOf(it.pubkeyHex), 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() = user?.let { TypedFilter( @@ -174,6 +188,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") { listOfNotNull( createUserInfoFilter(), createUserPostsFilter(), + createProfileGalleryFilter(), createFollowFilter(), createFollowersFilter(), createUserReceivedZapsFilter(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt index b1ec6e5a1..a4fc5eba2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt @@ -24,47 +24,65 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.ammolite.relays.FeedType -import com.vitorpamplona.ammolite.relays.Relay +import com.vitorpamplona.quartz.events.MuteListEvent +import com.vitorpamplona.quartz.events.PeopleListEvent +import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent class UserProfileGalleryFeedFilter( val user: User, val account: Account, -) : FeedFilter() { - override fun feedKey(): String = account.userProfile().pubkeyHex + "-Gallery-" + user.pubkeyHex +) : AdditiveFeedFilter() { + 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 { + val params = buildFilterParams(account) + val notes = - user.latestGalleryList - ?.taggedGalleryEntries() - ?.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() + LocalCache.notes.filterIntoSet { _, it -> + acceptableEvent(it, params, user) + } var finalnotes = setOf() - for (pair in notes) { - pair.first.headerImage = pair.second - if (pair.third != null) { - 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 + for (item in notes) { + item.associatedNote = (item.event as ProfileGalleryEntryEvent).event()?.let { LocalCache.getOrCreateNote(it) } + finalnotes = finalnotes + item } - println(finalnotes) - return (finalnotes) - .filter { account.isAcceptable(it) } + return sort(finalnotes) } + + override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) + + private fun innerApplyFilter(collection: Collection): Set { + 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): List = collection.sortedWith(DefaultFeedOrder) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 27e7b18d9..ccff7de2a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -671,11 +671,7 @@ fun DeleteFromGalleryDialog( buttonIcon = Icons.Default.Delete, buttonText = stringRes(R.string.quick_action_delete_dialog_btn), onClickDoOnce = { - note.headerImage.let { - if (it != null) { - accountViewModel.removefromMediaGallery(note, it) - } - } + accountViewModel.removefromMediaGallery(note) onDismiss() }, onDismiss = onDismiss, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 1b26192a7..52b00c91c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -677,11 +677,8 @@ class AccountViewModel( viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url, relay) } } - fun removefromMediaGallery( - note: Note, - url: String, - ) { - viewModelScope.launch(Dispatchers.IO) { account.removeFromGallery(note, url) } + fun removefromMediaGallery(note: Note) { + viewModelScope.launch(Dispatchers.IO) { account.removeFromGallery(note) } } fun addPrivateBookmark(note: Note) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt index 2520a8dd1..730db72dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt @@ -33,7 +33,6 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Surface 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.HalfPadding import com.vitorpamplona.amethyst.ui.theme.QuoteBorder -import com.vitorpamplona.quartz.events.TextNoteEvent +import com.vitorpamplona.quartz.events.ProfileGalleryEntryEvent @Composable fun RenderGalleryFeed( @@ -134,27 +133,45 @@ private fun GalleryFeedLoaded( itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } - Row(defaultModifier) { - GalleryCardCompose( - baseNote = item, - routeForLastRead = routeForLastRead, - modifier = Modifier, - forceEventKind = forceEventKind, - accountViewModel = accountViewModel, - nav = nav, - ) - } + if (item.associatedNote != null) { + if (item.associatedNote!!.event != null) { + if ((item.event as ProfileGalleryEntryEvent).hasUrl() && + (item.event as ProfileGalleryEntryEvent).hasEvent() + ) { + val image = (item.event as ProfileGalleryEntryEvent).url() - HorizontalDivider( - thickness = DividerThickness, - ) + Row(defaultModifier) { + 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 fun GalleryCardCompose( + galleryNote: Note, baseNote: Note, + image: String, routeForLastRead: String? = null, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, @@ -172,8 +189,11 @@ fun GalleryCardCompose( accountViewModel = accountViewModel, nav = nav, ) { canPreview -> + GalleryCard( + galleryNote = galleryNote, baseNote = baseNote, + image = image, modifier = modifier, parentBackgroundColor = parentBackgroundColor, accountViewModel = accountViewModel, @@ -185,16 +205,19 @@ fun GalleryCardCompose( @Composable fun GalleryCard( + galleryNote: Note, baseNote: Note, + image: String, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { // baseNote.event?.let { Text(text = it.pubKey()) } - LongPressToQuickActionGallery(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + LongPressToQuickActionGallery(baseNote = galleryNote, accountViewModel = accountViewModel) { showPopup -> CheckNewAndRenderChannelCard( baseNote, + image, modifier, parentBackgroundColor, accountViewModel, @@ -207,6 +230,7 @@ fun GalleryCard( @Composable private fun CheckNewAndRenderChannelCard( baseNote: Note, + image: String, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, accountViewModel: AccountViewModel, @@ -228,26 +252,14 @@ private fun CheckNewAndRenderChannelCard( showPopup = showPopup, nav = nav, ) { - InnerGalleryCardWithReactions( - baseNote = baseNote, - accountViewModel = accountViewModel, - nav = nav, - ) + InnerGalleryCardBox(baseNote, image, accountViewModel, nav) } } -@Composable -fun InnerGalleryCardWithReactions( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - InnerGalleryCardBox(baseNote, accountViewModel, nav) -} - @Composable fun InnerGalleryCardBox( baseNote: Note, + image: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -256,7 +268,7 @@ fun InnerGalleryCardBox( note = baseNote, accountViewModel = accountViewModel, ) { - RenderGalleryThumb(baseNote, accountViewModel, nav) + RenderGalleryThumb(baseNote, image, accountViewModel, nav) } } } @@ -272,21 +284,19 @@ data class GalleryThumb( @Composable fun RenderGalleryThumb( baseNote: Note, + image: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val noteEvent = baseNote.event as? TextNoteEvent ?: return - val card by baseNote .live() .metadata .map { - val noteEvent = baseNote.event as TextNoteEvent GalleryThumb( id = "", - image = baseNote.headerImage, - title = noteEvent.content(), + image = image, + title = "", // noteEvent?.title(), // price = noteEvent?.price(), ) @@ -294,11 +304,8 @@ fun RenderGalleryThumb( .observeAsState( GalleryThumb( id = "", - image = baseNote.headerImage, - title = noteEvent.content(), - // image = noteEvent.image(), - // title = noteEvent.title(), - // price = noteEvent.price(), + image = image, + title = "", ), ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index 6d4cbd8f4..19521379e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,11 +119,6 @@ open class Event( 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 firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt index 759a0bd17..288ae12e1 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt @@ -85,6 +85,7 @@ class EventFactory { EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(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) FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig) FileStorageHeaderEvent.KIND -> @@ -97,7 +98,6 @@ class EventFactory { GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig) GitRepositoryEvent.KIND -> GitRepositoryEvent(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) HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt index c7c162493..db11a3ea4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -145,8 +145,6 @@ interface EventInterface { fun firstTaggedK(): Int? - fun taggedGalleryEntries(): List - fun taggedEmojis(): List fun matchTag1With(text: String): Boolean diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/ProfileGalleryEntryEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/ProfileGalleryEntryEvent.kt new file mode 100644 index 000000000..2a04bfe38 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/ProfileGalleryEntryEvent.kt @@ -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>, + 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) = 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) + } + } +}