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