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.
This commit is contained in:
@@ -71,6 +71,7 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
|
|||||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageEvent
|
import com.vitorpamplona.quartz.events.FileStorageEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GalleryListEvent
|
||||||
import com.vitorpamplona.quartz.events.GeneralListEvent
|
import com.vitorpamplona.quartz.events.GeneralListEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||||
@@ -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(
|
fun addBookmark(
|
||||||
note: Note,
|
note: Note,
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
|
|||||||
import com.vitorpamplona.quartz.events.FileServersEvent
|
import com.vitorpamplona.quartz.events.FileServersEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageEvent
|
import com.vitorpamplona.quartz.events.FileStorageEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GalleryListEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.events.GitIssueEvent
|
import com.vitorpamplona.quartz.events.GitIssueEvent
|
||||||
@@ -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 =
|
fun formattedDateTime(timestamp: Long): String =
|
||||||
Instant
|
Instant
|
||||||
.ofEpochSecond(timestamp)
|
.ofEpochSecond(timestamp)
|
||||||
@@ -2523,6 +2537,7 @@ object LocalCache {
|
|||||||
is DraftEvent -> consume(event, relay)
|
is DraftEvent -> consume(event, relay)
|
||||||
is EmojiPackEvent -> consume(event, relay)
|
is EmojiPackEvent -> consume(event, relay)
|
||||||
is EmojiPackSelectionEvent -> consume(event, relay)
|
is EmojiPackSelectionEvent -> consume(event, relay)
|
||||||
|
is GalleryListEvent -> consume(event)
|
||||||
is GenericRepostEvent -> {
|
is GenericRepostEvent -> {
|
||||||
event.containedPost()?.let { verifyAndConsume(it, relay) }
|
event.containedPost()?.let { verifyAndConsume(it, relay) }
|
||||||
consume(event)
|
consume(event)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
|||||||
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
||||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GalleryListEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.events.MetadataEvent
|
import com.vitorpamplona.quartz.events.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.events.ReportEvent
|
import com.vitorpamplona.quartz.events.ReportEvent
|
||||||
@@ -60,6 +61,7 @@ class User(
|
|||||||
var latestMetadataRelay: String? = null
|
var latestMetadataRelay: String? = null
|
||||||
var latestContactList: ContactListEvent? = null
|
var latestContactList: ContactListEvent? = null
|
||||||
var latestBookmarkList: BookmarkListEvent? = null
|
var latestBookmarkList: BookmarkListEvent? = null
|
||||||
|
var latestGalleryList: GalleryListEvent? = null
|
||||||
|
|
||||||
var reports = mapOf<User, Set<Note>>()
|
var reports = mapOf<User, Set<Note>>()
|
||||||
private set
|
private set
|
||||||
@@ -123,6 +125,13 @@ class User(
|
|||||||
liveSet?.innerBookmarks?.invalidateData()
|
liveSet?.innerBookmarks?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateGallery(event: GalleryListEvent) {
|
||||||
|
if (event.id == latestGalleryList?.id) return
|
||||||
|
print("GALLERY " + event.id())
|
||||||
|
latestGalleryList = event
|
||||||
|
liveSet?.innerGallery?.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
fun clearEOSE() {
|
fun clearEOSE() {
|
||||||
latestEOSEs = emptyMap()
|
latestEOSEs = emptyMap()
|
||||||
}
|
}
|
||||||
@@ -488,6 +497,7 @@ class UserLiveSet(
|
|||||||
val innerRelayInfo = UserBundledRefresherLiveData(u)
|
val innerRelayInfo = UserBundledRefresherLiveData(u)
|
||||||
val innerZaps = UserBundledRefresherLiveData(u)
|
val innerZaps = UserBundledRefresherLiveData(u)
|
||||||
val innerBookmarks = UserBundledRefresherLiveData(u)
|
val innerBookmarks = UserBundledRefresherLiveData(u)
|
||||||
|
val innerGallery = UserBundledRefresherLiveData(u)
|
||||||
val innerStatuses = UserBundledRefresherLiveData(u)
|
val innerStatuses = UserBundledRefresherLiveData(u)
|
||||||
|
|
||||||
// UI Observers line up here.
|
// UI Observers line up here.
|
||||||
@@ -500,6 +510,7 @@ class UserLiveSet(
|
|||||||
val relayInfo = innerRelayInfo.map { it }
|
val relayInfo = innerRelayInfo.map { it }
|
||||||
val zaps = innerZaps.map { it }
|
val zaps = innerZaps.map { it }
|
||||||
val bookmarks = innerBookmarks.map { it }
|
val bookmarks = innerBookmarks.map { it }
|
||||||
|
val gallery = innerGallery.map { it }
|
||||||
val statuses = innerStatuses.map { it }
|
val statuses = innerStatuses.map { it }
|
||||||
|
|
||||||
val profilePictureChanges = innerMetadata.map { it.user.profilePicture() }.distinctUntilChanged()
|
val profilePictureChanges = innerMetadata.map { it.user.profilePicture() }.distinctUntilChanged()
|
||||||
@@ -517,7 +528,7 @@ class UserLiveSet(
|
|||||||
relays.hasObservers() ||
|
relays.hasObservers() ||
|
||||||
relayInfo.hasObservers() ||
|
relayInfo.hasObservers() ||
|
||||||
zaps.hasObservers() ||
|
zaps.hasObservers() ||
|
||||||
bookmarks.hasObservers() ||
|
bookmarks.hasObservers() || gallery.hasObservers() ||
|
||||||
statuses.hasObservers() ||
|
statuses.hasObservers() ||
|
||||||
profilePictureChanges.hasObservers() ||
|
profilePictureChanges.hasObservers() ||
|
||||||
nip05Changes.hasObservers() ||
|
nip05Changes.hasObservers() ||
|
||||||
@@ -533,6 +544,7 @@ class UserLiveSet(
|
|||||||
innerRelayInfo.destroy()
|
innerRelayInfo.destroy()
|
||||||
innerZaps.destroy()
|
innerZaps.destroy()
|
||||||
innerBookmarks.destroy()
|
innerBookmarks.destroy()
|
||||||
|
innerGallery.destroy()
|
||||||
innerStatuses.destroy()
|
innerStatuses.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -31,6 +31,7 @@ import com.vitorpamplona.quartz.events.BadgeAwardEvent
|
|||||||
import com.vitorpamplona.quartz.events.BadgeProfilesEvent
|
import com.vitorpamplona.quartz.events.BadgeProfilesEvent
|
||||||
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
|
import com.vitorpamplona.quartz.events.GalleryListEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.events.HighlightEvent
|
import com.vitorpamplona.quartz.events.HighlightEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
@@ -146,7 +147,7 @@ object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
|
|||||||
filter =
|
filter =
|
||||||
Filter(
|
Filter(
|
||||||
kinds =
|
kinds =
|
||||||
listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND),
|
listOf(BookmarkListEvent.KIND, PeopleListEvent.KIND, AppRecommendationEvent.KIND, GalleryListEvent.KIND),
|
||||||
authors = listOf(it.pubkeyHex),
|
authors = listOf(it.pubkeyHex),
|
||||||
limit = 100,
|
limit = 100,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
isFiniteHeight: Boolean,
|
isFiniteHeight: Boolean,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
|
nostrIdCallback: String? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onDialog: ((Boolean) -> Unit)? = null,
|
onDialog: ((Boolean) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
@@ -176,6 +177,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
artworkUri = thumbUri,
|
artworkUri = thumbUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
nostrIdCallback = nostrIdCallback,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onDialog = onDialog,
|
onDialog = onDialog,
|
||||||
)
|
)
|
||||||
@@ -190,6 +192,7 @@ fun LoadThumbAndThenVideoView(
|
|||||||
artworkUri = thumbUri,
|
artworkUri = thumbUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
nostrIdCallback = nostrIdCallback,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onDialog = onDialog,
|
onDialog = onDialog,
|
||||||
)
|
)
|
||||||
@@ -211,6 +214,7 @@ fun VideoView(
|
|||||||
dimensions: String? = null,
|
dimensions: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
|
nostrIdCallback: String? = null,
|
||||||
onDialog: ((Boolean) -> Unit)? = null,
|
onDialog: ((Boolean) -> Unit)? = null,
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -252,6 +256,7 @@ fun VideoView(
|
|||||||
artworkUri = artworkUri,
|
artworkUri = artworkUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
nostrIDCallback = nostrIdCallback,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
onDialog = onDialog,
|
onDialog = onDialog,
|
||||||
@@ -305,6 +310,7 @@ fun VideoView(
|
|||||||
artworkUri = artworkUri,
|
artworkUri = artworkUri,
|
||||||
authorName = authorName,
|
authorName = authorName,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
nostrIDCallback = nostrIdCallback,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
onControllerVisibilityChanged = onControllerVisibilityChanged,
|
||||||
onDialog = onDialog,
|
onDialog = onDialog,
|
||||||
@@ -329,6 +335,7 @@ fun VideoViewInner(
|
|||||||
artworkUri: String? = null,
|
artworkUri: String? = null,
|
||||||
authorName: String? = null,
|
authorName: String? = null,
|
||||||
nostrUriCallback: String? = null,
|
nostrUriCallback: String? = null,
|
||||||
|
nostrIDCallback: String? = null,
|
||||||
automaticallyStartPlayback: State<Boolean>,
|
automaticallyStartPlayback: State<Boolean>,
|
||||||
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
|
||||||
onDialog: ((Boolean) -> Unit)? = null,
|
onDialog: ((Boolean) -> Unit)? = null,
|
||||||
@@ -350,6 +357,7 @@ fun VideoViewInner(
|
|||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
nostrUriCallback = nostrUriCallback,
|
nostrUriCallback = nostrUriCallback,
|
||||||
|
nostrIDCallback = nostrIDCallback,
|
||||||
waveform = waveform,
|
waveform = waveform,
|
||||||
keepPlaying = keepPlaying,
|
keepPlaying = keepPlaying,
|
||||||
automaticallyStartPlayback = automaticallyStartPlayback,
|
automaticallyStartPlayback = automaticallyStartPlayback,
|
||||||
@@ -697,6 +705,7 @@ private fun RenderVideoPlayer(
|
|||||||
roundedCorner: Boolean,
|
roundedCorner: Boolean,
|
||||||
isFiniteHeight: Boolean,
|
isFiniteHeight: Boolean,
|
||||||
nostrUriCallback: String?,
|
nostrUriCallback: String?,
|
||||||
|
nostrIDCallback: String?,
|
||||||
waveform: ImmutableList<Int>? = null,
|
waveform: ImmutableList<Int>? = null,
|
||||||
keepPlaying: MutableState<Boolean>,
|
keepPlaying: MutableState<Boolean>,
|
||||||
automaticallyStartPlayback: State<Boolean>,
|
automaticallyStartPlayback: State<Boolean>,
|
||||||
@@ -810,7 +819,7 @@ private fun RenderVideoPlayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle ->
|
AnimatedShareButton(controllerVisible, Modifier.align(Alignment.TopEnd).padding(end = Size165dp)) { popupExpanded, toggle ->
|
||||||
ShareImageAction(popupExpanded, videoUri, nostrUriCallback, toggle)
|
ShareImageAction(accountViewModel = accountViewModel, popupExpanded, videoUri, nostrUriCallback, nostrIDCallback, toggle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -269,7 +269,7 @@ private fun DialogContent(
|
|||||||
contentDescription = stringRes(R.string.quick_action_share),
|
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
|
val localContext = LocalContext.current
|
||||||
|
|||||||
+26
@@ -138,6 +138,7 @@ fun ZoomableContentView(
|
|||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
nostrUriCallback = content.uri,
|
nostrUriCallback = content.uri,
|
||||||
|
nostrIdCallback = content.id,
|
||||||
onDialog = { dialogOpen = true },
|
onDialog = { dialogOpen = true },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
@@ -162,6 +163,7 @@ fun ZoomableContentView(
|
|||||||
roundedCorner = roundedCorner,
|
roundedCorner = roundedCorner,
|
||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
nostrUriCallback = content.uri,
|
nostrUriCallback = content.uri,
|
||||||
|
nostrIdCallback = content.id,
|
||||||
onDialog = { dialogOpen = true },
|
onDialog = { dialogOpen = true },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
@@ -596,22 +598,27 @@ fun DisplayBlurHash(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShareImageAction(
|
fun ShareImageAction(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
popupExpanded: MutableState<Boolean>,
|
popupExpanded: MutableState<Boolean>,
|
||||||
content: BaseMediaContent,
|
content: BaseMediaContent,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (content is MediaUrlContent) {
|
if (content is MediaUrlContent) {
|
||||||
ShareImageAction(
|
ShareImageAction(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
popupExpanded = popupExpanded,
|
popupExpanded = popupExpanded,
|
||||||
videoUri = content.url,
|
videoUri = content.url,
|
||||||
postNostrUri = content.uri,
|
postNostrUri = content.uri,
|
||||||
|
postNostrid = content.id,
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
)
|
)
|
||||||
} else if (content is MediaPreloadedContent) {
|
} else if (content is MediaPreloadedContent) {
|
||||||
ShareImageAction(
|
ShareImageAction(
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
popupExpanded = popupExpanded,
|
popupExpanded = popupExpanded,
|
||||||
videoUri = content.localFile?.toUri().toString(),
|
videoUri = content.localFile?.toUri().toString(),
|
||||||
postNostrUri = content.uri,
|
postNostrUri = content.uri,
|
||||||
|
postNostrid = content.id,
|
||||||
onDismiss = onDismiss,
|
onDismiss = onDismiss,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -620,9 +627,11 @@ fun ShareImageAction(
|
|||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ShareImageAction(
|
fun ShareImageAction(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
popupExpanded: MutableState<Boolean>,
|
popupExpanded: MutableState<Boolean>,
|
||||||
videoUri: String?,
|
videoUri: String?,
|
||||||
postNostrUri: String?,
|
postNostrUri: String?,
|
||||||
|
postNostrid: String?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
DropdownMenu(
|
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()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ fun AudioHeader(
|
|||||||
isFiniteHeight = isFiniteHeight,
|
isFiniteHeight = isFiniteHeight,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nostrUriCallback = note.toNostrUri(),
|
nostrUriCallback = note.toNostrUri(),
|
||||||
|
nostrIdCallback = note.idHex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ fun RenderLiveActivityEventInner(
|
|||||||
isFiniteHeight = false,
|
isFiniteHeight = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nostrUriCallback = "nostr:${baseNote.toNEvent()}",
|
nostrUriCallback = "nostr:${baseNote.toNEvent()}",
|
||||||
|
nostrIdCallback = baseNote.idHex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ fun VideoDisplay(
|
|||||||
val description = event.content.ifBlank { null } ?: event.alt()
|
val description = event.content.ifBlank { null } ?: event.alt()
|
||||||
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
||||||
val uri = note.toNostrUri()
|
val uri = note.toNostrUri()
|
||||||
|
val id = note.id()
|
||||||
val mimeType = event.mimeType()
|
val mimeType = event.mimeType()
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
|
|||||||
+7
-7
@@ -118,13 +118,6 @@ fun DiscoverScreen(
|
|||||||
ScrollStateKeys.DISCOVER_CONTENT,
|
ScrollStateKeys.DISCOVER_CONTENT,
|
||||||
AppDefinitionEvent.KIND,
|
AppDefinitionEvent.KIND,
|
||||||
),
|
),
|
||||||
TabItem(
|
|
||||||
R.string.discover_marketplace,
|
|
||||||
discoveryMarketplaceFeedViewModel,
|
|
||||||
Route.Discover.base + "Marketplace",
|
|
||||||
ScrollStateKeys.DISCOVER_MARKETPLACE,
|
|
||||||
ClassifiedsEvent.KIND,
|
|
||||||
),
|
|
||||||
TabItem(
|
TabItem(
|
||||||
R.string.discover_live,
|
R.string.discover_live,
|
||||||
discoveryLiveFeedViewModel,
|
discoveryLiveFeedViewModel,
|
||||||
@@ -139,6 +132,13 @@ fun DiscoverScreen(
|
|||||||
ScrollStateKeys.DISCOVER_COMMUNITY,
|
ScrollStateKeys.DISCOVER_COMMUNITY,
|
||||||
CommunityDefinitionEvent.KIND,
|
CommunityDefinitionEvent.KIND,
|
||||||
),
|
),
|
||||||
|
TabItem(
|
||||||
|
R.string.discover_marketplace,
|
||||||
|
discoveryMarketplaceFeedViewModel,
|
||||||
|
Route.Discover.base + "Marketplace",
|
||||||
|
ScrollStateKeys.DISCOVER_MARKETPLACE,
|
||||||
|
ClassifiedsEvent.KIND,
|
||||||
|
),
|
||||||
TabItem(
|
TabItem(
|
||||||
R.string.discover_chat,
|
R.string.discover_chat,
|
||||||
discoveryChatFeedViewModel,
|
discoveryChatFeedViewModel,
|
||||||
|
|||||||
+333
@@ -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<GalleryThumb>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
parentBackgroundColor: MutableState<Color>? = 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<Color>? = 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<Color>? = 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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
-9
@@ -433,7 +433,8 @@ private fun RenderSurface(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).fillMaxHeight()
|
)
|
||||||
|
.fillMaxHeight()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
RenderScreen(
|
RenderScreen(
|
||||||
@@ -532,13 +533,14 @@ private fun CreateAndRenderPages(
|
|||||||
when (page) {
|
when (page) {
|
||||||
0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav)
|
0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav)
|
||||||
1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav)
|
1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav)
|
||||||
2 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav)
|
2 -> Gallery(baseUser, followsFeedViewModel, accountViewModel, nav)
|
||||||
3 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav)
|
3 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav)
|
||||||
4 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
4 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav)
|
||||||
5 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav)
|
5 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
||||||
6 -> TabFollowedTags(baseUser, accountViewModel, nav)
|
6 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav)
|
||||||
7 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav)
|
7 -> TabFollowedTags(baseUser, accountViewModel, nav)
|
||||||
8 -> TabRelays(baseUser, accountViewModel, nav)
|
8 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav)
|
||||||
|
9 -> TabRelays(baseUser, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,6 +575,7 @@ private fun CreateAndRenderTabs(
|
|||||||
listOf<@Composable (() -> Unit)?>(
|
listOf<@Composable (() -> Unit)?>(
|
||||||
{ Text(text = stringRes(R.string.notes)) },
|
{ Text(text = stringRes(R.string.notes)) },
|
||||||
{ Text(text = stringRes(R.string.replies)) },
|
{ Text(text = stringRes(R.string.replies)) },
|
||||||
|
{ Text(text = "Gallery") },
|
||||||
{ FollowTabHeader(baseUser) },
|
{ FollowTabHeader(baseUser) },
|
||||||
{ FollowersTabHeader(baseUser) },
|
{ FollowersTabHeader(baseUser) },
|
||||||
{ ZapTabHeader(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<GalleryThumb>()
|
||||||
|
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
|
@Composable
|
||||||
fun TabFollowedTags(
|
fun TabFollowedTags(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
@@ -1545,7 +1592,11 @@ fun TabFollowedTags(
|
|||||||
baseUser.latestContactList?.unverifiedFollowTagSet()
|
baseUser.latestContactList?.unverifiedFollowTagSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight().padding(vertical = 0.dp)) {
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(vertical = 0.dp),
|
||||||
|
) {
|
||||||
items?.let {
|
items?.let {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
itemsIndexed(items) { index, hashtag ->
|
itemsIndexed(items) { index, hashtag ->
|
||||||
|
|||||||
@@ -586,6 +586,7 @@
|
|||||||
<string name="share_or_save">Share or Save</string>
|
<string name="share_or_save">Share or Save</string>
|
||||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
||||||
<string name="copy_the_note_id_to_the_clipboard">Copy Note ID to clipboard</string>
|
<string name="copy_the_note_id_to_the_clipboard">Copy Note ID to clipboard</string>
|
||||||
|
<string name="add_media_to_gallery">Add Media to Gallery</string>
|
||||||
|
|
||||||
<string name="created_at">Created at</string>
|
<string name="created_at">Created at</string>
|
||||||
<string name="rules">Rules</string>
|
<string name="rules">Rules</string>
|
||||||
|
|||||||
+4
@@ -38,6 +38,7 @@ abstract class MediaUrlContent(
|
|||||||
dim: String? = null,
|
dim: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
val uri: String? = null,
|
val uri: String? = null,
|
||||||
|
val id: String? = null,
|
||||||
val mimeType: String? = null,
|
val mimeType: String? = null,
|
||||||
) : BaseMediaContent(description, dim, blurhash)
|
) : BaseMediaContent(description, dim, blurhash)
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ class MediaUrlImage(
|
|||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
dim: String? = null,
|
dim: String? = null,
|
||||||
uri: String? = null,
|
uri: String? = null,
|
||||||
|
id: String? = null,
|
||||||
val contentWarning: String? = null,
|
val contentWarning: String? = null,
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
) : MediaUrlContent(url, description, hash, dim, blurhash, uri, mimeType)
|
||||||
@@ -60,6 +62,7 @@ class MediaUrlVideo(
|
|||||||
hash: String? = null,
|
hash: String? = null,
|
||||||
dim: String? = null,
|
dim: String? = null,
|
||||||
uri: String? = null,
|
uri: String? = null,
|
||||||
|
id: String? = null,
|
||||||
val artworkUri: String? = null,
|
val artworkUri: String? = null,
|
||||||
val authorName: String? = null,
|
val authorName: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
@@ -76,6 +79,7 @@ abstract class MediaPreloadedContent(
|
|||||||
dim: String? = null,
|
dim: String? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
val uri: String,
|
val uri: String,
|
||||||
|
val id: String? = null,
|
||||||
) : BaseMediaContent(description, dim, blurhash) {
|
) : BaseMediaContent(description, dim, blurhash) {
|
||||||
fun localFileExists() = localFile != null && localFile.exists()
|
fun localFileExists() = localFile != null && localFile.exists()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ class EventFactory {
|
|||||||
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
GalleryListEvent.KIND -> GalleryListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
HTTPAuthorizationEvent.KIND ->
|
HTTPAuthorizationEvent.KIND ->
|
||||||
HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -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<Array<String>>,
|
||||||
|
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<Array<String>>,
|
||||||
|
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<Array<String>>,
|
||||||
|
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<String>? = null,
|
||||||
|
videos: List<String>? = null,
|
||||||
|
audios: List<String>? = null,
|
||||||
|
privEvents: List<String>? = null,
|
||||||
|
privUsers: List<String>? = null,
|
||||||
|
privAddresses: List<ATag>? = null,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (GalleryListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val tags = mutableListOf<Array<String>>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user