mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-17 21:31:57 +01:00
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:
parent
e181296a91
commit
91caacd36d
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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<User, Set<Note>>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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<Boolean>,
|
||||
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<Int>? = null,
|
||||
keepPlaying: MutableState<Boolean>,
|
||||
automaticallyStartPlayback: State<Boolean>,
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<Boolean>,
|
||||
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<Boolean>,
|
||||
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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,6 +212,7 @@ fun AudioHeader(
|
||||
isFiniteHeight = isFiniteHeight,
|
||||
accountViewModel = accountViewModel,
|
||||
nostrUriCallback = note.toNostrUri(),
|
||||
nostrIdCallback = note.idHex,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ fun RenderLiveActivityEventInner(
|
||||
isFiniteHeight = false,
|
||||
accountViewModel = accountViewModel,
|
||||
nostrUriCallback = "nostr:${baseNote.toNEvent()}",
|
||||
nostrIdCallback = baseNote.idHex,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -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<BaseMediaContent>(
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
@ -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<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
|
||||
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 ->
|
||||
|
@ -586,6 +586,7 @@
|
||||
<string name="share_or_save">Share or Save</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="add_media_to_gallery">Add Media to Gallery</string>
|
||||
|
||||
<string name="created_at">Created at</string>
|
||||
<string name="rules">Rules</string>
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user