diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt index 8a5e53afb..18865dbb4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt @@ -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() + val notes = LargeCache() 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 { 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() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 4ff2fd4f7..1d4a08760 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -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", ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 27f5fef37..683f9a54d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ParticipantListBuilder.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ParticipantListBuilder.kt index db99be861..bd1ead6a3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ParticipantListBuilder.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ParticipantListBuilder.kt @@ -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) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 48e094605..dd0df3768 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index d6bf6f367..4cda055f4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -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, - ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index 464d7ec82..c8c44970e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -294,7 +294,6 @@ private fun DisplayOwnerInformation( UserCompose( baseUser = it, accountViewModel = accountViewModel, - showDiviser = false, nav = nav, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt index a8a6ec236..87e113353 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChannelFeedFilter.kt @@ -31,10 +31,11 @@ class ChannelFeedFilter(val channel: Channel, val account: Account) : AdditiveFe // returns the last Note of each user. override fun feed(): List { - 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): Set { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt index b78716deb..2a1e6b20d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt @@ -56,15 +56,12 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter - 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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverLiveFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverLiveFeedFilter.kt index 6fc737bb8..30aa19176 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverLiveFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverLiveFeedFilter.kt @@ -48,9 +48,8 @@ open class DiscoverLiveFeedFilter( } override fun feed(): List { - 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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 6770a48ed..c33dcc3a7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -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() 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() 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)) && diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt index 63923c457..629cfa1ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt @@ -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 { val notes = LocalCache.notes.filterIntoSet { _, it -> - acceptableEvent(it) + it !is AddressableNote && it.event !is AddressableEvent && acceptableEvent(it) } val longFormNotes = diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index 72cf14279..cc71bdf44 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -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)) + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index b3a7249e5..9a195f920 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -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 -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index f866a0b55..f34c99465 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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 } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 394bb33e3..f30e2c4fd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt index a39aa5c26..572700fac 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt @@ -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) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 6026fb233..89ef9551f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -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) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt index 055f77bd0..e021842b5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt @@ -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, + ) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt index 6d586ca52..905dd8283 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Classifieds.kt @@ -88,8 +88,9 @@ fun RenderClassifieds( contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(), ) + } ?: run { + DefaultImageHeader(note, accountViewModel) } - ?: DefaultImageHeader(note, accountViewModel) } Row( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 5d4dc7884..b38464f8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -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, - ) - } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt index 2e767e945..6f4d32154 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt @@ -127,8 +127,9 @@ private fun WikiNoteHeader( contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(), ) + } ?: run { + DefaultImageHeader(note, accountViewModel) } - ?: DefaultImageHeader(note, accountViewModel) } title?.let { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt index f4584efbe..df493c64c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LongForm.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt index a5d6c2ae0..59d146f47 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PeopleList.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt index dfa972402..335ca8f7d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RenderPostApproval.kt @@ -60,7 +60,6 @@ fun RenderPostApproval( baseNote?.let { CommunityHeader( baseNote = it, - showBottomDiviser = false, sendToCommunity = true, accountViewModel = accountViewModel, nav = nav, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt index 6bb41c231..8f094ed55 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Video.kt @@ -135,8 +135,9 @@ fun VideoDisplay( contentScale = ContentScale.FillWidth, modifier = MaterialTheme.colorScheme.imageModifier, ) + } ?: run { + DefaultImageHeader(note, accountViewModel) } - ?: DefaultImageHeader(note, accountViewModel) } } else { ZoomableContentView( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index 973d531ef..880e1ba63 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -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) + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt index 5203b1b8b..2b80040b3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt @@ -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, + ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt index e1f3a41d6..c3561e544 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt @@ -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() } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 780603458..3e427b4a0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -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, - ) - } - } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index bc52f5c4a..bc1ee84fe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index ae1be0d8f..c48b01841 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -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, - ) - } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 516594197..b9b1c6d83 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt index 2132d88ae..bce5dd76b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt @@ -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) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index a2ff9649a..37ee700b9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -312,10 +312,6 @@ fun MutedWordHeader( MutedWordActionOptions(tag, account) } } - - HorizontalDivider( - thickness = DividerThickness, - ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 2ba1c7532..d607f0ba5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index ef2cda5e0..b6995fe4f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 026a81072..426131ee0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -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, - ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt index 78219e9b0..9ac6ed716 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt @@ -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) } diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/LargeCache.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/LargeCache.kt index a82f35aba..444cd76e0 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/LargeCache.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/data/LargeCache.kt @@ -32,6 +32,17 @@ class LargeCache { 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, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/BaseTextNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/BaseTextNoteEvent.kt index aa65eb642..33d1fa0f9 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/BaseTextNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/BaseTextNoteEvent.kt @@ -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? = null @Transient private var citedNotesCache: Set? = null