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.compose.runtime.Stable
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.Hex
@ -33,7 +35,6 @@ import com.vitorpamplona.quartz.encoders.toNote
import com.vitorpamplona.quartz.events.ChannelCreateEvent import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import java.util.concurrent.ConcurrentHashMap
@Stable @Stable
class PublicChatChannel(idHex: String) : Channel(idHex) { class PublicChatChannel(idHex: String) : Channel(idHex) {
@ -110,7 +111,7 @@ abstract class Channel(val idHex: String) {
var updatedMetadataAt: Long = 0 var updatedMetadataAt: Long = 0
val notes = ConcurrentHashMap<HexKey, Note>() val notes = LargeCache<HexKey, Note>()
open fun id() = Hex.decode(idHex) open fun id() = Hex.decode(idHex)
@ -131,7 +132,7 @@ abstract class Channel(val idHex: String) {
} }
open fun profilePicture(): String? { open fun profilePicture(): String? {
return creator?.profilePicture() return creator?.info?.banner
} }
open fun updateChannelInfo( open fun updateChannelInfo(
@ -145,7 +146,7 @@ abstract class Channel(val idHex: String) {
} }
fun addNote(note: Note) { fun addNote(note: Note) {
notes[note.idHex] = note notes.put(note.idHex, note)
} }
fun removeNote(note: Note) { fun removeNote(note: Note) {
@ -163,18 +164,18 @@ abstract class Channel(val idHex: String) {
fun pruneOldAndHiddenMessages(account: Account): Set<Note> { fun pruneOldAndHiddenMessages(account: Account): Set<Note> {
val important = val important =
notes.values notes.filter { key, it ->
.filter { it.author?.let { it1 -> account.isHidden(it1) } == false } it.author?.let { author -> account.isHidden(author) } == false
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) }
.reversed() .sortedWith(DefaultFeedOrder)
.take(1000) .take(500)
.toSet() .toSet()
val toBeRemoved = notes.values.filter { it !in important }.toSet() val toBeRemoved = notes.filter { key, it -> it !in important }
toBeRemoved.forEach { notes.remove(it.idHex) } toBeRemoved.forEach { notes.remove(it.idHex) }
return toBeRemoved return toBeRemoved.toSet()
} }
} }

Wyświetl plik

@ -1809,9 +1809,9 @@ object LocalCache {
removeFromCache(childrenToBeRemoved) removeFromCache(childrenToBeRemoved)
if (toBeRemoved.size > 100 || it.value.notes.size > 100) { if (toBeRemoved.size > 100 || it.value.notes.size() > 100) {
println( println(
"PRUNE: ${toBeRemoved.size} messages removed from ${it.value.toBestDisplayName()}. ${it.value.notes.size} kept", "PRUNE: ${toBeRemoved.size} messages removed from ${it.value.toBestDisplayName()}. ${it.value.notes.size()} kept",
) )
} }
} }

Wyświetl plik

@ -171,7 +171,8 @@ open class Note(val idHex: String) {
event is LiveActivitiesEvent event is LiveActivitiesEvent
) { ) {
(event as? ChannelMessageEvent)?.channel() (event as? ChannelMessageEvent)?.channel()
?: (event as? ChannelMetadataEvent)?.channel() ?: (event as? ChannelCreateEvent)?.id ?: (event as? ChannelMetadataEvent)?.channel()
?: (event as? ChannelCreateEvent)?.id
?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag() ?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag()
?: (event as? LiveActivitiesEvent)?.address()?.toTag() ?: (event as? LiveActivitiesEvent)?.address()?.toTag()
} else { } else {

Wyświetl plik

@ -96,7 +96,7 @@ class ParticipantListBuilder {
it.replyTo?.forEach { addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet) } it.replyTo?.forEach { addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet) }
} }
LocalCache.getChannelIfExists(baseNote.idHex)?.notes?.values?.forEach { LocalCache.getChannelIfExists(baseNote.idHex)?.notes?.forEach { key, it ->
addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet) addFollowsThatDirectlyParticipateOnToSet(it, followingSet, mySet)
} }

Wyświetl plik

@ -46,6 +46,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.CurrencyBitcoin
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
@ -98,6 +99,7 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.Size5dp
@ -448,6 +450,9 @@ fun ShowUserSuggestionListForEdit(
key = { _, item -> item.pubkeyHex }, key = { _, item -> item.pubkeyHex },
) { _, item -> ) { _, item ->
UserLine(item, accountViewModel) { editPostViewModel.autocompleteWithUser(item) } UserLine(item, accountViewModel) { editPostViewModel.autocompleteWithUser(item) }
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }

Wyświetl plik

@ -357,6 +357,10 @@ private fun RenderSearchResults(
searchBarViewModel.clear() searchBarViewModel.clear()
} }
HorizontalDivider(
thickness = DividerThickness,
)
} }
itemsIndexed( itemsIndexed(
@ -367,6 +371,10 @@ private fun RenderSearchResults(
nav("Channel/${item.idHex}") nav("Channel/${item.idHex}")
searchBarViewModel.clear() searchBarViewModel.clear()
} }
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }
@ -404,39 +412,30 @@ fun UserComposeForChat(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Column( Row(
modifier = modifier =
Modifier.clickable( Modifier.clickable(
onClick = onClick, onClick = onClick,
).padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = 10.dp,
), ),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Row( ClickableUserPicture(baseUser, Size55dp, accountViewModel)
Column(
modifier = modifier =
Modifier.padding( Modifier
start = 12.dp, .padding(start = 10.dp)
end = 12.dp, .weight(1f),
top = 10.dp,
),
verticalAlignment = Alignment.CenterVertically,
) { ) {
ClickableUserPicture(baseUser, Size55dp, accountViewModel) Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
Column( DisplayUserAboutInfo(baseUser)
modifier =
Modifier
.padding(start = 10.dp)
.weight(1f),
) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
DisplayUserAboutInfo(baseUser)
}
} }
HorizontalDivider(
modifier = Modifier.padding(top = 10.dp),
thickness = DividerThickness,
)
} }
} }

Wyświetl plik

@ -294,7 +294,6 @@ private fun DisplayOwnerInformation(
UserCompose( UserCompose(
baseUser = it, baseUser = it,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
showDiviser = false,
nav = nav, 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. // returns the last Note of each user.
override fun feed(): List<Note> { override fun feed(): List<Note> {
return channel.notes.values return sort(
.filter { account.isAcceptable(it) } channel.notes.filterIntoSet { key, it ->
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) account.isAcceptable(it)
.reversed() },
)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {

Wyświetl plik

@ -56,15 +56,12 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
.selectedChatsFollowList() .selectedChatsFollowList()
.mapNotNull { LocalCache.getChannelIfExists(it) } .mapNotNull { LocalCache.getChannelIfExists(it) }
.mapNotNull { it -> .mapNotNull { it ->
it.notes.values it.notes.filter { key, it -> account.isAcceptable(it) && it.event != null }
.filter { account.isAcceptable(it) && it.event != null } .sortedWith(DefaultFeedOrder)
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .firstOrNull()
.lastOrNull()
} }
return (privateMessages + publicChannels) return (privateMessages + publicChannels).sortedWith(DefaultFeedOrder)
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed()
} }
override fun updateListWith( override fun updateListWith(

Wyświetl plik

@ -48,9 +48,8 @@ open class DiscoverLiveFeedFilter(
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val allChannelNotes = val allChannelNotes = LocalCache.channels.values.mapNotNull { LocalCache.getNoteIfExists(it.idHex) }
LocalCache.channels.values.mapNotNull { LocalCache.getNoteIfExists(it.idHex) } val allMessageNotes = LocalCache.channels.values.map { it.notes.filter { key, it -> it.event is LiveActivitiesEvent } }.flatten()
val allMessageNotes = LocalCache.channels.values.map { it.notes.values }.flatten()
val notes = innerApplyFilter(allChannelNotes + allMessageNotes) val notes = innerApplyFilter(allChannelNotes + allMessageNotes)

Wyświetl plik

@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.GitIssueEvent import com.vitorpamplona.quartz.events.GitIssueEvent
import com.vitorpamplona.quartz.events.GitPatchEvent import com.vitorpamplona.quartz.events.GitPatchEvent
import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.HighlightEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
@ -87,6 +88,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
filterParams: FilterByListParams, filterParams: FilterByListParams,
): Boolean { ): Boolean {
val loggedInUserHex = account.userProfile().pubkeyHex val loggedInUserHex = account.userProfile().pubkeyHex
val loggedInUser = account.userProfile()
return it.event !is ChannelCreateEvent && return it.event !is ChannelCreateEvent &&
it.event !is ChannelMetadataEvent && it.event !is ChannelMetadataEvent &&
@ -94,6 +96,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
it.event !is BadgeDefinitionEvent && it.event !is BadgeDefinitionEvent &&
it.event !is BadgeProfilesEvent && it.event !is BadgeProfilesEvent &&
it.event !is GiftWrapEvent && it.event !is GiftWrapEvent &&
(it.event is LnZapEvent || it.author !== loggedInUser) &&
(filterParams.isGlobal || filterParams.followLists?.users?.contains(it.author?.pubkeyHex) == true) && (filterParams.isGlobal || filterParams.followLists?.users?.contains(it.author?.pubkeyHex) == true) &&
it.event?.isTaggedUser(loggedInUserHex) ?: false && it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(filterParams.isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) && (filterParams.isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&

Wyświetl plik

@ -21,9 +21,11 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.AudioHeaderEvent import com.vitorpamplona.quartz.events.AudioHeaderEvent
import com.vitorpamplona.quartz.events.AudioTrackEvent import com.vitorpamplona.quartz.events.AudioTrackEvent
import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.ClassifiedsEvent
@ -43,7 +45,7 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
override fun feed(): List<Note> { override fun feed(): List<Note> {
val notes = val notes =
LocalCache.notes.filterIntoSet { _, it -> LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it) it !is AddressableNote && it.event !is AddressableEvent && acceptableEvent(it)
} }
val longFormNotes = val longFormNotes =

Wyświetl plik

@ -48,6 +48,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Alignment.Companion.BottomStart
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Alignment.Companion.TopEnd import androidx.compose.ui.Alignment.Companion.TopEnd
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -68,6 +70,7 @@ import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
@ -197,7 +200,7 @@ fun InnerChannelCardWithReactions(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
when (remember { baseNote.event }) { when (baseNote.event) {
is LiveActivitiesEvent -> { is LiveActivitiesEvent -> {
InnerCardRow(baseNote, accountViewModel, nav) InnerCardRow(baseNote, accountViewModel, nav)
} }
@ -255,7 +258,7 @@ private fun RenderNoteRow(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
when (remember { baseNote.event }) { when (baseNote.event) {
is LiveActivitiesEvent -> { is LiveActivitiesEvent -> {
RenderLiveActivityThumb(baseNote, accountViewModel, nav) RenderLiveActivityThumb(baseNote, accountViewModel, nav)
} }
@ -305,29 +308,29 @@ fun RenderClassifiedsThumb(
), ),
) )
RenderClassifiedsThumb(card, baseNote.author) InnerRenderClassifiedsThumb(card, baseNote)
} }
@Preview @Preview
@Composable @Composable
fun RenderClassifiedsThumbPreview() { fun RenderClassifiedsThumbPreview() {
Surface(Modifier.size(200.dp)) { Surface(Modifier.size(200.dp)) {
RenderClassifiedsThumb( InnerRenderClassifiedsThumb(
card = card =
ClassifiedsThumb( ClassifiedsThumb(
image = null, image = null,
title = "Like New", title = "Like New",
price = Price("800000", "SATS", null), price = Price("800000", "SATS", null),
), ),
author = null, note = Note("hex"),
) )
} }
} }
@Composable @Composable
fun RenderClassifiedsThumb( fun InnerRenderClassifiedsThumb(
card: ClassifiedsThumb, card: ClassifiedsThumb,
author: User?, note: Note,
) { ) {
Box( Box(
Modifier.fillMaxWidth().aspectRatio(1f), Modifier.fillMaxWidth().aspectRatio(1f),
@ -340,8 +343,7 @@ fun RenderClassifiedsThumb(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) )
} } ?: run { DisplayAuthorBanner(note) }
?: run { author?.let { DisplayAuthorBanner(it) } }
Row( Row(
Modifier.fillMaxWidth().background(Color.Black.copy(0.6f)).padding(Size5dp), Modifier.fillMaxWidth().background(Color.Black.copy(0.6f)).padding(Size5dp),
@ -449,8 +451,7 @@ fun RenderLiveActivityThumb(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier = Modifier.fillMaxSize().clip(QuoteBorder),
) )
} } ?: run { DisplayAuthorBanner(baseNote) }
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
Box(Modifier.padding(10.dp)) { Box(Modifier.padding(10.dp)) {
Crossfade(targetState = card.status, label = "RenderLiveActivityThumb") { Crossfade(targetState = card.status, label = "RenderLiveActivityThumb") {
@ -496,12 +497,11 @@ fun RenderLiveActivityThumb(
Spacer(modifier = DoubleVertSpacer) Spacer(modifier = DoubleVertSpacer)
ChannelHeader( ChannelHeader(
channelHex = remember { baseNote.idHex }, channelHex = baseNote.idHex,
showVideo = false, showVideo = false,
showBottomDiviser = false,
showFlag = false, showFlag = false,
sendToChannel = true, sendToChannel = true,
modifier = remember { Modifier.padding(start = 0.dp, end = 0.dp, top = 5.dp, bottom = 5.dp) }, modifier = Modifier,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
@ -559,8 +559,7 @@ fun RenderCommunitiesThumb(
modifier = Modifier.fillMaxSize().clip(QuoteBorder), modifier = Modifier.fillMaxSize().clip(QuoteBorder),
) )
} }
} } ?: run { DisplayAuthorBanner(baseNote) }
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
}, },
onTitleRow = { onTitleRow = {
Text( Text(
@ -780,16 +779,13 @@ fun RenderChannelThumb(
LeftPictureLayout( LeftPictureLayout(
onImage = { onImage = {
cover?.let { cover?.let {
Box(contentAlignment = BottomStart) { AsyncImage(
AsyncImage( model = it,
model = it, contentDescription = null,
contentDescription = null, contentScale = ContentScale.Crop,
contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize().clip(QuoteBorder),
modifier = Modifier.fillMaxSize().clip(QuoteBorder), )
) } ?: run { DisplayAuthorBanner(baseNote) }
}
}
?: run { baseNote.author?.let { DisplayAuthorBanner(it) } }
}, },
onTitleRow = { onTitleRow = {
Text( Text(
@ -829,7 +825,7 @@ fun RenderChannelThumb(
onBottomRow = { onBottomRow = {
if (participantUsers.isNotEmpty()) { if (participantUsers.isNotEmpty()) {
Spacer(modifier = StdVertSpacer) Spacer(modifier = StdVertSpacer)
Row { Gallery(participantUsers, accountViewModel) } Gallery(participantUsers, accountViewModel)
} }
}, },
) )
@ -846,27 +842,20 @@ fun Gallery(
if (users.size > 6) { if (users.size > 6) {
Text( Text(
text = remember(users) { " + " + (showCount(users.size - 6)) }, text = " + " + showCount(users.size - 6),
fontSize = 13.sp, fontSize = 13.sp,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.align(CenterVertically),
) )
} }
} }
} }
@Composable @Composable
fun DisplayAuthorBanner(author: User) { fun DisplayAuthorBanner(note: Note) {
val picture by val authorState by note.live().authorChanges.observeAsState(note.author)
author
.live()
.metadata
.map { it.user.info?.banner?.ifBlank { null } ?: it.user.info?.picture?.ifBlank { null } }
.observeAsState()
AsyncImage( authorState?.let { author ->
model = picture, BannerImage(author, Modifier.fillMaxSize().clip(QuoteBorder))
contentDescription = null, }
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
)
} }

Wyświetl plik

@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -110,6 +111,7 @@ fun ChatroomMessageCompose(
WatchBlockAndReport( WatchBlockAndReport(
note = baseNote, note = baseNote,
showHiddenWarning = innerQuote, showHiddenWarning = innerQuote,
modifier = Modifier.fillMaxWidth(),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) { canPreview -> ) { canPreview ->

Wyświetl plik

@ -239,7 +239,6 @@ fun AcceptableNote(
ChannelHeader( ChannelHeader(
channelNote = baseNote, channelNote = baseNote,
showVideo = !makeItShort, showVideo = !makeItShort,
showBottomDiviser = true,
sendToChannel = true, sendToChannel = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@ -273,7 +272,6 @@ fun AcceptableNote(
ChannelHeader( ChannelHeader(
channelNote = baseNote, channelNote = baseNote,
showVideo = !makeItShort, showVideo = !makeItShort,
showBottomDiviser = true,
sendToChannel = true, sendToChannel = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@ -692,7 +690,7 @@ fun RenderRepost(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
note.replyTo?.lastOrNull()?.let { note.replyTo?.lastOrNull { it.event !is CommunityDefinitionEvent }?.let {
NoteCompose( NoteCompose(
it, it,
modifier = Modifier, modifier = Modifier,
@ -748,23 +746,25 @@ private fun ReplyRow(
ChannelHeader( ChannelHeader(
channelHex = it, channelHex = it,
showVideo = false, showVideo = false,
showBottomDiviser = false,
sendToChannel = true, sendToChannel = true,
modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp), modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
Spacer(modifier = StdVertSpacer)
} }
if (showReply) { if (showReply) {
val replyingDirectlyTo = val replyingDirectlyTo =
remember(note) { remember(note) {
if (noteEvent is BaseTextNoteEvent) { if (noteEvent is BaseTextNoteEvent) {
val replyingTo = noteEvent.replyingTo() val replyingTo = noteEvent.replyingToAddressOrEvent()
if (replyingTo != null) { if (replyingTo != null) {
note.replyTo?.firstOrNull { val newNote = accountViewModel.getNoteIfExists(replyingTo)
// important to test both ids in case it's a replaceable event. if (newNote != null && newNote.channelHex() == null && newNote.event?.kind() != CommunityDefinitionEvent.KIND) {
it.idHex == replyingTo || it.event?.id() == replyingTo newNote
} else {
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
} }
} else { } else {
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND } note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }

Wyświetl plik

@ -120,7 +120,7 @@ val externalLinkForNote = { note: Note ->
} else if (note.event is AudioTrackEvent) { } else if (note.event is AudioTrackEvent) {
"https://zapstr.live/?track=${note.address()?.toNAddr()}" "https://zapstr.live/?track=${note.address()?.toNAddr()}"
} else { } else {
"https://habla.news/a/${note.address()?.toNAddr()}" "https://njump.me/${note.address()?.toNAddr()}"
} }
} else { } else {
if (note.event is FileHeaderEvent) { if (note.event is FileHeaderEvent) {

Wyświetl plik

@ -20,13 +20,13 @@
*/ */
package com.vitorpamplona.amethyst.ui.note package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -47,7 +47,7 @@ import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
@ -62,49 +62,44 @@ fun RelayCompose(
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column { Row(
Row( modifier = StdPadding,
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp), verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.Center,
) { ) {
Column( Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.padding(start = 10.dp).weight(1f),
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(
relay.url.trim().removePrefix("wss://"),
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
val lastTime by
remember(relay.lastEvent) {
derivedStateOf { timeAgo(relay.lastEvent, context = context) }
}
Text(
text = lastTime,
maxLines = 1,
)
}
Text( Text(
"${relay.counter} ${stringResource(R.string.posts_received)}", relay.url.trim().removePrefix("wss://"),
color = MaterialTheme.colorScheme.placeholderText, fontWeight = FontWeight.Bold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
val lastTime by
remember(relay.lastEvent) {
derivedStateOf { timeAgo(relay.lastEvent, context = context) }
}
Text(
text = lastTime,
maxLines = 1,
)
} }
Column(modifier = Modifier.padding(start = 10.dp)) { Text(
RelayOptions(accountViewModel, relay, onAddRelay, onRemoveRelay) "${relay.counter} ${stringResource(R.string.posts_received)}",
} color = MaterialTheme.colorScheme.placeholderText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} }
HorizontalDivider( Column(modifier = Modifier.padding(start = 10.dp)) {
modifier = Modifier.padding(top = 10.dp), RelayOptions(accountViewModel, relay, onAddRelay, onRemoveRelay)
thickness = DividerThickness, }
)
} }
} }

Wyświetl plik

@ -24,7 +24,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -32,7 +31,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding
@ -40,37 +38,26 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding
fun UserCompose( fun UserCompose(
baseUser: User, baseUser: User,
overallModifier: Modifier = StdPadding, overallModifier: Modifier = StdPadding,
showDiviser: Boolean = true,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
Column( Row(
modifier = modifier =
Modifier.clickable( overallModifier.clickable(
onClick = { nav("User/${baseUser.pubkeyHex}") }, onClick = { nav("User/${baseUser.pubkeyHex}") },
), ),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Row( UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
modifier = overallModifier,
verticalAlignment = Alignment.CenterVertically,
) {
UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) } Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
AboutDisplay(baseUser) AboutDisplay(baseUser)
}
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
UserActionOptions(baseUser, accountViewModel)
}
} }
if (showDiviser) { Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
HorizontalDivider( UserActionOptions(baseUser, accountViewModel)
thickness = DividerThickness,
)
} }
} }
} }

Wyświetl plik

@ -23,48 +23,67 @@ package com.vitorpamplona.amethyst.ui.note.elements
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.authorNotePictureForImageHeader import com.vitorpamplona.amethyst.ui.theme.authorNotePictureForImageHeader
import com.vitorpamplona.amethyst.ui.theme.imageHeaderBannerSize
@Composable @Composable
fun DefaultImageHeader( fun DefaultImageHeader(
note: Note, note: Note,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
Box { val authorState by note.live().authorChanges.observeAsState(note.author)
note.author?.info?.banner?.let {
AsyncImage(
model = it,
contentDescription =
stringResource(
R.string.preview_card_image_for,
it,
),
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(),
)
}
?: Image(
painter = painterResource(R.drawable.profile_banner),
contentDescription = stringResource(R.string.profile_banner),
contentScale = ContentScale.FillWidth,
modifier = imageHeaderBannerSize,
)
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) { authorState?.let { author ->
NoteAuthorPicture(baseNote = note, accountViewModel = accountViewModel, size = Size55dp) Box {
BannerImage(author)
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) {
BaseUserPicture(author, Size55dp, accountViewModel, Modifier)
}
} }
} }
} }
@Composable
fun BannerImage(
author: User,
imageModifier: Modifier = Modifier.fillMaxWidth().heightIn(max = 200.dp),
) {
val currentInfo by author.live().userMetadataInfo.observeAsState()
currentInfo?.banner?.let {
AsyncImage(
model = it,
contentDescription =
stringResource(
R.string.preview_card_image_for,
it,
),
contentScale = ContentScale.Crop,
modifier = imageModifier,
placeholder = painterResource(R.drawable.profile_banner),
)
} ?: run {
Image(
painter = painterResource(R.drawable.profile_banner),
contentDescription = stringResource(R.string.profile_banner),
contentScale = ContentScale.Crop,
modifier = imageModifier,
)
}
}

Wyświetl plik

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

Wyświetl plik

@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -68,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size10dp
@ -88,7 +86,6 @@ import java.util.Locale
@Composable @Composable
fun CommunityHeader( fun CommunityHeader(
baseNote: AddressableNote, baseNote: AddressableNote,
showBottomDiviser: Boolean,
sendToCommunity: Boolean, sendToCommunity: Boolean,
modifier: Modifier = StdPadding, modifier: Modifier = StdPadding,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@ -125,12 +122,6 @@ fun CommunityHeader(
} }
} }
} }
if (showBottomDiviser) {
HorizontalDivider(
thickness = DividerThickness,
)
}
} }
} }

Wyświetl plik

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

Wyświetl plik

@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -65,14 +64,14 @@ private fun LongFormHeader(
note: Note, note: Note,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
val image = remember(noteEvent) { noteEvent.image() } val image = noteEvent.image()
val title = remember(noteEvent) { noteEvent.title() } val title = noteEvent.title()
val summary = val summary =
remember(noteEvent) { remember(noteEvent) {
noteEvent.summary()?.ifBlank { null } ?: noteEvent.content.take(200).ifBlank { null } noteEvent.summary()?.ifBlank { null } ?: noteEvent.content.take(200).ifBlank { null }
} }
Row( Column(
modifier = modifier =
Modifier Modifier
.padding(top = Size5dp) .padding(top = Size5dp)
@ -83,51 +82,50 @@ private fun LongFormHeader(
QuoteBorder, QuoteBorder,
), ),
) { ) {
Column { val automaticallyShowUrlPreview =
val automaticallyShowUrlPreview = remember { accountViewModel.settings.showImages.value }
remember { accountViewModel.settings.showUrlPreview.value }
if (automaticallyShowUrlPreview) { if (automaticallyShowUrlPreview) {
image?.let { image?.let {
AsyncImage( AsyncImage(
model = it, model = it,
contentDescription = contentDescription =
stringResource( stringResource(
R.string.preview_card_image_for, R.string.preview_card_image_for,
it, it,
), ),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
)
}
?: DefaultImageHeader(note, accountViewModel)
}
title?.let {
Text(
text = it,
style = MaterialTheme.typography.bodyLarge,
modifier =
Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, top = 10.dp),
) )
} ?: run {
DefaultImageHeader(note, accountViewModel)
} }
}
summary?.let { title?.let {
Spacer(modifier = StdVertSpacer) Text(
Text( text = it,
text = it, style = MaterialTheme.typography.bodyLarge,
style = MaterialTheme.typography.bodySmall, modifier =
modifier = Modifier
Modifier .fillMaxWidth()
.fillMaxWidth() .padding(start = 10.dp, end = 10.dp, top = 10.dp),
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp), )
color = Color.Gray, }
maxLines = 3,
overflow = TextOverflow.Ellipsis, summary?.let {
) Spacer(modifier = StdVertSpacer)
} Text(
text = it,
style = MaterialTheme.typography.bodySmall,
modifier =
Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
color = Color.Gray,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
} }
} }
} }

Wyświetl plik

@ -23,11 +23,13 @@ package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -50,6 +52,7 @@ import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.getGradient import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -98,13 +101,16 @@ fun DisplayPeopleList(
Box { Box {
FlowRow(modifier = Modifier.padding(top = 5.dp)) { FlowRow(modifier = Modifier.padding(top = 5.dp)) {
toMembersShow.forEach { user -> toMembersShow.forEach { user ->
Row(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.fillMaxWidth()) {
UserCompose( UserCompose(
user, user,
overallModifier = Modifier,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -37,7 +37,10 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -120,6 +123,7 @@ fun LoggedInPage(
accountViewModel.serviceManager = activity.serviceManager accountViewModel.serviceManager = activity.serviceManager
if (accountViewModel.account.signer is NostrSignerExternal) { if (accountViewModel.account.signer is NostrSignerExternal) {
val lifeCycleOwner = LocalLifecycleOwner.current
val launcher = val launcher =
rememberLauncherForActivityResult( rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(), contract = ActivityResultContracts.StartActivityForResult(),
@ -137,7 +141,30 @@ fun LoggedInPage(
}, },
) )
DisposableEffect(accountViewModel, accountViewModel.account, launcher, activity) { DisposableEffect(accountViewModel, accountViewModel.account, launcher, activity, lifeCycleOwner) {
val observer =
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
accountViewModel.account.signer.launcher.registerLauncher(
launcher = {
try {
activity.prepareToLaunchSigner()
launcher.launch(it)
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("Signer", "Error opening Signer app", e)
accountViewModel.toast(
R.string.error_opening_external_signer,
R.string.error_opening_external_signer_description,
)
}
},
contentResolver = { Amethyst.instance.contentResolver },
)
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
accountViewModel.account.signer.launcher.registerLauncher( accountViewModel.account.signer.launcher.registerLauncher(
launcher = { launcher = {
try { try {
@ -154,7 +181,11 @@ fun LoggedInPage(
}, },
contentResolver = { Amethyst.instance.contentResolver }, contentResolver = { Amethyst.instance.contentResolver },
) )
onDispose { accountViewModel.account.signer.launcher.clearLauncher() } onDispose {
Log.d("onDispose", "Called onDispose")
accountViewModel.account.signer.launcher.clearLauncher()
lifeCycleOwner.lifecycle.removeObserver(observer)
}
} }
} }

Wyświetl plik

@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.pullrefresh.PullRefreshIndicator import androidx.compose.material3.pullrefresh.PullRefreshIndicator
import androidx.compose.material3.pullrefresh.pullRefresh import androidx.compose.material3.pullrefresh.pullRefresh
import androidx.compose.material3.pullrefresh.rememberPullRefreshState import androidx.compose.material3.pullrefresh.rememberPullRefreshState
@ -47,6 +48,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.RelayCompose import com.vitorpamplona.amethyst.ui.note.RelayCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -169,6 +171,9 @@ fun RelayFeedView(
onAddRelay = { wantsToAddRelay = item.url }, onAddRelay = { wantsToAddRelay = item.url },
onRemoveRelay = { wantsToAddRelay = item.url }, onRemoveRelay = { wantsToAddRelay = item.url },
) )
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }

Wyświetl plik

@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -37,17 +38,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun RefreshingFeedStringFeedView(
viewModel: StringFeedViewModel,
enablePullRefresh: Boolean = true,
inner: @Composable (String) -> Unit,
) {
RefresheableBox(viewModel, enablePullRefresh) { StringFeedView(viewModel, inner = inner) }
}
@Composable @Composable
fun StringFeedView( fun StringFeedView(
viewModel: StringFeedViewModel, viewModel: StringFeedViewModel,
@ -112,7 +105,13 @@ private fun FeedLoaded(
) { ) {
item { pre?.let { it() } } item { pre?.let { it() } }
itemsIndexed(state.feed.value, key = { _, item -> item }) { _, item -> inner(item) } itemsIndexed(state.feed.value, key = { _, item -> item }) { _, item ->
inner(item)
HorizontalDivider(
thickness = DividerThickness,
)
}
item { post?.let { it() } } item { post?.let { it() } }
} }

Wyświetl plik

@ -20,13 +20,10 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -35,6 +32,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -49,9 +47,6 @@ import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
import androidx.compose.material3.pullrefresh.pullRefresh
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@ -80,7 +75,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
@ -148,7 +142,6 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdTopPadding
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
@ -196,117 +189,100 @@ fun ThreadFeedView(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var refreshing by remember { mutableStateOf(false) } RefresheableBox(viewModel) {
val refresh = { RenderFeedState(
refreshing = true viewModel = viewModel,
viewModel.invalidateData() accountViewModel = accountViewModel,
refreshing = false listState = listState,
nav = nav,
routeForLastRead = null,
onLoaded = {
RenderThreadFeed(noteId, it, listState, accountViewModel, nav)
},
)
} }
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) }
Box(Modifier.pullRefresh(pullRefreshState)) { @Composable
Column { fun RenderThreadFeed(
Crossfade( noteId: String,
targetState = feedState, state: FeedState.Loaded,
animationSpec = tween(durationMillis = 100), listState: LazyListState,
label = "ThreadViewMainState", accountViewModel: AccountViewModel,
) { state -> nav: (String) -> Unit,
when (state) { ) {
is FeedState.Empty -> { LaunchedEffect(noteId) {
FeedEmpty { refreshing = true } // waits to load the thread to scroll to item.
} delay(100)
is FeedState.FeedError -> { val noteForPosition = state.feed.value.filter { it.idHex == noteId }.firstOrNull()
FeedError(state.errorMessage) { refreshing = true } var position = state.feed.value.indexOf(noteForPosition)
}
is FeedState.Loaded -> {
refreshing = false
LaunchedEffect(noteId) {
launch(Dispatchers.IO) {
// waits to load the thread to scroll to item.
delay(100)
val noteForPosition = state.feed.value.filter { it.idHex == noteId }.firstOrNull()
var position = state.feed.value.indexOf(noteForPosition)
if (position >= 0) { if (position >= 0) {
if (position >= 1 && position < state.feed.value.size - 1) { if (position >= 1 && position < state.feed.value.size - 1) {
position-- // show the replying note position-- // show the replying note
}
withContext(Dispatchers.Main) { listState.scrollToItem(position) }
}
}
}
LazyColumn(
contentPadding = FeedPadding,
state = listState,
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
if (index == 0) {
ProvideTextStyle(TextStyle(fontSize = 18.sp, lineHeight = 1.20.em)) {
NoteMaster(
item,
modifier =
Modifier.drawReplyLevel(
item.replyLevel(),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
accountViewModel = accountViewModel,
nav = nav,
)
}
} else {
val selectedNoteColor = MaterialTheme.colorScheme.selectedNote
val background =
remember {
if (item.idHex == noteId) mutableStateOf(selectedNoteColor) else null
}
NoteCompose(
item,
modifier =
Modifier.drawReplyLevel(
item.replyLevel(),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
parentBackgroundColor = background,
isBoostedNote = false,
unPackReply = false,
quotesLeft = 3,
accountViewModel = accountViewModel,
nav = nav,
)
}
HorizontalDivider(
modifier = StdTopPadding,
thickness = DividerThickness,
)
}
}
}
FeedState.Loading -> {
LoadingFeed()
}
}
} }
}
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter)) listState.scrollToItem(position)
}
}
LazyColumn(
contentPadding = FeedPadding,
state = listState,
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
if (index == 0) {
ProvideTextStyle(TextStyle(fontSize = 18.sp, lineHeight = 1.20.em)) {
NoteMaster(
item,
modifier =
Modifier.drawReplyLevel(
item.replyLevel(),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
accountViewModel = accountViewModel,
nav = nav,
)
}
} else {
val selectedNoteColor = MaterialTheme.colorScheme.selectedNote
val background =
remember {
if (item.idHex == noteId) mutableStateOf(selectedNoteColor) else null
}
NoteCompose(
item,
modifier =
Modifier.drawReplyLevel(
item.replyLevel(),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
parentBackgroundColor = background,
isBoostedNote = false,
unPackReply = false,
quotesLeft = 3,
accountViewModel = accountViewModel,
nav = nav,
)
}
HorizontalDivider(
thickness = DividerThickness,
)
}
} }
} }
@ -509,7 +485,6 @@ fun NoteMaster(
ChannelHeader( ChannelHeader(
channelHex = note.channelHex()!!, channelHex = note.channelHex()!!,
showVideo = true, showVideo = true,
showBottomDiviser = false,
sendToChannel = true, sendToChannel = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@ -819,43 +794,41 @@ private fun RenderClassifiedsReaderForThread(
@Composable @Composable
private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) { private fun RenderLongFormHeaderForThread(noteEvent: LongTextNoteEvent) {
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) { Column(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
Column { noteEvent.image()?.let {
noteEvent.image()?.let { AsyncImage(
AsyncImage( model = it,
model = it, contentDescription =
contentDescription = stringResource(
stringResource( R.string.preview_card_image_for,
R.string.preview_card_image_for, it,
it, ),
), contentScale = ContentScale.FillWidth,
contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(), )
) }
}
noteEvent.title()?.let { noteEvent.title()?.let {
Spacer(modifier = DoubleVertSpacer)
Text(
text = it,
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth(),
)
}
noteEvent
.summary()
?.ifBlank { null }
?.let {
Spacer(modifier = DoubleVertSpacer) Spacer(modifier = DoubleVertSpacer)
Text( Text(
text = it, text = it,
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = Color.Gray,
) )
} }
noteEvent
.summary()
?.ifBlank { null }
?.let {
Spacer(modifier = DoubleVertSpacer)
Text(
text = it,
modifier = Modifier.fillMaxWidth(),
color = Color.Gray,
)
}
}
} }
} }

Wyświetl plik

@ -25,11 +25,13 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable @Composable
@ -82,6 +84,9 @@ private fun FeedLoaded(
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, nav = nav) UserCompose(item, accountViewModel = accountViewModel, nav = nav)
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }

Wyświetl plik

@ -53,7 +53,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
@ -139,7 +138,6 @@ import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
@ -583,7 +581,6 @@ fun MyTextField(
fun ChannelHeader( fun ChannelHeader(
channelNote: Note, channelNote: Note,
showVideo: Boolean, showVideo: Boolean,
showBottomDiviser: Boolean,
sendToChannel: Boolean, sendToChannel: Boolean,
modifier: Modifier = StdPadding, modifier: Modifier = StdPadding,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@ -594,8 +591,8 @@ fun ChannelHeader(
ChannelHeader( ChannelHeader(
channelHex = it, channelHex = it,
showVideo = showVideo, showVideo = showVideo,
showBottomDiviser = showBottomDiviser,
sendToChannel = sendToChannel, sendToChannel = sendToChannel,
modifier = modifier,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
@ -606,7 +603,6 @@ fun ChannelHeader(
fun ChannelHeader( fun ChannelHeader(
channelHex: String, channelHex: String,
showVideo: Boolean, showVideo: Boolean,
showBottomDiviser: Boolean,
showFlag: Boolean = true, showFlag: Boolean = true,
sendToChannel: Boolean = false, sendToChannel: Boolean = false,
modifier: Modifier = StdPadding, modifier: Modifier = StdPadding,
@ -617,7 +613,6 @@ fun ChannelHeader(
ChannelHeader( ChannelHeader(
it, it,
showVideo, showVideo,
showBottomDiviser,
showFlag, showFlag,
sendToChannel, sendToChannel,
modifier, modifier,
@ -631,7 +626,6 @@ fun ChannelHeader(
fun ChannelHeader( fun ChannelHeader(
baseChannel: Channel, baseChannel: Channel,
showVideo: Boolean, showVideo: Boolean,
showBottomDiviser: Boolean,
showFlag: Boolean = true, showFlag: Boolean = true,
sendToChannel: Boolean = false, sendToChannel: Boolean = false,
modifier: Modifier = StdPadding, modifier: Modifier = StdPadding,
@ -667,12 +661,6 @@ fun ChannelHeader(
LongChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav) LongChannelHeader(baseChannel = baseChannel, accountViewModel = accountViewModel, nav = nav)
} }
} }
if (showBottomDiviser) {
HorizontalDivider(
thickness = DividerThickness,
)
}
} }
} }

Wyświetl plik

@ -520,6 +520,9 @@ fun ShowUserSuggestionList(
key = { _, item -> item.pubkeyHex }, key = { _, item -> item.pubkeyHex },
) { _, item -> ) { _, item ->
UserLine(item, accountViewModel) { channelScreenModel.autocompleteWithUser(item) } UserLine(item, accountViewModel) { channelScreenModel.autocompleteWithUser(item) }
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }
@ -836,6 +839,9 @@ fun LongRoomHeader(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) )
HorizontalDivider(
thickness = DividerThickness,
)
} }
} }
} }

Wyświetl plik

@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@ -47,7 +46,6 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding
@Composable @Composable
@ -142,27 +140,18 @@ fun HashtagHeader(
account: AccountViewModel, account: AccountViewModel,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
) { ) {
Column( Row(
Modifier.fillMaxWidth().clickable { onClick() }, modifier = Modifier.fillMaxWidth().clickable { onClick() }.then(modifier),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) { ) {
Column(modifier = modifier) { Text(
Row( "#$tag",
verticalAlignment = Alignment.CenterVertically, fontWeight = FontWeight.Bold,
horizontalArrangement = Arrangement.Center, modifier = Modifier.weight(1f),
) {
Text(
"#$tag",
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f),
)
HashtagActionOptions(tag, account)
}
}
HorizontalDivider(
thickness = DividerThickness,
) )
HashtagActionOptions(tag, account)
} }
} }

Wyświetl plik

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

Wyświetl plik

@ -120,7 +120,9 @@ fun NotificationScreen(
SummaryBar( SummaryBar(
model = userReactionsStatsModel, model = userReactionsStatsModel,
) )
HorizontalDivider(
thickness = DividerThickness,
)
RefreshableCardView( RefreshableCardView(
viewModel = notifFeedViewModel, viewModel = notifFeedViewModel,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
@ -229,10 +231,6 @@ fun SummaryBar(model: UserReactionsViewModel) {
} }
} }
} }
HorizontalDivider(
thickness = DividerThickness,
)
} }
@Composable @Composable

Wyświetl plik

@ -1547,18 +1547,17 @@ fun TabFollowedTags(
account: AccountViewModel, account: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight().padding(vertical = 0.dp)) {
Column( baseUser.latestContactList?.let {
modifier = Modifier.padding(vertical = 0.dp), it.unverifiedFollowTagSet().forEach { hashtag ->
) { HashtagHeader(
baseUser.latestContactList?.let { tag = hashtag,
it.unverifiedFollowTagSet().forEach { hashtag -> account = account,
HashtagHeader( onClick = { nav("Hashtag/$hashtag") },
tag = hashtag, )
account = account, HorizontalDivider(
onClick = { nav("Hashtag/$hashtag") }, thickness = DividerThickness,
) )
}
} }
} }
} }

Wyświetl plik

@ -374,6 +374,11 @@ private fun DisplaySearchResults(
key = { _, item -> "#$item" }, key = { _, item -> "#$item" },
) { _, item -> ) { _, item ->
HashtagLine(item) { nav("Hashtag/$item") } HashtagLine(item) { nav("Hashtag/$item") }
HorizontalDivider(
modifier = Modifier.padding(top = 10.dp),
thickness = DividerThickness,
)
} }
itemsIndexed( itemsIndexed(
@ -381,6 +386,10 @@ private fun DisplaySearchResults(
key = { _, item -> "u" + item.pubkeyHex }, key = { _, item -> "u" + item.pubkeyHex },
) { _, item -> ) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, nav = nav) UserCompose(item, accountViewModel = accountViewModel, nav = nav)
HorizontalDivider(
thickness = DividerThickness,
)
} }
itemsIndexed( itemsIndexed(
@ -432,33 +441,24 @@ fun HashtagLine(
tag: String, tag: String,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Column( Row(
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), modifier =
Modifier.fillMaxWidth().clickable(onClick = onClick).padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
),
) { ) {
Row( Row(
modifier = verticalAlignment = Alignment.CenterVertically,
Modifier.padding( horizontalArrangement = Arrangement.Center,
start = 12.dp, modifier = Modifier.fillMaxWidth(),
end = 12.dp,
top = 10.dp,
),
) { ) {
Row( Text(
verticalAlignment = Alignment.CenterVertically, "Search hashtag: #$tag",
horizontalArrangement = Arrangement.Center, fontWeight = FontWeight.Bold,
modifier = Modifier.fillMaxWidth(), )
) {
Text(
"Search hashtag: #$tag",
fontWeight = FontWeight.Bold,
)
}
} }
HorizontalDivider(
modifier = Modifier.padding(top = 10.dp),
thickness = DividerThickness,
)
} }
} }
@ -468,31 +468,23 @@ fun UserLine(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Column( Row(
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), modifier =
Modifier.fillMaxWidth().clickable(onClick = onClick).padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = 10.dp,
),
) { ) {
Row( ClickableUserPicture(baseUser, 55.dp, accountViewModel, Modifier, null)
modifier =
Modifier.padding( Column(
start = 12.dp, modifier = Modifier.padding(start = 10.dp).weight(1f),
end = 12.dp,
top = 10.dp,
),
) { ) {
ClickableUserPicture(baseUser, 55.dp, accountViewModel, Modifier, null) Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
Column( AboutDisplay(baseUser)
modifier = Modifier.padding(start = 10.dp).weight(1f),
) {
Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser) }
AboutDisplay(baseUser)
}
} }
HorizontalDivider(
modifier = Modifier.padding(top = 10.dp),
thickness = DividerThickness,
)
} }
} }

Wyświetl plik

@ -20,11 +20,8 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
@ -79,7 +76,5 @@ fun ThreadScreen(
onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) } onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
} }
Column(Modifier.fillMaxHeight()) { ThreadFeedView(noteId, feedViewModel, accountViewModel, nav)
Column { ThreadFeedView(noteId, feedViewModel, accountViewModel, nav) }
}
} }

Wyświetl plik

@ -32,6 +32,17 @@ class LargeCache<K, V> {
fun size() = cache.size fun size() = cache.size
fun isEmpty() = cache.isEmpty()
fun containsKey(key: K) = cache.containsKey(key)
fun put(
key: K,
value: V,
) {
cache.put(key, value)
}
fun getOrCreate( fun getOrCreate(
key: K, key: K,
builder: (key: K) -> V, builder: (key: K) -> V,

Wyświetl plik

@ -72,13 +72,29 @@ open class BaseTextNoteEvent(
} }
fun replyingTo(): HexKey? { fun replyingTo(): HexKey? {
val oldStylePositional = tags.lastOrNull { it.size > 1 && it[0] == "e" }?.get(1) val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && it[0] == "e" }?.get(1)
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1) val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1)
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1) val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
return newStyleReply ?: newStyleRoot ?: oldStylePositional return newStyleReply ?: newStyleRoot ?: oldStylePositional
} }
fun replyingToAddress(): ATag? {
val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && it[0] == "a" }?.let { ATag.parseAtag(it[1], it[2]) }
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "a" && it[3] == "reply" }?.let { ATag.parseAtag(it[1], it[2]) }
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "a" && it[3] == "root" }?.let { ATag.parseAtag(it[1], it[2]) }
return newStyleReply ?: newStyleRoot ?: oldStylePositional
}
fun replyingToAddressOrEvent(): String? {
val oldStylePositional = tags.lastOrNull { it.size > 1 && it.size <= 3 && (it[0] == "e" || it[0] == "a") }?.get(1)
val newStyleReply = tags.lastOrNull { it.size > 3 && (it[0] == "e" || it[0] == "a") && it[3] == "reply" }?.get(1)
val newStyleRoot = tags.lastOrNull { it.size > 3 && (it[0] == "e" || it[0] == "a") && it[3] == "root" }?.get(1)
return newStyleReply ?: newStyleRoot ?: oldStylePositional
}
@Transient private var citedUsersCache: Set<String>? = null @Transient private var citedUsersCache: Set<String>? = null
@Transient private var citedNotesCache: Set<String>? = null @Transient private var citedNotesCache: Set<String>? = null