Merge branch 'main' into main
This commit is contained in:
@@ -22,9 +22,11 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.commons.data.LargeCache
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
@@ -33,7 +35,6 @@ import com.vitorpamplona.quartz.encoders.toNote
|
||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Stable
|
||||
class PublicChatChannel(idHex: String) : Channel(idHex) {
|
||||
@@ -110,7 +111,7 @@ abstract class Channel(val idHex: String) {
|
||||
|
||||
var updatedMetadataAt: Long = 0
|
||||
|
||||
val notes = ConcurrentHashMap<HexKey, Note>()
|
||||
val notes = LargeCache<HexKey, Note>()
|
||||
|
||||
open fun id() = Hex.decode(idHex)
|
||||
|
||||
@@ -131,7 +132,7 @@ abstract class Channel(val idHex: String) {
|
||||
}
|
||||
|
||||
open fun profilePicture(): String? {
|
||||
return creator?.profilePicture()
|
||||
return creator?.info?.banner
|
||||
}
|
||||
|
||||
open fun updateChannelInfo(
|
||||
@@ -145,7 +146,7 @@ abstract class Channel(val idHex: String) {
|
||||
}
|
||||
|
||||
fun addNote(note: Note) {
|
||||
notes[note.idHex] = note
|
||||
notes.put(note.idHex, note)
|
||||
}
|
||||
|
||||
fun removeNote(note: Note) {
|
||||
@@ -163,18 +164,18 @@ abstract class Channel(val idHex: String) {
|
||||
|
||||
fun pruneOldAndHiddenMessages(account: Account): Set<Note> {
|
||||
val important =
|
||||
notes.values
|
||||
.filter { it.author?.let { it1 -> account.isHidden(it1) } == false }
|
||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
.reversed()
|
||||
.take(1000)
|
||||
notes.filter { key, it ->
|
||||
it.author?.let { author -> account.isHidden(author) } == false
|
||||
}
|
||||
.sortedWith(DefaultFeedOrder)
|
||||
.take(500)
|
||||
.toSet()
|
||||
|
||||
val toBeRemoved = notes.values.filter { it !in important }.toSet()
|
||||
val toBeRemoved = notes.filter { key, it -> it !in important }
|
||||
|
||||
toBeRemoved.forEach { notes.remove(it.idHex) }
|
||||
|
||||
return toBeRemoved
|
||||
return toBeRemoved.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1809,9 +1809,9 @@ object LocalCache {
|
||||
|
||||
removeFromCache(childrenToBeRemoved)
|
||||
|
||||
if (toBeRemoved.size > 100 || it.value.notes.size > 100) {
|
||||
if (toBeRemoved.size > 100 || it.value.notes.size() > 100) {
|
||||
println(
|
||||
"PRUNE: ${toBeRemoved.size} messages removed from ${it.value.toBestDisplayName()}. ${it.value.notes.size} kept",
|
||||
"PRUNE: ${toBeRemoved.size} messages removed from ${it.value.toBestDisplayName()}. ${it.value.notes.size()} kept",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,8 @@ open class Note(val idHex: String) {
|
||||
event is LiveActivitiesEvent
|
||||
) {
|
||||
(event as? ChannelMessageEvent)?.channel()
|
||||
?: (event as? ChannelMetadataEvent)?.channel() ?: (event as? ChannelCreateEvent)?.id
|
||||
?: (event as? ChannelMetadataEvent)?.channel()
|
||||
?: (event as? ChannelCreateEvent)?.id
|
||||
?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag()
|
||||
?: (event as? LiveActivitiesEvent)?.address()?.toTag()
|
||||
} else {
|
||||
|
||||
@@ -96,7 +96,7 @@ class ParticipantListBuilder {
|
||||
it.replyTo?.forEach { addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet) }
|
||||
}
|
||||
|
||||
LocalCache.getChannelIfExists(baseNote.idHex)?.notes?.values?.forEach {
|
||||
LocalCache.getChannelIfExists(baseNote.idHex)?.notes?.forEach { key, it ->
|
||||
addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CurrencyBitcoin
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
@@ -98,6 +99,7 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
@@ -448,6 +450,9 @@ fun ShowUserSuggestionListForEdit(
|
||||
key = { _, item -> item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserLine(item, accountViewModel) { editPostViewModel.autocompleteWithUser(item) }
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +357,10 @@ private fun RenderSearchResults(
|
||||
|
||||
searchBarViewModel.clear()
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
@@ -367,6 +371,10 @@ private fun RenderSearchResults(
|
||||
nav("Channel/${item.idHex}")
|
||||
searchBarViewModel.clear()
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,18 +412,15 @@ fun UserComposeForChat(
|
||||
accountViewModel: AccountViewModel,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.clickable(
|
||||
onClick = onClick,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
).padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 10.dp,
|
||||
bottom = 10.dp,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -432,12 +437,6 @@ fun UserComposeForChat(
|
||||
DisplayUserAboutInfo(baseUser)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -294,7 +294,6 @@ private fun DisplayOwnerInformation(
|
||||
UserCompose(
|
||||
baseUser = it,
|
||||
accountViewModel = accountViewModel,
|
||||
showDiviser = false,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ class ChannelFeedFilter(val channel: Channel, val account: Account) : AdditiveFe
|
||||
|
||||
// returns the last Note of each user.
|
||||
override fun feed(): List<Note> {
|
||||
return channel.notes.values
|
||||
.filter { account.isAcceptable(it) }
|
||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
.reversed()
|
||||
return sort(
|
||||
channel.notes.filterIntoSet { key, it ->
|
||||
account.isAcceptable(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun applyFilter(collection: Set<Note>): Set<Note> {
|
||||
|
||||
@@ -56,15 +56,12 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
||||
.selectedChatsFollowList()
|
||||
.mapNotNull { LocalCache.getChannelIfExists(it) }
|
||||
.mapNotNull { it ->
|
||||
it.notes.values
|
||||
.filter { account.isAcceptable(it) && it.event != null }
|
||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
.lastOrNull()
|
||||
it.notes.filter { key, it -> account.isAcceptable(it) && it.event != null }
|
||||
.sortedWith(DefaultFeedOrder)
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
return (privateMessages + publicChannels)
|
||||
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
.reversed()
|
||||
return (privateMessages + publicChannels).sortedWith(DefaultFeedOrder)
|
||||
}
|
||||
|
||||
override fun updateListWith(
|
||||
|
||||
@@ -48,9 +48,8 @@ open class DiscoverLiveFeedFilter(
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val allChannelNotes =
|
||||
LocalCache.channels.values.mapNotNull { LocalCache.getNoteIfExists(it.idHex) }
|
||||
val allMessageNotes = LocalCache.channels.values.map { it.notes.values }.flatten()
|
||||
val allChannelNotes = LocalCache.channels.values.mapNotNull { LocalCache.getNoteIfExists(it.idHex) }
|
||||
val allMessageNotes = LocalCache.channels.values.map { it.notes.filter { key, it -> it.event is LiveActivitiesEvent } }.flatten()
|
||||
|
||||
val notes = innerApplyFilter(allChannelNotes + allMessageNotes)
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.events.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.events.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.events.HighlightEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
@@ -87,6 +88,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
filterParams: FilterByListParams,
|
||||
): Boolean {
|
||||
val loggedInUserHex = account.userProfile().pubkeyHex
|
||||
val loggedInUser = account.userProfile()
|
||||
|
||||
return it.event !is ChannelCreateEvent &&
|
||||
it.event !is ChannelMetadataEvent &&
|
||||
@@ -94,6 +96,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
it.event !is BadgeDefinitionEvent &&
|
||||
it.event !is BadgeProfilesEvent &&
|
||||
it.event !is GiftWrapEvent &&
|
||||
(it.event is LnZapEvent || it.author !== loggedInUser) &&
|
||||
(filterParams.isGlobal || filterParams.followLists?.users?.contains(it.author?.pubkeyHex) == true) &&
|
||||
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
|
||||
(filterParams.isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
|
||||
|
||||
+3
-1
@@ -21,9 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||
import com.vitorpamplona.quartz.events.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
@@ -43,7 +45,7 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
|
||||
override fun feed(): List<Note> {
|
||||
val notes =
|
||||
LocalCache.notes.filterIntoSet { _, it ->
|
||||
acceptableEvent(it)
|
||||
it !is AddressableNote && it.event !is AddressableEvent && acceptableEvent(it)
|
||||
}
|
||||
|
||||
val longFormNotes =
|
||||
|
||||
@@ -48,6 +48,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.BottomStart
|
||||
import androidx.compose.ui.Alignment.Companion.Center
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Alignment.Companion.TopEnd
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -68,6 +70,7 @@ import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||
@@ -197,7 +200,7 @@ fun InnerChannelCardWithReactions(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
when (remember { baseNote.event }) {
|
||||
when (baseNote.event) {
|
||||
is LiveActivitiesEvent -> {
|
||||
InnerCardRow(baseNote, accountViewModel, nav)
|
||||
}
|
||||
@@ -255,7 +258,7 @@ private fun RenderNoteRow(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
when (remember { baseNote.event }) {
|
||||
when (baseNote.event) {
|
||||
is LiveActivitiesEvent -> {
|
||||
RenderLiveActivityThumb(baseNote, accountViewModel, nav)
|
||||
}
|
||||
@@ -305,29 +308,29 @@ fun RenderClassifiedsThumb(
|
||||
),
|
||||
)
|
||||
|
||||
RenderClassifiedsThumb(card, baseNote.author)
|
||||
InnerRenderClassifiedsThumb(card, baseNote)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderClassifiedsThumbPreview() {
|
||||
Surface(Modifier.size(200.dp)) {
|
||||
RenderClassifiedsThumb(
|
||||
InnerRenderClassifiedsThumb(
|
||||
card =
|
||||
ClassifiedsThumb(
|
||||
image = null,
|
||||
title = "Like New",
|
||||
price = Price("800000", "SATS", null),
|
||||
),
|
||||
author = null,
|
||||
note = Note("hex"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderClassifiedsThumb(
|
||||
fun InnerRenderClassifiedsThumb(
|
||||
card: ClassifiedsThumb,
|
||||
author: User?,
|
||||
note: Note,
|
||||
) {
|
||||
Box(
|
||||
Modifier.fillMaxWidth().aspectRatio(1f),
|
||||
@@ -340,8 +343,7 @@ fun RenderClassifiedsThumb(
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
?: run { author?.let { DisplayAuthorBanner(it) } }
|
||||
} ?: run { DisplayAuthorBanner(note) }
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth().background(Color.Black.copy(0.6f)).padding(Size5dp),
|
||||
@@ -449,8 +451,7 @@ fun RenderLiveActivityThumb(
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
|
||||
)
|
||||
}
|
||||
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
|
||||
} ?: run { DisplayAuthorBanner(baseNote) }
|
||||
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
Crossfade(targetState = card.status, label = "RenderLiveActivityThumb") {
|
||||
@@ -496,12 +497,11 @@ fun RenderLiveActivityThumb(
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
ChannelHeader(
|
||||
channelHex = remember { baseNote.idHex },
|
||||
channelHex = baseNote.idHex,
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
showFlag = false,
|
||||
sendToChannel = true,
|
||||
modifier = remember { Modifier.padding(start = 0.dp, end = 0.dp, top = 5.dp, bottom = 5.dp) },
|
||||
modifier = Modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
@@ -559,8 +559,7 @@ fun RenderCommunitiesThumb(
|
||||
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
|
||||
)
|
||||
}
|
||||
}
|
||||
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
|
||||
} ?: run { DisplayAuthorBanner(baseNote) }
|
||||
},
|
||||
onTitleRow = {
|
||||
Text(
|
||||
@@ -780,16 +779,13 @@ fun RenderChannelThumb(
|
||||
LeftPictureLayout(
|
||||
onImage = {
|
||||
cover?.let {
|
||||
Box(contentAlignment = BottomStart) {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
|
||||
)
|
||||
}
|
||||
}
|
||||
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
|
||||
} ?: run { DisplayAuthorBanner(baseNote) }
|
||||
},
|
||||
onTitleRow = {
|
||||
Text(
|
||||
@@ -829,7 +825,7 @@ fun RenderChannelThumb(
|
||||
onBottomRow = {
|
||||
if (participantUsers.isNotEmpty()) {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row { Gallery(participantUsers, accountViewModel) }
|
||||
Gallery(participantUsers, accountViewModel)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -846,27 +842,20 @@ fun Gallery(
|
||||
|
||||
if (users.size > 6) {
|
||||
Text(
|
||||
text = remember(users) { " + " + (showCount(users.size - 6)) },
|
||||
text = " + " + showCount(users.size - 6),
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.align(CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayAuthorBanner(author: User) {
|
||||
val picture by
|
||||
author
|
||||
.live()
|
||||
.metadata
|
||||
.map { it.user.info?.banner?.ifBlank { null } ?: it.user.info?.picture?.ifBlank { null } }
|
||||
.observeAsState()
|
||||
fun DisplayAuthorBanner(note: Note) {
|
||||
val authorState by note.live().authorChanges.observeAsState(note.author)
|
||||
|
||||
AsyncImage(
|
||||
model = picture,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
|
||||
)
|
||||
authorState?.let { author ->
|
||||
BannerImage(author, Modifier.fillMaxSize().clip(QuoteBorder))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -110,6 +111,7 @@ fun ChatroomMessageCompose(
|
||||
WatchBlockAndReport(
|
||||
note = baseNote,
|
||||
showHiddenWarning = innerQuote,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) { canPreview ->
|
||||
|
||||
@@ -239,7 +239,6 @@ fun AcceptableNote(
|
||||
ChannelHeader(
|
||||
channelNote = baseNote,
|
||||
showVideo = !makeItShort,
|
||||
showBottomDiviser = true,
|
||||
sendToChannel = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
@@ -273,7 +272,6 @@ fun AcceptableNote(
|
||||
ChannelHeader(
|
||||
channelNote = baseNote,
|
||||
showVideo = !makeItShort,
|
||||
showBottomDiviser = true,
|
||||
sendToChannel = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
@@ -692,7 +690,7 @@ fun RenderRepost(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
note.replyTo?.lastOrNull()?.let {
|
||||
note.replyTo?.lastOrNull { it.event !is CommunityDefinitionEvent }?.let {
|
||||
NoteCompose(
|
||||
it,
|
||||
modifier = Modifier,
|
||||
@@ -748,23 +746,25 @@ private fun ReplyRow(
|
||||
ChannelHeader(
|
||||
channelHex = it,
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
sendToChannel = true,
|
||||
modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp),
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
}
|
||||
|
||||
if (showReply) {
|
||||
val replyingDirectlyTo =
|
||||
remember(note) {
|
||||
if (noteEvent is BaseTextNoteEvent) {
|
||||
val replyingTo = noteEvent.replyingTo()
|
||||
val replyingTo = noteEvent.replyingToAddressOrEvent()
|
||||
if (replyingTo != null) {
|
||||
note.replyTo?.firstOrNull {
|
||||
// important to test both ids in case it's a replaceable event.
|
||||
it.idHex == replyingTo || it.event?.id() == replyingTo
|
||||
val newNote = accountViewModel.getNoteIfExists(replyingTo)
|
||||
if (newNote != null && newNote.channelHex() == null && newNote.event?.kind() != CommunityDefinitionEvent.KIND) {
|
||||
newNote
|
||||
} else {
|
||||
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
|
||||
}
|
||||
} else {
|
||||
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
|
||||
|
||||
@@ -120,7 +120,7 @@ val externalLinkForNote = { note: Note ->
|
||||
} else if (note.event is AudioTrackEvent) {
|
||||
"https://zapstr.live/?track=${note.address()?.toNAddr()}"
|
||||
} else {
|
||||
"https://habla.news/a/${note.address()?.toNAddr()}"
|
||||
"https://njump.me/${note.address()?.toNAddr()}"
|
||||
}
|
||||
} else {
|
||||
if (note.event is FileHeaderEvent) {
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -47,7 +47,7 @@ import com.vitorpamplona.amethyst.model.RelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -62,12 +62,13 @@ fun RelayCompose(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp),
|
||||
modifier = StdPadding,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 10.dp).weight(1f),
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
@@ -100,12 +101,6 @@ fun RelayCompose(
|
||||
RelayOptions(accountViewModel, relay, onAddRelay, onRemoveRelay)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -32,7 +31,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@@ -40,18 +38,14 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
fun UserCompose(
|
||||
baseUser: User,
|
||||
overallModifier: Modifier = StdPadding,
|
||||
showDiviser: Boolean = true,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.clickable(
|
||||
overallModifier.clickable(
|
||||
onClick = { nav("User/${baseUser.pubkeyHex}") },
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
modifier = overallModifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
|
||||
@@ -66,11 +60,4 @@ fun UserCompose(
|
||||
UserActionOptions(baseUser, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (showDiviser) {
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-13
@@ -23,28 +23,50 @@ package com.vitorpamplona.amethyst.ui.note.elements
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.authorNotePictureForImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageHeaderBannerSize
|
||||
|
||||
@Composable
|
||||
fun DefaultImageHeader(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val authorState by note.live().authorChanges.observeAsState(note.author)
|
||||
|
||||
authorState?.let { author ->
|
||||
Box {
|
||||
note.author?.info?.banner?.let {
|
||||
BannerImage(author)
|
||||
|
||||
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) {
|
||||
BaseUserPicture(author, Size55dp, accountViewModel, Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BannerImage(
|
||||
author: User,
|
||||
imageModifier: Modifier = Modifier.fillMaxWidth().heightIn(max = 200.dp),
|
||||
) {
|
||||
val currentInfo by author.live().userMetadataInfo.observeAsState()
|
||||
currentInfo?.banner?.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription =
|
||||
@@ -52,19 +74,16 @@ fun DefaultImageHeader(
|
||||
R.string.preview_card_image_for,
|
||||
it,
|
||||
),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = imageModifier,
|
||||
placeholder = painterResource(R.drawable.profile_banner),
|
||||
)
|
||||
}
|
||||
?: Image(
|
||||
} ?: run {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.profile_banner),
|
||||
contentDescription = stringResource(R.string.profile_banner),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = imageHeaderBannerSize,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = imageModifier,
|
||||
)
|
||||
|
||||
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) {
|
||||
NoteAuthorPicture(baseNote = note, accountViewModel = accountViewModel, size = Size55dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,9 @@ fun RenderClassifieds(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} ?: run {
|
||||
DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
?: DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
|
||||
@@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -68,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
@@ -88,7 +86,6 @@ import java.util.Locale
|
||||
@Composable
|
||||
fun CommunityHeader(
|
||||
baseNote: AddressableNote,
|
||||
showBottomDiviser: Boolean,
|
||||
sendToCommunity: Boolean,
|
||||
modifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -125,12 +122,6 @@ fun CommunityHeader(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showBottomDiviser) {
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,9 @@ private fun WikiNoteHeader(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} ?: run {
|
||||
DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
?: DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
|
||||
title?.let {
|
||||
|
||||
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -65,14 +64,14 @@ private fun LongFormHeader(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val image = remember(noteEvent) { noteEvent.image() }
|
||||
val title = remember(noteEvent) { noteEvent.title() }
|
||||
val image = noteEvent.image()
|
||||
val title = noteEvent.title()
|
||||
val summary =
|
||||
remember(noteEvent) {
|
||||
noteEvent.summary()?.ifBlank { null } ?: noteEvent.content.take(200).ifBlank { null }
|
||||
}
|
||||
|
||||
Row(
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = Size5dp)
|
||||
@@ -83,9 +82,8 @@ private fun LongFormHeader(
|
||||
QuoteBorder,
|
||||
),
|
||||
) {
|
||||
Column {
|
||||
val automaticallyShowUrlPreview =
|
||||
remember { accountViewModel.settings.showUrlPreview.value }
|
||||
remember { accountViewModel.settings.showImages.value }
|
||||
|
||||
if (automaticallyShowUrlPreview) {
|
||||
image?.let {
|
||||
@@ -99,8 +97,9 @@ private fun LongFormHeader(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} ?: run {
|
||||
DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
?: DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
|
||||
title?.let {
|
||||
@@ -129,5 +128,4 @@ private fun LongFormHeader(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ package com.vitorpamplona.amethyst.ui.note.types
|
||||
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.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -50,6 +52,7 @@ import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -98,13 +101,16 @@ fun DisplayPeopleList(
|
||||
Box {
|
||||
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
||||
toMembersShow.forEach { user ->
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
UserCompose(
|
||||
user,
|
||||
overallModifier = Modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ fun RenderPostApproval(
|
||||
baseNote?.let {
|
||||
CommunityHeader(
|
||||
baseNote = it,
|
||||
showBottomDiviser = false,
|
||||
sendToCommunity = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
|
||||
@@ -135,8 +135,9 @@ fun VideoDisplay(
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = MaterialTheme.colorScheme.imageModifier,
|
||||
)
|
||||
} ?: run {
|
||||
DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
?: DefaultImageHeader(note, accountViewModel)
|
||||
}
|
||||
} else {
|
||||
ZoomableContentView(
|
||||
|
||||
@@ -37,7 +37,10 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -120,6 +123,7 @@ fun LoggedInPage(
|
||||
accountViewModel.serviceManager = activity.serviceManager
|
||||
|
||||
if (accountViewModel.account.signer is NostrSignerExternal) {
|
||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||
val launcher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult(),
|
||||
@@ -137,7 +141,10 @@ fun LoggedInPage(
|
||||
},
|
||||
)
|
||||
|
||||
DisposableEffect(accountViewModel, accountViewModel.account, launcher, activity) {
|
||||
DisposableEffect(accountViewModel, accountViewModel.account, launcher, activity, lifeCycleOwner) {
|
||||
val observer =
|
||||
LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
accountViewModel.account.signer.launcher.registerLauncher(
|
||||
launcher = {
|
||||
try {
|
||||
@@ -154,7 +161,31 @@ fun LoggedInPage(
|
||||
},
|
||||
contentResolver = { Amethyst.instance.contentResolver },
|
||||
)
|
||||
onDispose { accountViewModel.account.signer.launcher.clearLauncher() }
|
||||
}
|
||||
}
|
||||
|
||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
||||
accountViewModel.account.signer.launcher.registerLauncher(
|
||||
launcher = {
|
||||
try {
|
||||
activity.prepareToLaunchSigner()
|
||||
launcher.launch(it)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("Signer", "Error opening Signer app", e)
|
||||
accountViewModel.toast(
|
||||
R.string.error_opening_external_signer,
|
||||
R.string.error_opening_external_signer_description,
|
||||
)
|
||||
}
|
||||
},
|
||||
contentResolver = { Amethyst.instance.contentResolver },
|
||||
)
|
||||
onDispose {
|
||||
Log.d("onDispose", "Called onDispose")
|
||||
accountViewModel.account.signer.launcher.clearLauncher()
|
||||
lifeCycleOwner.lifecycle.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
||||
@@ -47,6 +48,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||
import com.vitorpamplona.amethyst.ui.note.RelayCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -169,6 +171,9 @@ fun RelayFeedView(
|
||||
onAddRelay = { wantsToAddRelay = item.url },
|
||||
onRemoveRelay = { wantsToAddRelay = item.url },
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -37,17 +38,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
@Composable
|
||||
fun RefreshingFeedStringFeedView(
|
||||
viewModel: StringFeedViewModel,
|
||||
enablePullRefresh: Boolean = true,
|
||||
inner: @Composable (String) -> Unit,
|
||||
) {
|
||||
RefresheableBox(viewModel, enablePullRefresh) { StringFeedView(viewModel, inner = inner) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StringFeedView(
|
||||
viewModel: StringFeedViewModel,
|
||||
@@ -112,7 +105,13 @@ private fun FeedLoaded(
|
||||
) {
|
||||
item { pre?.let { it() } }
|
||||
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item }) { _, item -> inner(item) }
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item }) { _, item ->
|
||||
inner(item)
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
item { post?.let { it() } }
|
||||
}
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
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.Spacer
|
||||
@@ -35,6 +32,7 @@ import androidx.compose.foundation.layout.height
|
||||
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.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -49,9 +47,6 @@ import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -80,7 +75,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
@@ -148,7 +142,6 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdTopPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@@ -196,36 +189,31 @@ fun ThreadFeedView(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
var refreshing by remember { mutableStateOf(false) }
|
||||
val refresh = {
|
||||
refreshing = true
|
||||
viewModel.invalidateData()
|
||||
refreshing = false
|
||||
RefresheableBox(viewModel) {
|
||||
RenderFeedState(
|
||||
viewModel = viewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
listState = listState,
|
||||
nav = nav,
|
||||
routeForLastRead = null,
|
||||
onLoaded = {
|
||||
RenderThreadFeed(noteId, it, listState, accountViewModel, nav)
|
||||
},
|
||||
)
|
||||
}
|
||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||
}
|
||||
|
||||
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||
Column {
|
||||
Crossfade(
|
||||
targetState = feedState,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
label = "ThreadViewMainState",
|
||||
) { state ->
|
||||
when (state) {
|
||||
is FeedState.Empty -> {
|
||||
FeedEmpty { refreshing = true }
|
||||
}
|
||||
is FeedState.FeedError -> {
|
||||
FeedError(state.errorMessage) { refreshing = true }
|
||||
}
|
||||
is FeedState.Loaded -> {
|
||||
refreshing = false
|
||||
@Composable
|
||||
fun RenderThreadFeed(
|
||||
noteId: String,
|
||||
state: FeedState.Loaded,
|
||||
listState: LazyListState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(noteId) {
|
||||
launch(Dispatchers.IO) {
|
||||
// waits to load the thread to scroll to item.
|
||||
delay(100)
|
||||
val noteForPosition = state.feed.value.filter { it.idHex == noteId }.firstOrNull()
|
||||
@@ -236,8 +224,7 @@ fun ThreadFeedView(
|
||||
position-- // show the replying note
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) { listState.scrollToItem(position) }
|
||||
}
|
||||
listState.scrollToItem(position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,21 +280,10 @@ fun ThreadFeedView(
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = StdTopPadding,
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FeedState.Loading -> {
|
||||
LoadingFeed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a Zebra pattern where each bar is a reply level.
|
||||
@@ -509,7 +485,6 @@ fun NoteMaster(
|
||||
ChannelHeader(
|
||||
channelHex = note.channelHex()!!,
|
||||
showVideo = true,
|
||||
showBottomDiviser = false,
|
||||
sendToChannel = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
@@ -819,8 +794,7 @@ private fun RenderClassifiedsReaderForThread(
|
||||
|
||||
@Composable
|
||||
private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) {
|
||||
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
|
||||
Column {
|
||||
Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
|
||||
noteEvent.image()?.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
@@ -856,7 +830,6 @@ private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -25,11 +25,13 @@ import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
@Composable
|
||||
@@ -82,6 +84,9 @@ private fun FeedLoaded(
|
||||
) {
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item ->
|
||||
UserCompose(item, accountViewModel = accountViewModel, nav = nav)
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
@@ -139,7 +138,6 @@ import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
|
||||
@@ -583,7 +581,6 @@ fun MyTextField(
|
||||
fun ChannelHeader(
|
||||
channelNote: Note,
|
||||
showVideo: Boolean,
|
||||
showBottomDiviser: Boolean,
|
||||
sendToChannel: Boolean,
|
||||
modifier: Modifier = StdPadding,
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -594,8 +591,8 @@ fun ChannelHeader(
|
||||
ChannelHeader(
|
||||
channelHex = it,
|
||||
showVideo = showVideo,
|
||||
showBottomDiviser = showBottomDiviser,
|
||||
sendToChannel = sendToChannel,
|
||||
modifier = modifier,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
@@ -606,7 +603,6 @@ fun ChannelHeader(
|
||||
fun ChannelHeader(
|
||||
channelHex: String,
|
||||
showVideo: Boolean,
|
||||
showBottomDiviser: Boolean,
|
||||
showFlag: Boolean = true,
|
||||
sendToChannel: Boolean = false,
|
||||
modifier: Modifier = StdPadding,
|
||||
@@ -617,7 +613,6 @@ fun ChannelHeader(
|
||||
ChannelHeader(
|
||||
it,
|
||||
showVideo,
|
||||
showBottomDiviser,
|
||||
showFlag,
|
||||
sendToChannel,
|
||||
modifier,
|
||||
@@ -631,7 +626,6 @@ fun ChannelHeader(
|
||||
fun ChannelHeader(
|
||||
baseChannel: Channel,
|
||||
showVideo: Boolean,
|
||||
showBottomDiviser: Boolean,
|
||||
showFlag: Boolean = true,
|
||||
sendToChannel: Boolean = false,
|
||||
modifier: Modifier = StdPadding,
|
||||
@@ -667,12 +661,6 @@ fun ChannelHeader(
|
||||
LongChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
|
||||
}
|
||||
}
|
||||
|
||||
if (showBottomDiviser) {
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -520,6 +520,9 @@ fun ShowUserSuggestionList(
|
||||
key = { _, item -> item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserLine(item, accountViewModel) { channelScreenModel.autocompleteWithUser(item) }
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,6 +839,9 @@ fun LongRoomHeader(
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
@@ -47,7 +46,6 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
|
||||
@Composable
|
||||
@@ -142,11 +140,8 @@ fun HashtagHeader(
|
||||
account: AccountViewModel,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().clickable { onClick() },
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().clickable { onClick() }.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
@@ -158,12 +153,6 @@ fun HashtagHeader(
|
||||
|
||||
HashtagActionOptions(tag, account)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -312,10 +312,6 @@ fun MutedWordHeader(
|
||||
MutedWordActionOptions(tag, account)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -120,7 +120,9 @@ fun NotificationScreen(
|
||||
SummaryBar(
|
||||
model = userReactionsStatsModel,
|
||||
)
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
RefreshableCardView(
|
||||
viewModel = notifFeedViewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
@@ -229,10 +231,6 @@ fun SummaryBar(model: UserReactionsViewModel) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -1547,10 +1547,7 @@ fun TabFollowedTags(
|
||||
account: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp),
|
||||
) {
|
||||
Column(Modifier.fillMaxHeight().padding(vertical = 0.dp)) {
|
||||
baseUser.latestContactList?.let {
|
||||
it.unverifiedFollowTagSet().forEach { hashtag ->
|
||||
HashtagHeader(
|
||||
@@ -1558,7 +1555,9 @@ fun TabFollowedTags(
|
||||
account = account,
|
||||
onClick = { nav("Hashtag/$hashtag") },
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +374,11 @@ private fun DisplaySearchResults(
|
||||
key = { _, item -> "#$item" },
|
||||
) { _, item ->
|
||||
HashtagLine(item) { nav("Hashtag/$item") }
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
@@ -381,6 +386,10 @@ private fun DisplaySearchResults(
|
||||
key = { _, item -> "u" + item.pubkeyHex },
|
||||
) { _, item ->
|
||||
UserCompose(item, accountViewModel = accountViewModel, nav = nav)
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
@@ -432,12 +441,9 @@ fun HashtagLine(
|
||||
tag: String,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
Modifier.fillMaxWidth().clickable(onClick = onClick).padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 10.dp,
|
||||
@@ -454,12 +460,6 @@ fun HashtagLine(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -468,15 +468,13 @@ fun UserLine(
|
||||
accountViewModel: AccountViewModel,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
Modifier.fillMaxWidth().clickable(onClick = onClick).padding(
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
top = 10.dp,
|
||||
bottom = 10.dp,
|
||||
),
|
||||
) {
|
||||
ClickableUserPicture(baseUser, 55.dp, accountViewModel, Modifier, null)
|
||||
@@ -489,10 +487,4 @@ fun UserLine(
|
||||
AboutDisplay(baseUser)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
@@ -79,7 +76,5 @@ fun ThreadScreen(
|
||||
onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column { ThreadFeedView(noteId, feedViewModel, accountViewModel, nav) }
|
||||
}
|
||||
ThreadFeedView(noteId, feedViewModel, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,17 @@ class LargeCache<K, V> {
|
||||
|
||||
fun size() = cache.size
|
||||
|
||||
fun isEmpty() = cache.isEmpty()
|
||||
|
||||
fun containsKey(key: K) = cache.containsKey(key)
|
||||
|
||||
fun put(
|
||||
key: K,
|
||||
value: V,
|
||||
) {
|
||||
cache.put(key, value)
|
||||
}
|
||||
|
||||
fun getOrCreate(
|
||||
key: K,
|
||||
builder: (key: K) -> V,
|
||||
|
||||
@@ -72,13 +72,29 @@ open class BaseTextNoteEvent(
|
||||
}
|
||||
|
||||
fun replyingTo(): HexKey? {
|
||||
val oldStylePositional = tags.lastOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||
val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && it[0] == "e" }?.get(1)
|
||||
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1)
|
||||
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
|
||||
|
||||
return newStyleReply ?: newStyleRoot ?: oldStylePositional
|
||||
}
|
||||
|
||||
fun replyingToAddress(): ATag? {
|
||||
val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && it[0] == "a" }?.let { ATag.parseAtag(it[1], it[2]) }
|
||||
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "a" && it[3] == "reply" }?.let { ATag.parseAtag(it[1], it[2]) }
|
||||
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "a" && it[3] == "root" }?.let { ATag.parseAtag(it[1], it[2]) }
|
||||
|
||||
return newStyleReply ?: newStyleRoot ?: oldStylePositional
|
||||
}
|
||||
|
||||
fun replyingToAddressOrEvent(): String? {
|
||||
val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && (it[0] == "e" || it[0] == "a") }?.get(1)
|
||||
val newStyleReply = tags.lastOrNull { it.size > 3 && (it[0] == "e" || it[0] == "a") && it[3] == "reply" }?.get(1)
|
||||
val newStyleRoot = tags.lastOrNull { it.size > 3 && (it[0] == "e" || it[0] == "a") && it[3] == "root" }?.get(1)
|
||||
|
||||
return newStyleReply ?: newStyleRoot ?: oldStylePositional
|
||||
}
|
||||
|
||||
@Transient private var citedUsersCache: Set<String>? = null
|
||||
|
||||
@Transient private var citedNotesCache: Set<String>? = null
|
||||
|
||||
Reference in New Issue
Block a user