Merge branch 'main' into main

pull/749/head
greenart7c3 2024-03-22 08:48:47 -03:00 zatwierdzone przez GitHub
commit 62a114b981
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
41 zmienionych plików z 507 dodań i 508 usunięć

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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",
)
}
}

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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,
)
}
}

Wyświetl plik

@ -294,7 +294,6 @@ private fun DisplayOwnerInformation(
UserCompose(
baseUser = it,
accountViewModel = accountViewModel,
showDiviser = false,
nav = nav,
)
}

Wyświetl plik

@ -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> {

Wyświetl plik

@ -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(

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)) &&

Wyświetl plik

@ -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 =

Wyświetl plik

@ -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))
}
}

Wyświetl plik

@ -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 ->

Wyświetl plik

@ -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 }

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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,
)
}
}

Wyświetl plik

@ -88,8 +88,9 @@ fun RenderClassifieds(
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
)
} ?: run {
DefaultImageHeader(note, accountViewModel)
}
?: DefaultImageHeader(note, accountViewModel)
}
Row(

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -127,8 +127,9 @@ private fun WikiNoteHeader(
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
)
} ?: run {
DefaultImageHeader(note, accountViewModel)
}
?: DefaultImageHeader(note, accountViewModel)
}
title?.let {

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -60,7 +60,6 @@ fun RenderPostApproval(
baseNote?.let {
CommunityHeader(
baseNote = it,
showBottomDiviser = false,
sendToCommunity = true,
accountViewModel = accountViewModel,
nav = nav,

Wyświetl plik

@ -135,8 +135,9 @@ fun VideoDisplay(
contentScale = ContentScale.FillWidth,
modifier = MaterialTheme.colorScheme.imageModifier,
)
} ?: run {
DefaultImageHeader(note, accountViewModel)
}
?: DefaultImageHeader(note, accountViewModel)
}
} else {
ZoomableContentView(

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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,
)
}
}

Wyświetl plik

@ -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() } }
}

Wyświetl plik

@ -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,
)
}
}
}
}

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -312,10 +312,6 @@ fun MutedWordHeader(
MutedWordActionOptions(tag, account)
}
}
HorizontalDivider(
thickness = DividerThickness,
)
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -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,
)
}
}

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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