From 91caacd36dce6d3613e02eb44a056f61e306d361 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:01:56 +0200 Subject: [PATCH 01/23] added first attempt to add user galleries. can read 10011 events and shows profile tab What works: - can load existing 10011 lists and show images in profile tab What doesn't work: - gallery view is broken - can't load notes to click images to get to original note - adding media to gallery crashes amethyst atm - no functionality to delete media from gallery yet. --- .../vitorpamplona/amethyst/model/Account.kt | 48 +++ .../amethyst/model/LocalCache.kt | 15 + .../com/vitorpamplona/amethyst/model/User.kt | 14 +- .../service/NostrUserProfileDataSource.kt | 3 +- .../amethyst/ui/components/VideoView.kt | 11 +- .../ui/components/ZoomableContentDialog.kt | 2 +- .../ui/components/ZoomableContentView.kt | 26 ++ .../amethyst/ui/note/types/AudioTrack.kt | 1 + .../amethyst/ui/note/types/LiveActivity.kt | 1 + .../amethyst/ui/note/types/Video.kt | 1 + .../ui/screen/loggedIn/DiscoverScreen.kt | 14 +- .../ui/screen/loggedIn/ProfileGallery.kt | 333 ++++++++++++++++++ .../ui/screen/loggedIn/ProfileScreen.kt | 69 +++- amethyst/src/main/res/values/strings.xml | 1 + .../commons/richtext/MediaContentModels.kt | 4 + .../quartz/events/EventFactory.kt | 1 + .../quartz/events/GalleryListEvent.kt | 190 ++++++++++ 17 files changed, 714 insertions(+), 20 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.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 60fe2689f..2331f3c37 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -71,6 +71,7 @@ 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 @@ -2197,6 +2198,53 @@ class Account( } } + fun addToGallery( + idHex: String, + url: String, + ) { + if (!isWriteable()) return + GalleryListEvent.addEvent( + userProfile().latestGalleryList, + idHex, + url, + signer, + ) { + Client.send(it) + LocalCache.consume(it) + } + } + + fun removeFromGallery( + note: Note, + url: String, + ) { + if (!isWriteable()) return + + val galleryentries = userProfile().latestGalleryList ?: return + + if (note is AddressableNote) { + GalleryListEvent.removeReplaceable( + galleryentries, + note.address, + false, + signer, + ) { + Client.send(it) + LocalCache.consume(it) + } + } else { + GalleryListEvent.removeEvent( + galleryentries, + note.idHex, + false, + signer, + ) { + Client.send(it) + LocalCache.consume(it) + } + } + } + fun addBookmark( note: Note, isPrivate: Boolean, 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 5e605af24..1034d6065 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -75,6 +75,7 @@ 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 @@ -423,6 +424,19 @@ object LocalCache { } } + fun consume(event: GalleryListEvent) { + val user = getOrCreateUser(event.pubKey) + if (user.latestGalleryList == null || event.createdAt > user.latestGalleryList!!.createdAt) { + if (event.dTag() == "gallery") { + 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) @@ -2523,6 +2537,7 @@ 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) 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 01abf0ad7..2e09dbe9e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -40,6 +40,7 @@ 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 @@ -60,6 +61,7 @@ class User( var latestMetadataRelay: String? = null var latestContactList: ContactListEvent? = null var latestBookmarkList: BookmarkListEvent? = null + var latestGalleryList: GalleryListEvent? = null var reports = mapOf>() private set @@ -123,6 +125,13 @@ 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() } @@ -488,6 +497,7 @@ class UserLiveSet( val innerRelayInfo = UserBundledRefresherLiveData(u) val innerZaps = UserBundledRefresherLiveData(u) val innerBookmarks = UserBundledRefresherLiveData(u) + val innerGallery = UserBundledRefresherLiveData(u) val innerStatuses = UserBundledRefresherLiveData(u) // UI Observers line up here. @@ -500,6 +510,7 @@ class UserLiveSet( val relayInfo = innerRelayInfo.map { it } val zaps = innerZaps.map { it } val bookmarks = innerBookmarks.map { it } + val gallery = innerGallery.map { it } val statuses = innerStatuses.map { it } val profilePictureChanges = innerMetadata.map { it.user.profilePicture() }.distinctUntilChanged() @@ -517,7 +528,7 @@ class UserLiveSet( relays.hasObservers() || relayInfo.hasObservers() || zaps.hasObservers() || - bookmarks.hasObservers() || + bookmarks.hasObservers() || gallery.hasObservers() || statuses.hasObservers() || profilePictureChanges.hasObservers() || nip05Changes.hasObservers() || @@ -533,6 +544,7 @@ class UserLiveSet( innerRelayInfo.destroy() innerZaps.destroy() innerBookmarks.destroy() + innerGallery.destroy() innerStatuses.destroy() } } 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 f05299d2c..fc0144b2f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -31,6 +31,7 @@ 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 @@ -146,7 +147,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") { filter = Filter( kinds = - listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND), + listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND, GalleryListEvent.KIND), authors = listOf(it.pubkeyHex), limit = 100, ), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 463690343..8618ae622 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -142,6 +142,7 @@ fun LoadThumbAndThenVideoView( roundedCorner: Boolean, isFiniteHeight: Boolean, nostrUriCallback: String? = null, + nostrIdCallback: String? = null, accountViewModel: AccountViewModel, onDialog: ((Boolean) -> Unit)? = null, ) { @@ -176,6 +177,7 @@ fun LoadThumbAndThenVideoView( artworkUri = thumbUri, authorName = authorName, nostrUriCallback = nostrUriCallback, + nostrIdCallback = nostrIdCallback, accountViewModel = accountViewModel, onDialog = onDialog, ) @@ -190,6 +192,7 @@ fun LoadThumbAndThenVideoView( artworkUri = thumbUri, authorName = authorName, nostrUriCallback = nostrUriCallback, + nostrIdCallback = nostrIdCallback, accountViewModel = accountViewModel, onDialog = onDialog, ) @@ -211,6 +214,7 @@ fun VideoView( dimensions: String? = null, blurhash: String? = null, nostrUriCallback: String? = null, + nostrIdCallback: String? = null, onDialog: ((Boolean) -> Unit)? = null, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, accountViewModel: AccountViewModel, @@ -252,6 +256,7 @@ fun VideoView( artworkUri = artworkUri, authorName = authorName, nostrUriCallback = nostrUriCallback, + nostrIDCallback = nostrIdCallback, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, onDialog = onDialog, @@ -305,6 +310,7 @@ fun VideoView( artworkUri = artworkUri, authorName = authorName, nostrUriCallback = nostrUriCallback, + nostrIDCallback = nostrIdCallback, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, onDialog = onDialog, @@ -329,6 +335,7 @@ fun VideoViewInner( artworkUri: String? = null, authorName: String? = null, nostrUriCallback: String? = null, + nostrIDCallback: String? = null, automaticallyStartPlayback: State, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, onDialog: ((Boolean) -> Unit)? = null, @@ -350,6 +357,7 @@ fun VideoViewInner( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = nostrUriCallback, + nostrIDCallback = nostrIDCallback, waveform = waveform, keepPlaying = keepPlaying, automaticallyStartPlayback = automaticallyStartPlayback, @@ -697,6 +705,7 @@ private fun RenderVideoPlayer( roundedCorner: Boolean, isFiniteHeight: Boolean, nostrUriCallback: String?, + nostrIDCallback: String?, waveform: ImmutableList? = null, keepPlaying: MutableState, automaticallyStartPlayback: State, @@ -810,7 +819,7 @@ private fun RenderVideoPlayer( } AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle -> - ShareImageAction(popupExpanded, videoUri, nostrUriCallback, toggle) + ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, nostrIDCallback, toggle) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt index d028c9ce8..445045dd1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt @@ -269,7 +269,7 @@ private fun DialogContent( contentDescription = stringRes(R.string.quick_action_share), ) - ShareImageAction(popupExpanded = popupExpanded, myContent, onDismiss = { popupExpanded.value = false }) + ShareImageAction(accountViewModel = accountViewModel, popupExpanded = popupExpanded, myContent, onDismiss = { popupExpanded.value = false }) } val localContext = LocalContext.current diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 195cdac5f..1f1ed554a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -138,6 +138,7 @@ fun ZoomableContentView( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, + nostrIdCallback = content.id, onDialog = { dialogOpen = true }, accountViewModel = accountViewModel, ) @@ -162,6 +163,7 @@ fun ZoomableContentView( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, + nostrIdCallback = content.id, onDialog = { dialogOpen = true }, accountViewModel = accountViewModel, ) @@ -596,22 +598,27 @@ fun DisplayBlurHash( @Composable fun ShareImageAction( + accountViewModel: AccountViewModel, popupExpanded: MutableState, content: BaseMediaContent, onDismiss: () -> Unit, ) { if (content is MediaUrlContent) { ShareImageAction( + accountViewModel = accountViewModel, popupExpanded = popupExpanded, videoUri = content.url, postNostrUri = content.uri, + postNostrid = content.id, onDismiss = onDismiss, ) } else if (content is MediaPreloadedContent) { ShareImageAction( + accountViewModel = accountViewModel, popupExpanded = popupExpanded, videoUri = content.localFile?.toUri().toString(), postNostrUri = content.uri, + postNostrid = content.id, onDismiss = onDismiss, ) } @@ -620,9 +627,11 @@ fun ShareImageAction( @OptIn(ExperimentalPermissionsApi::class) @Composable fun ShareImageAction( + accountViewModel: AccountViewModel, popupExpanded: MutableState, videoUri: String?, postNostrUri: String?, + postNostrid: String?, onDismiss: () -> Unit, ) { DropdownMenu( @@ -650,6 +659,23 @@ fun ShareImageAction( }, ) } + + postNostrUri?.let { + DropdownMenuItem( + text = { Text(stringRes(R.string.add_media_to_gallery)) }, + onClick = { + if (videoUri != null) { + if (postNostrid != null) { + print("TODO") + print(postNostrid) + // TODO this still crashes + accountViewModel.account.addToGallery(postNostrid, videoUri) + } + } + onDismiss() + }, + ) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt index d7875628e..f1879ea25 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt @@ -212,6 +212,7 @@ fun AudioHeader( isFiniteHeight = isFiniteHeight, accountViewModel = accountViewModel, nostrUriCallback = note.toNostrUri(), + nostrIdCallback = note.idHex, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index 6d0a16f35..766aeea97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -160,6 +160,7 @@ fun RenderLiveActivityEventInner( isFiniteHeight = false, accountViewModel = accountViewModel, nostrUriCallback = "nostr:${baseNote.toNEvent()}", + nostrIdCallback = baseNote.idHex, ) } } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt index 080aca811..0f43d93c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt @@ -86,6 +86,7 @@ fun VideoDisplay( val description = event.content.ifBlank { null } ?: event.alt() val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl) val uri = note.toNostrUri() + val id = note.id() val mimeType = event.mimeType() mutableStateOf( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt index 250eca54f..2847381aa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt @@ -118,13 +118,6 @@ fun DiscoverScreen( ScrollStateKeys.DISCOVER_CONTENT, AppDefinitionEvent.KIND, ), - TabItem( - R.string.discover_marketplace, - discoveryMarketplaceFeedViewModel, - Route.Discover.base + "Marketplace", - ScrollStateKeys.DISCOVER_MARKETPLACE, - ClassifiedsEvent.KIND, - ), TabItem( R.string.discover_live, discoveryLiveFeedViewModel, @@ -139,6 +132,13 @@ fun DiscoverScreen( ScrollStateKeys.DISCOVER_COMMUNITY, CommunityDefinitionEvent.KIND, ), + TabItem( + R.string.discover_marketplace, + discoveryMarketplaceFeedViewModel, + Route.Discover.base + "Marketplace", + ScrollStateKeys.DISCOVER_MARKETPLACE, + ClassifiedsEvent.KIND, + ), TabItem( R.string.discover_chat, discoveryChatFeedViewModel, 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 new file mode 100644 index 000000000..08204061f --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt @@ -0,0 +1,333 @@ +/** + * 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.amethyst.ui.screen.loggedIn + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment.Companion.BottomStart +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map +import coil.compose.AsyncImage +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.SensitivityWarning +import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport +import com.vitorpamplona.amethyst.ui.note.ClickableNote +import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction +import com.vitorpamplona.amethyst.ui.note.WatchAuthor +import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent +import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor +import com.vitorpamplona.amethyst.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.ui.theme.HalfPadding +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.quartz.events.GalleryListEvent + +// TODO This is to large parts from the ChannelCardCompose +// Why does it not be in a grid, like the marketplace +@Composable +fun ProfileGallery( + baseNotes: List, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + isHiddenFeed: Boolean = false, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + for (thumb in baseNotes) { + thumb.baseNote?.let { + WatchNoteEvent(baseNote = it, accountViewModel = accountViewModel) { + if (thumb.baseNote.event?.kind() == GalleryListEvent.KIND) { + CheckHiddenFeedWatchBlockAndReport( + note = thumb.baseNote, + modifier = modifier, + ignoreAllBlocksAndReports = isHiddenFeed, + showHiddenWarning = false, + accountViewModel = accountViewModel, + nav = nav, + ) { canPreview -> + + thumb.image?.let { it1 -> + GalleryCard( + baseNote = thumb.baseNote, + url = it1, + modifier = modifier, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } + } + } + } +} + +@Composable +fun GalleryCard( + baseNote: Note, + url: String, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + // baseNote.event?.let { Text(text = it.pubKey()) } + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + + CheckNewAndRenderChannelCard( + baseNote, + url, + modifier, + parentBackgroundColor, + accountViewModel, + showPopup, + nav, + ) + } +} + +@Composable +private fun CheckNewAndRenderChannelCard( + baseNote: Note, + url: String, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + accountViewModel: AccountViewModel, + showPopup: () -> Unit, + nav: (String) -> Unit, +) { + val backgroundColor = + calculateBackgroundColor( + createdAt = baseNote.createdAt(), + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + ) + + ClickableNote( + baseNote = baseNote, + backgroundColor = backgroundColor, + modifier = modifier, + accountViewModel = accountViewModel, + showPopup = showPopup, + nav = nav, + ) { + // baseNote.event?.let { Text(text = it.pubKey()) } + InnerGalleryCardWithReactions( + baseNote = baseNote, + url = url, + accountViewModel = accountViewModel, + nav = nav, + ) + } +} + +@Composable +fun InnerGalleryCardWithReactions( + baseNote: Note, + url: String, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + InnerGalleryCardBox(baseNote, url, accountViewModel, nav) +} + +@Composable +fun InnerGalleryCardBox( + baseNote: Note, + url: String, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + Column(HalfPadding) { + SensitivityWarning( + note = baseNote, + accountViewModel = accountViewModel, + ) { + RenderGalleryThumb(baseNote, url, accountViewModel, nav) + } + } +} + +@Immutable +data class GalleryThumb( + val baseNote: Note?, + val id: String?, + val image: String?, + val title: String?, + // val price: Price?, +) + +@Composable +fun RenderGalleryThumb( + baseNote: Note, + url: String, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val noteEvent = baseNote.event as? GalleryListEvent ?: return + + val card by + baseNote + .live() + .metadata + .map { + val noteEvent = baseNote.event as GalleryListEvent + + GalleryThumb( + baseNote = baseNote, + id = "", + image = url, + title = "Hello", + // noteEvent?.title(), + // price = noteEvent?.price(), + ) + }.distinctUntilChanged() + .observeAsState( + GalleryThumb( + baseNote = baseNote, + id = "", + image = "https://gokaygokay-aurasr.hf.space/file=/tmp/gradio/68292f324a38d7071453cf6912dfb1da9d1305c8/image3.png", + title = "Hello", + // image = noteEvent.image(), + // title = noteEvent.title(), + // price = noteEvent.price(), + ), + ) + + InnerRenderGalleryThumb(card as GalleryThumb, baseNote) +} + +@Preview +@Composable +fun RenderGalleryThumbPreview() { + Surface(Modifier.size(200.dp)) { + InnerRenderGalleryThumb( + card = + GalleryThumb( + baseNote = null, + id = "", + image = null, + title = "Like New", + // price = Price("800000", "SATS", null), + ), + note = Note("hex"), + ) + } +} + +@Composable +fun InnerRenderGalleryThumb( + card: GalleryThumb, + note: Note, +) { + Box( + Modifier + .fillMaxWidth() + .aspectRatio(1f), + contentAlignment = BottomStart, + ) { + card.image?.let { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + } ?: run { DisplayGalleryAuthorBanner(note) } + + Row( + Modifier + .fillMaxWidth() + .background(Color.Black.copy(0.6f)) + .padding(Size5dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + card.title?.let { + Text( + text = it, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White, + modifier = Modifier.weight(1f), + ) + } + /* + card.price?.let { + val priceTag = + remember(card) { + val newAmount = it.amount.toBigDecimalOrNull()?.let { showAmountAxis(it) } ?: it.amount + + if (it.frequency != null && it.currency != null) { + "$newAmount ${it.currency}/${it.frequency}" + } else if (it.currency != null) { + "$newAmount ${it.currency}" + } else { + newAmount + } + } + + Text( + text = priceTag, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White, + ) + }*/ + } + } +} + +@Composable +fun DisplayGalleryAuthorBanner(note: Note) { + WatchAuthor(note) { + BannerImage( + it, + Modifier + .fillMaxSize() + .clip(QuoteBorder), + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index a7d3e1aca..735444d82 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -433,7 +433,8 @@ private fun RenderSurface( } } }, - ).fillMaxHeight() + ) + .fillMaxHeight() }, ) { RenderScreen( @@ -532,13 +533,14 @@ private fun CreateAndRenderPages( when (page) { 0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav) 1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav) - 2 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav) - 3 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav) - 4 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav) - 5 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav) - 6 -> TabFollowedTags(baseUser, accountViewModel, nav) - 7 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav) - 8 -> TabRelays(baseUser, accountViewModel, nav) + 2 -> Gallery(baseUser, followsFeedViewModel, accountViewModel, nav) + 3 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav) + 4 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav) + 5 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav) + 6 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav) + 7 -> TabFollowedTags(baseUser, accountViewModel, nav) + 8 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav) + 9 -> TabRelays(baseUser, accountViewModel, nav) } } @@ -573,6 +575,7 @@ private fun CreateAndRenderTabs( listOf<@Composable (() -> Unit)?>( { Text(text = stringRes(R.string.notes)) }, { Text(text = stringRes(R.string.replies)) }, + { Text(text = "Gallery") }, { FollowTabHeader(baseUser) }, { FollowersTabHeader(baseUser) }, { ZapTabHeader(baseUser) }, @@ -1534,6 +1537,50 @@ fun TabNotesConversations( } } +@Composable +fun Gallery( + baseUser: User, + feedViewModel: UserFeedViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + WatchFollowChanges(baseUser, feedViewModel) + + Column(Modifier.fillMaxHeight()) { + Column { + baseUser.latestGalleryList?.let { + // val note2 = getOrCreateAddressableNoteInternal(aTag) + val note = LocalCache.getOrCreateAddressableNote(it.address()) + note.event = it + var notes = listOf() + for (tag in note.event?.tags()!!) { + if (tag.size > 2) { + if (tag[0] == "g") { + // TODO get the node by id on main thread. LoadNote does nothing. + val thumb = + GalleryThumb( + baseNote = note, + id = tag[2], + // TODO use the original note once it's loaded baseNote = basenote, + image = tag[1], + title = null, + ) + notes = notes + thumb + // } + } + } + ProfileGallery( + baseNotes = notes, + modifier = Modifier, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } + } +} + @Composable fun TabFollowedTags( baseUser: User, @@ -1545,7 +1592,11 @@ fun TabFollowedTags( baseUser.latestContactList?.unverifiedFollowTagSet() } - Column(Modifier.fillMaxHeight().padding(vertical = 0.dp)) { + Column( + Modifier + .fillMaxHeight() + .padding(vertical = 0.dp), + ) { items?.let { LazyColumn { itemsIndexed(items) { index, hashtag -> diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 6d8f93237..37aa8a5e0 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -586,6 +586,7 @@ Share or Save Copy URL to clipboard Copy Note ID to clipboard + Add Media to Gallery Created at Rules diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt index 2db6e28db..28ee73ee7 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/richtext/MediaContentModels.kt @@ -38,6 +38,7 @@ abstract class MediaUrlContent( dim: String? = null, blurhash: String? = null, val uri: String? = null, + val id: String? = null, val mimeType: String? = null, ) : BaseMediaContent(description, dim, blurhash) @@ -49,6 +50,7 @@ class MediaUrlImage( blurhash: String? = null, dim: String? = null, uri: String? = null, + id: String? = null, val contentWarning: String? = null, mimeType: String? = null, ) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType) @@ -60,6 +62,7 @@ class MediaUrlVideo( hash: String? = null, dim: String? = null, uri: String? = null, + id: String? = null, val artworkUri: String? = null, val authorName: String? = null, blurhash: String? = null, @@ -76,6 +79,7 @@ abstract class MediaPreloadedContent( dim: String? = null, blurhash: String? = null, val uri: String, + val id: String? = null, ) : BaseMediaContent(description, dim, blurhash) { fun localFileExists() = localFile != null && localFile.exists() } 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 d8e071057..759a0bd17 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventFactory.kt @@ -97,6 +97,7 @@ 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/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt new file mode 100644 index 000000000..d43cd5523 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -0,0 +1,190 @@ +/** + * 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.ATag +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class GalleryListEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : GeneralListEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + companion object { + const val KIND = 10011 + const val ALT = "Gallery List" + const val DEFAULT_D_TAG_GALLERY = "gallery" + + fun addEvent( + earlierVersion: GalleryListEvent?, + eventId: HexKey, + url: String, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) = addTag(earlierVersion, "g", eventId, url, signer, createdAt, onReady) + + fun addTag( + earlierVersion: GalleryListEvent?, + tagName: String, + tagValue: HexKey, + url: String, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) { + add( + earlierVersion, + arrayOf(arrayOf(tagName, url, tagValue)), + signer, + createdAt, + onReady, + ) + } + + fun add( + earlierVersion: GalleryListEvent?, + listNewTags: Array>, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) { + create( + content = earlierVersion?.content ?: "", + tags = (earlierVersion?.tags ?: arrayOf(arrayOf("d", DEFAULT_D_TAG_GALLERY))).plus(listNewTags), + signer = signer, + createdAt = createdAt, + onReady = onReady, + ) + } + + fun removeEvent( + earlierVersion: GalleryListEvent, + eventId: HexKey, + isPrivate: Boolean, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) = removeTag(earlierVersion, "e", eventId, isPrivate, signer, createdAt, onReady) + + fun removeReplaceable( + earlierVersion: GalleryListEvent, + aTag: ATag, + isPrivate: Boolean, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) = removeTag(earlierVersion, "a", aTag.toTag(), isPrivate, signer, createdAt, onReady) + + private fun removeTag( + earlierVersion: GalleryListEvent, + tagName: String, + tagValue: HexKey, + isPrivate: Boolean, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) { + if (isPrivate) { + earlierVersion.privateTagsOrEmpty(signer) { privateTags -> + encryptTags( + privateTags = + privateTags + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } + .toTypedArray(), + signer = signer, + ) { encryptedTags -> + create( + content = encryptedTags, + tags = + earlierVersion.tags + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } + .toTypedArray(), + signer = signer, + createdAt = createdAt, + onReady = onReady, + ) + } + } + } else { + create( + content = earlierVersion.content, + tags = + earlierVersion.tags + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } + .toTypedArray(), + signer = signer, + createdAt = createdAt, + onReady = onReady, + ) + } + } + + fun create( + content: String, + tags: Array>, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) { + val newTags = + if (tags.any { it.size > 1 && it[0] == "alt" }) { + tags + } else { + tags + arrayOf("alt", ALT) + } + + signer.sign(createdAt, KIND, newTags, content, onReady) + } + + fun create( + name: String = "", + images: List? = null, + videos: List? = null, + audios: List? = null, + privEvents: List? = null, + privUsers: List? = null, + privAddresses: List? = null, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (GalleryListEvent) -> Unit, + ) { + val tags = mutableListOf>() + tags.add(arrayOf("d", name)) + + images?.forEach { tags.add(arrayOf("image", it)) } + videos?.forEach { tags.add(arrayOf("video", it)) } + audios?.forEach { tags.add(arrayOf("audio", it)) } + tags.add(arrayOf("alt", ALT)) + + createPrivateTags(privEvents, privUsers, privAddresses, signer) { content -> + signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady) + } + } + } +} From e073e58241ba46727ec3ba2b057a3722e76700e3 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:51:09 +0200 Subject: [PATCH 02/23] Update GalleryListEvent.kt --- .../quartz/events/GalleryListEvent.kt | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index d43cd5523..4311407c8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -86,63 +86,37 @@ class GalleryListEvent( fun removeEvent( earlierVersion: GalleryListEvent, eventId: HexKey, - isPrivate: Boolean, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "e", eventId, isPrivate, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "e", eventId, signer, createdAt, onReady) fun removeReplaceable( earlierVersion: GalleryListEvent, aTag: ATag, - isPrivate: Boolean, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "a", aTag.toTag(), isPrivate, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "a", aTag.toTag(), signer, createdAt, onReady) private fun removeTag( earlierVersion: GalleryListEvent, tagName: String, tagValue: HexKey, - isPrivate: Boolean, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, ) { - if (isPrivate) { - earlierVersion.privateTagsOrEmpty(signer) { privateTags -> - encryptTags( - privateTags = - privateTags - .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } - .toTypedArray(), - signer = signer, - ) { encryptedTags -> - create( - content = encryptedTags, - tags = - earlierVersion.tags - .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } - .toTypedArray(), - signer = signer, - createdAt = createdAt, - onReady = onReady, - ) - } - } - } else { - create( - content = earlierVersion.content, - tags = - earlierVersion.tags - .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } - .toTypedArray(), - signer = signer, - createdAt = createdAt, - onReady = onReady, - ) - } + create( + content = earlierVersion.content, + tags = + earlierVersion.tags + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } + .toTypedArray(), + signer = signer, + createdAt = createdAt, + onReady = onReady, + ) } fun create( @@ -177,9 +151,9 @@ class GalleryListEvent( val tags = mutableListOf>() tags.add(arrayOf("d", name)) - images?.forEach { tags.add(arrayOf("image", it)) } - videos?.forEach { tags.add(arrayOf("video", it)) } - audios?.forEach { tags.add(arrayOf("audio", it)) } + images?.forEach { tags.add(arrayOf("g", it)) } + videos?.forEach { tags.add(arrayOf("g", it)) } + audios?.forEach { tags.add(arrayOf("g", it)) } tags.add(arrayOf("alt", ALT)) createPrivateTags(privEvents, privUsers, privAddresses, signer) { content -> From a42762de53b3e00abca6d81852e14981a785958f Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:14:36 +0200 Subject: [PATCH 03/23] alternative attempt.. --- .../ui/dal/UserProfileGalleryFeedFilter.kt | 52 +++++++ .../amethyst/ui/screen/FeedViewModel.kt | 11 ++ .../ui/screen/loggedIn/ProfileGallery.kt | 134 +++++++++++++++++- .../ui/screen/loggedIn/ProfileScreen.kt | 47 +++++- 4 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt 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 new file mode 100644 index 000000000..6c5e5a86e --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt @@ -0,0 +1,52 @@ +/** + * 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.amethyst.ui.dal + +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User + +class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedFilter() { + override fun feedKey(): String { + return account.userProfile().pubkeyHex + "-" + user.pubkeyHex + } + + override fun feed(): List { + val notes = + user.latestGalleryList + ?.taggedEvents() + ?.mapNotNull { LocalCache.checkGetOrCreateNote(it) } + ?.toSet() + ?: emptySet() + + val addresses = + user.latestGalleryList + ?.taggedAddresses() + ?.map { LocalCache.getOrCreateAddressableNote(it) } + ?.toSet() + ?: emptySet() + + return (notes + addresses) + .filter { account.isAcceptable(it) } + .sortedWith(DefaultFeedOrder) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 422bd15a2..04169e499 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter +import com.vitorpamplona.amethyst.ui.dal.UserProfileGalleryFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter @@ -214,6 +215,16 @@ class NostrUserProfileReportFeedViewModel(val user: User) : } } +class NostrUserProfileGalleryFeedViewModel(val user: User, val account: Account) : + FeedViewModel(UserProfileGalleryFeedFilter(user, account)) { + class Factory(val user: User, val account: Account) : ViewModelProvider.Factory { + override fun create(modelClass: Class): NostrUserProfileGalleryFeedViewModel { + return NostrUserProfileGalleryFeedViewModel(user, account) + as NostrUserProfileGalleryFeedViewModel + } + } +} + class NostrUserProfileBookmarksFeedViewModel(val user: User, val account: Account) : FeedViewModel(UserProfileBookmarksFeedFilter(user, account)) { class Factory(val user: User, val account: Account) : ViewModelProvider.Factory { 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 08204061f..3f3529a04 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 @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -30,6 +32,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -37,6 +43,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -46,28 +53,149 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction +import com.vitorpamplona.amethyst.ui.note.NormalChannelCard import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor import com.vitorpamplona.amethyst.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.ui.screen.FeedEmpty +import com.vitorpamplona.amethyst.ui.screen.FeedError +import com.vitorpamplona.amethyst.ui.screen.FeedState +import com.vitorpamplona.amethyst.ui.screen.FeedViewModel +import com.vitorpamplona.amethyst.ui.screen.LoadingFeed +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.amethyst.ui.theme.Size5dp import com.vitorpamplona.quartz.events.GalleryListEvent +@Composable +private fun RenderGalleryFeed( + viewModel: FeedViewModel, + routeForLastRead: String?, + forceEventKind: Int?, + listState: LazyListState, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val feedState by viewModel.feedContent.collectAsStateWithLifecycle() + + CrossfadeIfEnabled( + targetState = feedState, + animationSpec = tween(durationMillis = 100), + label = "RenderDiscoverFeed", + accountViewModel = accountViewModel, + ) { state -> + when (state) { + is FeedState.Empty -> { + FeedEmpty { viewModel.invalidateData() } + } + is FeedState.FeedError -> { + FeedError(state.errorMessage) { viewModel.invalidateData() } + } + is FeedState.Loaded -> { + GalleryFeedLoaded( + state, + routeForLastRead, + listState, + forceEventKind, + accountViewModel, + nav, + ) + } + is FeedState.Loading -> { + LoadingFeed() + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun GalleryFeedLoaded( + state: FeedState.Loaded, + routeForLastRead: String?, + listState: LazyListState, + forceEventKind: Int?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LazyColumn( + contentPadding = FeedPadding, + state = listState, + ) { + itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } + + Row(defaultModifier) { + GalleryCardCompose( + baseNote = item, + routeForLastRead = routeForLastRead, + modifier = Modifier.fillMaxWidth(), + forceEventKind = forceEventKind, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + HorizontalDivider( + thickness = DividerThickness, + ) + } + } +} + +@Composable +fun GalleryCardCompose( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + forceEventKind: Int?, + isHiddenFeed: Boolean = false, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { + if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) { + CheckHiddenFeedWatchBlockAndReport( + note = baseNote, + modifier = modifier, + ignoreAllBlocksAndReports = isHiddenFeed, + showHiddenWarning = false, + accountViewModel = accountViewModel, + nav = nav, + ) { canPreview -> + NormalChannelCard( + baseNote = baseNote, + routeForLastRead = routeForLastRead, + modifier = modifier, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } +} + // TODO This is to large parts from the ChannelCardCompose -// Why does it not be in a grid, like the marketplace + +/*@OptIn(ExperimentalFoundationApi::class) @Composable fun ProfileGallery( - baseNotes: List, + baseNote: List, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, isHiddenFeed: Boolean = false, @@ -103,7 +231,7 @@ fun ProfileGallery( } } } - +*/ @Composable fun GalleryCard( baseNote: Note, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 735444d82..59e1c16b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -148,6 +148,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileBookmarksFeedViewMod import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileConversationsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileFollowersUserFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileFollowsUserFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileGalleryFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileNewThreadsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel @@ -294,6 +295,16 @@ fun PrepareViewModels( ), ) + val galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel = + viewModel( + key = baseUser.pubkeyHex + "UserGalleryFeedViewModel", + factory = + NostrUserProfileGalleryFeedViewModel.Factory( + baseUser, + accountViewModel.account, + ), + ) + val reportsFeedViewModel: NostrUserProfileReportFeedViewModel = viewModel( key = baseUser.pubkeyHex + "UserProfileReportFeedViewModel", @@ -312,6 +323,7 @@ fun PrepareViewModels( appRecommendations, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel = accountViewModel, nav = nav, @@ -328,6 +340,7 @@ fun ProfileScreen( appRecommendations: NostrUserAppRecommendationsFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -372,6 +385,7 @@ fun ProfileScreen( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -388,6 +402,7 @@ private fun RenderSurface( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -448,6 +463,7 @@ private fun RenderSurface( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -470,6 +486,7 @@ private fun RenderScreen( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -501,6 +518,7 @@ private fun RenderScreen( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -519,6 +537,7 @@ private fun CreateAndRenderPages( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -533,7 +552,7 @@ private fun CreateAndRenderPages( when (page) { 0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav) 1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav) - 2 -> Gallery(baseUser, followsFeedViewModel, accountViewModel, nav) + 2 -> TabGallery(galleryFeedViewModel, accountViewModel, nav) 3 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav) 4 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav) 5 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav) @@ -1537,7 +1556,31 @@ fun TabNotesConversations( } } +@OptIn(ExperimentalFoundationApi::class) @Composable +fun TabGallery( + feedViewModel: NostrUserProfileGalleryFeedViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LaunchedEffect(Unit) { feedViewModel.invalidateData() } + + Column(Modifier.fillMaxHeight()) { + Column( + modifier = Modifier.padding(vertical = 0.dp), + ) { + RefresheableFeedView( + feedViewModel, + null, + enablePullRefresh = false, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } +} + +/*@Composable fun Gallery( baseUser: User, feedViewModel: UserFeedViewModel, @@ -1579,7 +1622,7 @@ fun Gallery( } } } -} +} */ @Composable fun TabFollowedTags( From f331398316298d8e1892ec4b1794c4498d5076d3 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:13:52 +0200 Subject: [PATCH 04/23] gallery is visualised --- .../vitorpamplona/amethyst/model/Account.kt | 2 - .../ui/dal/UserProfileGalleryFeedFilter.kt | 14 ++-- .../ui/screen/loggedIn/ProfileGallery.kt | 83 +++++++++---------- .../ui/screen/loggedIn/ProfileScreen.kt | 7 +- .../quartz/events/EmojiPackEvent.kt | 18 ++++ .../com/vitorpamplona/quartz/events/Event.kt | 2 + .../quartz/events/EventInterface.kt | 2 + 7 files changed, 73 insertions(+), 55 deletions(-) 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 2331f3c37..903579a7c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -2226,7 +2226,6 @@ class Account( GalleryListEvent.removeReplaceable( galleryentries, note.address, - false, signer, ) { Client.send(it) @@ -2236,7 +2235,6 @@ class Account( GalleryListEvent.removeEvent( galleryentries, note.idHex, - false, signer, ) { Client.send(it) 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 6c5e5a86e..8b81e5bde 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 @@ -27,26 +27,26 @@ import com.vitorpamplona.amethyst.model.User class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedFilter() { override fun feedKey(): String { - return account.userProfile().pubkeyHex + "-" + user.pubkeyHex + return account.userProfile().pubkeyHex + "-Gallery-" + user.pubkeyHex } override fun feed(): List { val notes = user.latestGalleryList - ?.taggedEvents() - ?.mapNotNull { LocalCache.checkGetOrCreateNote(it) } + ?.taggedGalleryEntries() + ?.mapNotNull { LocalCache.checkGetOrCreateNote(it.id) } ?.toSet() ?: emptySet() - val addresses = + /*val addresses = user.latestGalleryList ?.taggedAddresses() ?.map { LocalCache.getOrCreateAddressableNote(it) } ?.toSet() - ?: emptySet() + ?: emptySet() */ - return (notes + addresses) + // .sortedWith(DefaultFeedOrder) + return (notes) .filter { account.isAcceptable(it) } - .sortedWith(DefaultFeedOrder) } } 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 3f3529a04..df02459ae 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 @@ -32,8 +32,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState +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 @@ -63,7 +65,6 @@ import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction -import com.vitorpamplona.amethyst.ui.note.NormalChannelCard import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor @@ -78,19 +79,18 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size5dp -import com.vitorpamplona.quartz.events.GalleryListEvent +import com.vitorpamplona.quartz.events.TextNoteEvent @Composable -private fun RenderGalleryFeed( +fun RenderGalleryFeed( viewModel: FeedViewModel, routeForLastRead: String?, forceEventKind: Int?, - listState: LazyListState, + listState: LazyGridState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() - CrossfadeIfEnabled( targetState = feedState, animationSpec = tween(durationMillis = 100), @@ -126,12 +126,13 @@ private fun RenderGalleryFeed( private fun GalleryFeedLoaded( state: FeedState.Loaded, routeForLastRead: String?, - listState: LazyListState, + listState: LazyGridState, forceEventKind: Int?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - LazyColumn( + LazyVerticalGrid( + columns = GridCells.Fixed(3), contentPadding = FeedPadding, state = listState, ) { @@ -142,7 +143,7 @@ private fun GalleryFeedLoaded( GalleryCardCompose( baseNote = item, routeForLastRead = routeForLastRead, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier, forceEventKind = forceEventKind, accountViewModel = accountViewModel, nav = nav, @@ -167,25 +168,30 @@ fun GalleryCardCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + /* baseNote.event?.let { + Text(text = it.id() + " " + it.firstTaggedUrl() + " ") // TODO why does it.taggedGalleryEntries() not return something? whats different? + } baseNote.event?.let { + for (entry in it.taggedGalleryEntries()) { + Text(text = entry.url + " " + entry.id) + } + }*/ + WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { - if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) { - CheckHiddenFeedWatchBlockAndReport( - note = baseNote, + CheckHiddenFeedWatchBlockAndReport( + note = baseNote, + modifier = modifier, + ignoreAllBlocksAndReports = isHiddenFeed, + showHiddenWarning = false, + accountViewModel = accountViewModel, + nav = nav, + ) { canPreview -> + GalleryCard( + baseNote = baseNote, modifier = modifier, - ignoreAllBlocksAndReports = isHiddenFeed, - showHiddenWarning = false, + parentBackgroundColor = parentBackgroundColor, accountViewModel = accountViewModel, nav = nav, - ) { canPreview -> - NormalChannelCard( - baseNote = baseNote, - routeForLastRead = routeForLastRead, - modifier = modifier, - parentBackgroundColor = parentBackgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) - } + ) } } } @@ -235,7 +241,6 @@ fun ProfileGallery( @Composable fun GalleryCard( baseNote: Note, - url: String, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, accountViewModel: AccountViewModel, @@ -246,7 +251,6 @@ fun GalleryCard( CheckNewAndRenderChannelCard( baseNote, - url, modifier, parentBackgroundColor, accountViewModel, @@ -259,7 +263,6 @@ fun GalleryCard( @Composable private fun CheckNewAndRenderChannelCard( baseNote: Note, - url: String, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, accountViewModel: AccountViewModel, @@ -284,7 +287,6 @@ private fun CheckNewAndRenderChannelCard( // baseNote.event?.let { Text(text = it.pubKey()) } InnerGalleryCardWithReactions( baseNote = baseNote, - url = url, accountViewModel = accountViewModel, nav = nav, ) @@ -294,17 +296,15 @@ private fun CheckNewAndRenderChannelCard( @Composable fun InnerGalleryCardWithReactions( baseNote: Note, - url: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - InnerGalleryCardBox(baseNote, url, accountViewModel, nav) + InnerGalleryCardBox(baseNote, accountViewModel, nav) } @Composable fun InnerGalleryCardBox( baseNote: Note, - url: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -313,14 +313,13 @@ fun InnerGalleryCardBox( note = baseNote, accountViewModel = accountViewModel, ) { - RenderGalleryThumb(baseNote, url, accountViewModel, nav) + RenderGalleryThumb(baseNote, accountViewModel, nav) } } } @Immutable data class GalleryThumb( - val baseNote: Note?, val id: String?, val image: String?, val title: String?, @@ -330,34 +329,31 @@ data class GalleryThumb( @Composable fun RenderGalleryThumb( baseNote: Note, - url: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val noteEvent = baseNote.event as? GalleryListEvent ?: return + val noteEvent = baseNote.event as? TextNoteEvent ?: return val card by baseNote .live() .metadata .map { - val noteEvent = baseNote.event as GalleryListEvent + val noteEvent = baseNote.event as TextNoteEvent GalleryThumb( - baseNote = baseNote, id = "", - image = url, - title = "Hello", + image = noteEvent.firstTaggedUrl(), + title = noteEvent.content(), // noteEvent?.title(), // price = noteEvent?.price(), ) }.distinctUntilChanged() .observeAsState( GalleryThumb( - baseNote = baseNote, id = "", - image = "https://gokaygokay-aurasr.hf.space/file=/tmp/gradio/68292f324a38d7071453cf6912dfb1da9d1305c8/image3.png", - title = "Hello", + image = noteEvent.firstTaggedUrl(), + title = noteEvent.content(), // image = noteEvent.image(), // title = noteEvent.title(), // price = noteEvent.price(), @@ -374,7 +370,6 @@ fun RenderGalleryThumbPreview() { InnerRenderGalleryThumb( card = GalleryThumb( - baseNote = null, id = "", image = null, title = "Like New", diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 59e1c16b6..e15700961 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState @@ -1569,10 +1570,12 @@ fun TabGallery( Column( modifier = Modifier.padding(vertical = 0.dp), ) { - RefresheableFeedView( + var state = LazyGridState() + RenderGalleryFeed( feedViewModel, null, - enablePullRefresh = false, + 0, + state, accountViewModel = accountViewModel, nav = nav, ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt index 73a09f013..356e8ce47 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt @@ -72,3 +72,21 @@ data class EmojiUrl(val code: String, val url: String) { } } } + +@Immutable +data class GalleryUrl(val id: String, val url: String) { + fun encode(): String { + return ":$id:$url" + } + + companion object { + fun decode(encodedGallerySetup: String): EmojiUrl? { + val emojiParts = encodedGallerySetup.split(":", limit = 3) + return if (emojiParts.size > 2) { + EmojiUrl(emojiParts[1], emojiParts[2]) + } else { + null + } + } + } +} 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 19521379e..a921dfb44 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,6 +119,8 @@ 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] == "g" }.map { GalleryUrl(it[1], it[2]) } + 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/EventInterface.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt index db11a3ea4..d444f4195 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -145,6 +145,8 @@ interface EventInterface { fun firstTaggedK(): Int? + fun taggedGalleryEntries(): List + fun taggedEmojis(): List fun matchTag1With(text: String): Boolean From 45df7713ce48db889198c11b5ef3c1bacc868e14 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 23:38:41 +0200 Subject: [PATCH 05/23] remove text preview from images --- .../vitorpamplona/amethyst/model/Account.kt | 3 + .../amethyst/ui/components/VideoView.kt | 11 +- .../ui/components/ZoomableContentView.kt | 16 +- .../amethyst/ui/note/NoteQuickActionMenu.kt | 306 ++++++++++++++++++ .../amethyst/ui/note/types/AudioTrack.kt | 1 - .../amethyst/ui/note/types/LiveActivity.kt | 1 - .../ui/screen/loggedIn/AccountViewModel.kt | 14 + .../ui/screen/loggedIn/ProfileGallery.kt | 20 +- .../ui/screen/loggedIn/ProfileScreen.kt | 20 +- amethyst/src/main/res/values/strings.xml | 2 + .../quartz/events/GalleryListEvent.kt | 38 +-- 11 files changed, 358 insertions(+), 74 deletions(-) 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 903579a7c..59f8501e9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -2203,6 +2203,7 @@ class Account( url: String, ) { if (!isWriteable()) return + GalleryListEvent.addEvent( userProfile().latestGalleryList, idHex, @@ -2226,6 +2227,7 @@ class Account( GalleryListEvent.removeReplaceable( galleryentries, note.address, + url, signer, ) { Client.send(it) @@ -2235,6 +2237,7 @@ class Account( GalleryListEvent.removeEvent( galleryentries, note.idHex, + url, signer, ) { Client.send(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 8618ae622..239d88b0d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -142,7 +142,6 @@ fun LoadThumbAndThenVideoView( roundedCorner: Boolean, isFiniteHeight: Boolean, nostrUriCallback: String? = null, - nostrIdCallback: String? = null, accountViewModel: AccountViewModel, onDialog: ((Boolean) -> Unit)? = null, ) { @@ -177,7 +176,6 @@ fun LoadThumbAndThenVideoView( artworkUri = thumbUri, authorName = authorName, nostrUriCallback = nostrUriCallback, - nostrIdCallback = nostrIdCallback, accountViewModel = accountViewModel, onDialog = onDialog, ) @@ -192,7 +190,6 @@ fun LoadThumbAndThenVideoView( artworkUri = thumbUri, authorName = authorName, nostrUriCallback = nostrUriCallback, - nostrIdCallback = nostrIdCallback, accountViewModel = accountViewModel, onDialog = onDialog, ) @@ -214,7 +211,6 @@ fun VideoView( dimensions: String? = null, blurhash: String? = null, nostrUriCallback: String? = null, - nostrIdCallback: String? = null, onDialog: ((Boolean) -> Unit)? = null, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, accountViewModel: AccountViewModel, @@ -256,7 +252,6 @@ fun VideoView( artworkUri = artworkUri, authorName = authorName, nostrUriCallback = nostrUriCallback, - nostrIDCallback = nostrIdCallback, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, onDialog = onDialog, @@ -310,7 +305,6 @@ fun VideoView( artworkUri = artworkUri, authorName = authorName, nostrUriCallback = nostrUriCallback, - nostrIDCallback = nostrIdCallback, automaticallyStartPlayback = automaticallyStartPlayback, onControllerVisibilityChanged = onControllerVisibilityChanged, onDialog = onDialog, @@ -335,7 +329,6 @@ fun VideoViewInner( artworkUri: String? = null, authorName: String? = null, nostrUriCallback: String? = null, - nostrIDCallback: String? = null, automaticallyStartPlayback: State, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, onDialog: ((Boolean) -> Unit)? = null, @@ -357,7 +350,6 @@ fun VideoViewInner( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = nostrUriCallback, - nostrIDCallback = nostrIDCallback, waveform = waveform, keepPlaying = keepPlaying, automaticallyStartPlayback = automaticallyStartPlayback, @@ -705,7 +697,6 @@ private fun RenderVideoPlayer( roundedCorner: Boolean, isFiniteHeight: Boolean, nostrUriCallback: String?, - nostrIDCallback: String?, waveform: ImmutableList? = null, keepPlaying: MutableState, automaticallyStartPlayback: State, @@ -819,7 +810,7 @@ private fun RenderVideoPlayer( } AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle -> - ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, nostrIDCallback, toggle) + ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, toggle) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 1f1ed554a..5fed68693 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.quartz.crypto.CryptoUtils +import com.vitorpamplona.quartz.encoders.Nip19Bech32 import com.vitorpamplona.quartz.encoders.toHexKey import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -138,7 +139,6 @@ fun ZoomableContentView( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, - nostrIdCallback = content.id, onDialog = { dialogOpen = true }, accountViewModel = accountViewModel, ) @@ -163,7 +163,6 @@ fun ZoomableContentView( roundedCorner = roundedCorner, isFiniteHeight = isFiniteHeight, nostrUriCallback = content.uri, - nostrIdCallback = content.id, onDialog = { dialogOpen = true }, accountViewModel = accountViewModel, ) @@ -609,7 +608,6 @@ fun ShareImageAction( popupExpanded = popupExpanded, videoUri = content.url, postNostrUri = content.uri, - postNostrid = content.id, onDismiss = onDismiss, ) } else if (content is MediaPreloadedContent) { @@ -618,7 +616,6 @@ fun ShareImageAction( popupExpanded = popupExpanded, videoUri = content.localFile?.toUri().toString(), postNostrUri = content.uri, - postNostrid = content.id, onDismiss = onDismiss, ) } @@ -631,7 +628,6 @@ fun ShareImageAction( popupExpanded: MutableState, videoUri: String?, postNostrUri: String?, - postNostrid: String?, onDismiss: () -> Unit, ) { DropdownMenu( @@ -665,13 +661,13 @@ fun ShareImageAction( text = { Text(stringRes(R.string.add_media_to_gallery)) }, onClick = { if (videoUri != null) { - if (postNostrid != null) { - print("TODO") - print(postNostrid) - // TODO this still crashes - accountViewModel.account.addToGallery(postNostrid, videoUri) + var n19 = Nip19Bech32.uriToRoute(postNostrUri)?.entity as? Nip19Bech32.NEvent + if (n19 != null) { + accountViewModel.addMediaToGallery(n19.hex, videoUri) + accountViewModel.toast(R.string.image_saved_to_the_gallery, R.string.image_saved_to_the_gallery) } } + onDismiss() }, ) 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 2cc7a5c62..957a0e77e 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 @@ -154,6 +154,38 @@ fun LongPressToQuickAction( } } +@Composable +fun LongPressToQuickActionGallery( + baseNote: Note, + accountViewModel: AccountViewModel, + content: @Composable (() -> Unit) -> Unit, +) { + val popupExpanded = remember { mutableStateOf(false) } + + content { popupExpanded.value = true } + + if (popupExpanded.value) { + NoteQuickActionMenuGallery( + note = baseNote, + onDismiss = { popupExpanded.value = false }, + accountViewModel = accountViewModel, + nav = {}, + ) + } +} + +@Composable +fun NoteQuickActionMenuGallery( + note: Note, + onDismiss: () -> Unit, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + DeleteFromGalleryDialog(note, accountViewModel) { + onDismiss() + } +} + @Composable fun NoteQuickActionMenu( note: Note, @@ -435,6 +467,169 @@ private fun RenderMainPopup( } } +@Composable +private fun RenderDeleteFromGalleryPopup( + accountViewModel: AccountViewModel, + note: Note, + showDeleteAlertDialog: MutableState, + onDismiss: () -> Unit, +) { + val context = LocalContext.current + val primaryLight = lightenColor(MaterialTheme.colorScheme.primary, 0.1f) + val cardShape = RoundedCornerShape(5.dp) + val clipboardManager = LocalClipboardManager.current + val scope = rememberCoroutineScope() + + val backgroundColor = + if (MaterialTheme.colorScheme.isLight) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.secondaryButtonBackground + } + + val showToast = { stringRes: Int -> + scope.launch { + Toast + .makeText( + context, + stringRes(context, stringRes), + Toast.LENGTH_SHORT, + ).show() + } + } + + val isOwnNote = accountViewModel.isLoggedUser(note.author) + val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author) + + Popup(onDismissRequest = onDismiss, alignment = Alignment.Center) { + Card( + modifier = Modifier.shadow(elevation = 6.dp, shape = cardShape), + shape = cardShape, + colors = CardDefaults.cardColors(containerColor = backgroundColor), + ) { + Column(modifier = Modifier.width(IntrinsicSize.Min)) { + Row(modifier = Modifier.height(IntrinsicSize.Min)) { + NoteQuickActionItem( + icon = Icons.Default.ContentCopy, + label = stringRes(R.string.quick_action_copy_text), + ) { + accountViewModel.decrypt(note) { + clipboardManager.setText(AnnotatedString(it)) + showToast(R.string.copied_note_text_to_clipboard) + } + + onDismiss() + } + VerticalDivider(color = primaryLight) + NoteQuickActionItem( + Icons.Default.AlternateEmail, + stringRes(R.string.quick_action_copy_user_id), + ) { + note.author?.let { + scope.launch { + clipboardManager.setText(AnnotatedString(it.toNostrUri())) + showToast(R.string.copied_user_id_to_clipboard) + onDismiss() + } + } + } + VerticalDivider(color = primaryLight) + NoteQuickActionItem( + Icons.Default.FormatQuote, + stringRes(R.string.quick_action_copy_note_id), + ) { + scope.launch { + clipboardManager.setText(AnnotatedString(note.toNostrUri())) + showToast(R.string.copied_note_id_to_clipboard) + onDismiss() + } + } + } + HorizontalDivider( + color = primaryLight, + ) + Row(modifier = Modifier.height(IntrinsicSize.Min)) { + if (isOwnNote) { + NoteQuickActionItem( + Icons.Default.Delete, + stringRes(R.string.quick_action_delete), + ) { + if (accountViewModel.hideDeleteRequestDialog) { + accountViewModel.delete(note) + onDismiss() + } else { + showDeleteAlertDialog.value = true + } + } + } else if (isFollowingUser) { + NoteQuickActionItem( + Icons.Default.PersonRemove, + stringRes(R.string.quick_action_unfollow), + ) { + accountViewModel.unfollow(note.author!!) + onDismiss() + } + } else { + NoteQuickActionItem( + Icons.Default.PersonAdd, + stringRes(R.string.quick_action_follow), + ) { + accountViewModel.follow(note.author!!) + onDismiss() + } + } + + VerticalDivider(color = primaryLight) + NoteQuickActionItem( + icon = ImageVector.vectorResource(id = R.drawable.relays), + label = stringRes(R.string.broadcast), + ) { + accountViewModel.broadcast(note) + // showSelectTextDialog = true + onDismiss() + } + VerticalDivider(color = primaryLight) + if (isOwnNote && note.isDraft()) { + NoteQuickActionItem( + Icons.Default.Edit, + stringRes(R.string.edit_draft), + ) { + onDismiss() + } + } else { + NoteQuickActionItem( + icon = Icons.Default.Share, + label = stringRes(R.string.quick_action_share), + ) { + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra( + Intent.EXTRA_TEXT, + externalLinkForNote(note), + ) + putExtra( + Intent.EXTRA_TITLE, + stringRes(context, R.string.quick_action_share_browser_link), + ) + } + + val shareIntent = + Intent.createChooser( + sendIntent, + stringRes(context, R.string.quick_action_share), + ) + ContextCompat.startActivity(context, shareIntent, null) + onDismiss() + } + } + } + } + } + } +} + @Composable fun NoteQuickActionItem( icon: ImageVector, @@ -462,6 +657,25 @@ fun NoteQuickActionItem( } } +@Composable +fun DeleteFromGalleryDialog( + note: Note, + accountViewModel: AccountViewModel, + onDismiss: () -> Unit, +) { + QuickActionAlertDialogOneButton( + title = stringRes(R.string.quick_action_request_deletion_gallery_title), + textContent = stringRes(R.string.quick_action_request_deletion_gallery_alert_body), + buttonIcon = Icons.Default.Delete, + buttonText = stringRes(R.string.quick_action_delete_dialog_btn), + onClickDoOnce = { + note.event?.firstTaggedUrl()?.let { accountViewModel.removefromMediaGallery(note, it) } + onDismiss() + }, + onDismiss = onDismiss, + ) +} + @Composable fun DeleteAlertDialog( note: Note, @@ -612,3 +826,95 @@ fun QuickActionAlertDialog( }, ) } + +@Composable +fun QuickActionAlertDialogOneButton( + title: String, + textContent: String, + buttonIcon: ImageVector, + buttonText: String, + buttonColors: ButtonColors = ButtonDefaults.buttonColors(), + onClickDoOnce: () -> Unit, + onDismiss: () -> Unit, +) { + QuickActionAlertDialogOneButton( + title = title, + textContent = textContent, + icon = { + Icon( + imageVector = buttonIcon, + contentDescription = null, + ) + }, + buttonText = buttonText, + buttonColors = buttonColors, + onClickDoOnce = onClickDoOnce, + onDismiss = onDismiss, + ) +} + +@Composable +fun QuickActionAlertDialogOneButton( + title: String, + textContent: String, + buttonIconResource: Int, + buttonText: String, + buttonColors: ButtonColors = ButtonDefaults.buttonColors(), + onClickDoOnce: () -> Unit, + onDismiss: () -> Unit, +) { + QuickActionAlertDialogOneButton( + title = title, + textContent = textContent, + icon = { + Icon( + painter = painterResource(buttonIconResource), + contentDescription = null, + ) + }, + buttonText = buttonText, + buttonColors = buttonColors, + onClickDoOnce = onClickDoOnce, + onDismiss = onDismiss, + ) +} + +@Composable +fun QuickActionAlertDialogOneButton( + title: String, + textContent: String, + icon: @Composable () -> Unit, + buttonText: String, + buttonColors: ButtonColors = ButtonDefaults.buttonColors(), + onClickDoOnce: () -> Unit, + onDismiss: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { Text(textContent) }, + confirmButton = { + Row( + modifier = + Modifier + .padding(all = 8.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Button( + onClick = onClickDoOnce, + colors = buttonColors, + contentPadding = PaddingValues(horizontal = 16.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + icon() + Spacer(Modifier.width(8.dp)) + Text(buttonText) + } + } + } + }, + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt index f1879ea25..d7875628e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AudioTrack.kt @@ -212,7 +212,6 @@ fun AudioHeader( isFiniteHeight = isFiniteHeight, accountViewModel = accountViewModel, nostrUriCallback = note.toNostrUri(), - nostrIdCallback = note.idHex, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index 766aeea97..6d0a16f35 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -160,7 +160,6 @@ fun RenderLiveActivityEventInner( isFiniteHeight = false, accountViewModel = accountViewModel, nostrUriCallback = "nostr:${baseNote.toNEvent()}", - nostrIdCallback = baseNote.idHex, ) } } else { 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 b1e5c4eef..5c6504503 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 @@ -669,6 +669,20 @@ class AccountViewModel( viewModelScope.launch(Dispatchers.IO) { account.addEmojiPack(usersEmojiList, emojiList) } } + fun addMediaToGallery( + hex: String, + url: String, + ) { + viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url) } + } + + fun removefromMediaGallery( + note: Note, + url: String, + ) { + viewModelScope.launch(Dispatchers.IO) { account.removeFromGallery(note, url) } + } + fun addPrivateBookmark(note: Note) { viewModelScope.launch(Dispatchers.IO) { account.addBookmark(note, true) } } 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 df02459ae..53271adfb 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 @@ -22,15 +22,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState @@ -39,7 +36,6 @@ 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.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState @@ -51,8 +47,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -64,7 +58,7 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote -import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction +import com.vitorpamplona.amethyst.ui.note.LongPressToQuickActionGallery import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor @@ -78,7 +72,6 @@ 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.amethyst.ui.theme.Size5dp import com.vitorpamplona.quartz.events.TextNoteEvent @Composable @@ -247,8 +240,7 @@ fun GalleryCard( nav: (String) -> Unit, ) { // baseNote.event?.let { Text(text = it.pubKey()) } - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - + LongPressToQuickActionGallery(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> CheckNewAndRenderChannelCard( baseNote, modifier, @@ -400,7 +392,7 @@ fun InnerRenderGalleryThumb( ) } ?: run { DisplayGalleryAuthorBanner(note) } - Row( + /* Row( Modifier .fillMaxWidth() .background(Color.Black.copy(0.6f)) @@ -417,7 +409,7 @@ fun InnerRenderGalleryThumb( modifier = Modifier.weight(1f), ) } - /* + card.price?.let { val priceTag = remember(card) { @@ -438,8 +430,8 @@ fun InnerRenderGalleryThumb( overflow = TextOverflow.Ellipsis, color = Color.White, ) - }*/ - } + } + }*/ } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index e15700961..0a0ec78a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -157,6 +157,7 @@ import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -1571,14 +1572,17 @@ fun TabGallery( modifier = Modifier.padding(vertical = 0.dp), ) { var state = LazyGridState() - RenderGalleryFeed( - feedViewModel, - null, - 0, - state, - accountViewModel = accountViewModel, - nav = nav, - ) + + SaveableGridFeedState(feedViewModel, scrollStateKey = "gallery") { listState -> + RenderGalleryFeed( + feedViewModel, + null, + 0, + state, + accountViewModel = accountViewModel, + nav = nav, + ) + } } } } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 37aa8a5e0..4803fe72d 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -270,6 +270,8 @@ Delete Unfollow Follow + Delete from Gallery + Remove this media from your Gallery, you can readd it later Request Deletion Amethyst will request that your note be deleted from the relays you are currently connected to. There is no guarantee that your note will be permanently deleted from those relays, or from other relays where it may be stored. Block diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 4311407c8..1b65359c0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -60,7 +60,7 @@ class GalleryListEvent( ) { add( earlierVersion, - arrayOf(arrayOf(tagName, url, tagValue)), + arrayOf(arrayOf(tagName, tagValue, url)), signer, createdAt, onReady, @@ -76,7 +76,7 @@ class GalleryListEvent( ) { create( content = earlierVersion?.content ?: "", - tags = (earlierVersion?.tags ?: arrayOf(arrayOf("d", DEFAULT_D_TAG_GALLERY))).plus(listNewTags), + tags = listNewTags.plus(earlierVersion?.tags ?: arrayOf(arrayOf("d", DEFAULT_D_TAG_GALLERY))), signer = signer, createdAt = createdAt, onReady = onReady, @@ -86,23 +86,26 @@ class GalleryListEvent( fun removeEvent( earlierVersion: GalleryListEvent, eventId: HexKey, + url: String, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "e", eventId, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "g", eventId, url, signer, createdAt, onReady) fun removeReplaceable( earlierVersion: GalleryListEvent, aTag: ATag, + url: String, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "a", aTag.toTag(), signer, createdAt, onReady) + ) = removeTag(earlierVersion, "g", aTag.toTag(), url, signer, createdAt, onReady) private fun removeTag( earlierVersion: GalleryListEvent, tagName: String, tagValue: HexKey, + url: String, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, @@ -111,7 +114,7 @@ class GalleryListEvent( content = earlierVersion.content, tags = earlierVersion.tags - .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue) } + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue && it[2] == url) } .toTypedArray(), signer = signer, createdAt = createdAt, @@ -135,30 +138,5 @@ class GalleryListEvent( signer.sign(createdAt, KIND, newTags, content, onReady) } - - fun create( - name: String = "", - images: List? = null, - videos: List? = null, - audios: List? = null, - privEvents: List? = null, - privUsers: List? = null, - privAddresses: List? = null, - signer: NostrSigner, - createdAt: Long = TimeUtils.now(), - onReady: (GalleryListEvent) -> Unit, - ) { - val tags = mutableListOf>() - tags.add(arrayOf("d", name)) - - images?.forEach { tags.add(arrayOf("g", it)) } - videos?.forEach { tags.add(arrayOf("g", it)) } - audios?.forEach { tags.add(arrayOf("g", it)) } - tags.add(arrayOf("alt", ALT)) - - createPrivateTags(privEvents, privUsers, privAddresses, signer) { content -> - signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady) - } - } } } From 0d18a89177df2aa5b37b128210049cb33cd9a4af Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 23:53:40 +0200 Subject: [PATCH 06/23] cleanup --- .../ui/screen/loggedIn/ProfileGallery.kt | 50 ------------------- 1 file changed, 50 deletions(-) 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 53271adfb..41850616d 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 @@ -161,14 +161,6 @@ fun GalleryCardCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - /* baseNote.event?.let { - Text(text = it.id() + " " + it.firstTaggedUrl() + " ") // TODO why does it.taggedGalleryEntries() not return something? whats different? - } baseNote.event?.let { - for (entry in it.taggedGalleryEntries()) { - Text(text = entry.url + " " + entry.id) - } - }*/ - WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { CheckHiddenFeedWatchBlockAndReport( note = baseNote, @@ -189,48 +181,6 @@ fun GalleryCardCompose( } } -// TODO This is to large parts from the ChannelCardCompose - -/*@OptIn(ExperimentalFoundationApi::class) -@Composable -fun ProfileGallery( - baseNote: List, - modifier: Modifier = Modifier, - parentBackgroundColor: MutableState? = null, - isHiddenFeed: Boolean = false, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - for (thumb in baseNotes) { - thumb.baseNote?.let { - WatchNoteEvent(baseNote = it, accountViewModel = accountViewModel) { - if (thumb.baseNote.event?.kind() == GalleryListEvent.KIND) { - CheckHiddenFeedWatchBlockAndReport( - note = thumb.baseNote, - modifier = modifier, - ignoreAllBlocksAndReports = isHiddenFeed, - showHiddenWarning = false, - accountViewModel = accountViewModel, - nav = nav, - ) { canPreview -> - - thumb.image?.let { it1 -> - GalleryCard( - baseNote = thumb.baseNote, - url = it1, - modifier = modifier, - parentBackgroundColor = parentBackgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) - } - } - } - } - } - } -} -*/ @Composable fun GalleryCard( baseNote: Note, From 16eb6097cef73e573e1ec2b1cdab1c6ea52a405f Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Sat, 29 Jun 2024 15:08:58 +0200 Subject: [PATCH 07/23] actually use the image from the 10011 event --- .../java/com/vitorpamplona/amethyst/model/Note.kt | 2 ++ .../amethyst/ui/dal/UserProfileGalleryFeedFilter.kt | 12 ++++++++++-- .../amethyst/ui/note/NoteQuickActionMenu.kt | 6 +++++- .../amethyst/ui/screen/loggedIn/ProfileGallery.kt | 7 ++++--- 4 files changed, 21 insertions(+), 6 deletions(-) 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 055476c11..acc2ae0ad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -139,6 +139,8 @@ open class Note( var relays = listOf() private set + var headerImage: String? = null + var lastReactionsDownloadTime: Map = emptyMap() fun id() = Hex.decode(idHex) 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 8b81e5bde..59faa9376 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 @@ -34,7 +34,7 @@ class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedF val notes = user.latestGalleryList ?.taggedGalleryEntries() - ?.mapNotNull { LocalCache.checkGetOrCreateNote(it.id) } + ?.map { Pair(LocalCache.getOrCreateNote(it.id), it.url) } ?.toSet() ?: emptySet() @@ -46,7 +46,15 @@ class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedF ?: emptySet() */ // .sortedWith(DefaultFeedOrder) - return (notes) + + var finalnotes = setOf() + for (pair in notes) { + pair.first.headerImage = pair.second + finalnotes = finalnotes + pair.first + } + println(finalnotes) + + return (finalnotes) .filter { account.isAcceptable(it) } } } 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 957a0e77e..aa442f935 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 @@ -669,7 +669,11 @@ fun DeleteFromGalleryDialog( buttonIcon = Icons.Default.Delete, buttonText = stringRes(R.string.quick_action_delete_dialog_btn), onClickDoOnce = { - note.event?.firstTaggedUrl()?.let { accountViewModel.removefromMediaGallery(note, it) } + note.headerImage.let { + if (it != null) { + accountViewModel.removefromMediaGallery(note, it) + } + } onDismiss() }, onDismiss = onDismiss, 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 41850616d..606916fb7 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 @@ -282,10 +282,9 @@ fun RenderGalleryThumb( .metadata .map { val noteEvent = baseNote.event as TextNoteEvent - GalleryThumb( id = "", - image = noteEvent.firstTaggedUrl(), + image = baseNote.headerImage, title = noteEvent.content(), // noteEvent?.title(), // price = noteEvent?.price(), @@ -294,7 +293,7 @@ fun RenderGalleryThumb( .observeAsState( GalleryThumb( id = "", - image = noteEvent.firstTaggedUrl(), + image = baseNote.headerImage, title = noteEvent.content(), // image = noteEvent.image(), // title = noteEvent.title(), @@ -342,6 +341,8 @@ fun InnerRenderGalleryThumb( ) } ?: run { DisplayGalleryAuthorBanner(note) } + // TODO what if video? + /* Row( Modifier .fillMaxWidth() From 9fe08f6c458559d0b91bcfb6739c5ea14d7df963 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:29:11 +0200 Subject: [PATCH 08/23] allow videos in the gallery --- .../amethyst/ui/components/VideoView.kt | 25 +++++++---- .../ui/dal/UserProfileGalleryFeedFilter.kt | 22 ++++----- .../ui/screen/loggedIn/ProfileGallery.kt | 45 +++++++++++++------ .../vitorpamplona/amethyst/ui/theme/Shape.kt | 1 + .../vitorpamplona/amethyst/ui/theme/Theme.kt | 30 +++++++++++++ 5 files changed, 92 insertions(+), 31 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 239d88b0d..41e6fe1cb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -109,15 +109,13 @@ import com.vitorpamplona.amethyst.ui.note.MutedIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize -import com.vitorpamplona.amethyst.ui.theme.Size110dp -import com.vitorpamplona.amethyst.ui.theme.Size165dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size22Modifier import com.vitorpamplona.amethyst.ui.theme.Size50Modifier -import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize import com.vitorpamplona.amethyst.ui.theme.imageModifier +import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers @@ -204,6 +202,7 @@ fun VideoView( title: String? = null, thumb: VideoThumb? = null, roundedCorner: Boolean, + gallery: Boolean = false, isFiniteHeight: Boolean, waveform: ImmutableList? = null, artworkUri: String? = null, @@ -247,6 +246,7 @@ fun VideoView( title = title, thumb = thumb, roundedCorner = roundedCorner, + gallery = gallery, isFiniteHeight = isFiniteHeight, waveform = waveform, artworkUri = artworkUri, @@ -324,6 +324,7 @@ fun VideoViewInner( title: String? = null, thumb: VideoThumb? = null, roundedCorner: Boolean, + gallery: Boolean = false, isFiniteHeight: Boolean, waveform: ImmutableList? = null, artworkUri: String? = null, @@ -348,6 +349,7 @@ fun VideoViewInner( controller = controller, thumbData = thumb, roundedCorner = roundedCorner, + gallery = gallery, isFiniteHeight = isFiniteHeight, nostrUriCallback = nostrUriCallback, waveform = waveform, @@ -695,6 +697,7 @@ private fun RenderVideoPlayer( controller: MediaController, thumbData: VideoThumb?, roundedCorner: Boolean, + gallery: Boolean = false, isFiniteHeight: Boolean, nostrUriCallback: String?, waveform: ImmutableList? = null, @@ -712,13 +715,18 @@ private fun RenderVideoPlayer( Box { val borders = MaterialTheme.colorScheme.imageModifier - + val bordersSquare = MaterialTheme.colorScheme.videoGalleryModifier val myModifier = remember(controller) { if (roundedCorner) { modifier.then( borders.defaultMinSize(minHeight = 75.dp).align(Alignment.Center), ) + } else if (gallery) { + Modifier + modifier.then( + bordersSquare.defaultMinSize(minHeight = 75.dp).align(Alignment.Center), + ) } else { modifier.fillMaxWidth().defaultMinSize(minHeight = 75.dp).align(Alignment.Center) } @@ -737,6 +745,7 @@ private fun RenderVideoPlayer( setBackgroundColor(Color.Transparent.toArgb()) setShutterBackgroundColor(Color.Transparent.toArgb()) controllerAutoShow = false + useController = !gallery thumbData?.thumb?.let { defaultArtwork = it } hideController() resizeMode = @@ -745,7 +754,7 @@ private fun RenderVideoPlayer( } else { AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH } - onDialog?.let { innerOnDialog -> + /*onDialog?.let { innerOnDialog -> setFullscreenButtonClickListener { controller.pause() innerOnDialog(it) @@ -756,11 +765,11 @@ private fun RenderVideoPlayer( controllerVisible.value = visible == View.VISIBLE onControllerVisibilityChanged?.let { callback -> callback(visible == View.VISIBLE) } }, - ) + ) */ } }, ) - +/* waveform?.let { Waveform(it, controller, remember { Modifier.align(Alignment.Center) }) } val startingMuteState = remember(controller) { controller.volume < 0.001 } @@ -811,7 +820,7 @@ private fun RenderVideoPlayer( AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle -> ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, toggle) - } + } */ } } 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 59faa9376..8d726b89c 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 @@ -34,19 +34,21 @@ class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedF val notes = user.latestGalleryList ?.taggedGalleryEntries() - ?.map { Pair(LocalCache.getOrCreateNote(it.id), it.url) } + ?.map { + Pair( + // ( + // if (ATag.isATag(it.id)) { + // ATag.parse(it.id, null)?.let { it1 -> LocalCache.getOrCreateAddressableNote(it1) } + // } else { + LocalCache.getOrCreateNote(it.id), + // } + // )!! + it.url, + ) + } ?.toSet() ?: emptySet() - /*val addresses = - user.latestGalleryList - ?.taggedAddresses() - ?.map { LocalCache.getOrCreateAddressableNote(it) } - ?.toSet() - ?: emptySet() */ - - // .sortedWith(DefaultFeedOrder) - var finalnotes = setOf() for (pair in notes) { pair.first.headerImage = pair.second 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 606916fb7..0bde747d0 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 @@ -56,6 +56,7 @@ import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning +import com.vitorpamplona.amethyst.ui.components.VideoView import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote import com.vitorpamplona.amethyst.ui.note.LongPressToQuickActionGallery @@ -301,12 +302,12 @@ fun RenderGalleryThumb( ), ) - InnerRenderGalleryThumb(card as GalleryThumb, baseNote) + InnerRenderGalleryThumb(card as GalleryThumb, baseNote, accountViewModel) } @Preview @Composable -fun RenderGalleryThumbPreview() { +fun RenderGalleryThumbPreview(accountViewModel: AccountViewModel) { Surface(Modifier.size(200.dp)) { InnerRenderGalleryThumb( card = @@ -317,6 +318,7 @@ fun RenderGalleryThumbPreview() { // price = Price("800000", "SATS", null), ), note = Note("hex"), + accountViewModel = accountViewModel, ) } } @@ -325,6 +327,7 @@ fun RenderGalleryThumbPreview() { fun InnerRenderGalleryThumb( card: GalleryThumb, note: Note, + accountViewModel: AccountViewModel, ) { Box( Modifier @@ -333,15 +336,31 @@ fun InnerRenderGalleryThumb( contentAlignment = BottomStart, ) { card.image?.let { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize(), - ) - } ?: run { DisplayGalleryAuthorBanner(note) } - - // TODO what if video? + if (it.endsWith("mp4") || it.endsWith("mov") || it.endsWith("mpeg")) { + // TODO how to long press? + VideoView( + videoUri = it, + mimeType = null, + title = "", + dimensions = "1x1", + authorName = note.author?.toBestDisplayName(), + roundedCorner = false, + gallery = true, + isFiniteHeight = false, + alwaysShowVideo = true, + accountViewModel = accountViewModel, + ) + } else { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + } + } + ?: run { DisplayGalleryAuthorBanner(note) } + } /* Row( Modifier @@ -382,8 +401,8 @@ fun InnerRenderGalleryThumb( color = Color.White, ) } - }*/ - } + } + }*/ } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 2af199445..954c525c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -56,6 +56,7 @@ val TabRowHeight = Modifier val SmallBorder = RoundedCornerShape(7.dp) val SmallishBorder = RoundedCornerShape(9.dp) val QuoteBorder = RoundedCornerShape(15.dp) + val ButtonBorder = RoundedCornerShape(20.dp) val EditFieldBorder = RoundedCornerShape(25.dp) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index ef220986b..4fedca007 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalView @@ -124,6 +125,18 @@ val LightImageModifier = .clip(shape = QuoteBorder) .border(1.dp, LightSubtleBorder, QuoteBorder) +val DarkVideoModifier = + Modifier + .fillMaxWidth() + .clip(shape = RectangleShape) + .border(1.dp, DarkSubtleBorder, RectangleShape) + +val LightVideoModifier = + Modifier + .fillMaxWidth() + .clip(shape = RectangleShape) + .border(1.dp, LightSubtleBorder, RectangleShape) + val DarkProfile35dpModifier = Modifier .size(Size35dp) @@ -148,6 +161,20 @@ val LightReplyBorderModifier = .clip(shape = QuoteBorder) .border(1.dp, LightSubtleBorder, QuoteBorder) +val DarkVideoBorderModifier = + Modifier + .padding(top = 5.dp) + .fillMaxWidth() + .clip(shape = RectangleShape) + .border(1.dp, DarkSubtleBorder, RectangleShape) + +val LightVideoBorderModifier = + Modifier + .padding(top = 2.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) + .fillMaxWidth() + .clip(shape = RectangleShape) + .border(1.dp, LightSubtleBorder, RectangleShape) + val DarkInnerPostBorderModifier = Modifier .padding(vertical = 5.dp) @@ -356,6 +383,9 @@ val ColorScheme.markdownStyle: RichTextStyle val ColorScheme.imageModifier: Modifier get() = if (isLight) LightImageModifier else DarkImageModifier +val ColorScheme.videoGalleryModifier: Modifier + get() = if (isLight) LightVideoModifier else DarkVideoModifier + val ColorScheme.profile35dpModifier: Modifier get() = if (isLight) LightProfile35dpModifier else DarkProfile35dpModifier From ec065a65955fbeb1a367daa889189201e0aec4a4 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Sun, 30 Jun 2024 23:14:37 +0200 Subject: [PATCH 09/23] readd regular video elements for regular playing, use isVideo funtion to check --- .../amethyst/ui/components/VideoView.kt | 110 ++++++++++-------- .../ui/screen/loggedIn/ProfileGallery.kt | 4 +- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 41e6fe1cb..363868d52 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -109,9 +109,12 @@ import com.vitorpamplona.amethyst.ui.note.MutedIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.PinBottomIconSize +import com.vitorpamplona.amethyst.ui.theme.Size110dp +import com.vitorpamplona.amethyst.ui.theme.Size165dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size22Modifier import com.vitorpamplona.amethyst.ui.theme.Size50Modifier +import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -754,73 +757,78 @@ private fun RenderVideoPlayer( } else { AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH } - /*onDialog?.let { innerOnDialog -> - setFullscreenButtonClickListener { - controller.pause() - innerOnDialog(it) + if (!gallery) { + onDialog?.let { innerOnDialog -> + setFullscreenButtonClickListener { + controller.pause() + innerOnDialog(it) + } } + setControllerVisibilityListener( + PlayerView.ControllerVisibilityListener { visible -> + controllerVisible.value = visible == View.VISIBLE + onControllerVisibilityChanged?.let { callback -> callback(visible == View.VISIBLE) } + }, + ) } - setControllerVisibilityListener( - PlayerView.ControllerVisibilityListener { visible -> - controllerVisible.value = visible == View.VISIBLE - onControllerVisibilityChanged?.let { callback -> callback(visible == View.VISIBLE) } - }, - ) */ } }, ) -/* + waveform?.let { Waveform(it, controller, remember { Modifier.align(Alignment.Center) }) } + if (!gallery) { + val startingMuteState = remember(controller) { controller.volume < 0.001 } - val startingMuteState = remember(controller) { controller.volume < 0.001 } + MuteButton( + controllerVisible, + startingMuteState, + Modifier.align(Alignment.TopEnd), + ) { mute: Boolean -> + // makes the new setting the default for new creations. + DEFAULT_MUTED_SETTING.value = mute - MuteButton( - controllerVisible, - startingMuteState, - Modifier.align(Alignment.TopEnd), - ) { mute: Boolean -> - // makes the new setting the default for new creations. - DEFAULT_MUTED_SETTING.value = mute - - // if the user unmutes a video and it's not the current playing, switches to that one. - if (!mute && keepPlayingMutex != null && keepPlayingMutex != controller) { - keepPlayingMutex?.stop() - keepPlayingMutex?.release() - keepPlayingMutex = null - } - - controller.volume = if (mute) 0f else 1f - } - - KeepPlayingButton( - keepPlaying, - controllerVisible, - Modifier.align(Alignment.TopEnd).padding(end = Size55dp), - ) { newKeepPlaying: Boolean -> - // If something else is playing and the user marks this video to keep playing, stops the other - // one. - if (newKeepPlaying) { - if (keepPlayingMutex != null && keepPlayingMutex != controller) { + // if the user unmutes a video and it's not the current playing, switches to that one. + if (!mute && keepPlayingMutex != null && keepPlayingMutex != controller) { keepPlayingMutex?.stop() keepPlayingMutex?.release() - } - keepPlayingMutex = controller - } else { - if (keepPlayingMutex == controller) { keepPlayingMutex = null } + + controller.volume = if (mute) 0f else 1f } - keepPlaying.value = newKeepPlaying - } + KeepPlayingButton( + keepPlaying, + controllerVisible, + Modifier.align(Alignment.TopEnd).padding(end = Size55dp), + ) { newKeepPlaying: Boolean -> + // If something else is playing and the user marks this video to keep playing, stops the other + // one. + if (newKeepPlaying) { + if (keepPlayingMutex != null && keepPlayingMutex != controller) { + keepPlayingMutex?.stop() + keepPlayingMutex?.release() + } + keepPlayingMutex = controller + } else { + if (keepPlayingMutex == controller) { + keepPlayingMutex = null + } + } - AnimatedSaveButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size110dp)) { context -> - saveImage(videoUri, mimeType, context, accountViewModel) - } + keepPlaying.value = newKeepPlaying + } - AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle -> - ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, toggle) - } */ + AnimatedSaveButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size110dp)) { context -> + saveImage(videoUri, mimeType, context, accountViewModel) + } + + AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle -> + ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, toggle) + } + } else { + controller.volume = 0f + } } } 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 0bde747d0..9dfe05e10 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 @@ -53,6 +53,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import coil.compose.AsyncImage +import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.isVideoUrl import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning @@ -336,8 +337,7 @@ fun InnerRenderGalleryThumb( contentAlignment = BottomStart, ) { card.image?.let { - if (it.endsWith("mp4") || it.endsWith("mov") || it.endsWith("mpeg")) { - // TODO how to long press? + if (isVideoUrl(it)) { VideoView( videoUri = it, mimeType = null, From 1d42440e269f3ad42f2f0086f63c3e5c3275d92c Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:21:00 +0200 Subject: [PATCH 10/23] add gallery entry to dictionary --- .../vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt | 2 +- amethyst/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 0a0ec78a9..638f0d492 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -596,7 +596,7 @@ private fun CreateAndRenderTabs( listOf<@Composable (() -> Unit)?>( { Text(text = stringRes(R.string.notes)) }, { Text(text = stringRes(R.string.replies)) }, - { Text(text = "Gallery") }, + { Text(text = stringRes(R.string.gallery)) }, { FollowTabHeader(baseUser) }, { FollowersTabHeader(baseUser) }, { ZapTabHeader(baseUser) }, diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index e24567d9a..75a83cc4c 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -134,6 +134,7 @@ Conversations Notes Replies + Gallery "Follows" "Reports" More Options From b12d14d6f5e9a84c73939b8d2473b3baf54633ce Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:19:16 +0200 Subject: [PATCH 11/23] add a shortpreview to not mess up grid layout on load --- .../amethyst/ui/note/BlankNote.kt | 8 +++- .../amethyst/ui/note/WatchNoteEvent.kt | 2 + .../ui/screen/RememberForeverStates.kt | 1 + .../ui/screen/loggedIn/ProfileGallery.kt | 46 +------------------ .../ui/screen/loggedIn/ProfileScreen.kt | 31 ++++++------- amethyst/src/main/res/values/strings.xml | 1 + 6 files changed, 27 insertions(+), 62 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index 75af3272e..4f8e2c6d1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -59,6 +59,7 @@ fun BlankNotePreview() { fun BlankNote( modifier: Modifier = Modifier, idHex: String? = null, + shortPreview: Boolean = false, ) { Column(modifier = modifier) { Row { @@ -75,7 +76,12 @@ fun BlankNote( horizontalArrangement = Arrangement.Center, ) { Text( - text = stringRes(R.string.post_not_found) + if (idHex != null) ": $idHex" else "", + text = + if (shortPreview) { + stringRes(R.string.post_not_found_short) + } else { + stringRes(R.string.post_not_found) + if (idHex != null) ": $idHex" else "" + }, modifier = Modifier.padding(30.dp), color = Color.Gray, textAlign = TextAlign.Center, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt index 33db44e8b..1983a4ba3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt @@ -37,6 +37,7 @@ fun WatchNoteEvent( baseNote: Note, accountViewModel: AccountViewModel, modifier: Modifier = Modifier, + shortPreview: Boolean = false, onNoteEventFound: @Composable () -> Unit, ) { WatchNoteEvent( @@ -54,6 +55,7 @@ fun WatchNoteEvent( onLongClick = showPopup, ) }, + shortPreview = shortPreview, ) } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt index b47a2fec0..c44bafda9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt @@ -45,6 +45,7 @@ object ScrollStateKeys { const val DISCOVER_SCREEN = "Discover" val HOME_FOLLOWS = Route.Home.base + "Follows" val HOME_REPLIES = Route.Home.base + "FollowsReplies" + val PROFILE_GALLERY = Route.Home.base + "ProfileGallery" val DRAFTS = Route.Home.base + "Drafts" 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 9dfe05e10..2520a8dd1 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 @@ -163,7 +163,7 @@ fun GalleryCardCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { + WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel, shortPreview = true) { CheckHiddenFeedWatchBlockAndReport( note = baseNote, modifier = modifier, @@ -228,7 +228,6 @@ private fun CheckNewAndRenderChannelCard( showPopup = showPopup, nav = nav, ) { - // baseNote.event?.let { Text(text = it.pubKey()) } InnerGalleryCardWithReactions( baseNote = baseNote, accountViewModel = accountViewModel, @@ -342,7 +341,6 @@ fun InnerRenderGalleryThumb( videoUri = it, mimeType = null, title = "", - dimensions = "1x1", authorName = note.author?.toBestDisplayName(), roundedCorner = false, gallery = true, @@ -361,48 +359,6 @@ fun InnerRenderGalleryThumb( } ?: run { DisplayGalleryAuthorBanner(note) } } - - /* Row( - Modifier - .fillMaxWidth() - .background(Color.Black.copy(0.6f)) - .padding(Size5dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - card.title?.let { - Text( - text = it, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = Color.White, - modifier = Modifier.weight(1f), - ) - } - - card.price?.let { - val priceTag = - remember(card) { - val newAmount = it.amount.toBigDecimalOrNull()?.let { showAmountAxis(it) } ?: it.amount - - if (it.frequency != null && it.currency != null) { - "$newAmount ${it.currency}/${it.frequency}" - } else if (it.currency != null) { - "$newAmount ${it.currency}" - } else { - newAmount - } - } - - Text( - text = priceTag, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = Color.White, - ) - } - } - }*/ } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 638f0d492..2aedc388e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -42,7 +42,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState @@ -153,11 +152,13 @@ import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileGalleryFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileNewThreadsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState +import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -1567,24 +1568,22 @@ fun TabGallery( ) { LaunchedEffect(Unit) { feedViewModel.invalidateData() } - Column(Modifier.fillMaxHeight()) { - Column( - modifier = Modifier.padding(vertical = 0.dp), - ) { - var state = LazyGridState() + // Column(Modifier.fillMaxHeight()) { - SaveableGridFeedState(feedViewModel, scrollStateKey = "gallery") { listState -> - RenderGalleryFeed( - feedViewModel, - null, - 0, - state, - accountViewModel = accountViewModel, - nav = nav, - ) - } + RefresheableBox(feedViewModel, true) { + SaveableGridFeedState(feedViewModel, scrollStateKey = ScrollStateKeys.PROFILE_GALLERY) { listState -> + RenderGalleryFeed( + feedViewModel, + null, + 0, + listState, + accountViewModel = accountViewModel, + nav = nav, + ) } } + + // } } /*@Composable diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 75a83cc4c..ebf7df349 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -10,6 +10,7 @@ This post was hidden because it mentions your hidden users or words Post was muted or reported by Event is loading or can\'t be found in your relay list + 👀 Channel Image Referenced event not found Could not decrypt the message From 8421dbfa4cacef25db820d86c3c1d9e274b6307a Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 1 Jul 2024 22:06:40 +0200 Subject: [PATCH 12/23] use r tag instead of g (reserved for geotag) --- .../src/main/java/com/vitorpamplona/quartz/events/Event.kt | 2 +- .../com/vitorpamplona/quartz/events/GalleryListEvent.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 a921dfb44..6d9cb1b81 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,7 +119,7 @@ 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] == "g" }.map { GalleryUrl(it[1], it[2]) } + override fun taggedGalleryEntries() = tags.filter { it.size > 2 && it[0] == "r" }.map { GalleryUrl(it[1], it[2]) } override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 1b65359c0..91aba31b5 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -47,7 +47,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = addTag(earlierVersion, "g", eventId, url, signer, createdAt, onReady) + ) = addTag(earlierVersion, "r", eventId, url, signer, createdAt, onReady) fun addTag( earlierVersion: GalleryListEvent?, @@ -90,7 +90,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "g", eventId, url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "r", eventId, url, signer, createdAt, onReady) fun removeReplaceable( earlierVersion: GalleryListEvent, @@ -99,7 +99,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "g", aTag.toTag(), url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "r", aTag.toTag(), url, signer, createdAt, onReady) private fun removeTag( earlierVersion: GalleryListEvent, From 0ed87db3b3be60672f382441025654be7a7c47bc Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Mon, 1 Jul 2024 22:45:50 +0200 Subject: [PATCH 13/23] moved to gallery tag --- .../src/main/java/com/vitorpamplona/quartz/events/Event.kt | 2 +- .../com/vitorpamplona/quartz/events/GalleryListEvent.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 6d9cb1b81..eda93d632 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,7 +119,7 @@ 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] == "r" }.map { GalleryUrl(it[1], it[2]) } + override fun taggedGalleryEntries() = tags.filter { it.size > 2 && it[0] == "gallery" }.map { GalleryUrl(it[1], it[2]) } override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 91aba31b5..048ee1050 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -47,7 +47,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = addTag(earlierVersion, "r", eventId, url, signer, createdAt, onReady) + ) = addTag(earlierVersion, "gallery", eventId, url, signer, createdAt, onReady) fun addTag( earlierVersion: GalleryListEvent?, @@ -90,7 +90,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "r", eventId, url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "gallery", eventId, url, signer, createdAt, onReady) fun removeReplaceable( earlierVersion: GalleryListEvent, @@ -99,7 +99,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "r", aTag.toTag(), url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, "gallery", aTag.toTag(), url, signer, createdAt, onReady) private fun removeTag( earlierVersion: GalleryListEvent, From 7a76bf2ae52843c6cf74a5220f92ccdb590f5704 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:31:06 +0200 Subject: [PATCH 14/23] remove unneeded dtag from 10011 --- .../vitorpamplona/quartz/events/GalleryListEvent.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 048ee1050..4ec728427 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -37,8 +37,8 @@ class GalleryListEvent( ) : GeneralListEvent(id, pubKey, createdAt, KIND, tags, content, sig) { companion object { const val KIND = 10011 - const val ALT = "Gallery List" - const val DEFAULT_D_TAG_GALLERY = "gallery" + const val ALT = "Profile Gallery" + const val GALLERYTAGNAME = "gallery" fun addEvent( earlierVersion: GalleryListEvent?, @@ -47,7 +47,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = addTag(earlierVersion, "gallery", eventId, url, signer, createdAt, onReady) + ) = addTag(earlierVersion, GALLERYTAGNAME, eventId, url, signer, createdAt, onReady) fun addTag( earlierVersion: GalleryListEvent?, @@ -76,7 +76,7 @@ class GalleryListEvent( ) { create( content = earlierVersion?.content ?: "", - tags = listNewTags.plus(earlierVersion?.tags ?: arrayOf(arrayOf("d", DEFAULT_D_TAG_GALLERY))), + tags = listNewTags.plus(earlierVersion?.tags ?: arrayOf()), signer = signer, createdAt = createdAt, onReady = onReady, @@ -90,7 +90,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "gallery", eventId, url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, GALLERYTAGNAME, eventId, url, signer, createdAt, onReady) fun removeReplaceable( earlierVersion: GalleryListEvent, @@ -99,7 +99,7 @@ class GalleryListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = removeTag(earlierVersion, "gallery", aTag.toTag(), url, signer, createdAt, onReady) + ) = removeTag(earlierVersion, GALLERYTAGNAME, aTag.toTag(), url, signer, createdAt, onReady) private fun removeTag( earlierVersion: GalleryListEvent, From d1ed73f78b54cea300b1e3295c3ea64a350c9fc2 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:36:26 +0200 Subject: [PATCH 15/23] Gallery: get rid of dtag, --- .../main/java/com/vitorpamplona/amethyst/model/LocalCache.kt | 4 +--- quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) 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 1034d6065..2d7f5133a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -427,9 +427,7 @@ object LocalCache { fun consume(event: GalleryListEvent) { val user = getOrCreateUser(event.pubKey) if (user.latestGalleryList == null || event.createdAt > user.latestGalleryList!!.createdAt) { - if (event.dTag() == "gallery") { - user.updateGallery(event) - } + 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()} 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 eda93d632..7410fc477 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,7 +119,7 @@ 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] == "gallery" }.map { GalleryUrl(it[1], it[2]) } + override fun taggedGalleryEntries() = tags.filter { it.size > 2 && it[0] == GalleryListEvent.GALLERYTAGNAME }.map { GalleryUrl(it[1], it[2]) } override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } From cc8e432336109805f4e357015a275e37e3193005 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:42:10 +0200 Subject: [PATCH 16/23] make sure only owner of images get delete dialog in Gallery --- .../amethyst/ui/note/NoteQuickActionMenu.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 aa442f935..27e7b18d9 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 @@ -165,12 +165,14 @@ fun LongPressToQuickActionGallery( content { popupExpanded.value = true } if (popupExpanded.value) { - NoteQuickActionMenuGallery( - note = baseNote, - onDismiss = { popupExpanded.value = false }, - accountViewModel = accountViewModel, - nav = {}, - ) + if (baseNote.author == accountViewModel.account.userProfile()) { + NoteQuickActionMenuGallery( + note = baseNote, + onDismiss = { popupExpanded.value = false }, + accountViewModel = accountViewModel, + nav = {}, + ) + } } } From 9b26bdac5c5a7e4981b8c3baf3a794e81c69c591 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:11:59 +0200 Subject: [PATCH 17/23] move GalleryUrl to GalleryListEvent, merge recent changes --- .../ui/dal/UserProfileGalleryFeedFilter.kt | 12 ++++++------ .../amethyst/ui/screen/FeedViewModel.kt | 15 +++++++++++++++ .../ui/screen/loggedIn/ProfileScreen.kt | 3 +-- .../quartz/events/EmojiPackEvent.kt | 18 ------------------ .../com/vitorpamplona/quartz/events/Event.kt | 2 +- .../quartz/events/EventInterface.kt | 2 +- .../quartz/events/GalleryListEvent.kt | 19 +++++++++++++++++++ 7 files changed, 43 insertions(+), 28 deletions(-) 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 8d726b89c..f27abe3a6 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 @@ -25,10 +25,11 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedFilter() { - override fun feedKey(): String { - return account.userProfile().pubkeyHex + "-Gallery-" + user.pubkeyHex - } +class UserProfileGalleryFeedFilter( + val user: User, + val account: Account, +) : FeedFilter() { + override fun feedKey(): String = account.userProfile().pubkeyHex + "-Gallery-" + user.pubkeyHex override fun feed(): List { val notes = @@ -45,8 +46,7 @@ class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedF // )!! it.url, ) - } - ?.toSet() + }?.toSet() ?: emptySet() var finalnotes = setOf() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 94e80f342..3343d89d7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter +import com.vitorpamplona.amethyst.ui.dal.UserProfileGalleryFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter @@ -248,6 +249,20 @@ class NostrUserProfileReportFeedViewModel( } } +class NostrUserProfileGalleryFeedViewModel( + val user: User, + val account: Account, +) : FeedViewModel(UserProfileGalleryFeedFilter(user, account)) { + class Factory( + val user: User, + val account: Account, + ) : ViewModelProvider.Factory { + override fun create(modelClass: Class): NostrUserProfileGalleryFeedViewModel = + NostrUserProfileGalleryFeedViewModel(user, account) + as NostrUserProfileGalleryFeedViewModel + } +} + class NostrUserProfileBookmarksFeedViewModel( val user: User, val account: Account, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index be815f1f9..056371ea2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -451,8 +451,7 @@ private fun RenderSurface( } } }, - ) - .fillMaxHeight() + ).fillMaxHeight() }, ) { RenderScreen( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt index 9d7291dd1..0599fc9b8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EmojiPackEvent.kt @@ -73,21 +73,3 @@ data class EmojiUrl( } } } - -@Immutable -data class GalleryUrl(val id: String, val url: String) { - fun encode(): String { - return ":$id:$url" - } - - companion object { - fun decode(encodedGallerySetup: String): EmojiUrl? { - val emojiParts = encodedGallerySetup.split(":", limit = 3) - return if (emojiParts.size > 2) { - EmojiUrl(emojiParts[1], emojiParts[2]) - } else { - null - } - } - } -} 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 7410fc477..e534dde0b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,7 +119,7 @@ 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 { GalleryUrl(it[1], it[2]) } + override fun taggedGalleryEntries() = tags.filter { it.size > 2 && it[0] == GalleryListEvent.GALLERYTAGNAME }.map { GalleryListEvent.GalleryUrl(it[1], it[2]) } override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } 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 d444f4195..c7c162493 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -145,7 +145,7 @@ interface EventInterface { fun firstTaggedK(): Int? - fun taggedGalleryEntries(): List + fun taggedGalleryEntries(): List fun taggedEmojis(): List diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 4ec728427..73620d6e5 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -139,4 +139,23 @@ class GalleryListEvent( signer.sign(createdAt, KIND, newTags, content, onReady) } } + + @Immutable + data class GalleryUrl( + val id: String, + val url: String, + ) { + fun encode(): String = ":$id:$url" + + companion object { + fun decode(encodedGallerySetup: String): EmojiUrl? { + val emojiParts = encodedGallerySetup.split(":", limit = 3) + return if (emojiParts.size > 2) { + EmojiUrl(emojiParts[1], emojiParts[2]) + } else { + null + } + } + } + } } From a55da5f62e4071dbde5003df5527d67dd481d211 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Wed, 3 Jul 2024 23:28:15 +0200 Subject: [PATCH 18/23] linting --- .../src/main/java/com/vitorpamplona/amethyst/model/User.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 2e09dbe9e..43d0fe6fa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -528,7 +528,8 @@ class UserLiveSet( relays.hasObservers() || relayInfo.hasObservers() || zaps.hasObservers() || - bookmarks.hasObservers() || gallery.hasObservers() || + bookmarks.hasObservers() || + gallery.hasObservers() || statuses.hasObservers() || profilePictureChanges.hasObservers() || nip05Changes.hasObservers() || From c81324843ca46191788f07bf785bdb727faab09e Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:41:14 +0200 Subject: [PATCH 19/23] move to G Tag for Gallery entries --- .../java/com/vitorpamplona/quartz/events/GalleryListEvent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 73620d6e5..64c8fc9b1 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -38,7 +38,7 @@ class GalleryListEvent( companion object { const val KIND = 10011 const val ALT = "Profile Gallery" - const val GALLERYTAGNAME = "gallery" + const val GALLERYTAGNAME = "G" fun addEvent( earlierVersion: GalleryListEvent?, From ad6dcfc0306f944997d5f0850ff2dc264405106e Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 5 Jul 2024 08:29:49 +0200 Subject: [PATCH 20/23] use url tag, add relay hint --- .../vitorpamplona/amethyst/model/Account.kt | 2 ++ .../ui/components/ZoomableContentView.kt | 2 +- .../ui/screen/loggedIn/AccountViewModel.kt | 3 +- .../com/vitorpamplona/quartz/events/Event.kt | 5 ++- .../quartz/events/GalleryListEvent.kt | 32 ++++++++++++------- 5 files changed, 29 insertions(+), 15 deletions(-) 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 8c832c4f2..20b750ebe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -2201,6 +2201,7 @@ class Account( fun addToGallery( idHex: String, url: String, + relay: String?, ) { if (!isWriteable()) return @@ -2208,6 +2209,7 @@ class Account( userProfile().latestGalleryList, idHex, url, + relay, signer, ) { Client.send(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 5fed68693..3544d639e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -663,7 +663,7 @@ fun ShareImageAction( if (videoUri != null) { var n19 = Nip19Bech32.uriToRoute(postNostrUri)?.entity as? Nip19Bech32.NEvent if (n19 != null) { - accountViewModel.addMediaToGallery(n19.hex, videoUri) + accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay[0]) // TODO Whole list or first? accountViewModel.toast(R.string.image_saved_to_the_gallery, R.string.image_saved_to_the_gallery) } } 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 5c6504503..1b26192a7 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 @@ -672,8 +672,9 @@ class AccountViewModel( fun addMediaToGallery( hex: String, url: String, + relay: String?, ) { - viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url) } + viewModelScope.launch(Dispatchers.IO) { account.addToGallery(hex, url, relay) } } fun removefromMediaGallery( 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 e534dde0b..6d4cbd8f4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -119,7 +119,10 @@ 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]) } + 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] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt index 64c8fc9b1..808155184 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GalleryListEvent.kt @@ -38,29 +38,36 @@ class GalleryListEvent( companion object { const val KIND = 10011 const val ALT = "Profile Gallery" - const val GALLERYTAGNAME = "G" + const val GALLERYTAGNAME = "url" fun addEvent( earlierVersion: GalleryListEvent?, eventId: HexKey, url: String, + relay: String?, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, - ) = addTag(earlierVersion, GALLERYTAGNAME, eventId, url, signer, createdAt, onReady) + ) = addTag(earlierVersion, GALLERYTAGNAME, eventId, url, relay, signer, createdAt, onReady) fun addTag( earlierVersion: GalleryListEvent?, tagName: String, - tagValue: HexKey, + eventid: HexKey, url: String, + relay: String?, signer: NostrSigner, createdAt: Long = TimeUtils.now(), onReady: (GalleryListEvent) -> Unit, ) { + var tags = arrayOf(tagName, url, eventid) + if (relay != null) { + tags + relay + } + add( earlierVersion, - arrayOf(arrayOf(tagName, tagValue, url)), + arrayOf(tags), signer, createdAt, onReady, @@ -104,7 +111,7 @@ class GalleryListEvent( private fun removeTag( earlierVersion: GalleryListEvent, tagName: String, - tagValue: HexKey, + eventid: HexKey, url: String, signer: NostrSigner, createdAt: Long = TimeUtils.now(), @@ -114,7 +121,7 @@ class GalleryListEvent( content = earlierVersion.content, tags = earlierVersion.tags - .filter { it.size <= 1 || !(it[0] == tagName && it[1] == tagValue && it[2] == url) } + .filter { it.size <= 1 || !(it[0] == tagName && it[1] == url && it[2] == eventid) } .toTypedArray(), signer = signer, createdAt = createdAt, @@ -142,16 +149,17 @@ class GalleryListEvent( @Immutable data class GalleryUrl( - val id: String, val url: String, + val id: String, + val relay: String?, ) { - fun encode(): String = ":$id:$url" + fun encode(): String = ":$url:$id:$relay" companion object { - fun decode(encodedGallerySetup: String): EmojiUrl? { - val emojiParts = encodedGallerySetup.split(":", limit = 3) - return if (emojiParts.size > 2) { - EmojiUrl(emojiParts[1], emojiParts[2]) + fun decode(encodedGallerySetup: String): GalleryUrl? { + val galleryParts = encodedGallerySetup.split(":", limit = 3) + return if (galleryParts.size > 3) { + GalleryUrl(galleryParts[1], galleryParts[2], galleryParts[3]) } else { null } From 06a1862d17b405e8fea7d9bef8d300b23dac8793 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:35:36 +0200 Subject: [PATCH 21/23] add relay info --- .../amethyst/ui/dal/UserProfileGalleryFeedFilter.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 f27abe3a6..b1ec6e5a1 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,6 +24,8 @@ 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 class UserProfileGalleryFeedFilter( val user: User, @@ -36,7 +38,7 @@ class UserProfileGalleryFeedFilter( user.latestGalleryList ?.taggedGalleryEntries() ?.map { - Pair( + Triple( // ( // if (ATag.isATag(it.id)) { // ATag.parse(it.id, null)?.let { it1 -> LocalCache.getOrCreateAddressableNote(it1) } @@ -45,6 +47,7 @@ class UserProfileGalleryFeedFilter( // } // )!! it.url, + it.relay, ) }?.toSet() ?: emptySet() @@ -52,6 +55,11 @@ class UserProfileGalleryFeedFilter( 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 } println(finalnotes) 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 22/23] 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) + } + } +} From dcad12f8360a6799852ece874dd9c57939a3bad2 Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:01:16 +0200 Subject: [PATCH 23/23] load older notes in feed --- .../service/NostrUserProfileDataSource.kt | 2 +- .../ui/dal/UserProfileGalleryFeedFilter.kt | 14 ++-- .../ui/screen/loggedIn/ProfileGallery.kt | 69 ++++++++----------- 3 files changed, 39 insertions(+), 46 deletions(-) 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 3f7139549..cdd60db09 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -84,7 +84,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") { WikiNoteEvent.KIND, ), authors = listOf(it.pubkeyHex), - limit = 500, + limit = 200, ), ) } 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 a4fc5eba2..5b84eeacf 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 @@ -32,7 +32,7 @@ class UserProfileGalleryFeedFilter( val user: User, val account: Account, ) : AdditiveFeedFilter() { - override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultStoriesFollowList.value + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + "ProfileGallery" override fun showHiddenKey(): Boolean = account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || @@ -46,13 +46,17 @@ class UserProfileGalleryFeedFilter( acceptableEvent(it, params, user) } + var sorted = sort(notes) var finalnotes = setOf() - for (item in notes) { - item.associatedNote = (item.event as ProfileGalleryEntryEvent).event()?.let { LocalCache.getOrCreateNote(it) } - finalnotes = finalnotes + item + for (item in sorted) { + val note = (item.event as ProfileGalleryEntryEvent).event()?.let { LocalCache.checkGetOrCreateNote(it) } + if (note != null) { + note.associatedNote = item + finalnotes = finalnotes + note + } } - return sort(finalnotes) + return finalnotes.toList() } override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) 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 730db72dc..fbf2af6a8 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 @@ -133,45 +133,27 @@ private fun GalleryFeedLoaded( itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } - 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() - - 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) - } - } + Row(defaultModifier) { + GalleryCardCompose( + baseNote = item, + routeForLastRead = routeForLastRead, + modifier = Modifier, + forceEventKind = forceEventKind, + accountViewModel = accountViewModel, + nav = nav, + ) } + + HorizontalDivider( + thickness = DividerThickness, + ) } } } @Composable fun GalleryCardCompose( - galleryNote: Note, baseNote: Note, - image: String, routeForLastRead: String? = null, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, @@ -190,15 +172,22 @@ fun GalleryCardCompose( nav = nav, ) { canPreview -> - GalleryCard( - galleryNote = galleryNote, - baseNote = baseNote, - image = image, - modifier = modifier, - parentBackgroundColor = parentBackgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) + if (baseNote.associatedNote != null) { + if (baseNote.associatedNote!!.event != null) { + val image = (baseNote.associatedNote!!.event as ProfileGalleryEntryEvent).url() + if (image != null) { + GalleryCard( + galleryNote = baseNote.associatedNote!!, + baseNote = baseNote, + image = image, + modifier = modifier, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } } } }