From 80d497154820b43e0b127060073074b69892b04b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 6 May 2025 13:23:43 -0400 Subject: [PATCH] - Adds FollowSets to the discovery tabs. - Refactors discovery screen, creating a package for each feature. - Fixes Android studio previews --- .../ThreadDualAxisChartAssemblerTest.kt | 2 +- .../com/vitorpamplona/amethyst/Amethyst.kt | 3 +- ...laySubscriptionsCoordinatorSubscription.kt | 4 +- ...hannelFinderFilterAssemblerSubscription.kt | 7 +- .../reqCommand/channel/ChannelObservers.kt | 30 +- .../EventFinderFilterAssemblerSubscription.kt | 7 +- .../reqCommand/event/EventObservers.kt | 101 +- .../UserFinderFilterAssemblerSubscription.kt | 7 +- .../reqCommand/user/UserObservers.kt | 169 ++- .../TextSearchDataSourceSubscription.kt | 7 +- .../UserSearchDataSourceSubscription.kt | 7 +- .../amethyst/ui/components/ClickableRoute.kt | 11 +- .../amethyst/ui/components/RichTextViewer.kt | 2 +- .../markdown/MarkdownMediaRenderer.kt | 2 +- .../ui/feeds/RememberForeverStates.kt | 1 + .../ui/navigation/AccountSwitchBottomSheet.kt | 20 +- .../amethyst/ui/navigation/AppNavigation.kt | 2 +- .../amethyst/ui/navigation/AppTopBar.kt | 6 +- .../amethyst/ui/navigation/DrawerContent.kt | 9 +- .../ui/navigation/FeedFilterSpinner.kt | 13 +- .../amethyst/ui/note/BadgeCompose.kt | 4 +- .../amethyst/ui/note/BlankNote.kt | 2 +- .../amethyst/ui/note/ChannelCardCompose.kt | 1064 ----------------- .../amethyst/ui/note/DisplayAuthorBanner.kt | 46 + .../vitorpamplona/amethyst/ui/note/Gallery.kt | 84 ++ .../vitorpamplona/amethyst/ui/note/Loaders.kt | 4 +- .../amethyst/ui/note/MultiSetCompose.kt | 5 +- .../ui/note/NIP05VerificationDisplay.kt | 17 +- .../amethyst/ui/note/NoteCompose.kt | 34 +- .../amethyst/ui/note/PollNote.kt | 5 +- .../amethyst/ui/note/ReactionsRow.kt | 32 +- .../amethyst/ui/note/ReplyInformation.kt | 7 +- .../ui/note/UpdateReactionTypeDialog.kt | 2 +- .../amethyst/ui/note/UserCompose.kt | 2 +- .../amethyst/ui/note/UserProfilePicture.kt | 9 +- .../amethyst/ui/note/UsernameDisplay.kt | 9 +- .../amethyst/ui/note/WatchNoteEvent.kt | 2 +- .../amethyst/ui/note/ZapNoteCompose.kt | 22 +- .../amethyst/ui/note/ZapUserSetCompose.kt | 2 +- .../WatchAndLoadMyEmojiList.kt | 4 +- .../userSuggestions/ShowUserSuggestionList.kt | 4 +- .../note/elements/AddInboxRelayForDMCard.kt | 2 +- .../ui/note/elements/DefaultImageHeader.kt | 7 +- .../ui/note/elements/DisplayReward.kt | 2 +- .../amethyst/ui/note/elements/DropDownMenu.kt | 4 +- .../amethyst/ui/note/elements/ForkInfo.kt | 10 +- .../ui/note/elements/ZapTheDevsCard.kt | 2 +- .../amethyst/ui/note/types/Badge.kt | 9 +- .../amethyst/ui/note/types/CommunityHeader.kt | 6 +- .../amethyst/ui/note/types/Emoji.kt | 6 +- .../amethyst/ui/note/types/FileStorage.kt | 2 +- .../amethyst/ui/note/types/Git.kt | 2 +- .../amethyst/ui/note/types/Highlight.kt | 6 +- .../ui/note/types/InteractiveStory.kt | 8 +- .../amethyst/ui/note/types/LiveActivity.kt | 4 +- .../amethyst/ui/note/types/RelayList.kt | 2 +- .../ui/note/types/TextModification.kt | 2 +- .../amethyst/ui/note/types/TorrentComment.kt | 2 +- .../loggedIn/AccountFeedContentStates.kt | 12 +- .../ui/screen/loggedIn/AccountViewModel.kt | 58 +- .../ui/screen/loggedIn/LoggedInPage.kt | 3 +- .../ui/screen/loggedIn/NewPostScreen.kt | 12 +- .../loggedIn/chats/feed/DrawAuthorInfo.kt | 2 +- .../privateDM/header/RoomNameOnlyDisplay.kt | 6 +- .../header/ShortEphemeralChatChannelHeader.kt | 4 +- .../header/LongPublicChatChannelHeader.kt | 6 +- .../header/ShortPublicChatChannelHeader.kt | 4 +- .../LongLiveActivityChannelHeader.kt | 4 +- .../ShortLiveActivityChannelHeader.kt | 2 +- .../nip53LiveActivities/ShowVideoStreaming.kt | 2 +- .../chats/rooms/ChatroomHeaderCompose.kt | 10 +- .../loggedIn/discover/ChannelCardCompose.kt | 219 ++++ .../loggedIn/discover/DiscoverScreen.kt | 26 +- .../loggedIn/discover/DiscoveryTopBar.kt | 1 + .../datasource/DiscoveryFilterAssembler.kt | 21 + .../DiscoverChatFeedFilter.kt | 8 +- .../discover/nip28Chats/RenderChannelThumb.kt | 198 +++ .../DiscoverFollowSetsFeedFilter.kt | 76 ++ .../discover/nip51FollowSets/FollowSetCard.kt | 204 ++++ .../DiscoverLiveFeedFilter.kt | 8 +- .../nip53LiveActivities/LiveActivityCard.kt | 255 ++++ .../nip72Communities/CommunityCard.kt | 225 ++++ .../DiscoverCommunityFeedFilter.kt | 14 +- .../loggedIn/discover/nip90DVMs/DVMCard.kt | 219 ++++ .../DiscoverNIP89FeedFilter.kt | 8 +- .../nip99Classifieds/ClassifiedsThumb.kt | 164 +++ .../DiscoverMarketplaceFeedFilter.kt | 8 +- .../NewProductScreen.kt | 2 +- .../NewProductViewModel.kt | 2 +- .../SellProduct.kt | 2 +- .../dvms/DvmContentDiscoveryScreen.kt | 21 +- .../ui/screen/loggedIn/dvms/DvmTopBar.kt | 2 +- .../screen/loggedIn/geohash/GeoHashScreen.kt | 2 +- .../screen/loggedIn/hashtag/HashtagScreen.kt | 2 +- .../ui/screen/loggedIn/home/HomeTopBar.kt | 1 + .../home/live/RenderEphemeralBubble.kt | 10 +- .../notifications/NotificationTopBar.kt | 1 + .../screen/loggedIn/profile/ProfileScreen.kt | 16 +- .../profile/bookmarks/BookmarkTabHeader.kt | 8 +- .../profile/followers/FollowersTabHeader.kt | 8 +- .../profile/followers/TabFollowers.kt | 5 +- .../profile/follows/FollowTabHeader.kt | 8 +- .../loggedIn/profile/follows/TabFollows.kt | 5 +- .../profile/gallery/GalleryCardCompose.kt | 2 +- .../loggedIn/profile/gallery/GalleryThumb.kt | 13 +- .../profile/hashtags/FollowedTagsTabHeader.kt | 8 +- .../header/DisplayFollowUnfollowButton.kt | 4 +- .../profile/header/DrawAdditionalInfo.kt | 2 +- .../loggedIn/profile/header/DrawBanner.kt | 2 +- .../header/apps/DisplayAppRecommendations.kt | 5 +- .../loggedIn/profile/header/apps/WatchApp.kt | 4 +- .../profile/header/badges/DisplayBadges.kt | 56 +- .../profile/relays/RelaysTabHeader.kt | 8 +- .../profile/reports/ReportsTabHeader.kt | 8 +- .../loggedIn/profile/reports/TabReports.kt | 2 +- .../reports/WatchReportsAndUpdateFeed.kt | 4 +- .../loggedIn/profile/zaps/TabReceivedZaps.kt | 2 +- .../profile/zaps/WatchZapsAndUpdateFeed.kt | 4 +- .../loggedIn/profile/zaps/ZapTabHeader.kt | 8 +- .../loggedIn/redirect/LoadRedirectScreen.kt | 2 +- .../search/AddInboxRelayForSearchCard.kt | 2 +- .../ui/screen/loggedIn/search/SearchScreen.kt | 5 +- .../loggedIn/threadview/ThreadFeedView.kt | 6 +- .../loggedIn/threadview/ThreadScreen.kt | 2 +- .../ui/screen/loggedIn/video/StoriesTopBar.kt | 1 + .../ui/screen/loggedIn/video/VideoScreen.kt | 2 +- amethyst/src/main/res/values/strings.xml | 7 +- 127 files changed, 2376 insertions(+), 1503 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/DisplayAuthorBanner.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Gallery.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{dal => nip28Chats}/DiscoverChatFeedFilter.kt (94%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderChannelThumb.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/FollowSetCard.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{dal => nip53LiveActivities}/DiscoverLiveFeedFilter.kt (92%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{dal => nip72Communities}/DiscoverCommunityFeedFilter.kt (90%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{dal => nip90DVMs}/DiscoverNIP89FeedFilter.kt (91%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{dal => nip99Classifieds}/DiscoverMarketplaceFeedFilter.kt (90%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{marketplace => nip99Classifieds}/NewProductScreen.kt (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{marketplace => nip99Classifieds}/NewProductViewModel.kt (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/{marketplace => nip99Classifieds}/SellProduct.kt (99%) diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt index ccbf85ebd..9a30721d9 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ThreadDualAxisChartAssemblerTest.kt @@ -140,7 +140,7 @@ class ThreadDualAxisChartAssemblerTest { val account = Account(AccountSettings(KeyPair()), scope = CoroutineScope(Dispatchers.IO + SupervisorJob())) withContext(Dispatchers.Main) { - val user = account.userProfile().live() + val user = account.userProfile().flow() } val filter = ThreadFeedFilter(account, naddr.toTag()) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 23fa7d789..47da7cfc6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.service.ots.OtsBlockHeightCache import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory import com.vitorpamplona.amethyst.service.relayClient.CacheClientConnector +import com.vitorpamplona.amethyst.service.relayClient.RelayLogger import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.AuthCoordinator import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model.NotifyCoordinator import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator @@ -109,7 +110,7 @@ class Amethyst : Application() { // Authenticates with relays. val authCoordinator = AuthCoordinator(client) - // val logger = if (isDebug) RelayLogger(client) else null + val logger = if (isDebug) RelayLogger(client) else null // Organizes cache clearing val trimmingService = MemoryTrimmingService(cache) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinatorSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinatorSubscription.kt index d95216430..81ef233bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinatorSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinatorSubscription.kt @@ -22,10 +22,10 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun RelaySubscriptionsCoordinatorSubscription() = RelaySubscriptionsCoordinatorSubscription(Amethyst.instance.sources) +fun RelaySubscriptionsCoordinatorSubscription(accountViewModel: AccountViewModel) = RelaySubscriptionsCoordinatorSubscription(accountViewModel.dataSources()) @Composable fun RelaySubscriptionsCoordinatorSubscription(dataSource: RelaySubscriptionsCoordinator) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblerSubscription.kt index 7f05d153b..d756710e0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelFinderFilterAssemblerSubscription.kt @@ -22,12 +22,15 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun ChannelFinderFilterAssemblerSubscription(channel: Channel) = ChannelFinderFilterAssemblerSubscription(channel, Amethyst.instance.sources.channelFinder) +fun ChannelFinderFilterAssemblerSubscription( + channel: Channel, + accountViewModel: AccountViewModel, +) = ChannelFinderFilterAssemblerSubscription(channel, accountViewModel.dataSources().channelFinder) @Composable fun ChannelFinderFilterAssemblerSubscription( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelObservers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelObservers.kt index 13a605be0..fcdcc2848 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelObservers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/channel/ChannelObservers.kt @@ -27,8 +27,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.ChannelState import com.vitorpamplona.amethyst.model.LiveActivitiesChannel -import com.vitorpamplona.amethyst.model.LocalCache.notes import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -41,8 +41,11 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onStart @Composable -fun observeChannel(baseChannel: Channel): State { - ChannelFinderFilterAssemblerSubscription(baseChannel) +fun observeChannel( + baseChannel: Channel, + accountViewModel: AccountViewModel, +): State { + ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel) return baseChannel .flow() @@ -52,8 +55,11 @@ fun observeChannel(baseChannel: Channel): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeChannelNoteAuthors(baseChannel: Channel): State> { - ChannelFinderFilterAssemblerSubscription(baseChannel) +fun observeChannelNoteAuthors( + baseChannel: Channel, + accountViewModel: AccountViewModel, +): State> { + ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -80,9 +86,12 @@ fun observeChannelNoteAuthors(baseChannel: Channel): State> @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeChannelPicture(baseChannel: Channel): State { +fun observeChannelPicture( + baseChannel: Channel, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - ChannelFinderFilterAssemblerSubscription(baseChannel) + ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -99,9 +108,12 @@ fun observeChannelPicture(baseChannel: Channel): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeChannelInfo(baseChannel: LiveActivitiesChannel): State { +fun observeChannelInfo( + baseChannel: LiveActivitiesChannel, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - ChannelFinderFilterAssemblerSubscription(baseChannel) + ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt index a515f8c27..a4fc2fae7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventFinderFilterAssemblerSubscription.kt @@ -22,12 +22,15 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun EventFinderFilterAssemblerSubscription(note: Note) = EventFinderFilterAssemblerSubscription(note, Amethyst.instance.sources.eventFinder) +fun EventFinderFilterAssemblerSubscription( + note: Note, + accountViewModel: AccountViewModel, +) = EventFinderFilterAssemblerSubscription(note, accountViewModel.dataSources().eventFinder) @Composable fun EventFinderFilterAssemblerSubscription( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt index 6f382acf8..99891a41e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Event import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,9 +39,12 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.sample @Composable -fun observeNote(note: Note): State { +fun observeNote( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -52,9 +56,12 @@ fun observeNote(note: Note): State { @Suppress("UNCHECKED_CAST") @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeNoteEvent(note: Note): State { +fun observeNoteEvent( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -72,10 +79,11 @@ fun observeNoteEvent(note: Note): State { @Composable fun observeNoteAndMap( note: Note, + accountViewModel: AccountViewModel, map: (Note) -> T, ): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) val flow = remember(note) { @@ -96,10 +104,11 @@ fun observeNoteAndMap( @Composable fun observeNoteEventAndMap( note: Note, + accountViewModel: AccountViewModel, map: (T) -> U, ): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -118,9 +127,12 @@ fun observeNoteEventAndMap( @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeNoteHasEvent(note: Note): State { +fun observeNoteHasEvent( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -136,9 +148,12 @@ fun observeNoteHasEvent(note: Note): State { } @Composable -fun observeNoteReplies(note: Note): State { +fun observeNoteReplies( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -149,9 +164,12 @@ fun observeNoteReplies(note: Note): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeNoteReplyCount(note: Note): State { +fun observeNoteReplyCount( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -168,9 +186,12 @@ fun observeNoteReplyCount(note: Note): State { } @Composable -fun observeNoteReactions(note: Note): State { +fun observeNoteReactions( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -181,9 +202,12 @@ fun observeNoteReactions(note: Note): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeNoteReactionCount(note: Note): State { +fun observeNoteReactionCount( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -202,9 +226,12 @@ fun observeNoteReactionCount(note: Note): State { } @Composable -fun observeNoteZaps(note: Note): State { +fun observeNoteZaps( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -214,9 +241,12 @@ fun observeNoteZaps(note: Note): State { } @Composable -fun observeNoteReposts(note: Note): State { +fun observeNoteReposts( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -230,9 +260,10 @@ fun observeNoteReposts(note: Note): State { fun observeNoteRepostsBy( note: Note, user: User, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -250,9 +281,12 @@ fun observeNoteRepostsBy( @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeNoteRepostCount(note: Note): State { +fun observeNoteRepostCount( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -269,9 +303,12 @@ fun observeNoteRepostCount(note: Note): State { } @Composable -fun observeNoteReferences(note: Note): State { +fun observeNoteReferences( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -289,9 +326,12 @@ fun observeNoteReferences(note: Note): State { } @Composable -fun observeNoteOts(note: Note): State { +fun observeNoteOts( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note @@ -302,9 +342,12 @@ fun observeNoteOts(note: Note): State { } @Composable -fun observeNoteEdits(note: Note): State { +fun observeNoteEdits( + note: Note, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in this note. - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return note diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssemblerSubscription.kt index 50feb6dbb..c86988336 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserFinderFilterAssemblerSubscription.kt @@ -22,12 +22,15 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun UserFinderFilterAssemblerSubscription(user: User) = UserFinderFilterAssemblerSubscription(user, Amethyst.instance.sources.userFinder) +fun UserFinderFilterAssemblerSubscription( + user: User, + accountViewModel: AccountViewModel, +) = UserFinderFilterAssemblerSubscription(user, accountViewModel.dataSources().userFinder) @Composable fun UserFinderFilterAssemblerSubscription( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt index 04c95aa1e..8b0dda65f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.PublicChatChannel import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter @@ -48,9 +49,12 @@ import kotlinx.coroutines.flow.sample import java.math.BigDecimal @Composable -fun observeUser(user: User): State { +fun observeUser( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -61,9 +65,12 @@ fun observeUser(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserName(user: User): State { +fun observeUserName( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) val flow = remember(user) { @@ -80,9 +87,12 @@ fun observeUserName(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserNip05(user: User): State { +fun observeUserNip05( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) val flow = remember(user) { @@ -99,9 +109,12 @@ fun observeUserNip05(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserAboutMe(user: User): State { +fun observeUserAboutMe( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -118,9 +131,12 @@ fun observeUserAboutMe(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserInfo(user: User): State { +fun observeUserInfo( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -137,9 +153,12 @@ fun observeUserInfo(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserBanner(user: User): State { +fun observeUserBanner( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -156,9 +175,12 @@ fun observeUserBanner(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserPicture(user: User): State { +fun observeUserPicture( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -175,9 +197,12 @@ fun observeUserPicture(user: User): State { @OptIn(ExperimentalCoroutinesApi::class) @Composable -fun observeUserShortName(user: User): State { +fun observeUserShortName( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -193,9 +218,12 @@ fun observeUserShortName(user: User): State { } @Composable -fun observeUserFollows(user: User): State { +fun observeUserFollows( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -206,9 +234,12 @@ fun observeUserFollows(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserFollowCount(user: User): State { +fun observeUserFollowCount( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -228,9 +259,12 @@ fun observeUserFollowCount(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserTagFollows(user: User): State { +fun observeUserTagFollows( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -249,9 +283,12 @@ fun observeUserTagFollows(user: User): State { } @Composable -fun observeUserBookmarks(user: User): State { +fun observeUserBookmarks( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -262,9 +299,12 @@ fun observeUserBookmarks(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserBookmarkCount(user: User): State { +fun observeUserBookmarkCount( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -283,9 +323,12 @@ fun observeUserBookmarkCount(user: User): State { } @Composable -fun observeUserFollowers(user: User): State { +fun observeUserFollowers( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -296,9 +339,12 @@ fun observeUserFollowers(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserFollowerCount(user: User): State { +fun observeUserFollowerCount( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -321,9 +367,10 @@ fun observeUserFollowerCount(user: User): State { fun observeUserIsFollowing( user1: User, user2: User, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user1) + UserFinderFilterAssemblerSubscription(user1, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -346,9 +393,10 @@ fun observeUserIsFollowing( fun observeUserIsFollowingHashtag( user: User, hashtag: String, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -371,9 +419,10 @@ fun observeUserIsFollowingHashtag( fun observeUserIsFollowingGeohash( user: User, geohash: String, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -396,9 +445,10 @@ fun observeUserIsFollowingGeohash( fun observeUserIsFollowingChannel( account: Account, channel: PublicChatChannel, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(account.userProfile()) + UserFinderFilterAssemblerSubscription(account.userProfile(), accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -421,9 +471,10 @@ fun observeUserIsFollowingChannel( fun observeUserIsFollowingChannel( account: Account, channel: EphemeralChatChannel, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(account.userProfile()) + UserFinderFilterAssemblerSubscription(account.userProfile(), accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -442,9 +493,12 @@ fun observeUserIsFollowingChannel( } @Composable -fun observeUserZaps(user: User): State { +fun observeUserZaps( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -455,9 +509,12 @@ fun observeUserZaps(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserZapAmount(user: User): State { +fun observeUserZapAmount( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -476,9 +533,12 @@ fun observeUserZapAmount(user: User): State { } @Composable -fun observeUserReports(user: User): State { +fun observeUserReports( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device return user @@ -489,9 +549,12 @@ fun observeUserReports(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserReportCount(user: User): State { +fun observeUserReportCount( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -512,9 +575,12 @@ fun observeUserReportCount(user: User): State { @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class) @Composable -fun observeUserStatuses(user: User): State> { +fun observeUserStatuses( + user: User, + accountViewModel: AccountViewModel, +): State> { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -538,9 +604,10 @@ fun observeUserStatuses(user: User): State> { fun observeUserRelayIntoList( user: User, relayUrl: String, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -566,9 +633,10 @@ fun observeUserRelayIntoList( fun observeUserRoomSubject( user: User, room: ChatroomKey, + accountViewModel: AccountViewModel, ): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = @@ -594,9 +662,12 @@ data class RelayUsage( @OptIn(FlowPreview::class) @Composable -fun observeUserRelaysUsing(user: User): State { +fun observeUserRelaysUsing( + user: User, + accountViewModel: AccountViewModel, +): State { // Subscribe in the relay for changes in the metadata of this user. - UserFinderFilterAssemblerSubscription(user) + UserFinderFilterAssemblerSubscription(user, accountViewModel) // Subscribe in the LocalCache for changes that arrive in the device val flow = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/TextSearchDataSourceSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/TextSearchDataSourceSubscription.kt index 2cac3ea17..f2e9efcff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/TextSearchDataSourceSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/TextSearchDataSourceSubscription.kt @@ -21,12 +21,15 @@ package com.vitorpamplona.amethyst.service.relayClient.searchCommand import androidx.compose.runtime.Composable -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel @Composable -fun TextSearchDataSourceSubscription(searchBarViewModel: SearchBarViewModel) = TextSearchDataSourceSubscription(searchBarViewModel, Amethyst.instance.sources.search) +fun TextSearchDataSourceSubscription( + searchBarViewModel: SearchBarViewModel, + accountViewModel: AccountViewModel, +) = TextSearchDataSourceSubscription(searchBarViewModel, accountViewModel.dataSources().search) @Composable fun TextSearchDataSourceSubscription( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/UserSearchDataSourceSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/UserSearchDataSourceSubscription.kt index 40d2d1bcb..9907471a3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/UserSearchDataSourceSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/searchCommand/UserSearchDataSourceSubscription.kt @@ -21,12 +21,15 @@ package com.vitorpamplona.amethyst.service.relayClient.searchCommand import androidx.compose.runtime.Composable -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun UserSearchDataSourceSubscription(userSuggestions: UserSuggestionState) = UserSearchDataSourceSubscription(userSuggestions, Amethyst.instance.sources.search) +fun UserSearchDataSourceSubscription( + userSuggestions: UserSuggestionState, + accountViewModel: AccountViewModel, +) = UserSearchDataSourceSubscription(userSuggestions, accountViewModel.dataSources().search) @Composable fun UserSearchDataSourceSubscription( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 04eb408b9..6c47db72c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -192,7 +192,7 @@ private fun DisplayNoteLink( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(it) + val noteState by observeNote(it, accountViewModel) val note = remember(noteState) { noteState?.note } ?: return val channelHex = remember(noteState) { note.channelHex() } @@ -215,7 +215,7 @@ private fun DisplayNoteLink( ) } else if (channelHex != null) { LoadChannel(baseChannelHex = channelHex, accountViewModel) { baseChannel -> - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channelDisplayName by remember(channelState) { derivedStateOf { channelState?.channel?.toBestDisplayName() ?: noteIdDisplayNote } @@ -255,7 +255,7 @@ private fun DisplayAddress( } noteBase?.let { - val noteState by observeNote(it) + val noteState by observeNote(it, accountViewModel) val route = remember(noteState) { Route.Note(nip19.aTag()) } val displayName = remember(noteState) { "@${noteState?.note?.idDisplayNote()}" } @@ -303,7 +303,7 @@ fun DisplayUser( } } - userBase?.let { RenderUserAsClickableText(it, additionalChars, nav) } + userBase?.let { RenderUserAsClickableText(it, additionalChars, accountViewModel, nav) } if (userBase == null) { val uri = LocalUriHandler.current @@ -323,9 +323,10 @@ fun DisplayUser( fun RenderUserAsClickableText( baseUser: User, additionalChars: String?, + accountViewModel: AccountViewModel, nav: INav, ) { - val userState by observeUserInfo(baseUser) + val userState by observeUserInfo(baseUser, accountViewModel) CreateClickableTextWithEmoji( clickablePart = "@" + (userState?.bestName() ?: baseUser.pubkeyDisplayHex()), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index f1d191a36..9cf1db491 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -777,7 +777,7 @@ private fun DisplayUserFromTag( accountViewModel: AccountViewModel, nav: INav, ) { - val meta by observeUserInfo(baseUser) + val meta by observeUserInfo(baseUser, accountViewModel) CrossfadeIfEnabled(targetState = meta, label = "DisplayUserFromTag", accountViewModel = accountViewModel) { Row { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt index 5e1e8e4bd..938808bf2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt @@ -227,7 +227,7 @@ class MarkdownMediaRenderer( ) { renderInvisible(richTextStringBuilder) { // Preloads note if not loaded yet. - EventFinderFilterAssemblerSubscription(baseNote) + EventFinderFilterAssemblerSubscription(baseNote, accountViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt index 187d6a1d4..b8f9daf3e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt @@ -45,6 +45,7 @@ object ScrollStateKeys { const val DRAFTS = "DraftsFeed" + const val DISCOVER_FOLLOWS = "DiscoverFollowSetsFeed" const val DISCOVER_CONTENT = "DiscoverDiscoverContentFeed" const val DISCOVER_MARKETPLACE = "DiscoverMarketplaceFeed" const val DISCOVER_LIVE = "DiscoverLiveFeed" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index 15be32fce..ab64bf1a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -173,14 +173,10 @@ fun DisplayAccount( .width(55.dp) .padding(0.dp), ) { - AccountPicture( - it, - accountViewModel.settings.showProfilePictures.value, - loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, - ) + AccountPicture(it, accountViewModel) } Spacer(modifier = Modifier.width(16.dp)) - Column(modifier = Modifier.weight(1f)) { AccountName(acc, it) } + Column(modifier = Modifier.weight(1f)) { AccountName(acc, it, accountViewModel) } Column(modifier = Modifier.width(32.dp)) { ActiveMarker(acc, accountViewModel) } } } @@ -212,18 +208,17 @@ private fun ActiveMarker( @Composable private fun AccountPicture( user: User, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, ) { - val profilePicture by observeUserPicture(user) + val profilePicture by observeUserPicture(user, accountViewModel) RobohashFallbackAsyncImage( robot = user.pubkeyHex, model = profilePicture, contentDescription = stringRes(R.string.profile_image), modifier = AccountPictureModifier, - loadProfilePicture = loadProfilePicture, - loadRobohash = loadRobohash, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } @@ -231,8 +226,9 @@ private fun AccountPicture( private fun AccountName( acc: AccountInfo, user: User, + accountViewModel: AccountViewModel, ) { - val info by observeUserInfo(user) + val info by observeUserInfo(user, accountViewModel) info?.let { it.bestName()?.let { name -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 49bc2776e..efa372397 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -66,7 +66,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28P import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.CommunityScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.marketplace.NewProductScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.NewProductScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.drafts.DraftListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.DvmContentDiscoveryScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index ac2fdbe5d..78a1e6ce6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -89,7 +89,7 @@ private fun LoggedInUserPictureDrawer( onClick: () -> Unit, ) { IconButton(onClick = onClick) { - val profilePicture by observeUserPicture(accountViewModel.userProfile()) + val profilePicture by observeUserPicture(accountViewModel.userProfile(), accountViewModel) RobohashFallbackAsyncImage( robot = accountViewModel.userProfile().pubkeyHex, @@ -107,6 +107,7 @@ private fun LoggedInUserPictureDrawer( fun FollowListWithRoutes( followListsModel: FollowListState, listName: String, + accountViewModel: AccountViewModel, onChange: (FeedDefinition) -> Unit, ) { val allLists by followListsModel.kind3GlobalPeopleRoutes.collectAsStateWithLifecycle() @@ -116,6 +117,7 @@ fun FollowListWithRoutes( explainer = stringRes(R.string.select_list_to_filter), options = allLists, onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + accountViewModel = accountViewModel, ) } @@ -123,6 +125,7 @@ fun FollowListWithRoutes( fun FollowListWithoutRoutes( followListsModel: FollowListState, listName: String, + accountViewModel: AccountViewModel, onChange: (FeedDefinition) -> Unit, ) { val allLists by followListsModel.kind3GlobalPeople.collectAsStateWithLifecycle() @@ -132,5 +135,6 @@ fun FollowListWithoutRoutes( explainer = stringRes(R.string.select_list_to_filter), options = allLists, onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.kind3Follow) }, + accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 9711dab52..fe1c320d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -157,7 +157,7 @@ fun DrawerContent( EditStatusBoxes(accountViewModel.account.userProfile(), accountViewModel, nav) } - FollowingAndFollowerCounts(accountViewModel.account, onClickUser) + FollowingAndFollowerCounts(accountViewModel.account, accountViewModel, onClickUser) HorizontalDivider( thickness = DividerThickness, @@ -191,7 +191,7 @@ fun ProfileContent( accountViewModel: AccountViewModel, onClickUser: () -> Unit, ) { - val userInfo by observeUserInfo(baseAccountUser) + val userInfo by observeUserInfo(baseAccountUser, accountViewModel) ProfileContentTemplate( profilePubHex = baseAccountUser.pubkeyHex, @@ -278,7 +278,7 @@ private fun EditStatusBoxes( StatusEditBar(accountViewModel = accountViewModel, nav = nav) } else { statuses.forEach { - val noteStatus by observeNote(it) + val noteStatus by observeNote(it, accountViewModel) StatusEditBar(noteStatus.note.event?.content, it.address, accountViewModel, nav) } @@ -387,13 +387,14 @@ fun UserStatusDeleteButton(onClick: () -> Unit) { @Composable private fun FollowingAndFollowerCounts( baseAccountUser: Account, + accountViewModel: AccountViewModel, onClick: () -> Unit, ) { Row( modifier = drawerSpacing.clickable(onClick = onClick), ) { val followingCount = baseAccountUser.liveKind3Follows.collectAsStateWithLifecycle() - val followerCount by observeUserFollowerCount(baseAccountUser.userProfile()) + val followerCount by observeUserFollowerCount(baseAccountUser.userProfile(), accountViewModel) Text( text = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt index a402e4078..f13276944 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/FeedFilterSpinner.kt @@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.screen.HashtagName import com.vitorpamplona.amethyst.ui.screen.Name import com.vitorpamplona.amethyst.ui.screen.PeopleListName import com.vitorpamplona.amethyst.ui.screen.ResourceName +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size20Modifier @@ -82,6 +83,7 @@ fun FeedFilterSpinner( options: ImmutableList, onSelect: (Int) -> Unit, modifier: Modifier = Modifier, + accountViewModel: AccountViewModel, ) { var optionsShowing by remember { mutableStateOf(false) } @@ -208,14 +210,17 @@ fun FeedFilterSpinner( onSelect(it) }, ) { - RenderOption(it.name) + RenderOption(it.name, accountViewModel) } } } } @Composable -fun RenderOption(option: Name) { +fun RenderOption( + option: Name, + accountViewModel: AccountViewModel, +) { when (option) { is GeoHashName -> { LoadCityName(option.geoHashTag) { @@ -251,7 +256,7 @@ fun RenderOption(option: Name) { horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth(), ) { - val noteState by observeNote(option.note) + val noteState by observeNote(option.note, accountViewModel) val noteEvent = noteState.note.event val name = @@ -271,7 +276,7 @@ fun RenderOption(option: Name) { horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth(), ) { - val it by observeNote(option.note) + val it by observeNote(option.note, accountViewModel) Text(text = "/n/${((it?.note as? AddressableNote)?.dTag() ?: "")}", color = MaterialTheme.colorScheme.onSurface) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt index b65a3510d..83a43e0ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt @@ -61,7 +61,7 @@ fun BadgeCompose( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(likeSetCard.note) + val noteState by observeNote(likeSetCard.note, accountViewModel) val note = noteState?.note val context = LocalContext.current.applicationContext @@ -129,7 +129,7 @@ fun BadgeCompose( } note.replyTo?.firstOrNull()?.let { - BadgeDisplay(baseNote = it) + BadgeDisplay(baseNote = it, accountViewModel) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index b98804779..3d571b57f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -147,8 +147,8 @@ fun HiddenNote( NoteAuthorPicture( baseNote = it, size = Size35dp, - nav = nav, accountViewModel = accountViewModel, + nav = nav, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt deleted file mode 100644 index 166c239ff..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ /dev/null @@ -1,1064 +0,0 @@ -/** - * Copyright (c) 2024 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.ui.note - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -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.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.BottomStart -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 -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import coil3.compose.AsyncImage -import coil3.compose.AsyncImagePainter -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.Channel -import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.ParticipantListBuilder -import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap -import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled -import com.vitorpamplona.amethyst.ui.components.SensitivityWarning -import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout -import com.vitorpamplona.amethyst.ui.navigation.INav -import com.vitorpamplona.amethyst.ui.note.elements.BannerImage -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.EndedFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.OfflineFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition -import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline -import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer -import com.vitorpamplona.amethyst.ui.theme.HalfPadding -import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding -import com.vitorpamplona.amethyst.ui.theme.QuoteBorder -import com.vitorpamplona.amethyst.ui.theme.RowColSpacing -import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp -import com.vitorpamplona.amethyst.ui.theme.Size25dp -import com.vitorpamplona.amethyst.ui.theme.Size5dp -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.theme.StdPadding -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import com.vitorpamplona.amethyst.ui.theme.bitcoinColor -import com.vitorpamplona.amethyst.ui.theme.grayText -import com.vitorpamplona.amethyst.ui.theme.nip05 -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent -import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent -import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag -import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag -import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent -import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent -import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent -import com.vitorpamplona.quartz.nip99Classifieds.tags.PriceTag -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -@Composable -fun ChannelCardCompose( - baseNote: Note, - routeForLastRead: String? = null, - modifier: Modifier = Modifier, - parentBackgroundColor: MutableState? = null, - forceEventKind: Int?, - isHiddenFeed: Boolean = false, - accountViewModel: AccountViewModel, - nav: INav, -) { - WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { - if (forceEventKind == null || baseNote.event?.kind == forceEventKind) { - CheckHiddenFeedWatchBlockAndReport( - note = baseNote, - modifier = modifier, - ignoreAllBlocksAndReports = isHiddenFeed, - showHiddenWarning = false, - accountViewModel = accountViewModel, - nav = nav, - ) { canPreview -> - NormalChannelCard( - baseNote = baseNote, - routeForLastRead = routeForLastRead, - modifier = modifier, - parentBackgroundColor = parentBackgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) - } - } - } -} - -@Composable -fun NormalChannelCard( - baseNote: Note, - routeForLastRead: String? = null, - modifier: Modifier = Modifier, - parentBackgroundColor: MutableState? = null, - accountViewModel: AccountViewModel, - nav: INav, -) { - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - CheckNewAndRenderChannelCard( - baseNote, - routeForLastRead, - modifier, - parentBackgroundColor, - accountViewModel, - showPopup, - nav, - ) - } -} - -@Composable -private fun CheckNewAndRenderChannelCard( - baseNote: Note, - routeForLastRead: String? = null, - modifier: Modifier = Modifier, - parentBackgroundColor: MutableState? = null, - accountViewModel: AccountViewModel, - showPopup: () -> Unit, - nav: INav, -) { - val backgroundColor = - calculateBackgroundColor( - createdAt = baseNote.createdAt(), - routeForLastRead = routeForLastRead, - parentBackgroundColor = parentBackgroundColor, - accountViewModel = accountViewModel, - ) - - ClickableNote( - baseNote = baseNote, - backgroundColor = backgroundColor, - modifier = modifier, - accountViewModel = accountViewModel, - showPopup = showPopup, - nav = nav, - ) { - InnerChannelCardWithReactions( - baseNote = baseNote, - accountViewModel = accountViewModel, - nav = nav, - ) - } -} - -@Composable -fun InnerChannelCardWithReactions( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - when (baseNote.event) { - is LiveActivitiesEvent -> { - InnerCardRow(baseNote, accountViewModel, nav) - } - is CommunityDefinitionEvent -> { - InnerCardRow(baseNote, accountViewModel, nav) - } - is ChannelCreateEvent -> { - InnerCardRow(baseNote, accountViewModel, nav) - } - is ClassifiedsEvent -> { - InnerCardBox(baseNote, accountViewModel, nav) - } - is AppDefinitionEvent -> { - InnerCardRow(baseNote, accountViewModel, nav) - } - } -} - -@Composable -fun InnerCardRow( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - Column(StdPadding) { - SensitivityWarning( - note = baseNote, - accountViewModel = accountViewModel, - ) { - RenderNoteRow( - baseNote, - accountViewModel, - nav, - ) - } - } -} - -@Composable -fun InnerCardBox( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - Column(HalfPadding) { - SensitivityWarning( - note = baseNote, - accountViewModel = accountViewModel, - ) { - RenderClassifiedsThumb(baseNote, accountViewModel, nav) - } - } -} - -@Composable -private fun RenderNoteRow( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - when (baseNote.event) { - is LiveActivitiesEvent -> { - RenderLiveActivityThumb(baseNote, accountViewModel, nav) - } - is CommunityDefinitionEvent -> { - RenderCommunitiesThumb(baseNote, accountViewModel, nav) - } - is ChannelCreateEvent -> { - RenderChannelThumb(baseNote, accountViewModel, nav) - } - is AppDefinitionEvent -> { - RenderContentDVMThumb(baseNote, accountViewModel, nav) - } - } -} - -@Immutable -data class ClassifiedsThumb( - val image: String?, - val title: String?, - val price: PriceTag?, -) - -@Composable -fun RenderClassifiedsThumb( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - if (baseNote.event !is ClassifiedsEvent) return - - val card by observeNoteAndMap(baseNote) { - val noteEvent = it.event as? ClassifiedsEvent - ClassifiedsThumb( - image = noteEvent?.image(), - title = noteEvent?.title(), - price = noteEvent?.price(), - ) - } - - InnerRenderClassifiedsThumb(card, baseNote) -} - -@Preview -@Composable -fun RenderClassifiedsThumbPreview() { - Surface(Modifier.size(200.dp)) { - InnerRenderClassifiedsThumb( - card = - ClassifiedsThumb( - image = null, - title = "Like New", - price = PriceTag("800000", "SATS", null), - ), - note = Note("hex"), - ) - } -} - -@Composable -fun InnerRenderClassifiedsThumb( - card: ClassifiedsThumb, - note: Note, -) { - Box( - Modifier - .fillMaxWidth() - .aspectRatio(1f), - contentAlignment = BottomStart, - ) { - card.image?.let { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize(), - ) - } ?: run { DisplayAuthorBanner(note) } - - Row( - Modifier - .fillMaxWidth() - .background(Color.Black.copy(0.6f)) - .padding(Size5dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - card.title?.let { - Text( - text = it, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = Color.White, - modifier = Modifier.weight(1f), - ) - } - - card.price?.let { - val priceTag = - remember(card) { - val newAmount = it.amount.toBigDecimalOrNull()?.let { showAmountInteger(it) } ?: it.amount - - if (it.frequency != null && it.currency != null) { - "$newAmount ${it.currency}/${it.frequency}" - } else if (it.currency != null) { - "$newAmount ${it.currency}" - } else { - newAmount - } - } - - Text( - text = priceTag, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = Color.White, - ) - } - } - } -} - -@Immutable -data class LiveActivityCard( - val name: String, - val cover: String?, - val media: String?, - val subject: String?, - val content: String?, - val participants: ImmutableList, - val status: String?, - val starts: Long?, -) - -@Composable -fun RenderLiveActivityThumb( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - val card by observeNoteAndMap(baseNote) { - val noteEvent = it.event as? LiveActivitiesEvent - - LiveActivityCard( - name = noteEvent?.dTag() ?: "", - cover = noteEvent?.image()?.ifBlank { null }, - media = noteEvent?.streaming(), - subject = noteEvent?.title()?.ifBlank { null }, - content = noteEvent?.summary(), - participants = noteEvent?.participants()?.toImmutableList() ?: persistentListOf(), - status = noteEvent?.status(), - starts = noteEvent?.starts(), - ) - } - - RenderLiveActivityThumb( - card, - baseNote, - accountViewModel, - nav, - ) -} - -@Composable -fun RenderLiveActivityThumb( - card: LiveActivityCard, - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - Column( - modifier = Modifier.fillMaxWidth(), - ) { - Box( - contentAlignment = TopEnd, - modifier = - Modifier - .aspectRatio(ratio = 16f / 9f) - .fillMaxWidth(), - ) { - card.cover?.let { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - Modifier - .fillMaxSize() - .clip(QuoteBorder), - ) - } ?: run { DisplayAuthorBanner(baseNote) } - - Box(Modifier.padding(10.dp)) { - CrossfadeIfEnabled(targetState = card.status, label = "RenderLiveActivityThumb", accountViewModel = accountViewModel) { - when (it) { - StatusTag.STATUS.LIVE.code -> { - val url = card.media - if (url.isNullOrBlank()) { - LiveFlag() - } else { - CheckIfVideoIsOnline(url, accountViewModel) { isOnline -> - if (isOnline) { - LiveFlag() - } else { - OfflineFlag() - } - } - } - } - StatusTag.STATUS.ENDED.code -> { - EndedFlag() - } - StatusTag.STATUS.PLANNED.code -> { - ScheduledFlag(card.starts) - } - else -> { - EndedFlag() - } - } - } - } - - LoadParticipants(card.participants, baseNote, accountViewModel) { participantUsers -> - Box( - Modifier - .padding(10.dp) - .align(BottomStart), - ) { - if (participantUsers.isNotEmpty()) { - Gallery(participantUsers, Modifier, accountViewModel) - } - } - } - } - - Spacer(modifier = DoubleVertSpacer) - - ChannelHeader( - channelHex = baseNote.idHex, - showVideo = false, - showFlag = false, - sendToChannel = true, - modifier = Modifier, - accountViewModel = accountViewModel, - nav = nav, - ) - } -} - -@Immutable -data class CommunityCard( - val name: String, - val description: String?, - val cover: String?, - val moderators: ImmutableList, -) - -@Immutable -data class DVMCard( - val name: String, - val description: String?, - val cover: String?, - val amount: String?, - val personalized: Boolean?, -) - -@Composable -fun RenderCommunitiesThumb( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - val noteState by observeNote(baseNote) - val noteEvent = noteState?.note?.event as? CommunityDefinitionEvent ?: return - - RenderCommunitiesThumb( - CommunityCard( - name = noteEvent.dTag(), - description = noteEvent.description(), - cover = noteEvent.image()?.imageUrl, - moderators = noteEvent.moderatorKeys().toImmutableList(), - ), - baseNote, - accountViewModel, - nav, - ) -} - -@Composable -fun RenderCommunitiesThumb( - card: CommunityCard, - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - LeftPictureLayout( - onImage = { - card.cover?.let { - Box(contentAlignment = BottomStart) { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - Modifier - .fillMaxSize() - .clip(QuoteBorder), - ) - } - } ?: run { DisplayAuthorBanner(baseNote) } - }, - onTitleRow = { - Text( - text = card.name, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), - ) - - Spacer(modifier = StdHorzSpacer) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = RowColSpacing, - ) { - LikeReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav, - ) - } - Spacer(modifier = StdHorzSpacer) - ZapReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav = nav, - ) - }, - onDescription = { - Text( - text = card.description ?: stringRes(R.string.community_about_topic, card.name), - color = MaterialTheme.colorScheme.grayText, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - fontSize = 14.sp, - lineHeight = 18.sp, - modifier = HalfTopPadding, - ) - }, - onBottomRow = { - LoadModerators(card.moderators, baseNote, accountViewModel) { participantUsers -> - if (participantUsers.isNotEmpty()) { - Gallery(participantUsers, HalfTopPadding, accountViewModel) - } - } - }, - ) -} - -@Composable -fun LoadModerators( - moderators: ImmutableList, - baseNote: Note, - accountViewModel: AccountViewModel, - content: @Composable (ImmutableList) -> Unit, -) { - var participantUsers by remember { - mutableStateOf>( - persistentListOf(), - ) - } - - LaunchedEffect(key1 = moderators) { - launch(Dispatchers.IO) { - val hosts = - moderators.mapNotNull { part -> - if (part != baseNote.author?.pubkeyHex) { - LocalCache.checkGetOrCreateUser(part) - } else { - null - } - } - - val followingKeySet = - accountViewModel.account.liveDiscoveryFollowLists.value - ?.authors - val allParticipants = - ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts) - - val newParticipantUsers = - if (followingKeySet == null) { - val allFollows = accountViewModel.account.liveKind3Follows.value.authors - val followingParticipants = - ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts) - - (hosts + followingParticipants + (allParticipants - followingParticipants)) - .toImmutableList() - } else { - (hosts + allParticipants).toImmutableList() - } - - if (!equalImmutableLists(newParticipantUsers, participantUsers)) { - participantUsers = newParticipantUsers - } - } - } - - content(participantUsers) -} - -@Composable -private fun LoadParticipants( - participants: ImmutableList, - baseNote: Note, - accountViewModel: AccountViewModel, - inner: @Composable (ImmutableList) -> Unit, -) { - var participantUsers by remember { - mutableStateOf>( - persistentListOf(), - ) - } - - LaunchedEffect(key1 = participants) { - launch(Dispatchers.IO) { - val hosts = - participants.mapNotNull { part -> - if (part.pubKey != baseNote.author?.pubkeyHex) { - LocalCache.checkGetOrCreateUser(part.pubKey) - } else { - null - } - } - - val hostsAuthor = hosts + (baseNote.author?.let { listOf(it) } ?: emptyList()) - - val followingKeySet = - accountViewModel.account.liveDiscoveryFollowLists.value - ?.authors - - val allParticipants = - ParticipantListBuilder() - .followsThatParticipateOn(baseNote, followingKeySet) - .minus(hostsAuthor) - - val newParticipantUsers = - if (followingKeySet == null) { - val allFollows = accountViewModel.account.liveKind3Follows.value.authors - val followingParticipants = - ParticipantListBuilder() - .followsThatParticipateOn(baseNote, allFollows) - .minus(hostsAuthor) - - (hosts + followingParticipants + (allParticipants - followingParticipants)) - .toImmutableList() - } else { - (hosts + allParticipants).toImmutableList() - } - - if (!equalImmutableLists(newParticipantUsers, participantUsers)) { - participantUsers = newParticipantUsers - } - } - } - - inner(participantUsers) -} - -@Composable -fun RenderContentDVMThumb( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - // downloads user metadata to pre-load the NIP-65 relays. - baseNote.author?.let { UserFinderFilterAssemblerSubscription(it) } - val card = observeAppDefinition(appDefinitionNote = baseNote) - - LeftPictureLayout( - imageFraction = 0.20f, - onImage = { - card.cover?.let { - Box(contentAlignment = BottomStart) { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - Modifier - .fillMaxSize() - .clip(QuoteBorder), - ) - } - } ?: run { - baseNote.author?.let { - BannerImage( - it, - Modifier - .fillMaxSize() - .clip(QuoteBorder), - ) - } - } - }, - onTitleRow = { - Text( - text = card.name, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), - ) - Spacer(modifier = StdVertSpacer) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = RowColSpacing5dp, - ) { - LikeReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav, - ) - } - Spacer(modifier = StdHorzSpacer) - ZapReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav = nav, - ) - }, - onDescription = { - card.description?.let { - Text( - text = it, - color = MaterialTheme.colorScheme.grayText, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - fontSize = 14.sp, - lineHeight = 16.sp, - modifier = HalfTopPadding, - ) - } - }, - onBottomRow = { - card.amount?.let { - var color = Color.DarkGray - var amount = it - if (card.amount == "free" || card.amount == "0") { - color = MaterialTheme.colorScheme.secondary - amount = "Free" - } else if (card.amount == "flexible") { - color = MaterialTheme.colorScheme.primaryContainer - amount = "Flexible" - } else if (card.amount == "") { - color = MaterialTheme.colorScheme.grayText - amount = "Unknown" - } else { - color = MaterialTheme.colorScheme.primary - amount = card.amount + " Sats" - } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Absolute.Right, - ) { - Text( - textAlign = TextAlign.End, - text = " $amount ", - color = color, - maxLines = 3, - modifier = - Modifier - .weight(1f, fill = false) - .border(Dp(.1f), color, shape = RoundedCornerShape(20)), - fontSize = 12.sp, - ) - } - } - Spacer(modifier = StdHorzSpacer) - card.personalized?.let { - var color = Color.DarkGray - var name = "generic" - if (card.personalized == true) { - color = MaterialTheme.colorScheme.bitcoinColor - name = "Personalized" - } else { - color = MaterialTheme.colorScheme.nip05 - name = "Generic" - } - Spacer(modifier = StdVertSpacer) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Absolute.Right, - ) { - Text( - textAlign = TextAlign.End, - text = " $name ", - color = color, - maxLines = 3, - modifier = - Modifier - .padding(start = 4.dp) - .weight(1f, fill = false) - .border(Dp(.1f), color, shape = RoundedCornerShape(20)), - fontSize = 12.sp, - ) - } - } - }, - ) -} - -@Composable -fun RenderChannelThumb( - baseNote: Note, - accountViewModel: AccountViewModel, - nav: INav, -) { - val noteEvent = baseNote.event as? ChannelCreateEvent ?: return - - LoadChannel(baseChannelHex = baseNote.idHex, accountViewModel) { - RenderChannelThumb(baseNote = baseNote, channel = it, accountViewModel, nav) - } -} - -@Composable -fun RenderChannelThumb( - baseNote: Note, - channel: Channel, - accountViewModel: AccountViewModel, - nav: INav, -) { - val channelUpdates by observeChannel(channel) - - val name = remember(channelUpdates) { channelUpdates?.channel?.toBestDisplayName() ?: "" } - val description = remember(channelUpdates) { channelUpdates?.channel?.summary()?.ifBlank { null } } - var cover by - remember(channelUpdates) { - mutableStateOf(channelUpdates?.channel?.profilePicture()?.ifBlank { null }) - } - - var participantUsers by - remember(baseNote) { - mutableStateOf>( - persistentListOf(), - ) - } - - LaunchedEffect(key1 = channelUpdates) { - launch(Dispatchers.IO) { - val followingKeySet = - accountViewModel.account.liveDiscoveryFollowLists.value - ?.authors - val allParticipants = - ParticipantListBuilder() - .followsThatParticipateOn(baseNote, followingKeySet) - .toImmutableList() - - val newParticipantUsers = - if (followingKeySet == null) { - val allFollows = accountViewModel.account.liveKind3Follows.value.authors - val followingParticipants = - ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).toList() - - (followingParticipants + (allParticipants - followingParticipants)).toImmutableList() - } else { - allParticipants.toImmutableList() - } - - if (!equalImmutableLists(newParticipantUsers, participantUsers)) { - participantUsers = newParticipantUsers - } - } - } - - LeftPictureLayout( - onImage = { - cover?.let { - AsyncImage( - model = it, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = - Modifier - .fillMaxSize() - .clip(QuoteBorder), - onState = { - if (it is AsyncImagePainter.State.Error) { - cover = null - } - }, - ) - } ?: run { DisplayAuthorBanner(baseNote) } - }, - onTitleRow = { - Text( - text = name, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f), - ) - Spacer(modifier = StdHorzSpacer) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = RowColSpacing, - ) { - LikeReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav, - ) - } - Spacer(modifier = StdHorzSpacer) - ZapReaction( - baseNote = baseNote, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav = nav, - ) - }, - onDescription = { - Text( - text = description ?: stringRes(R.string.chat_about_topic, name), - color = MaterialTheme.colorScheme.grayText, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - fontSize = 14.sp, - lineHeight = 18.sp, - modifier = HalfTopPadding, - ) - }, - onBottomRow = { - if (participantUsers.isNotEmpty()) { - Gallery(participantUsers, HalfTopPadding, accountViewModel) - } - }, - ) -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun Gallery( - users: ImmutableList, - modifier: Modifier, - accountViewModel: AccountViewModel, - maxPictures: Int = 6, -) { - FlowRow( - modifier, - verticalArrangement = Arrangement.Center, - horizontalArrangement = Arrangement.spacedBy((-6).dp), - ) { - users.take(maxPictures).forEach { - ClickableUserPicture( - it, - Size25dp, - accountViewModel, - Modifier, - ) - } - - if (users.size > maxPictures) { - Text( - text = " + " + showCount(users.size - maxPictures), - fontSize = 13.sp, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(start = 6.dp).align(CenterVertically), - ) - } - } -} - -@Composable -fun DisplayAuthorBanner(note: Note) { - WatchAuthor(note) { - BannerImage( - it, - Modifier - .fillMaxSize() - .clip(QuoteBorder), - ) - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/DisplayAuthorBanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/DisplayAuthorBanner.kt new file mode 100644 index 000000000..8bae16b5a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/DisplayAuthorBanner.kt @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.note + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder + +@Composable +fun DisplayAuthorBanner( + note: Note, + accountViewModel: AccountViewModel, +) { + WatchAuthor(note, accountViewModel) { + BannerImage( + it, + Modifier + .fillMaxSize() + .clip(QuoteBorder), + accountViewModel, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Gallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Gallery.kt new file mode 100644 index 000000000..4c90939e3 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Gallery.kt @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.note + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.navigation.routeFor +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.Size25dp +import kotlinx.collections.immutable.ImmutableList + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun Gallery( + users: ImmutableList, + modifier: Modifier, + accountViewModel: AccountViewModel, + nav: INav, + maxPictures: Int = 6, +) { + FlowRow( + modifier, + verticalArrangement = Arrangement.Center, + horizontalArrangement = Arrangement.spacedBy((-5).dp), + ) { + users.take(maxPictures).forEach { + ClickableUserPicture( + it, + Size25dp, + accountViewModel, + onClick = { + nav.nav(routeFor(it)) + }, + ) + } + + if (users.size > maxPictures) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(Size25dp).clip(shape = CircleShape).background(MaterialTheme.colorScheme.secondaryContainer), + ) { + Text( + text = "+" + showCount(users.size - maxPictures), + fontSize = 10.sp, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt index 831498a8a..58e44cb07 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Loaders.kt @@ -144,7 +144,7 @@ fun LoadStatuses( accountViewModel: AccountViewModel, content: @Composable (ImmutableList) -> Unit, ) { - val userStatuses by observeUserStatuses(user) + val userStatuses by observeUserStatuses(user, accountViewModel) content(userStatuses) } @@ -158,7 +158,7 @@ fun LoadOts( ) { var earliestDate: GenericLoadable by remember { mutableStateOf(GenericLoadable.Loading()) } - val noteStatus by observeNoteOts(note) + val noteStatus by observeNoteOts(note, accountViewModel) LaunchedEffect(key1 = noteStatus) { accountViewModel.findOtsEventsForNote(noteStatus?.note ?: note) { newOts -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index c47fd79b4..403bb2e22 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -597,7 +597,7 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture( author: User, accountViewModel: AccountViewModel, ) { - WatchUserMetadata(author) { baseUserPicture -> + WatchUserMetadata(author, accountViewModel) { baseUserPicture -> RobohashFallbackAsyncImage( robot = author.pubkeyHex, model = baseUserPicture, @@ -621,9 +621,10 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture( @Composable private fun WatchUserMetadata( author: User, + accountViewModel: AccountViewModel, onNewMetadata: @Composable (String?) -> Unit, ) { - val userProfile by observeUserPicture(author) + val userProfile by observeUserPicture(author, accountViewModel) onNewMetadata(userProfile) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index b7bb586fb..a3277dae4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -123,7 +123,7 @@ fun ObserveDisplayNip05Status( accountViewModel: AccountViewModel, nav: INav, ) { - WatchAuthor(baseNote = baseNote) { + WatchAuthor(baseNote = baseNote, accountViewModel) { ObserveDisplayNip05Status(it, columnModifier, accountViewModel, nav) } } @@ -135,8 +135,8 @@ fun ObserveDisplayNip05Status( accountViewModel: AccountViewModel, nav: INav, ) { - val nip05 by observeUserNip05(baseUser) - val statuses by observeUserStatuses(baseUser) + val nip05 by observeUserNip05(baseUser, accountViewModel) + val statuses by observeUserStatuses(baseUser, accountViewModel) CrossfadeIfEnabled( targetState = nip05, @@ -193,7 +193,7 @@ fun ObserveRotateStatuses( accountViewModel: AccountViewModel, nav: INav, ) { - ObserveAllStatusesToAvoidSwitchigAllTheTime(statuses) + ObserveAllStatusesToAvoidSwitchigAllTheTime(statuses, accountViewModel) RotateStatuses( statuses, @@ -203,9 +203,12 @@ fun ObserveRotateStatuses( } @Composable -fun ObserveAllStatusesToAvoidSwitchigAllTheTime(statuses: ImmutableList) { +fun ObserveAllStatusesToAvoidSwitchigAllTheTime( + statuses: ImmutableList, + accountViewModel: AccountViewModel, +) { statuses.map { - EventFinderFilterAssemblerSubscription(it) + EventFinderFilterAssemblerSubscription(it, accountViewModel) } } @@ -246,7 +249,7 @@ fun DisplayStatus( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(addressableNote) + val noteState by observeNote(addressableNote, accountViewModel) val noteEvent = noteState?.note?.event as? StatusEvent ?: return DisplayStatus(noteEvent, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index f01574667..1e4ff2904 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -291,7 +291,7 @@ fun AcceptableNote( nav = nav, ) } - is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) + is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote, accountViewModel) else -> LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> CheckNewAndRenderNote( @@ -331,7 +331,7 @@ fun AcceptableNote( nav = nav, ) } - is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) + is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote, accountViewModel) else -> LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> CheckNewAndRenderNote( @@ -856,7 +856,7 @@ fun ObserveDraftEvent( accountViewModel: AccountViewModel, render: @Composable (Note) -> Unit, ) { - val noteEvent by observeNoteEvent(note) + val noteEvent by observeNoteEvent(note, accountViewModel) noteEvent?.let { val innerNote by produceCachedStateAsync(cache = accountViewModel.draftNoteCache, key = it) @@ -1051,7 +1051,7 @@ fun FirstUserInfoRow( val textColor = if (isRepost) MaterialTheme.colorScheme.grayText else Color.Unspecified if (showAuthorPicture) { - NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp) + NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav) Spacer(HalfPadding) NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } else { @@ -1116,7 +1116,7 @@ fun observeEdits( ) } - val updatedNote by observeNoteEdits(baseNote) + val updatedNote by observeNoteEdits(baseNote, accountViewModel) LaunchedEffect(key1 = updatedNote) { updatedNote?.note?.let { @@ -1167,10 +1167,10 @@ private fun RenderAuthorImages( if (baseRepost != null) { RepostNoteAuthorPicture(baseNote, baseRepost, accountViewModel, nav) } else { - NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp) + NoteAuthorPicture(baseNote, Size55dp, accountViewModel = accountViewModel, nav = nav) } } else { - NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp) + NoteAuthorPicture(baseNote, Size55dp, accountViewModel = accountViewModel, nav = nav) } if (baseNote.event is ChannelMessageEvent) { @@ -1179,8 +1179,7 @@ private fun RenderAuthorImages( LoadChannel(baseChannelHex, accountViewModel) { channel -> ChannelNotePicture( channel, - loadProfilePicture = accountViewModel.settings.showProfilePictures.value, - loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + accountViewModel, ) } } @@ -1190,18 +1189,17 @@ private fun RenderAuthorImages( @Composable private fun ChannelNotePicture( baseChannel: Channel, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, ) { - val model by observeChannelPicture(baseChannel) + val model by observeChannelPicture(baseChannel, accountViewModel) RobohashFallbackAsyncImage( robot = baseChannel.idHex, model = model, contentDescription = stringRes(R.string.group_picture), modifier = MaterialTheme.colorScheme.channelNotePictureModifier, - loadProfilePicture = loadProfilePicture, - loadRobohash = loadRobohash, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } @@ -1216,17 +1214,17 @@ private fun RepostNoteAuthorPicture( baseAuthorPicture = { NoteAuthorPicture( baseNote = baseNote, - nav = nav, - accountViewModel = accountViewModel, size = Size34dp, + accountViewModel = accountViewModel, + nav = nav, ) }, repostAuthorPicture = { NoteAuthorPicture( baseNote = baseRepost, - nav = nav, - accountViewModel = accountViewModel, size = Size34dp, + accountViewModel = accountViewModel, + nav = nav, ) }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index 9f4037f02..6c183383e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -264,7 +264,7 @@ fun PollNote( accountViewModel: AccountViewModel, nav: INav, ) { - WatchZapsAndUpdateTallies(baseNote, pollViewModel) + WatchZapsAndUpdateTallies(baseNote, pollViewModel, accountViewModel) pollViewModel.tallies.forEach { option -> OptionNote( @@ -283,8 +283,9 @@ fun PollNote( private fun WatchZapsAndUpdateTallies( baseNote: Note, pollViewModel: PollNoteViewModel, + accountViewModel: AccountViewModel, ) { - val zapsState by observeNoteZaps(baseNote) + val zapsState by observeNoteZaps(baseNote, accountViewModel) LaunchedEffect(key1 = zapsState) { pollViewModel.refreshTallies() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index fc283216c..4464bb44c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -202,7 +202,7 @@ private fun InnerReactionRow( showReactionDetail = showReactionDetail, addPadding = addPadding, one = { - WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) { + WatchReactionsZapsBoostsAndDisplayIfExists(baseNote, accountViewModel) { RenderShowIndividualReactionsButton(wantsToSeeReactions, accountViewModel) } }, @@ -345,7 +345,7 @@ fun RenderZapRaiser( details: Boolean, accountViewModel: AccountViewModel, ) { - val zapsState by observeNoteZaps(baseNote) + val zapsState by observeNoteZaps(baseNote, accountViewModel) var zapraiserStatus by remember { mutableStateOf(ZapraiserStatus(0F, "$zapraiserAmount")) } @@ -393,9 +393,10 @@ fun RenderZapRaiser( @Composable private fun WatchReactionsZapsBoostsAndDisplayIfExists( baseNote: Note, + accountViewModel: AccountViewModel, content: @Composable () -> Unit, ) { - val hasReactions by observeNoteReferences(baseNote) + val hasReactions by observeNoteReferences(baseNote, accountViewModel) if (hasReactions) { content() @@ -434,7 +435,7 @@ private fun ReactionDetailGallery( val defaultBackgroundColor = MaterialTheme.colorScheme.background val backgroundColor = remember { mutableStateOf(defaultBackgroundColor) } - val hasReactions by observeNoteReferences(baseNote) + val hasReactions by observeNoteReferences(baseNote, accountViewModel) if (hasReactions) { Row( @@ -456,7 +457,7 @@ private fun WatchBoostsAndRenderGallery( nav: INav, accountViewModel: AccountViewModel, ) { - val boostsEvents by observeNoteReposts(baseNote) + val boostsEvents by observeNoteReposts(baseNote, accountViewModel) boostsEvents?.let { if (it.note.boosts.isNotEmpty()) { @@ -475,7 +476,7 @@ private fun WatchReactionsAndRenderGallery( nav: INav, accountViewModel: AccountViewModel, ) { - val reactionsState by observeNoteReactions(baseNote) + val reactionsState by observeNoteReactions(baseNote, accountViewModel) val reactionEvents = reactionsState?.note?.reactions ?: return if (reactionEvents.isNotEmpty()) { @@ -498,7 +499,7 @@ private fun WatchZapAndRenderGallery( nav: INav, accountViewModel: AccountViewModel, ) { - val zapsState by observeNoteZaps(baseNote) + val zapsState by observeNoteZaps(baseNote, accountViewModel) var zapEvents by remember(zapsState) { @@ -655,7 +656,7 @@ fun ReplyCounter( textColor: Color, accountViewModel: AccountViewModel, ) { - val repliesState by observeNoteReplyCount(baseNote) + val repliesState by observeNoteReplyCount(baseNote, accountViewModel) SlidingAnimationCount(repliesState, textColor, accountViewModel) } @@ -790,7 +791,7 @@ fun ObserveBoostIcon( accountViewModel: AccountViewModel, inner: @Composable (Boolean) -> Unit, ) { - val hasBoosted by observeNoteRepostsBy(baseNote, accountViewModel.userProfile()) + val hasBoosted by observeNoteRepostsBy(baseNote, accountViewModel.userProfile(), accountViewModel) inner(hasBoosted) } @@ -801,7 +802,7 @@ fun BoostText( grayTint: Color, accountViewModel: AccountViewModel, ) { - val boostState by observeNoteRepostCount(baseNote) + val boostState by observeNoteRepostCount(baseNote, accountViewModel) SlidingAnimationCount(boostState, grayTint, accountViewModel) } @@ -862,7 +863,7 @@ fun LikeReaction( } } - ObserveLikeText(baseNote) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) } + ObserveLikeText(baseNote, accountViewModel) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) } } @Composable @@ -871,7 +872,7 @@ fun ObserveLikeIcon( accountViewModel: AccountViewModel, inner: @Composable (String?) -> Unit, ) { - val reactionsState by observeNoteReactions(baseNote) + val reactionsState by observeNoteReactions(baseNote, accountViewModel) @Suppress("ProduceStateDoesNotAssignValue") val reactionType by @@ -926,9 +927,10 @@ private fun RenderReactionType( @Composable fun ObserveLikeText( baseNote: Note, + accountViewModel: AccountViewModel, inner: @Composable (Int) -> Unit, ) { - val reactionCount by observeNoteReactionCount(baseNote) + val reactionCount by observeNoteReactionCount(baseNote, accountViewModel) inner(reactionCount) } @@ -1182,7 +1184,7 @@ fun ObserveZapIcon( val wasZappedByLoggedInUser = remember { mutableStateOf(false) } if (!wasZappedByLoggedInUser.value) { - val zapsState by observeNoteZaps(baseNote) + val zapsState by observeNoteZaps(baseNote, accountViewModel) LaunchedEffect(key1 = zapsState) { if (zapsState?.note?.zapPayments?.isNotEmpty() == true || zapsState?.note?.zaps?.isNotEmpty() == true) { @@ -1204,7 +1206,7 @@ fun ObserveZapAmountText( accountViewModel: AccountViewModel, inner: @Composable (String) -> Unit, ) { - val zapsState by observeNoteZaps(baseNote) + val zapsState by observeNoteZaps(baseNote, accountViewModel) if (zapsState?.note?.zapPayments?.isNotEmpty() == true) { @Suppress("ProduceStateDoesNotAssignValue") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt index 592828410..afdc46128 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt @@ -69,6 +69,7 @@ fun ReplyInformationChannel( ReplyInformationChannel( replyTo, sortedMentions, + accountViewModel = accountViewModel, onUserTagClick = { nav.nav(routeFor(it)) }, ) Spacer(modifier = StdVertSpacer) @@ -81,6 +82,7 @@ fun ReplyInformationChannel( replyTo: ImmutableList?, mentions: ImmutableList?, prefix: String = "", + accountViewModel: AccountViewModel, onUserTagClick: (User) -> Unit, ) { FlowRow { @@ -93,7 +95,7 @@ fun ReplyInformationChannel( ) mentions.forEachIndexed { idx, user -> - ReplyInfoMention(user, prefix, onUserTagClick) + ReplyInfoMention(user, prefix, accountViewModel, onUserTagClick) if (idx < mentions.size - 2) { Text( @@ -118,9 +120,10 @@ fun ReplyInformationChannel( private fun ReplyInfoMention( user: User, prefix: String, + accountViewModel: AccountViewModel, onUserTagClick: (User) -> Unit, ) { - val innerUserState by observeUserInfo(user) + val innerUserState by observeUserInfo(user, accountViewModel) CreateClickableTextWithEmoji( clickablePart = "$prefix${innerUserState?.bestName()}", diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index a9f1b4482..ed74a043e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -385,7 +385,7 @@ private fun EmojiSelector( accountViewModel, ) { emptyNote -> emptyNote?.let { usersEmojiList -> - val collections by observeNoteEventAndMap(usersEmojiList) { event: EmojiPackSelectionEvent -> + val collections by observeNoteEventAndMap(usersEmojiList, accountViewModel) { event: EmojiPackSelectionEvent -> event.emojiPackIds().toImmutableList() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 0a7016e5c..9c3a0bf53 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -57,7 +57,7 @@ fun UserCompose( UsernameDisplay(baseUser, accountViewModel = accountViewModel) } - AboutDisplay(baseUser) + AboutDisplay(baseUser, accountViewModel) } Column(modifier = remember { Modifier.padding(start = 10.dp) }) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt index 9fbdb3914..9d6cc27c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt @@ -52,10 +52,10 @@ import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey @Composable fun NoteAuthorPicture( baseNote: Note, - nav: INav, - accountViewModel: AccountViewModel, size: Dp, pictureModifier: Modifier = Modifier, + accountViewModel: AccountViewModel, + nav: INav, ) { NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) { nav.nav(routeFor(it)) @@ -304,7 +304,7 @@ fun BaseUserPicture( outerModifier: Modifier = Modifier.size(size), ) { Box(outerModifier, contentAlignment = Alignment.TopEnd) { - LoadUserProfilePicture(baseUser) { userProfilePicture, userName -> + LoadUserProfilePicture(baseUser, accountViewModel) { userProfilePicture, userName -> InnerUserPicture( userHex = baseUser.pubkeyHex, userPicture = userProfilePicture, @@ -326,9 +326,10 @@ fun BaseUserPicture( @Composable fun LoadUserProfilePicture( baseUser: User, + accountViewModel: AccountViewModel, innerContent: @Composable (String?, String?) -> Unit, ) { - val userProfile by observeUserInfo(baseUser) + val userProfile by observeUserInfo(baseUser, accountViewModel) innerContent(userProfile?.profilePicture(), userProfile?.bestName()) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 45f06c424..1f25fcfa0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -54,7 +54,7 @@ fun NoteUsernameDisplay( textColor: Color = Color.Unspecified, accountViewModel: AccountViewModel, ) { - WatchAuthor(baseNote) { + WatchAuthor(baseNote, accountViewModel) { UsernameDisplay(it, weight, textColor = textColor, accountViewModel = accountViewModel) } } @@ -62,13 +62,14 @@ fun NoteUsernameDisplay( @Composable fun WatchAuthor( baseNote: Note, + accountViewModel: AccountViewModel, inner: @Composable (User) -> Unit, ) { val noteAuthor = baseNote.author if (noteAuthor != null) { inner(noteAuthor) } else { - val authorState by observeNote(baseNote) + val authorState by observeNote(baseNote, accountViewModel) authorState?.note?.author?.let { inner(it) } @@ -86,7 +87,7 @@ fun WatchAuthorWithBlank( if (noteAuthor != null) { inner(noteAuthor) } else { - val authorState by observeNote(baseNote) + val authorState by observeNote(baseNote, accountViewModel) CrossfadeIfEnabled(targetState = authorState?.note?.author, modifier = modifier, label = "WatchAuthorWithBlank", accountViewModel = accountViewModel) { newAuthor -> inner(newAuthor) } @@ -101,7 +102,7 @@ fun UsernameDisplay( textColor: Color = Color.Unspecified, accountViewModel: AccountViewModel, ) { - val userMetadata by observeUserInfo(baseUser) + val userMetadata by observeUserInfo(baseUser, accountViewModel) CrossfadeIfEnabled(targetState = userMetadata, modifier = weight, label = "UsernameDisplay", accountViewModel = accountViewModel) { val name = it?.bestName() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt index 5c47b0998..376b508a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/WatchNoteEvent.kt @@ -74,7 +74,7 @@ fun WatchNoteEvent( onNoteEventFound() } else { // avoid observing costs if already has an event. - val hasEvent by observeNoteHasEvent(baseNote) + val hasEvent by observeNoteHasEvent(baseNote, accountViewModel) CrossfadeIfEnabled(targetState = hasEvent, label = "Event presence", accountViewModel = accountViewModel) { if (it) { onNoteEventFound() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 59c2e7ad7..6e7301013 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -66,7 +66,7 @@ fun ZapNoteCompose( accountViewModel: AccountViewModel, nav: INav, ) { - val baseNoteRequest by observeNote(baseReqResponse.zapRequest) + val baseNoteRequest by observeNote(baseReqResponse.zapRequest, accountViewModel) var baseAuthor by remember { mutableStateOf(null) } @@ -115,14 +115,14 @@ private fun RenderZapNote( modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }, ) { Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseAuthor, accountViewModel = accountViewModel) } - Row(verticalAlignment = Alignment.CenterVertically) { AboutDisplay(baseAuthor) } + Row(verticalAlignment = Alignment.CenterVertically) { AboutDisplay(baseAuthor, accountViewModel) } } Column( modifier = remember { Modifier.padding(start = 10.dp) }, verticalArrangement = Arrangement.Center, ) { - ZapAmount(zapNote) + ZapAmount(zapNote, accountViewModel) } Column(modifier = Modifier.padding(start = 10.dp)) { @@ -132,8 +132,11 @@ private fun RenderZapNote( } @Composable -private fun ZapAmount(zapEventNote: Note) { - val noteState by observeNote(zapEventNote) +private fun ZapAmount( + zapEventNote: Note, + accountViewModel: AccountViewModel, +) { + val noteState by observeNote(zapEventNote, accountViewModel) var zapAmount by remember { mutableStateOf(null) } @@ -174,7 +177,7 @@ fun ShowFollowingOrUnfollowingButton( baseAuthor: User, accountViewModel: AccountViewModel, ) { - var isFollowing = observeUserIsFollowing(accountViewModel.account.userProfile(), baseAuthor) + var isFollowing = observeUserIsFollowing(accountViewModel.account.userProfile(), baseAuthor, accountViewModel) if (isFollowing.value) { UnfollowButton { @@ -202,8 +205,11 @@ fun ShowFollowingOrUnfollowingButton( } @Composable -fun AboutDisplay(baseAuthor: User) { - val aboutMe by observeUserAboutMe(baseAuthor) +fun AboutDisplay( + baseAuthor: User, + accountViewModel: AccountViewModel, +) { + val aboutMe by observeUserAboutMe(baseAuthor, accountViewModel) Text( aboutMe, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt index 6f35ada2f..4abcd3da9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt @@ -106,7 +106,7 @@ fun ZapUserSetCompose( Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(zapSetCard.user, accountViewModel = accountViewModel) } - AboutDisplay(zapSetCard.user) + AboutDisplay(zapSetCard.user, accountViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/emojiSuggestions/WatchAndLoadMyEmojiList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/emojiSuggestions/WatchAndLoadMyEmojiList.kt index ff1d0403b..f53e79a06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/emojiSuggestions/WatchAndLoadMyEmojiList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/emojiSuggestions/WatchAndLoadMyEmojiList.kt @@ -37,14 +37,14 @@ fun WatchAndLoadMyEmojiList(accountViewModel: AccountViewModel) { accountViewModel, ) { emptyNote -> emptyNote?.let { usersEmojiList -> - val collections by observeNoteEventAndMap(usersEmojiList) { event: EmojiPackSelectionEvent -> + val collections by observeNoteEventAndMap(usersEmojiList, accountViewModel) { event: EmojiPackSelectionEvent -> event.taggedAddresses().toImmutableList() } collections?.forEach { address -> LoadAddressableNote(address, accountViewModel) { note -> if (note != null) { - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt index ba8dc7b40..17f0c66ca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt @@ -55,7 +55,7 @@ fun ShowUserSuggestionList( accountViewModel: AccountViewModel, modifier: Modifier = Modifier.heightIn(0.dp, 200.dp), ) { - UserSearchDataSourceSubscription(userSuggestions) + UserSearchDataSourceSubscription(userSuggestions, accountViewModel) val listState = rememberLazyListState() @@ -135,7 +135,7 @@ fun UserLine( ) } - AboutDisplay(baseUser) + AboutDisplay(baseUser, accountViewModel) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt index 43643d5f7..ced1f2e99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt @@ -106,7 +106,7 @@ fun ObserveRelayListForDMs( accountViewModel, ) { relayList -> if (relayList != null) { - val relayListEvent by observeNoteEvent(relayList) + val relayListEvent by observeNoteEvent(relayList, accountViewModel) inner(relayListEvent) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt index 461d5bbfa..fa49b0bd0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DefaultImageHeader.kt @@ -48,9 +48,9 @@ fun DefaultImageHeader( note: Note, accountViewModel: AccountViewModel, ) { - WatchAuthor(baseNote = note) { + WatchAuthor(baseNote = note, accountViewModel) { Box { - BannerImage(it, Modifier.fillMaxWidth().heightIn(max = 200.dp)) + BannerImage(it, Modifier.fillMaxWidth().heightIn(max = 200.dp), accountViewModel) Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) { BaseUserPicture(it, Size55dp, accountViewModel, Modifier) @@ -63,8 +63,9 @@ fun DefaultImageHeader( fun BannerImage( author: User, modifier: Modifier = Modifier, + accountViewModel: AccountViewModel, ) { - val banner by observeUserBanner(author) + val banner by observeUserBanner(author, accountViewModel) BannerImage(banner, modifier) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt index 3423514ae..d31882dc0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayReward.kt @@ -115,7 +115,7 @@ private fun RenderPledgeAmount( baseReward: Reward, accountViewModel: AccountViewModel, ) { - val repliesState by observeNoteReplies(baseNote) + val repliesState by observeNoteReplies(baseNote, accountViewModel) var reward by remember { mutableStateOf( showAmount(baseReward.amount), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt index 3384d14e3..10b77f9db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt @@ -382,8 +382,8 @@ fun WatchBookmarksFollowsAndAccount( accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit, ) { - val followState by observeUserFollows(accountViewModel.userProfile()) - val bookmarkState by observeUserBookmarks(accountViewModel.userProfile()) + val followState by observeUserFollows(accountViewModel.userProfile(), accountViewModel) + val bookmarkState by observeUserBookmarks(accountViewModel.userProfile(), accountViewModel) val showSensitiveContent by accountViewModel.showSensitiveContent().collectAsStateWithLifecycle() LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = showSensitiveContent) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt index 22380b4d2..6ab9fc8bb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ForkInfo.kt @@ -82,7 +82,7 @@ fun ForkInformationRowLightColor( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(originalVersion) + val noteState by observeNote(originalVersion, accountViewModel) val note = noteState?.note ?: return val author = note.author ?: return val route = remember(note) { routeFor(note, accountViewModel.userProfile()) } @@ -105,7 +105,7 @@ fun ForkInformationRowLightColor( overflow = TextOverflow.Visible, ) - val userState by observeUser(author) + val userState by observeUser(author, accountViewModel) userState?.user?.toBestDisplayName()?.let { CreateClickableTextWithEmoji( clickablePart = it, @@ -128,19 +128,19 @@ fun ForkInformationRow( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(originalVersion) + val noteState by observeNote(originalVersion, accountViewModel) val note = noteState?.note ?: return val route = remember(note) { routeFor(note, accountViewModel.userProfile()) } if (route != null) { Row(modifier) { val author = note.author ?: return - val meta by observeUserInfo(author) + val meta by observeUserInfo(author, accountViewModel) Text(stringRes(id = R.string.forked_from)) Spacer(modifier = StdHorzSpacer) - val userMetadata by observeUserInfo(author) + val userMetadata by observeUserInfo(author, accountViewModel) CreateClickableTextWithEmoji( clickablePart = remember(meta) { meta?.bestName() ?: author.pubkeyDisplayHex() }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt index 53538a51a..9bfc40b71 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt @@ -188,7 +188,7 @@ fun ZapTheDevsCard( accountViewModel: AccountViewModel, nav: INav, ) { - val releaseNoteState by observeNote(baseNote) + val releaseNoteState by observeNote(baseNote, accountViewModel) val releaseNote = releaseNoteState?.note ?: return Row(modifier = Modifier.padding(start = Size10dp, end = Size10dp, bottom = Size10dp)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt index dd76b4271..fc5184f92 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Badge.kt @@ -57,8 +57,11 @@ import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent @Composable -fun BadgeDisplay(baseNote: Note) { - val badgeData by observeNoteEvent(baseNote) +fun BadgeDisplay( + baseNote: Note, + accountViewModel: AccountViewModel, +) { + val badgeData by observeNoteEvent(baseNote, accountViewModel) badgeData?.let { RenderBadge( @@ -174,6 +177,6 @@ fun RenderBadgeAward( } note.replyTo?.firstOrNull()?.let { - BadgeDisplay(baseNote = it) + BadgeDisplay(baseNote = it, accountViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 81e03f9d4..45abe6588 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -119,7 +119,7 @@ fun LongCommunityHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val noteEvent by observeNoteEvent(baseNote) + val noteEvent by observeNoteEvent(baseNote, accountViewModel) Row( lineModifier, @@ -195,7 +195,7 @@ fun LongCommunityHeader( modifier = Modifier.width(75.dp), ) Spacer(DoubleHorzSpacer) - NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp) + NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav) Spacer(DoubleHorzSpacer) NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) } @@ -264,7 +264,7 @@ fun ShortCommunityHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val noteEvent by observeNoteEvent(baseNote) + val noteEvent by observeNoteEvent(baseNote, accountViewModel) Row(verticalAlignment = Alignment.CenterVertically) { noteEvent?.image()?.let { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt index 5912e66f9..df22ff60c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Emoji.kt @@ -70,7 +70,7 @@ public fun RenderEmojiPack( accountViewModel: AccountViewModel, onClick: ((EmojiUrlTag) -> Unit)? = null, ) { - val noteEvent by observeNoteEvent(baseNote) + val noteEvent by observeNoteEvent(baseNote, accountViewModel) noteEvent?.let { RenderEmojiPack( @@ -179,7 +179,9 @@ private fun EmojiListOptions( accountViewModel, ) { it?.let { usersEmojiList -> - val hasAddedThis by observeNoteAndMap(usersEmojiList) { usersEmojiList.event?.isTaggedAddressableNote(emojiPackNote.idHex) } + val hasAddedThis by observeNoteAndMap(usersEmojiList, accountViewModel) { + usersEmojiList.event?.isTaggedAddressableNote(emojiPackNote.idHex) + } CrossfadeIfEnabled(targetState = hasAddedThis, label = "EmojiListOptions", accountViewModel = accountViewModel) { if (it != true) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt index af6299375..8654a9a4c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/FileStorage.kt @@ -67,7 +67,7 @@ private fun ObserverAndRenderNIP95( ) { val eventHeader = (header.event as? FileStorageHeaderEvent) ?: return - val noteState by observeNote(content) + val noteState by observeNote(content, accountViewModel) val content by remember(noteState) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt index df3e18d9b..82a7845bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt @@ -100,7 +100,7 @@ private fun RenderShortRepositoryHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val noteEvent by observeNoteEvent(baseNote) + val noteEvent by observeNoteEvent(baseNote, accountViewModel) Column( modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt index 2dbb15673..8cd535818 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt @@ -236,7 +236,7 @@ fun DisplayEntryForUser( accountViewModel: AccountViewModel, nav: INav, ) { - val userMetadata by observeUserInfo(baseUser) + val userMetadata by observeUserInfo(baseUser, accountViewModel) CreateClickableTextWithEmoji( clickablePart = userMetadata?.bestName() ?: baseUser.pubkeyDisplayHex(), @@ -254,12 +254,12 @@ fun DisplayEntryForNote( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(note) + val noteState by observeNote(note, accountViewModel) val author = userBase ?: noteState?.note?.author if (author != null) { - RenderUserAsClickableText(author, null, nav) + RenderUserAsClickableText(author, null, accountViewModel, nav) } val noteEvent = noteState?.note?.event as? BaseThreadedEvent ?: return diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/InteractiveStory.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/InteractiveStory.kt index 276837be7..5c8dcf595 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/InteractiveStory.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/InteractiveStory.kt @@ -65,19 +65,19 @@ fun RenderInteractiveStory( val address = baseNote.address() ?: return // keep updating the root event with new versions - val note = observeNote(baseNote) + val note = observeNote(baseNote, accountViewModel) val rootEvent = note.value?.note?.event as? InteractiveStoryBaseEvent ?: return // keep updating the reading state event with new versions val readingStateNote = accountViewModel.getInteractiveStoryReadingState(address.toValue()) - val readingState by observeNoteEvent(readingStateNote) + val readingState by observeNoteEvent(readingStateNote, accountViewModel) val currentScene = readingState?.currentScene() if (currentScene != null && currentScene != rootEvent.address()) { LoadAddressableNote(currentScene, accountViewModel) { currentSceneBaseNote -> currentSceneBaseNote?.let { - val currentSceneEvent by observeNoteEvent(it) + val currentSceneEvent by observeNoteEvent(it, accountViewModel) currentSceneEvent?.let { RenderInteractiveStory( @@ -166,7 +166,7 @@ fun RenderInteractiveStory( options.forEach { opt -> LoadAddressableNote(opt.address, accountViewModel) { note -> if (note != null) { - EventFinderFilterAssemblerSubscription(note) + EventFinderFilterAssemblerSubscription(note, accountViewModel) OutlinedButton( onClick = { onSelect(note) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index 4445c493e..14789fbfb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -130,7 +130,7 @@ fun RenderLiveActivityEventInner( ) { val noteEvent = baseNote.event as? LiveActivitiesEvent ?: return - val eventUpdates by observeNote(baseNote) + val eventUpdates by observeNote(baseNote, accountViewModel) val media = remember(eventUpdates) { noteEvent.streaming() } val cover = remember(eventUpdates) { noteEvent.image() } @@ -222,7 +222,7 @@ fun RenderLiveActivityEventInner( ) { AsyncImage(model = it, contentDescription = null, modifier = MaterialTheme.colorScheme.imageModifier) } - } ?: run { DisplayAuthorBanner(baseNote) } + } ?: run { DisplayAuthorBanner(baseNote, accountViewModel) } Text( text = stringRes(id = R.string.live_stream_has_ended), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt index 8371e239e..8543847c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt @@ -278,7 +278,7 @@ private fun RelayOptionsAction( accountViewModel: AccountViewModel, nav: INav, ) { - val isCurrentlyOnTheUsersList by observeUserRelayIntoList(accountViewModel.userProfile(), relay) + val isCurrentlyOnTheUsersList by observeUserRelayIntoList(accountViewModel.userProfile(), relay, accountViewModel) if (isCurrentlyOnTheUsersList) { AddRelayButton { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt index d91f3affa..0c9136dae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TextModification.kt @@ -131,7 +131,7 @@ fun RenderTextModificationEvent( noteEvent.editedNote()?.let { LoadNote(baseNoteHex = it.eventId, accountViewModel = accountViewModel) { baseNote -> baseNote?.let { - val noteState by observeNote(baseNote) + val noteState by observeNote(baseNote, accountViewModel) val editStateOriginalNote = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt index 2ebfe7c93..4d0646768 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt @@ -223,7 +223,7 @@ fun ShortTorrentHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val noteEvent by observeNoteEvent(baseNote) + val noteEvent by observeNoteEvent(baseNote, accountViewModel) ShortTorrentHeader( title = noteEvent?.title() ?: TorrentEvent.ALT_DESCRIPTION, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt index bf38ef5e9..12aab5654 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt @@ -28,11 +28,12 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.screen.FollowListState import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListNewFeedFilter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal.DiscoverChatFeedFilter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal.DiscoverCommunityFeedFilter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal.DiscoverLiveFeedFilter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal.DiscoverMarketplaceFeedFilter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal.DiscoverNIP89FeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.DiscoverChatFeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.DiscoverFollowSetsFeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.DiscoverLiveFeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.DiscoverCommunityFeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.DiscoverNIP89FeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.DiscoverMarketplaceFeedFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeLiveFilter import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter @@ -53,6 +54,7 @@ class AccountFeedContentStates( val videoFeed = FeedContentState(VideoFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val discoverFollowSets = FeedContentState(DiscoverFollowSetsFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) val discoverMarketplace = FeedContentState(DiscoverMarketplaceFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) val discoverDVMs = FeedContentState(DiscoverNIP89FeedFilter(accountViewModel.account), accountViewModel.viewModelScope) val discoverLive = FeedContentState(DiscoverLiveFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index f1e06ecb1..6b60abcfb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -151,6 +151,7 @@ import okhttp3.OkHttpClient class AccountViewModel( accountSettings: AccountSettings, val settings: SharedSettingsState, + val app: Amethyst, ) : ViewModel(), Dao { val account = Account(accountSettings, accountSettings.createSigner(), viewModelScope) @@ -1010,7 +1011,7 @@ class AccountViewModel( .verifyNip05( nip05, okttpClient = { - Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP05(it)) + app.okHttpClients.getHttpClient(account.shouldUseTorForNIP05(it)) }, onSuccess = { // Marks user as verified @@ -1196,16 +1197,16 @@ class AccountViewModel( onReady: (ImmutableList) -> Unit, ) { viewModelScope.launch(Dispatchers.Default) { - onReady( - hexList - .mapNotNull { hex -> checkGetOrCreateUser(hex) } - .sortedBy { account.isFollowing(it) } - .reversed() - .toImmutableList(), - ) + onReady(loadUsersSync(hexList).toImmutableList()) } } + fun loadUsersSync(hexList: List): List = + hexList + .mapNotNull { hex -> checkGetOrCreateUser(hex) } + .sortedBy { account.isFollowing(it) } + .reversed() + fun loadUsers( event: GeneralListEvent, onReady: (ImmutableList) -> Unit, @@ -1292,43 +1293,44 @@ class AccountViewModel( viewModelScope.launch(Dispatchers.IO) { // Only restart relay connections if port or type changes if (account.settings.setTorSettings(newTorSettings)) { - Amethyst.instance.serviceManager.forceRestart() + app.serviceManager.forceRestart() } } fun forceRestartServices() = viewModelScope.launch(Dispatchers.IO) { - Amethyst.instance.serviceManager.setAccountAndRestart(account) + app.serviceManager.setAccountAndRestart(account) } fun justStart() = viewModelScope.launch(Dispatchers.IO) { - Amethyst.instance.serviceManager.justStartIfItHasAccount() + app.serviceManager.justStartIfItHasAccount() } fun justPause() = viewModelScope.launch(Dispatchers.IO) { - Amethyst.instance.serviceManager.cleanObservers() - Amethyst.instance.serviceManager.pauseForGood() + app.serviceManager.cleanObservers() + app.serviceManager.pauseForGood() } fun pauseAndLogOff() = viewModelScope.launch(Dispatchers.IO) { - Amethyst.instance.serviceManager.cleanObservers() - Amethyst.instance.serviceManager.pauseAndLogOff() + app.serviceManager.cleanObservers() + app.serviceManager.pauseAndLogOff() } fun changeProxyPort(port: Int) = viewModelScope.launch(Dispatchers.IO) { - Amethyst.instance.serviceManager.forceRestart() + app.serviceManager.forceRestart() } class Factory( val accountSettings: AccountSettings, val settings: SharedSettingsState, + val app: Amethyst, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = AccountViewModel(accountSettings, settings) as T + override fun create(modelClass: Class): T = AccountViewModel(accountSettings, settings, app) as T } private var collectorJob: Job? = null @@ -1537,23 +1539,23 @@ class AccountViewModel( } } - fun proxyPortFor(url: String): Int? = Amethyst.instance.okHttpClients.getCurrentProxyPort(account.shouldUseTorForVideoDownload(url)) + fun proxyPortFor(url: String): Int? = app.okHttpClients.getCurrentProxyPort(account.shouldUseTorForVideoDownload(url)) - fun okHttpClientForNip96(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(url)) + fun okHttpClientForNip96(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(url)) - fun okHttpClientForImage(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload(url)) + fun okHttpClientForImage(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload(url)) - fun okHttpClientForVideo(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForVideoDownload(url)) + fun okHttpClientForVideo(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForVideoDownload(url)) - fun okHttpClientForMoney(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForMoneyOperations(url)) + fun okHttpClientForMoney(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForMoneyOperations(url)) - fun okHttpClientForPreview(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForPreviewUrl(url)) + fun okHttpClientForPreview(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForPreviewUrl(url)) - fun okHttpClientForDirty(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForDirty(url)) + fun okHttpClientForDirty(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForDirty(url)) - fun okHttpClientForTrustedRelays(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForTrustedRelays()) + fun okHttpClientForTrustedRelays(url: String): OkHttpClient = app.okHttpClients.getHttpClient(account.shouldUseTorForTrustedRelays()) - fun dataSources() = Amethyst.instance.sources + fun dataSources() = app.sources suspend fun deleteDraft(draftTag: String) { account.deleteDraft(draftTag) @@ -1699,7 +1701,7 @@ class AccountViewModel( suspend fun findUsersStartingWithSync(prefix: String) = LocalCache.findUsersStartingWith(prefix, account) - fun relayStatusFlow() = Amethyst.instance.client.relayStatusFlow() + fun relayStatusFlow() = app.client.relayStatusFlow() fun allAccountsSync(): List = runBlocking { @@ -1802,6 +1804,7 @@ fun mockAccountViewModel(): AccountViewModel { ), ), sharedPreferencesViewModel.sharedPrefs, + Amethyst(), ) } @@ -1819,5 +1822,6 @@ fun mockVitorAccountViewModel(): AccountViewModel { ), ), sharedPreferencesViewModel.sharedPrefs, + Amethyst(), ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt index 4ba0f36e4..cada2ff43 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt @@ -76,6 +76,7 @@ fun LoggedInPage( AccountViewModel.Factory( accountSettings, sharedPreferencesViewModel.sharedPrefs, + Amethyst.instance, ), ) @@ -97,7 +98,7 @@ fun LoggedInPage( DiscoveryFilterAssemblerSubscription(accountViewModel) // TODO: Is this needed? - RelaySubscriptionsCoordinatorSubscription() + RelaySubscriptionsCoordinatorSubscription(accountViewModel) // Updates local cache of the anti-spam filter choice of this user. ObserveAntiSpamFilterSettings(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt index a5dc39403..7e0cf3bda 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt @@ -329,7 +329,7 @@ fun NewPostScreen( } Row { - Notifying(postViewModel.pTags?.toImmutableList()) { + Notifying(postViewModel.pTags?.toImmutableList(), accountViewModel) { postViewModel.removeFromReplyList(it) } } @@ -579,6 +579,7 @@ fun DisplayPreviews( @Composable fun Notifying( baseMentions: ImmutableList?, + accountViewModel: AccountViewModel, onClick: (User) -> Unit, ) { val mentions = baseMentions?.toSet() @@ -601,7 +602,7 @@ fun Notifying( ), onClick = { onClick(user) }, ) { - DisplayUserNameWithDeleteMark(user) + DisplayUserNameWithDeleteMark(user, accountViewModel) } } } @@ -609,8 +610,11 @@ fun Notifying( } @Composable -private fun DisplayUserNameWithDeleteMark(user: User) { - val innerUserState by observeUser(user) +private fun DisplayUserNameWithDeleteMark( + user: User, + accountViewModel: AccountViewModel, +) { + val innerUserState by observeUser(user, accountViewModel) innerUserState?.user?.let { myUser -> CreateTextWithEmoji( text = remember(innerUserState) { "✖ ${myUser.toBestDisplayName()}" }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DrawAuthorInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DrawAuthorInfo.kt index e0a5ae308..9822fb1fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DrawAuthorInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/DrawAuthorInfo.kt @@ -55,7 +55,7 @@ private fun WatchAndDisplayUser( accountViewModel: AccountViewModel, nav: INav, ) { - val userState by observeUserInfo(author) + val userState by observeUserInfo(author, accountViewModel) UserDisplayNameLayout( picture = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RoomNameOnlyDisplay.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RoomNameOnlyDisplay.kt index ea8f82b1f..4aeb2e5e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RoomNameOnlyDisplay.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RoomNameOnlyDisplay.kt @@ -47,7 +47,7 @@ fun RoomNameOnlyDisplay( fontWeight: FontWeight = FontWeight.Bold, accountViewModel: AccountViewModel, ) { - val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room) + val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room, accountViewModel) CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) { if (!it.isNullOrBlank()) { @@ -98,7 +98,7 @@ fun RoomNameDisplay( modifier: Modifier, accountViewModel: AccountViewModel, ) { - val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room) + val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room, accountViewModel) CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) { if (!it.isNullOrBlank()) { @@ -158,7 +158,7 @@ fun ShortUsernameDisplay( fontWeight: FontWeight = FontWeight.Bold, accountViewModel: AccountViewModel, ) { - val userName by observeUserShortName(baseUser) + val userName by observeUserShortName(baseUser, accountViewModel) CrossfadeIfEnabled(targetState = userName, modifier = weight, accountViewModel = accountViewModel) { CreateTextWithEmoji( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/header/ShortEphemeralChatChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/header/ShortEphemeralChatChannelHeader.kt index fa9e0c957..d1445d20d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/header/ShortEphemeralChatChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/header/ShortEphemeralChatChannelHeader.kt @@ -82,7 +82,7 @@ fun ShortEphemeralChatChannelHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channel = channelState?.channel as? EphemeralChatChannel ?: return Row(verticalAlignment = Alignment.CenterVertically) { @@ -153,7 +153,7 @@ fun JoinEphemeralChatButtonIfNotAlreadyJoined( accountViewModel: AccountViewModel, nav: INav, ) { - val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel) + val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel, accountViewModel) if (!isFollowing) { JoinChatButton(channel, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt index 0d1f93517..56381bf66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt @@ -73,7 +73,7 @@ fun LongPublicChatChannelHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channel = channelState?.channel as? PublicChatChannel ?: return Spacer(StdVertSpacer) @@ -147,7 +147,7 @@ fun LongPublicChatChannelHeader( modifier = Modifier.width(75.dp), ) Spacer(DoubleHorzSpacer) - NoteAuthorPicture(note, nav, accountViewModel, Size25dp) + NoteAuthorPicture(note, Size25dp, accountViewModel = accountViewModel, nav = nav) Spacer(DoubleHorzSpacer) NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel) } @@ -211,7 +211,7 @@ fun LeaveButtonIfFollowing( accountViewModel: AccountViewModel, nav: INav, ) { - val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel) + val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel, accountViewModel) if (isFollowing) { LeaveChatButton(channel, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt index 458238609..5013290ea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt @@ -60,7 +60,7 @@ fun ShortPublicChatChannelHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channel = channelState?.channel as? PublicChatChannel ?: return Row(verticalAlignment = Alignment.CenterVertically) { @@ -139,7 +139,7 @@ fun JoinChatButtonIfNotAlreadyJoined( accountViewModel: AccountViewModel, nav: INav, ) { - val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel) + val isFollowing by observeUserIsFollowingChannel(accountViewModel.account, channel, accountViewModel) if (!isFollowing) { JoinChatButton(channel, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LongLiveActivityChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LongLiveActivityChannelHeader.kt index a8ac04453..266b64db5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LongLiveActivityChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LongLiveActivityChannelHeader.kt @@ -77,7 +77,7 @@ fun LongLiveActivityChannelHeader( accountViewModel: AccountViewModel, nav: INav, ) { - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channel = channelState?.channel as? LiveActivitiesChannel ?: return Row( @@ -129,7 +129,7 @@ fun LongLiveActivityChannelHeader( modifier = Modifier.width(75.dp), ) Spacer(DoubleHorzSpacer) - NoteAuthorPicture(note, nav, accountViewModel, Size25dp) + NoteAuthorPicture(note, Size25dp, accountViewModel = accountViewModel, nav = nav) Spacer(DoubleHorzSpacer) NoteUsernameDisplay(note, Modifier.weight(1f), accountViewModel = accountViewModel) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShortLiveActivityChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShortLiveActivityChannelHeader.kt index a2e841b96..01f71cd14 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShortLiveActivityChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShortLiveActivityChannelHeader.kt @@ -48,7 +48,7 @@ fun ShortLiveActivityChannelHeader( nav: INav, showFlag: Boolean, ) { - val channelState by observeChannel(baseChannel) + val channelState by observeChannel(baseChannel, accountViewModel) val channel = channelState?.channel as? LiveActivitiesChannel ?: return Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShowVideoStreaming.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShowVideoStreaming.kt index a38dabe5c..ee4f767fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShowVideoStreaming.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ShowVideoStreaming.kt @@ -42,7 +42,7 @@ fun ShowVideoStreaming( accountViewModel: AccountViewModel, ) { baseChannel.info?.let { - val streamingInfoEvent by observeChannelInfo(baseChannel) + val streamingInfoEvent by observeChannelInfo(baseChannel, accountViewModel) streamingInfoEvent?.let { event -> event.streaming()?.let { url -> val zoomableUrlVideo = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index 27ca003d6..8f559631b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -105,7 +105,7 @@ fun ChatroomHeaderCompose( if (baseNote.event != null) { ChatroomComposeChannelOrUser(baseNote, accountViewModel, nav) } else { - val hasEvent by observeNoteHasEvent(baseNote) + val hasEvent by observeNoteHasEvent(baseNote, accountViewModel) if (hasEvent) { ChatroomComposeChannelOrUser(baseNote, accountViewModel, nav) } else { @@ -186,8 +186,8 @@ private fun ChannelRoomCompose( accountViewModel: AccountViewModel, nav: INav, ) { - val authorName by observeUserName(note.author!!) - val channelState by observeChannel(channel) + val authorName by observeUserName(note.author!!, accountViewModel) + val channelState by observeChannel(channel, accountViewModel) val channelPicture = channelState?.channel?.profilePicture() ?: channel.profilePicture() val channelName = channelState?.channel?.toBestDisplayName() ?: channel.toBestDisplayName() @@ -227,8 +227,8 @@ private fun ChannelRoomCompose( accountViewModel: AccountViewModel, nav: INav, ) { - val authorName by observeUserName(note.author!!) - val channelState by observeChannel(channel) + val authorName by observeUserName(note.author!!, accountViewModel) + val channelState by observeChannel(channel, accountViewModel) val relayInfo = loadRelayInfo(channel.roomId.relayUrl, accountViewModel) val info = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt new file mode 100644 index 000000000..03e211b01 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.SensitivityWarning +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport +import com.vitorpamplona.amethyst.ui.note.ClickableNote +import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction +import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent +import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.RenderChannelThumb +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.RenderFollowSetThumb +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.RenderCommunitiesThumb +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.RenderContentDVMThumb +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.RenderClassifiedsThumb +import com.vitorpamplona.amethyst.ui.theme.HalfPadding +import com.vitorpamplona.amethyst.ui.theme.StdPadding +import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import com.vitorpamplona.quartz.nip51Lists.FollowListEvent +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent +import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent + +@Composable +fun ChannelCardCompose( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + forceEventKind: Int?, + isHiddenFeed: Boolean = false, + accountViewModel: AccountViewModel, + nav: INav, +) { + WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { + if (forceEventKind == null || baseNote.event?.kind == forceEventKind) { + CheckHiddenFeedWatchBlockAndReport( + note = baseNote, + modifier = modifier, + ignoreAllBlocksAndReports = isHiddenFeed, + showHiddenWarning = false, + accountViewModel = accountViewModel, + nav = nav, + ) { canPreview -> + NormalChannelCard( + baseNote = baseNote, + routeForLastRead = routeForLastRead, + modifier = modifier, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } +} + +@Composable +fun NormalChannelCard( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + accountViewModel: AccountViewModel, + nav: INav, +) { + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + CheckNewAndRenderChannelCard( + baseNote, + routeForLastRead, + modifier, + parentBackgroundColor, + accountViewModel, + showPopup, + nav, + ) + } +} + +@Composable +private fun CheckNewAndRenderChannelCard( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + accountViewModel: AccountViewModel, + showPopup: () -> Unit, + nav: INav, +) { + val backgroundColor = + calculateBackgroundColor( + createdAt = baseNote.createdAt(), + routeForLastRead = routeForLastRead, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + ) + + ClickableNote( + baseNote = baseNote, + backgroundColor = backgroundColor, + modifier = modifier, + accountViewModel = accountViewModel, + showPopup = showPopup, + nav = nav, + ) { + InnerChannelCardWithReactions( + baseNote = baseNote, + accountViewModel = accountViewModel, + nav = nav, + ) + } +} + +@Composable +fun InnerChannelCardWithReactions( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + when (baseNote.event) { + is LiveActivitiesEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is CommunityDefinitionEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is ChannelCreateEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is ClassifiedsEvent -> InnerCardBox(baseNote, accountViewModel, nav) + is AppDefinitionEvent -> InnerCardRow(baseNote, accountViewModel, nav) + is FollowListEvent -> InnerCardRow(baseNote, accountViewModel, nav) + } +} + +@Composable +fun InnerCardRow( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Column(StdPadding) { + SensitivityWarning( + note = baseNote, + accountViewModel = accountViewModel, + ) { + RenderNoteRow( + baseNote, + accountViewModel, + nav, + ) + } + } +} + +@Composable +fun InnerCardBox( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Column(HalfPadding) { + SensitivityWarning( + note = baseNote, + accountViewModel = accountViewModel, + ) { + RenderClassifiedsThumb(baseNote, accountViewModel, nav) + } + } +} + +@Composable +private fun RenderNoteRow( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + when (baseNote.event) { + is LiveActivitiesEvent -> { + RenderLiveActivityThumb(baseNote, accountViewModel, nav) + } + is CommunityDefinitionEvent -> { + RenderCommunitiesThumb(baseNote, accountViewModel, nav) + } + is ChannelCreateEvent -> { + RenderChannelThumb(baseNote, accountViewModel, nav) + } + is AppDefinitionEvent -> { + RenderContentDVMThumb(baseNote, accountViewModel, nav) + } + is FollowListEvent -> { + RenderFollowSetThumb(baseNote, accountViewModel, nav) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt index 096efcdf4..58d41063b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt @@ -52,7 +52,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R @@ -73,7 +72,6 @@ import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.Route -import com.vitorpamplona.amethyst.ui.note.ChannelCardCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.TabItem @@ -84,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size26Modifier import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.ui.theme.TabRowHeight import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import com.vitorpamplona.quartz.nip51Lists.FollowListEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent @@ -98,6 +97,7 @@ fun DiscoverScreen( nav: INav, ) { DiscoverScreen( + discoveryFollowSetsFeedContentState = accountViewModel.feedStates.discoverFollowSets, discoveryContentNIP89FeedContentState = accountViewModel.feedStates.discoverDVMs, discoveryMarketplaceFeedContentState = accountViewModel.feedStates.discoverMarketplace, discoveryLiveFeedContentState = accountViewModel.feedStates.discoverLive, @@ -111,6 +111,7 @@ fun DiscoverScreen( @OptIn(ExperimentalFoundationApi::class) @Composable fun DiscoverScreen( + discoveryFollowSetsFeedContentState: FeedContentState, discoveryContentNIP89FeedContentState: FeedContentState, discoveryMarketplaceFeedContentState: FeedContentState, discoveryLiveFeedContentState: FeedContentState, @@ -119,18 +120,17 @@ fun DiscoverScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val lifeCycleOwner = LocalLifecycleOwner.current - val tabs by - remember( - discoveryContentNIP89FeedContentState, - discoveryLiveFeedContentState, - discoveryCommunityFeedContentState, - discoveryChatFeedContentState, - discoveryMarketplaceFeedContentState, - ) { + remember(accountViewModel) { mutableStateOf( listOf( + TabItem( + R.string.discover_follows, + discoveryFollowSetsFeedContentState, + "DiscoverFollowSets", + ScrollStateKeys.DISCOVER_CONTENT, + FollowListEvent.KIND, + ), TabItem( R.string.discover_content, discoveryContentNIP89FeedContentState, @@ -174,6 +174,7 @@ fun DiscoverScreen( val pagerState = rememberForeverPagerState(key = PagerStateKeys.DISCOVER_SCREEN) { tabs.size } WatchAccountForDiscoveryScreen( + discoveryFollowSetsFeedContentState = discoveryFollowSetsFeedContentState, discoveryContentNIP89FeedContentState = discoveryContentNIP89FeedContentState, discoveryMarketplaceFeedContentState = discoveryMarketplaceFeedContentState, discoveryLiveFeedContentState = discoveryLiveFeedContentState, @@ -182,6 +183,7 @@ fun DiscoverScreen( accountViewModel = accountViewModel, ) + WatchLifecycleAndUpdateModel(discoveryFollowSetsFeedContentState) WatchLifecycleAndUpdateModel(discoveryContentNIP89FeedContentState) WatchLifecycleAndUpdateModel(discoveryMarketplaceFeedContentState) WatchLifecycleAndUpdateModel(discoveryLiveFeedContentState) @@ -376,6 +378,7 @@ private fun RenderDiscoverFeed( @Composable fun WatchAccountForDiscoveryScreen( + discoveryFollowSetsFeedContentState: FeedContentState, discoveryContentNIP89FeedContentState: FeedContentState, discoveryMarketplaceFeedContentState: FeedContentState, discoveryLiveFeedContentState: FeedContentState, @@ -386,6 +389,7 @@ fun WatchAccountForDiscoveryScreen( val listState by accountViewModel.account.liveDiscoveryFollowLists.collectAsStateWithLifecycle() LaunchedEffect(accountViewModel, listState) { + discoveryFollowSetsFeedContentState.checkKeysInvalidateDataAndSendToTop() discoveryContentNIP89FeedContentState.checkKeysInvalidateDataAndSendToTop() discoveryMarketplaceFeedContentState.checkKeysInvalidateDataAndSendToTop() discoveryLiveFeedContentState.checkKeysInvalidateDataAndSendToTop() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt index eb5676e38..67ceb780c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoveryTopBar.kt @@ -40,6 +40,7 @@ fun DiscoveryTopBar( FollowListWithoutRoutes( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, + accountViewModel, ) { listName -> accountViewModel.account.settings.changeDefaultDiscoveryFollowList(listName.code) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt index 6d9313ac0..5e12277db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/datasource/DiscoveryFilterAssembler.kt @@ -33,6 +33,7 @@ import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent +import com.vitorpamplona.quartz.nip51Lists.FollowListEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent @@ -238,6 +239,25 @@ class DiscoveryFilterAssembler( ) } + fun createFollowSetFilter(key: DiscoveryQueryState): List { + val follows = + key.account.liveDiscoveryListAuthorsPerRelay.value + ?.ifEmpty { null } + + return listOfNotNull( + TypedFilter( + types = setOf(FeedType.FOLLOWS), + filter = + SinceAuthorPerRelayFilter( + authors = follows, + kinds = listOf(FollowListEvent.KIND), + limit = 500, + since = since(key), + ), + ), + ) + } + fun createCommunitiesFilter(key: DiscoveryQueryState): TypedFilter { val follows = key.account.liveDiscoveryListAuthorsPerRelay.value @@ -450,6 +470,7 @@ class DiscoveryFilterAssembler( createLiveStreamFilter(key) .plus(createNIP89Filter(key)) .plus(createPublicChatFilter(key)) + .plus(createFollowSetFilter(key)) .plus(createMarketplaceFilter(key)) .plus( listOfNotNull( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverChatFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/DiscoverChatFeedFilter.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverChatFeedFilter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/DiscoverChatFeedFilter.kt index e18b23705..ccba710ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverChatFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/DiscoverChatFeedFilter.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -38,9 +38,9 @@ open class DiscoverChatFeedFilter( override fun showHiddenKey(): Boolean = account.settings.defaultDiscoveryFollowList.value == - PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || + PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || account.settings.defaultDiscoveryFollowList.value == - MuteListEvent.blockListFor(account.userProfile().pubkeyHex) + MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { val params = buildFilterParams(account) @@ -67,7 +67,7 @@ open class DiscoverChatFeedFilter( override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) fun buildFilterParams(account: Account): FilterByListParams = - FilterByListParams.create( + FilterByListParams.Companion.create( userHex = account.userProfile().pubkeyHex, selectedListName = account.settings.defaultDiscoveryFollowList.value, followLists = account.liveDiscoveryFollowLists.value, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderChannelThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderChannelThumb.kt new file mode 100644 index 000000000..8efa30cc3 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderChannelThumb.kt @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import coil3.compose.AsyncImagePainter +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.ParticipantListBuilder +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel +import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner +import com.vitorpamplona.amethyst.ui.note.Gallery +import com.vitorpamplona.amethyst.ui.note.LikeReaction +import com.vitorpamplona.amethyst.ui.note.LoadChannel +import com.vitorpamplona.amethyst.ui.note.ZapReaction +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.RowColSpacing +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.grayText +import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun RenderChannelThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteEvent = baseNote.event as? ChannelCreateEvent ?: return + + LoadChannel(baseChannelHex = baseNote.idHex, accountViewModel) { + RenderChannelThumb(baseNote = baseNote, channel = it, accountViewModel, nav) + } +} + +@Composable +fun RenderChannelThumb( + baseNote: Note, + channel: Channel, + accountViewModel: AccountViewModel, + nav: INav, +) { + val channelUpdates by observeChannel(channel, accountViewModel) + + val name = remember(channelUpdates) { channelUpdates?.channel?.toBestDisplayName() ?: "" } + val description = remember(channelUpdates) { channelUpdates?.channel?.summary()?.ifBlank { null } } + var cover by + remember(channelUpdates) { + mutableStateOf(channelUpdates?.channel?.profilePicture()?.ifBlank { null }) + } + + var participantUsers by + remember(baseNote) { + mutableStateOf>( + persistentListOf(), + ) + } + + LaunchedEffect(key1 = channelUpdates) { + launch(Dispatchers.IO) { + val followingKeySet = + accountViewModel.account.liveDiscoveryFollowLists.value + ?.authors + val allParticipants = + ParticipantListBuilder() + .followsThatParticipateOn(baseNote, followingKeySet) + .toImmutableList() + + val newParticipantUsers = + if (followingKeySet == null) { + val allFollows = accountViewModel.account.liveKind3Follows.value.authors + val followingParticipants = + ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).toList() + + (followingParticipants + (allParticipants - followingParticipants)).toImmutableList() + } else { + allParticipants.toImmutableList() + } + + if (!equalImmutableLists(newParticipantUsers, participantUsers)) { + participantUsers = newParticipantUsers + } + } + } + + LeftPictureLayout( + onImage = { + cover?.let { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxSize() + .clip(QuoteBorder), + onState = { + if (it is AsyncImagePainter.State.Error) { + cover = null + } + }, + ) + } ?: run { DisplayAuthorBanner(baseNote, accountViewModel) } + }, + onTitleRow = { + Text( + text = name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = StdHorzSpacer) + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = RowColSpacing, + ) { + LikeReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + } + Spacer(modifier = StdHorzSpacer) + ZapReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + }, + onDescription = { + Text( + text = description ?: stringRes(R.string.chat_about_topic, name), + color = MaterialTheme.colorScheme.grayText, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + fontSize = 14.sp, + lineHeight = 18.sp, + modifier = HalfTopPadding, + ) + }, + onBottomRow = { + if (participantUsers.isNotEmpty()) { + Gallery(participantUsers, HalfTopPadding, accountViewModel, nav) + } + }, + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt new file mode 100644 index 000000000..789038b44 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/DiscoverFollowSetsFeedFilter.kt @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets + +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder +import com.vitorpamplona.amethyst.ui.dal.FilterByListParams +import com.vitorpamplona.quartz.nip51Lists.FollowListEvent +import com.vitorpamplona.quartz.nip51Lists.MuteListEvent +import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent + +open class DiscoverFollowSetsFeedFilter( + val account: Account, +) : AdditiveFeedFilter() { + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList() + + open fun followList(): String = account.settings.defaultDiscoveryFollowList.value + + override fun showHiddenKey(): Boolean = + followList() == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || + followList() == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) + + override fun feed(): List { + val params = buildFilterParams(account) + + val notes = + LocalCache.addressables.filterIntoSet { _, it -> + val noteEvent = it.event + noteEvent is FollowListEvent && params.match(noteEvent) + } + + return sort(notes) + } + + override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) + + fun buildFilterParams(account: Account): FilterByListParams = + FilterByListParams.Companion.create( + account.userProfile().pubkeyHex, + account.settings.defaultDiscoveryFollowList.value, + account.liveDiscoveryFollowLists.value, + account.flowHiddenUsers.value, + ) + + protected open fun innerApplyFilter(collection: Collection): Set { + val params = buildFilterParams(account) + + return collection.filterTo(HashSet()) { + val noteEvent = it.event + noteEvent is FollowListEvent && params.match(noteEvent) + } + } + + override fun sort(collection: Set): List = collection.sortedWith(DefaultFeedOrder) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/FollowSetCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/FollowSetCard.kt new file mode 100644 index 000000000..5ad7bd306 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip51FollowSets/FollowSetCard.kt @@ -0,0 +1,204 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import coil3.compose.AsyncImage +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner +import com.vitorpamplona.amethyst.ui.note.Gallery +import com.vitorpamplona.amethyst.ui.note.LikeReaction +import com.vitorpamplona.amethyst.ui.note.UserPicture +import com.vitorpamplona.amethyst.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.ui.note.ZapReaction +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp +import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size25dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn +import com.vitorpamplona.quartz.nip51Lists.FollowListEvent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +@Immutable +data class FollowSetCard( + val name: String, + val media: String?, + val description: String?, + val users: ImmutableList, +) + +@Composable +fun RenderFollowSetThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val card by observeNoteAndMap(baseNote, accountViewModel) { + val noteEvent = it.event as? FollowListEvent + + FollowSetCard( + name = noteEvent?.nameOrTitle()?.ifBlank { null } ?: noteEvent?.dTag() ?: "", + media = noteEvent?.image()?.ifBlank { null }, + description = noteEvent?.description(), + users = + accountViewModel + .loadUsersSync( + noteEvent?.pubKeys() ?: emptyList(), + ).toImmutableList(), + ) + } + + RenderFollowSetThumb( + card, + baseNote, + accountViewModel, + nav, + ) +} + +@Preview +@Composable +fun RenderFollowSetThumbPreview() { + val accountViewModel = mockAccountViewModel() + val nav = EmptyNav + + ThemeComparisonColumn( + toPreview = { + RenderFollowSetThumb( + card = + FollowSetCard( + "Orange Pill Perú", + "https://i.postimg.cc/GtDgGY5v/5062563795762785335.jpg", + "Desc", + persistentListOf( + accountViewModel.userProfile(), + accountViewModel.userProfile(), + accountViewModel.userProfile(), + accountViewModel.userProfile(), + accountViewModel.userProfile(), + ), + ), + baseNote = Note(""), + accountViewModel = accountViewModel, + nav = nav, + ) + }, + ) +} + +@Composable +fun RenderFollowSetThumb( + card: FollowSetCard, + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Box( + contentAlignment = Alignment.BottomStart, + ) { + card.media?.let { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxWidth() + .aspectRatio(ratio = 21f / 9f) + .clip(QuoteBorder), + ) + } ?: run { DisplayAuthorBanner(baseNote, accountViewModel) } + + Gallery(card.users, Modifier.padding(Size10dp), accountViewModel, nav) + } + + Spacer(modifier = DoubleVertSpacer) + + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = RowColSpacing5dp, + ) { + Text( + text = card.name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = StdHorzSpacer) + LikeReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + Spacer(modifier = StdHorzSpacer) + ZapReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + } + baseNote.author?.let { author -> + Spacer(modifier = DoubleVertSpacer) + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = RowColSpacing5dp, + ) { + UserPicture(author, Size25dp, accountViewModel = accountViewModel, nav = nav) + UsernameDisplay(author, fontWeight = FontWeight.Normal, accountViewModel = accountViewModel) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverLiveFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverLiveFeedFilter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt index 6b338e201..090d15d86 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverLiveFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -39,8 +39,8 @@ open class DiscoverLiveFeedFilter( open fun followList(): String = account.settings.defaultDiscoveryFollowList.value override fun showHiddenKey(): Boolean = - followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || - followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) + followList() == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || + followList() == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { val allChannelNotes = LocalCache.channels.mapNotNull { _, channel -> LocalCache.getNoteIfExists(channel.idHex) } @@ -55,7 +55,7 @@ open class DiscoverLiveFeedFilter( protected open fun innerApplyFilter(collection: Collection): Set { val filterParams = - FilterByListParams.create( + FilterByListParams.Companion.create( userHex = account.userProfile().pubkeyHex, selectedListName = account.settings.defaultDiscoveryFollowList.value, followLists = account.liveDiscoveryFollowLists.value, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt new file mode 100644 index 000000000..a3cf1a831 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt @@ -0,0 +1,255 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.TopEnd +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.ParticipantListBuilder +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner +import com.vitorpamplona.amethyst.ui.note.Gallery +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.EndedFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.OfflineFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.ScheduledFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline +import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists +import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag +import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Immutable +data class LiveActivityCard( + val name: String, + val cover: String?, + val media: String?, + val subject: String?, + val content: String?, + val participants: ImmutableList, + val status: String?, + val starts: Long?, +) + +@Composable +fun RenderLiveActivityThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val card by observeNoteAndMap(baseNote, accountViewModel) { + val noteEvent = it.event as? LiveActivitiesEvent + + LiveActivityCard( + name = noteEvent?.dTag() ?: "", + cover = noteEvent?.image()?.ifBlank { null }, + media = noteEvent?.streaming(), + subject = noteEvent?.title()?.ifBlank { null }, + content = noteEvent?.summary(), + participants = noteEvent?.participants()?.toImmutableList() ?: persistentListOf(), + status = noteEvent?.status(), + starts = noteEvent?.starts(), + ) + } + + RenderLiveActivityThumb( + card, + baseNote, + accountViewModel, + nav, + ) +} + +@Composable +fun RenderLiveActivityThumb( + card: LiveActivityCard, + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + Column( + modifier = Modifier.fillMaxWidth(), + ) { + Box( + contentAlignment = TopEnd, + modifier = + Modifier + .aspectRatio(ratio = 16f / 9f) + .fillMaxWidth(), + ) { + card.cover?.let { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxSize() + .clip(QuoteBorder), + ) + } ?: run { DisplayAuthorBanner(baseNote, accountViewModel) } + + Box(Modifier.padding(10.dp)) { + CrossfadeIfEnabled(targetState = card.status, label = "RenderLiveActivityThumb", accountViewModel = accountViewModel) { + when (it) { + StatusTag.STATUS.LIVE.code -> { + val url = card.media + if (url.isNullOrBlank()) { + LiveFlag() + } else { + CheckIfVideoIsOnline(url, accountViewModel) { isOnline -> + if (isOnline) { + LiveFlag() + } else { + OfflineFlag() + } + } + } + } + StatusTag.STATUS.ENDED.code -> { + EndedFlag() + } + StatusTag.STATUS.PLANNED.code -> { + ScheduledFlag(card.starts) + } + else -> { + EndedFlag() + } + } + } + } + + LoadParticipants(card.participants, baseNote, accountViewModel) { participantUsers -> + Box( + Modifier + .padding(10.dp) + .align(BottomStart), + ) { + if (participantUsers.isNotEmpty()) { + Gallery(participantUsers, Modifier, accountViewModel, nav) + } + } + } + } + + Spacer(modifier = DoubleVertSpacer) + + ChannelHeader( + channelHex = baseNote.idHex, + showVideo = false, + showFlag = false, + sendToChannel = true, + modifier = Modifier, + accountViewModel = accountViewModel, + nav = nav, + ) + } +} + +@Composable +fun LoadParticipants( + participants: ImmutableList, + baseNote: Note, + accountViewModel: AccountViewModel, + inner: @Composable (ImmutableList) -> Unit, +) { + var participantUsers by remember { + mutableStateOf>( + persistentListOf(), + ) + } + + LaunchedEffect(key1 = participants) { + launch(Dispatchers.IO) { + val hosts = + participants.mapNotNull { part -> + if (part.pubKey != baseNote.author?.pubkeyHex) { + LocalCache.checkGetOrCreateUser(part.pubKey) + } else { + null + } + } + + val hostsAuthor = hosts + (baseNote.author?.let { listOf(it) } ?: emptyList()) + + val followingKeySet = + accountViewModel.account.liveDiscoveryFollowLists.value + ?.authors + + val allParticipants = + ParticipantListBuilder() + .followsThatParticipateOn(baseNote, followingKeySet) + .minus(hostsAuthor) + + val newParticipantUsers = + if (followingKeySet == null) { + val allFollows = accountViewModel.account.liveKind3Follows.value.authors + val followingParticipants = + ParticipantListBuilder() + .followsThatParticipateOn(baseNote, allFollows) + .minus(hostsAuthor) + + (hosts + followingParticipants + (allParticipants - followingParticipants)) + .toImmutableList() + } else { + (hosts + allParticipants).toImmutableList() + } + + if (!equalImmutableLists(newParticipantUsers, participantUsers)) { + participantUsers = newParticipantUsers + } + } + } + + inner(participantUsers) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt new file mode 100644 index 000000000..0b0f8e5f5 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +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.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.ParticipantListBuilder +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote +import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner +import com.vitorpamplona.amethyst.ui.note.Gallery +import com.vitorpamplona.amethyst.ui.note.LikeReaction +import com.vitorpamplona.amethyst.ui.note.ZapReaction +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.RowColSpacing +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.grayText +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Immutable +data class CommunityCard( + val name: String, + val description: String?, + val cover: String?, + val moderators: ImmutableList, +) + +@Composable +fun RenderCommunitiesThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteState by observeNote(baseNote, accountViewModel) + val noteEvent = noteState.note.event as? CommunityDefinitionEvent ?: return + + RenderCommunitiesThumb( + CommunityCard( + name = noteEvent.dTag(), + description = noteEvent.description(), + cover = noteEvent.image()?.imageUrl, + moderators = noteEvent.moderatorKeys().toImmutableList(), + ), + baseNote, + accountViewModel, + nav, + ) +} + +@Composable +fun RenderCommunitiesThumb( + card: CommunityCard, + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + LeftPictureLayout( + onImage = { + card.cover?.let { + Box(contentAlignment = BottomStart) { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxSize() + .clip(QuoteBorder), + ) + } + } ?: run { DisplayAuthorBanner(baseNote, accountViewModel) } + }, + onTitleRow = { + Text( + text = card.name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + + Spacer(modifier = StdHorzSpacer) + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = RowColSpacing, + ) { + LikeReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + } + Spacer(modifier = StdHorzSpacer) + ZapReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + }, + onDescription = { + Text( + text = card.description ?: stringRes(R.string.community_about_topic, card.name), + color = MaterialTheme.colorScheme.grayText, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontSize = 14.sp, + lineHeight = 18.sp, + modifier = HalfTopPadding, + ) + }, + onBottomRow = { + LoadModerators(card.moderators, baseNote, accountViewModel) { participantUsers -> + if (participantUsers.isNotEmpty()) { + Gallery(participantUsers, HalfTopPadding, accountViewModel, nav) + } + } + }, + ) +} + +@Composable +fun LoadModerators( + moderators: ImmutableList, + baseNote: Note, + accountViewModel: AccountViewModel, + content: @Composable (ImmutableList) -> Unit, +) { + var participantUsers by remember { + mutableStateOf>( + persistentListOf(), + ) + } + + LaunchedEffect(key1 = moderators) { + launch(Dispatchers.IO) { + val hosts = + moderators.mapNotNull { part -> + if (part != baseNote.author?.pubkeyHex) { + LocalCache.checkGetOrCreateUser(part) + } else { + null + } + } + + val followingKeySet = + accountViewModel.account.liveDiscoveryFollowLists.value + ?.authors + val allParticipants = + ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts) + + val newParticipantUsers = + if (followingKeySet == null) { + val allFollows = accountViewModel.account.liveKind3Follows.value.authors + val followingParticipants = + ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts) + + (hosts + followingParticipants + (allParticipants - followingParticipants)) + .toImmutableList() + } else { + (hosts + allParticipants).toImmutableList() + } + + if (!equalImmutableLists(newParticipantUsers, participantUsers)) { + participantUsers = newParticipantUsers + } + } + } + + content(participantUsers) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverCommunityFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverCommunityFeedFilter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt index 5ad57b706..df3f8aacf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverCommunityFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/DiscoverCommunityFeedFilter.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -38,13 +38,13 @@ open class DiscoverCommunityFeedFilter( override fun showHiddenKey(): Boolean = account.settings.defaultDiscoveryFollowList.value == - PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || + PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || account.settings.defaultDiscoveryFollowList.value == - MuteListEvent.blockListFor(account.userProfile().pubkeyHex) + MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { val filterParams = - FilterByListParams.create( + FilterByListParams.Companion.create( userHex = account.userProfile().pubkeyHex, selectedListName = account.settings.defaultDiscoveryFollowList.value, followLists = account.liveDiscoveryFollowLists.value, @@ -55,7 +55,7 @@ open class DiscoverCommunityFeedFilter( val notes = LocalCache.addressables.mapNotNullIntoSet { key, note -> val noteEvent = note.event - if (noteEvent == null && shouldInclude(Address.parse(key), filterParams)) { + if (noteEvent == null && shouldInclude(Address.Companion.parse(key), filterParams)) { // send unloaded communities to the screen note } else if (noteEvent is CommunityDefinitionEvent && filterParams.match(noteEvent)) { @@ -73,7 +73,7 @@ open class DiscoverCommunityFeedFilter( protected open fun innerApplyFilter(collection: Collection): Set { // here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals val filterParams = - FilterByListParams.create( + FilterByListParams.Companion.create( userHex = account.userProfile().pubkeyHex, selectedListName = account.settings.defaultDiscoveryFollowList.value, followLists = account.liveDiscoveryFollowLists.value, @@ -109,7 +109,7 @@ open class DiscoverCommunityFeedFilter( private fun shouldInclude( aTag: Address?, params: FilterByListParams, - ) = aTag != null && aTag.kind == CommunityDefinitionEvent.KIND && params.match(aTag) + ) = aTag != null && aTag.kind == CommunityDefinitionEvent.Companion.KIND && params.match(aTag) override fun sort(collection: Set): List { val lastNote = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt new file mode 100644 index 000000000..71da80a42 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Alignment.Companion.BottomStart +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil3.compose.AsyncImage +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription +import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.LikeReaction +import com.vitorpamplona.amethyst.ui.note.ZapReaction +import com.vitorpamplona.amethyst.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition +import com.vitorpamplona.amethyst.ui.theme.HalfTopPadding +import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.bitcoinColor +import com.vitorpamplona.amethyst.ui.theme.grayText +import com.vitorpamplona.amethyst.ui.theme.nip05 + +@Immutable +data class DVMCard( + val name: String, + val description: String?, + val cover: String?, + val amount: String?, + val personalized: Boolean?, +) + +@Composable +fun RenderContentDVMThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + // downloads user metadata to pre-load the NIP-65 relays. + baseNote.author?.let { UserFinderFilterAssemblerSubscription(it, accountViewModel) } + val card = observeAppDefinition(appDefinitionNote = baseNote, accountViewModel) + + LeftPictureLayout( + imageFraction = 0.20f, + onImage = { + card.cover?.let { + Box(contentAlignment = BottomStart) { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = + Modifier + .fillMaxSize() + .clip(QuoteBorder), + ) + } + } ?: run { + baseNote.author?.let { + BannerImage( + it, + Modifier + .fillMaxSize() + .clip(QuoteBorder), + accountViewModel, + ) + } + } + }, + onTitleRow = { + Text( + text = card.name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = StdVertSpacer) + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = RowColSpacing5dp, + ) { + LikeReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + } + Spacer(modifier = StdHorzSpacer) + ZapReaction( + baseNote = baseNote, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + }, + onDescription = { + card.description?.let { + Text( + text = it, + color = MaterialTheme.colorScheme.grayText, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + fontSize = 14.sp, + lineHeight = 16.sp, + modifier = HalfTopPadding, + ) + } + }, + onBottomRow = { + card.amount?.let { + var color = Color.DarkGray + var amount = it + if (card.amount == "free" || card.amount == "0") { + color = MaterialTheme.colorScheme.secondary + amount = "Free" + } else if (card.amount == "flexible") { + color = MaterialTheme.colorScheme.primaryContainer + amount = "Flexible" + } else if (card.amount == "") { + color = MaterialTheme.colorScheme.grayText + amount = "Unknown" + } else { + color = MaterialTheme.colorScheme.primary + amount = card.amount + " Sats" + } + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = Arrangement.Absolute.Right, + ) { + Text( + textAlign = TextAlign.End, + text = " $amount ", + color = color, + maxLines = 3, + modifier = + Modifier + .weight(1f, fill = false) + .border(Dp(.1f), color, shape = RoundedCornerShape(20)), + fontSize = 12.sp, + ) + } + } + Spacer(modifier = StdHorzSpacer) + card.personalized?.let { + var color = Color.DarkGray + var name = "generic" + if (card.personalized == true) { + color = MaterialTheme.colorScheme.bitcoinColor + name = "Personalized" + } else { + color = MaterialTheme.colorScheme.nip05 + name = "Generic" + } + Spacer(modifier = StdVertSpacer) + Row( + verticalAlignment = CenterVertically, + horizontalArrangement = Arrangement.Absolute.Right, + ) { + Text( + textAlign = TextAlign.End, + text = " $name ", + color = color, + maxLines = 3, + modifier = + Modifier + .padding(start = 4.dp) + .weight(1f, fill = false) + .border(Dp(.1f), color, shape = RoundedCornerShape(20)), + fontSize = 12.sp, + ) + } + } + }, + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverNIP89FeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverNIP89FeedFilter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt index 99ffbf849..1666cc0d9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverNIP89FeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DiscoverNIP89FeedFilter.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -41,8 +41,8 @@ open class DiscoverNIP89FeedFilter( open fun followList(): String = account.settings.defaultDiscoveryFollowList.value override fun showHiddenKey(): Boolean = - followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || - followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) + followList() == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || + followList() == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { val notes = @@ -56,7 +56,7 @@ open class DiscoverNIP89FeedFilter( override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) fun buildFilterParams(account: Account): FilterByListParams = - FilterByListParams.create( + FilterByListParams.Companion.create( account.userProfile().pubkeyHex, account.settings.defaultDiscoveryFollowList.value, account.liveDiscoveryFollowLists.value, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt new file mode 100644 index 000000000..87334c094 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.BottomStart +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner +import com.vitorpamplona.amethyst.ui.note.showAmountInteger +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nip99Classifieds.tags.PriceTag + +@Immutable +data class ClassifiedsThumb( + val image: String?, + val title: String?, + val price: PriceTag?, +) + +@Composable +fun RenderClassifiedsThumb( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + if (baseNote.event !is ClassifiedsEvent) return + + val card by observeNoteAndMap(baseNote, accountViewModel) { + val noteEvent = it.event as? ClassifiedsEvent + ClassifiedsThumb( + image = noteEvent?.image(), + title = noteEvent?.title(), + price = noteEvent?.price(), + ) + } + + InnerRenderClassifiedsThumb(card, baseNote, accountViewModel) +} + +@Preview +@Composable +fun RenderClassifiedsThumbPreview() { + Surface(Modifier.size(200.dp)) { + InnerRenderClassifiedsThumb( + card = + ClassifiedsThumb( + image = null, + title = "Like New", + price = PriceTag("800000", "SATS", null), + ), + note = Note("hex"), + mockAccountViewModel(), + ) + } +} + +@Composable +fun InnerRenderClassifiedsThumb( + card: ClassifiedsThumb, + note: Note, + accountViewModel: AccountViewModel, +) { + Box( + Modifier + .fillMaxWidth() + .aspectRatio(1f), + contentAlignment = BottomStart, + ) { + card.image?.let { + AsyncImage( + model = it, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize(), + ) + } ?: run { DisplayAuthorBanner(note, accountViewModel) } + + Row( + Modifier + .fillMaxWidth() + .background(Color.Black.copy(0.6f)) + .padding(Size5dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + card.title?.let { + Text( + text = it, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White, + modifier = Modifier.weight(1f), + ) + } + + card.price?.let { + val priceTag = + remember(card) { + val newAmount = it.amount.toBigDecimalOrNull()?.let { showAmountInteger(it) } ?: it.amount + + if (it.frequency != null && it.currency != null) { + "$newAmount ${it.currency}/${it.frequency}" + } else if (it.currency != null) { + "$newAmount ${it.currency}" + } else { + newAmount + } + } + + Text( + text = priceTag, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = Color.White, + ) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverMarketplaceFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverMarketplaceFeedFilter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt index ca4a07b0e..3c7cc4073 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/dal/DiscoverMarketplaceFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/DiscoverMarketplaceFeedFilter.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.dal +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache @@ -37,8 +37,8 @@ open class DiscoverMarketplaceFeedFilter( open fun followList(): String = account.settings.defaultDiscoveryFollowList.value override fun showHiddenKey(): Boolean = - followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || - followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) + followList() == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || + followList() == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) override fun feed(): List { val params = buildFilterParams(account) @@ -55,7 +55,7 @@ open class DiscoverMarketplaceFeedFilter( override fun applyFilter(collection: Set): Set = innerApplyFilter(collection) fun buildFilterParams(account: Account): FilterByListParams = - FilterByListParams.create( + FilterByListParams.Companion.create( account.userProfile().pubkeyHex, account.settings.defaultDiscoveryFollowList.value, account.liveDiscoveryFollowLists.value, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 464a3995c..be17e2208 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.marketplace +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import android.net.Uri import androidx.compose.foundation.horizontalScroll diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt index 1e3951d2e..8d9026d5a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/NewProductViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.marketplace +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import android.content.Context import android.util.Log diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/SellProduct.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/SellProduct.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt index d8e3d2e13..d638bcb74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/marketplace/SellProduct.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.marketplace +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Box diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt index e7e457b26..3e8c211b0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt @@ -70,7 +70,6 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.INav -import com.vitorpamplona.amethyst.ui.note.DVMCard import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog @@ -83,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.DVMCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.NIP90ContentDiscoveryFeedViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer @@ -195,7 +195,7 @@ fun ObserverContentDiscoveryResponse( ) { val noteAuthor = appDefinition.author ?: return - EventFinderFilterAssemblerSubscription(dvmRequestId) + EventFinderFilterAssemblerSubscription(dvmRequestId, accountViewModel) val resultFlow = remember(dvmRequestId) { @@ -321,7 +321,7 @@ fun FeedDVM( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val card = observeAppDefinition(appDefinitionNote) + val card = observeAppDefinition(appDefinitionNote, accountViewModel) card.cover?.let { AsyncImage( @@ -333,7 +333,7 @@ fun FeedDVM( .size(Size75dp) .clip(QuoteBorder), ) - } ?: run { NoteAuthorPicture(appDefinitionNote, nav, accountViewModel, Size75dp) } + } ?: run { NoteAuthorPicture(appDefinitionNote, Size75dp, accountViewModel = accountViewModel, nav = nav) } Spacer(modifier = DoubleVertSpacer) @@ -449,7 +449,7 @@ fun ZapDVMButton( } // Makes sure the user is loaded to get his ln address ahead of time. - UserFinderFilterAssemblerSubscription(noteAuthor) + UserFinderFilterAssemblerSubscription(noteAuthor, accountViewModel) val context = LocalContext.current val scope = rememberCoroutineScope() @@ -589,7 +589,7 @@ fun FeedEmptyWithStatus( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val card = observeAppDefinition(appDefinitionNote) + val card = observeAppDefinition(appDefinitionNote, accountViewModel) card.cover?.let { AsyncImage( @@ -601,7 +601,7 @@ fun FeedEmptyWithStatus( .size(Size75dp) .clip(QuoteBorder), ) - } ?: run { NoteAuthorPicture(appDefinitionNote, nav, accountViewModel, Size75dp) } + } ?: run { NoteAuthorPicture(appDefinitionNote, Size75dp, accountViewModel = accountViewModel, nav = nav) } Spacer(modifier = DoubleVertSpacer) @@ -641,8 +641,11 @@ fun convertAppMetadataToCard(metadata: AppMetadata?): DVMCard { } @Composable -fun observeAppDefinition(appDefinitionNote: Note): DVMCard { - val card by observeNoteAndMap(appDefinitionNote) { +fun observeAppDefinition( + appDefinitionNote: Note, + accountViewModel: AccountViewModel, +): DVMCard { + val card by observeNoteAndMap(appDefinitionNote, accountViewModel) { convertAppMetadataToCard((it.event as? AppDefinitionEvent)?.appMetaData()) } return card diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt index d7b3214b5..c3d4e6a65 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt @@ -49,7 +49,7 @@ fun DvmTopBar( title = { LoadNote(baseNoteHex = appDefinitionId, accountViewModel = accountViewModel) { appDefinitionNote -> if (appDefinitionNote != null) { - val card = observeAppDefinition(appDefinitionNote) + val card = observeAppDefinition(appDefinitionNote, accountViewModel) card.cover?.let { AsyncImage( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt index d5ed08dd2..cc91c157e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt @@ -121,7 +121,7 @@ fun GeoHashActionOptions( tag: String, accountViewModel: AccountViewModel, ) { - val isFollowingTag by observeUserIsFollowingGeohash(accountViewModel.userProfile(), tag) + val isFollowingTag by observeUserIsFollowingGeohash(accountViewModel.userProfile(), tag, accountViewModel) if (isFollowingTag) { UnfollowButton { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt index fe21ee4eb..5c9d4dc8f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt @@ -138,7 +138,7 @@ fun HashtagActionOptions( tag: String, accountViewModel: AccountViewModel, ) { - val isFollowingTag by observeUserIsFollowingHashtag(accountViewModel.userProfile(), tag) + val isFollowingTag by observeUserIsFollowingHashtag(accountViewModel.userProfile(), tag, accountViewModel) if (isFollowingTag) { UnfollowButton { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt index f1b07a9d8..19d6edc03 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeTopBar.kt @@ -40,6 +40,7 @@ fun HomeTopBar( FollowListWithRoutes( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, + accountViewModel = accountViewModel, ) { listName -> if (listName.route != null) { nav.nav(listName.route) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt index b4e3de3ac..4c266f15a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt @@ -20,18 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.live -import android.R.attr.onClick import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.EphemeralChatChannel import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelNoteAuthors +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.Gallery @@ -50,7 +49,7 @@ fun RenderEphemeralBubble( nav.nav { routeFor(channel) } }, ) { - RenderUsers(channel, accountViewModel) + RenderUsers(channel, accountViewModel, nav) Spacer(StdHorzSpacer) Text( channel.toBestDisplayName(), @@ -62,8 +61,9 @@ fun RenderEphemeralBubble( fun RenderUsers( channel: EphemeralChatChannel, accountViewModel: AccountViewModel, + nav: INav, ) { - val authors by observeChannelNoteAuthors(channel) + val authors by observeChannelNoteAuthors(channel, accountViewModel) - Gallery(authors, Modifier, accountViewModel, 3) + Gallery(authors, Modifier, accountViewModel, nav, 3) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt index e468af880..4d7d98433 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/NotificationTopBar.kt @@ -40,6 +40,7 @@ fun NotificationTopBar( FollowListWithoutRoutes( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, + accountViewModel, ) { listName -> accountViewModel.account.settings.changeDefaultNotificationFollowList(listName.code) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt index eab695472..fdfed4760 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt @@ -402,6 +402,7 @@ private fun RenderScreen( CreateAndRenderTabs( baseUser, pagerState, + accountViewModel, ) } HorizontalPager( @@ -484,6 +485,7 @@ fun UpdateThreadsAndRepliesWhenBlockUnblock( private fun CreateAndRenderTabs( baseUser: User, pagerState: PagerState, + accountViewModel: AccountViewModel, ) { val coroutineScope = rememberCoroutineScope() @@ -493,13 +495,13 @@ private fun CreateAndRenderTabs( { Text(text = stringRes(R.string.replies)) }, { Text(text = stringRes(R.string.mutual)) }, { Text(text = stringRes(R.string.gallery)) }, - { FollowTabHeader(baseUser) }, - { FollowersTabHeader(baseUser) }, - { ZapTabHeader(baseUser) }, - { BookmarkTabHeader(baseUser) }, - { FollowedTagsTabHeader(baseUser) }, - { ReportsTabHeader(baseUser) }, - { RelaysTabHeader(baseUser) }, + { FollowTabHeader(baseUser, accountViewModel) }, + { FollowersTabHeader(baseUser, accountViewModel) }, + { ZapTabHeader(baseUser, accountViewModel) }, + { BookmarkTabHeader(baseUser, accountViewModel) }, + { FollowedTagsTabHeader(baseUser, accountViewModel) }, + { ReportsTabHeader(baseUser, accountViewModel) }, + { RelaysTabHeader(baseUser, accountViewModel) }, ) tabs.forEachIndexed { index, function -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt index c53291336..7ce6cb55d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserBookmarkCount +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun BookmarkTabHeader(baseUser: User) { - val userBookmarks by observeUserBookmarkCount(baseUser) +fun BookmarkTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val userBookmarks by observeUserBookmarkCount(baseUser, accountViewModel) Text(text = "$userBookmarks ${stringRes(R.string.bookmarks)}") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt index fcd9a42cd..9d8728388 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowerCount +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun FollowersTabHeader(baseUser: User) { - val followerCount by observeUserFollowerCount(baseUser) +fun FollowersTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val followerCount by observeUserFollowerCount(baseUser, accountViewModel) val text = if (followerCount > 0) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt index 91371741d..672acc7be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt @@ -40,7 +40,7 @@ fun TabFollowers( accountViewModel: AccountViewModel, nav: INav, ) { - WatchFollowerChanges(baseUser, feedViewModel) + WatchFollowerChanges(baseUser, feedViewModel, accountViewModel) Column(Modifier.fillMaxHeight()) { RefreshingFeedUserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false) @@ -51,8 +51,9 @@ fun TabFollowers( private fun WatchFollowerChanges( baseUser: User, feedViewModel: UserFeedViewModel, + accountViewModel: AccountViewModel, ) { - val userState by observeUserFollowers(baseUser) + val userState by observeUserFollowers(baseUser, accountViewModel) LaunchedEffect(userState) { feedViewModel.invalidateData() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt index 2216db55f..ca3414cce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowCount +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun FollowTabHeader(baseUser: User) { - val followCount by observeUserFollowCount(baseUser) +fun FollowTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val followCount by observeUserFollowCount(baseUser, accountViewModel) val text = if (followCount > 0) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt index 23c0e9e73..329159f21 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt @@ -40,7 +40,7 @@ fun TabFollows( accountViewModel: AccountViewModel, nav: INav, ) { - WatchFollowChanges(baseUser, feedViewModel) + WatchFollowChanges(baseUser, feedViewModel, accountViewModel) Column(Modifier.fillMaxHeight()) { RefreshingFeedUserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false) @@ -51,8 +51,9 @@ fun TabFollows( private fun WatchFollowChanges( baseUser: User, feedViewModel: UserFeedViewModel, + accountViewModel: AccountViewModel, ) { - val userState by observeUserFollows(baseUser) + val userState by observeUserFollows(baseUser, accountViewModel) LaunchedEffect(userState) { feedViewModel.invalidateData() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt index 767c010c9..0402dc825 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt @@ -120,7 +120,7 @@ fun RedirectableGalleryCard( ) { if (sourceNote != galleryNote) { // preloads target note - EventFinderFilterAssemblerSubscription(sourceNote) + EventFinderFilterAssemblerSubscription(sourceNote, accountViewModel) } SensitivityWarning( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt index 299c6753c..3c6508db8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt @@ -78,7 +78,7 @@ fun GalleryThumbnail( nav: INav, ratio: Float = 1.0f, ) { - val noteState by observeNote(baseNote) + val noteState by observeNote(baseNote, accountViewModel) val noteEvent = noteState?.note?.event ?: return val content = @@ -147,14 +147,17 @@ fun InnerRenderGalleryThumb( if (content.isNotEmpty()) { GalleryContentView(content, accountViewModel, ratio = ratio) } else { - DisplayGalleryAuthorBanner(note) + DisplayGalleryAuthorBanner(note, accountViewModel) } } @Composable -fun DisplayGalleryAuthorBanner(note: Note) { - WatchAuthor(note) { author -> - BannerImage(author, Modifier.fillMaxSize().clip(QuoteBorder)) +fun DisplayGalleryAuthorBanner( + note: Note, + accountViewModel: AccountViewModel, +) { + WatchAuthor(note, accountViewModel) { author -> + BannerImage(author, Modifier.fillMaxSize().clip(QuoteBorder), accountViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt index f14ca7f9d..2e4a4d588 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserTagFollows +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun FollowedTagsTabHeader(baseUser: User) { - val usertags by observeUserTagFollows(baseUser) +fun FollowedTagsTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val usertags by observeUserTagFollows(baseUser, accountViewModel) Text(text = "$usertags ${stringRes(R.string.followed_tags)}") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt index 01caf423b..5915c0997 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt @@ -34,8 +34,8 @@ fun DisplayFollowUnfollowButton( baseUser: User, accountViewModel: AccountViewModel, ) { - val isLoggedInFollowingUser by observeUserIsFollowing(accountViewModel.account.userProfile(), baseUser) - val isUserFollowingLoggedIn by observeUserIsFollowing(baseUser, accountViewModel.account.userProfile()) + val isLoggedInFollowingUser by observeUserIsFollowing(accountViewModel.account.userProfile(), baseUser, accountViewModel) + val isUserFollowingLoggedIn by observeUserIsFollowing(baseUser, accountViewModel.account.userProfile(), accountViewModel) if (isLoggedInFollowingUser) { UnfollowButton { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt index e47cb5644..88b9fcec3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt @@ -80,7 +80,7 @@ fun DrawAdditionalInfo( accountViewModel: AccountViewModel, nav: INav, ) { - val userState by observeUser(baseUser) + val userState by observeUser(baseUser, accountViewModel) val user = userState?.user ?: return val uri = LocalUriHandler.current val clipboardManager = LocalClipboardManager.current diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt index 65a36df4c..26216a779 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt @@ -51,7 +51,7 @@ fun DrawBanner( baseUser: User, accountViewModel: AccountViewModel, ) { - val banner by observeUserBanner(baseUser) + val banner by observeUserBanner(baseUser, accountViewModel) DrawBanner(banner, accountViewModel) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt index 68f0ce654..e61925ee7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt @@ -60,7 +60,7 @@ fun DisplayAppRecommendations( Column { Text(stringRes(id = R.string.recommended_apps)) - Recommends(state, nav) + Recommends(state, accountViewModel, nav) } } else -> {} @@ -72,6 +72,7 @@ fun DisplayAppRecommendations( @OptIn(ExperimentalLayoutApi::class) fun Recommends( loaded: FeedState.Loaded, + accountViewModel: AccountViewModel, nav: INav, ) { val items by loaded.feed.collectAsStateWithLifecycle() @@ -79,6 +80,6 @@ fun Recommends( verticalArrangement = Arrangement.Center, modifier = Modifier.padding(vertical = 5.dp), ) { - items.list.forEach { app -> WatchApp(app, nav) } + items.list.forEach { app -> WatchApp(app, accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/WatchApp.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/WatchApp.kt index 07a25f53c..b71b10e08 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/WatchApp.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/WatchApp.kt @@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.Route +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import kotlinx.coroutines.Dispatchers @@ -45,9 +46,10 @@ import kotlinx.coroutines.withContext @Composable fun WatchApp( baseApp: Note, + accountViewModel: AccountViewModel, nav: INav, ) { - val appState by observeNote(baseApp) + val appState by observeNote(baseApp, accountViewModel) var appLogo by remember(baseApp) { mutableStateOf(null) } var appName by remember(baseApp) { mutableStateOf(null) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt index e4b0a9060..22abc7a38 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt @@ -75,12 +75,7 @@ fun DisplayBadges( accountViewModel, ) { note -> if (note != null) { - WatchAndRenderBadgeList( - note = note, - loadProfilePicture = accountViewModel.settings.showProfilePictures.value, - loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, - nav = nav, - ) + WatchAndRenderBadgeList(note, accountViewModel, nav) } } } @@ -88,38 +83,35 @@ fun DisplayBadges( @Composable private fun WatchAndRenderBadgeList( note: AddressableNote, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, nav: INav, ) { - val badgeList by observeNoteEventAndMap(note) { event: BadgeProfilesEvent -> + val badgeList by observeNoteEventAndMap(note, accountViewModel) { event: BadgeProfilesEvent -> event.badgeAwardEvents().toImmutableList() } - badgeList?.let { list -> RenderBadgeList(list, loadProfilePicture, loadRobohash, nav) } + badgeList?.let { list -> RenderBadgeList(list, accountViewModel, nav) } } @Composable @OptIn(ExperimentalLayoutApi::class) private fun RenderBadgeList( list: ImmutableList, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, nav: INav, ) { FlowRow( verticalArrangement = Arrangement.Center, modifier = Modifier.padding(vertical = 5.dp), ) { - list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, loadProfilePicture, loadRobohash, nav) } + list.forEach { badgeAwardEvent -> LoadAndRenderBadge(badgeAwardEvent, accountViewModel, nav) } } } @Composable private fun LoadAndRenderBadge( badgeAwardEvent: ETag, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, nav: INav, ) { var baseNote by remember(badgeAwardEvent) { mutableStateOf(LocalCache.getNoteIfExists(badgeAwardEvent)) } @@ -132,38 +124,35 @@ private fun LoadAndRenderBadge( } } - baseNote?.let { ObserveAndRenderBadge(it, loadProfilePicture, loadRobohash, nav) } + baseNote?.let { ObserveAndRenderBadge(it, accountViewModel, nav) } } @Composable private fun ObserveAndRenderBadge( it: Note, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, nav: INav, ) { - val badgeAwardState by observeNote(it) - val baseBadgeDefinition = badgeAwardState?.note?.replyTo?.firstOrNull() - baseBadgeDefinition?.let { BadgeThumb(it, loadProfilePicture, loadRobohash, nav, Size35dp) } + val badgeAwardState by observeNote(it, accountViewModel) + val baseBadgeDefinition = badgeAwardState.note.replyTo?.firstOrNull() + baseBadgeDefinition?.let { BadgeThumb(it, accountViewModel, nav, Size35dp) } } @Composable fun BadgeThumb( note: Note, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, nav: INav, size: Dp, pictureModifier: Modifier = Modifier, ) { - BadgeThumb(note, loadProfilePicture, loadRobohash, size, pictureModifier) { nav.nav(Route.Note(note.idHex)) } + BadgeThumb(note, accountViewModel, size, pictureModifier) { nav.nav(Route.Note(note.idHex)) } } @Composable fun BadgeThumb( baseNote: Note, - loadProfilePicture: Boolean, - loadRobohash: Boolean, + accountViewModel: AccountViewModel, size: Dp, pictureModifier: Modifier = Modifier, onClick: ((String) -> Unit)? = null, @@ -175,25 +164,24 @@ fun BadgeThumb( .height(size) }, ) { - WatchAndRenderBadgeImage(baseNote, loadProfilePicture, loadRobohash, size, pictureModifier, onClick) + WatchAndRenderBadgeImage(baseNote, size, pictureModifier, accountViewModel, onClick) } } @Composable private fun WatchAndRenderBadgeImage( baseNote: Note, - loadProfilePicture: Boolean, - loadRobohash: Boolean, size: Dp, pictureModifier: Modifier, + accountViewModel: AccountViewModel, onClick: ((String) -> Unit)?, ) { - val noteState by observeNote(baseNote) + val noteState by observeNote(baseNote, accountViewModel) val eventId = remember(noteState) { noteState?.note?.idHex } ?: return val image by remember(noteState) { derivedStateOf { - val event = noteState?.note?.event as? BadgeDefinitionEvent + val event = noteState.note.event as? BadgeDefinitionEvent event?.thumb()?.ifBlank { null } ?: event?.image()?.ifBlank { null } } } @@ -208,7 +196,7 @@ private fun WatchAndRenderBadgeImage( .width(size) .height(size) }, - loadRobohash = loadRobohash, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } else { RobohashFallbackAsyncImage( @@ -229,8 +217,8 @@ private fun WatchAndRenderBadgeImage( } } }, - loadProfilePicture = loadProfilePicture, - loadRobohash = loadRobohash, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelaysTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelaysTabHeader.kt index 3b895757a..864524e05 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelaysTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelaysTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRelaysUsing +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun RelaysTabHeader(baseUser: User) { - val userState by observeUserRelaysUsing(baseUser) +fun RelaysTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val userState by observeUserRelaysUsing(baseUser, accountViewModel) Text(text = "${sizeAsString(userState.userRelayList.size)} / ${sizeAsString(userState.relays.size)} ${stringRes(R.string.relays)}") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt index 80caa6068..5940e62b4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt @@ -26,11 +26,15 @@ import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserReportCount +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun ReportsTabHeader(baseUser: User) { - val reportCount by observeUserReportCount(baseUser) +fun ReportsTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val reportCount by observeUserReportCount(baseUser, accountViewModel) if (reportCount > 0) { Text(text = stringRes(R.string.number_reports, reportCount)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt index e4e4a848e..3be8bc50a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt @@ -37,7 +37,7 @@ fun TabReports( accountViewModel: AccountViewModel, nav: INav, ) { - WatchReportsAndUpdateFeed(baseUser, feedViewModel) + WatchReportsAndUpdateFeed(baseUser, feedViewModel, accountViewModel) Column(Modifier.fillMaxHeight()) { RefresheableFeedView( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/WatchReportsAndUpdateFeed.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/WatchReportsAndUpdateFeed.kt index 04a5b818b..1a23c46a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/WatchReportsAndUpdateFeed.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/WatchReportsAndUpdateFeed.kt @@ -25,13 +25,15 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserReports +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel @Composable fun WatchReportsAndUpdateFeed( baseUser: User, feedViewModel: UserProfileReportFeedViewModel, + accountViewModel: AccountViewModel, ) { - val userState by observeUserReports(baseUser) + val userState by observeUserReports(baseUser, accountViewModel) LaunchedEffect(userState) { feedViewModel.invalidateData() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt index 08d6e0462..0e03a0aca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt @@ -36,7 +36,7 @@ fun TabReceivedZaps( accountViewModel: AccountViewModel, nav: INav, ) { - WatchZapsAndUpdateFeed(baseUser, zapFeedViewModel) + WatchZapsAndUpdateFeed(baseUser, zapFeedViewModel, accountViewModel) Column(Modifier.fillMaxHeight()) { LnZapFeedView(zapFeedViewModel, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/WatchZapsAndUpdateFeed.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/WatchZapsAndUpdateFeed.kt index 823ee4116..580c8e5f0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/WatchZapsAndUpdateFeed.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/WatchZapsAndUpdateFeed.kt @@ -25,14 +25,16 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserZaps +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsFeedViewModel @Composable fun WatchZapsAndUpdateFeed( baseUser: User, feedViewModel: UserProfileZapsFeedViewModel, + accountViewModel: AccountViewModel, ) { - val userState by observeUserZaps(baseUser) + val userState by observeUserZaps(baseUser, accountViewModel) LaunchedEffect(userState) { feedViewModel.invalidateData() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt index bab1d5c32..d7f019072 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt @@ -27,11 +27,15 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserZapAmount import com.vitorpamplona.amethyst.ui.note.showAmountInteger +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes @Composable -fun ZapTabHeader(baseUser: User) { - val zapAmount by observeUserZapAmount(baseUser) +fun ZapTabHeader( + baseUser: User, + accountViewModel: AccountViewModel, +) { + val zapAmount by observeUserZapAmount(baseUser, accountViewModel) Text(text = "${showAmountInteger(zapAmount)} ${stringRes(id = R.string.zaps)}") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt index 5caa05ecd..acaf0f0a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt @@ -92,7 +92,7 @@ fun LoadRedirectScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val noteState by observeNote(baseNote) + val noteState by observeNote(baseNote, accountViewModel) LaunchedEffect(key1 = noteState) { val note = noteState?.note ?: return@LaunchedEffect diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddInboxRelayForSearchCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddInboxRelayForSearchCard.kt index b2d34926b..bad9a206f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddInboxRelayForSearchCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddInboxRelayForSearchCard.kt @@ -105,7 +105,7 @@ fun ObserveRelayListForSearch( accountViewModel, ) { relayList -> if (relayList != null) { - val relayListEvent by observeNoteEvent(relayList) + val relayListEvent by observeNoteEvent(relayList, accountViewModel) inner(relayListEvent) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt index 016cb69e2..915015361 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt @@ -111,7 +111,7 @@ fun SearchScreen( DisappearingScaffold( isInvertedLayout = false, topBar = { - SearchBar(searchBarViewModel, listState, nav) + SearchBar(searchBarViewModel, listState, accountViewModel, nav) }, bottomBar = { AppBottomBar(Route.Search, accountViewModel) { route -> @@ -134,9 +134,10 @@ fun SearchScreen( private fun SearchBar( searchBarViewModel: SearchBarViewModel, listState: LazyListState, + accountViewModel: AccountViewModel, nav: INav, ) { - TextSearchDataSourceSubscription(searchBarViewModel) + TextSearchDataSourceSubscription(searchBarViewModel, accountViewModel) LaunchedEffect(Unit) { LocalCache.live.newEventBundles.collect { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index 9136199b2..652c1128a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -438,9 +438,9 @@ private fun FullBleedNoteCompose( ) { NoteAuthorPicture( baseNote = baseNote, - nav = nav, - accountViewModel = accountViewModel, size = Size55dp, + accountViewModel = accountViewModel, + nav = nav, ) Column(modifier = Modifier.padding(start = 10.dp)) { @@ -500,7 +500,7 @@ private fun FullBleedNoteCompose( Spacer(modifier = Modifier.height(10.dp)) when (noteEvent) { - is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) + is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote, accountViewModel) is LongTextNoteEvent -> RenderLongFormHeaderForThread(noteEvent) is WikiNoteEvent -> RenderWikiHeaderForThread(noteEvent, accountViewModel, nav) is ClassifiedsEvent -> RenderClassifiedsReaderForThread(noteEvent, baseNote, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt index 45e4a0f12..5da55ae4b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt @@ -58,7 +58,7 @@ fun ThreadScreen( LoadNote(noteId, accountViewModel) { if (it != null) { // this will force loading every post from this thread. - EventFinderFilterAssemblerSubscription(it) + EventFinderFilterAssemblerSubscription(it, accountViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt index 68bb1cdce..ecc0aed8e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/StoriesTopBar.kt @@ -40,6 +40,7 @@ fun StoriesTopBar( FollowListWithRoutes( followListsModel = accountViewModel.feedStates.feedListOptions, listName = list, + accountViewModel = accountViewModel, ) { listName -> accountViewModel.account.settings.changeDefaultStoriesFollowList(listName.code) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt index 848f22cd7..7aa8de864 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt @@ -313,7 +313,7 @@ private fun RenderAuthorInformation( accountViewModel: AccountViewModel, ) { Row(modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp), verticalAlignment = Alignment.CenterVertically) { - NoteAuthorPicture(note, nav, accountViewModel, Size55dp) + NoteAuthorPicture(note, Size55dp, accountViewModel = accountViewModel, nav = nav) Spacer(modifier = DoubleHorzSpacer) diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 905f66e90..de1886752 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -648,10 +648,11 @@ Relays - Note Discovery + Follow Packs + Feed Algorithms Marketplace - Live - Community + Live Streams + Communities Chats Approved Posts