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