- Adds FollowSets to the discovery tabs.
- Refactors discovery screen, creating a package for each feature. - Fixes Android studio previews
This commit is contained in:
+1
-1
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+5
-2
@@ -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(
|
||||
|
||||
+21
-9
@@ -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<ChannelState?> {
|
||||
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
||||
fun observeChannel(
|
||||
baseChannel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<ChannelState?> {
|
||||
ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel)
|
||||
|
||||
return baseChannel
|
||||
.flow()
|
||||
@@ -52,8 +55,11 @@ fun observeChannel(baseChannel: Channel): State<ChannelState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeChannelNoteAuthors(baseChannel: Channel): State<ImmutableList<User>> {
|
||||
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
||||
fun observeChannelNoteAuthors(
|
||||
baseChannel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<ImmutableList<User>> {
|
||||
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<ImmutableList<User>>
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeChannelPicture(baseChannel: Channel): State<String?> {
|
||||
fun observeChannelPicture(
|
||||
baseChannel: Channel,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String?> {
|
||||
// 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<String?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeChannelInfo(baseChannel: LiveActivitiesChannel): State<LiveActivitiesEvent?> {
|
||||
fun observeChannelInfo(
|
||||
baseChannel: LiveActivitiesChannel,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<LiveActivitiesEvent?> {
|
||||
// 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 =
|
||||
|
||||
+5
-2
@@ -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(
|
||||
|
||||
+72
-29
@@ -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<NoteState> {
|
||||
fun observeNote(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState> {
|
||||
// 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<NoteState> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun <T : Event> observeNoteEvent(note: Note): State<T?> {
|
||||
fun <T : Event> observeNoteEvent(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<T?> {
|
||||
// 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 <T : Event> observeNoteEvent(note: Note): State<T?> {
|
||||
@Composable
|
||||
fun <T> observeNoteAndMap(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
map: (Note) -> T,
|
||||
): State<T> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note)
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
val flow =
|
||||
remember(note) {
|
||||
@@ -96,10 +104,11 @@ fun <T> observeNoteAndMap(
|
||||
@Composable
|
||||
fun <T, U> observeNoteEventAndMap(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
map: (T) -> U,
|
||||
): State<U?> {
|
||||
// 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 <T, U> observeNoteEventAndMap(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeNoteHasEvent(note: Note): State<Boolean> {
|
||||
fun observeNoteHasEvent(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// 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<Boolean> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteReplies(note: Note): State<NoteState?> {
|
||||
fun observeNoteReplies(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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<NoteState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteReplyCount(note: Note): State<Int> {
|
||||
fun observeNoteReplyCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteReactions(note: Note): State<NoteState?> {
|
||||
fun observeNoteReactions(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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<NoteState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteReactionCount(note: Note): State<Int> {
|
||||
fun observeNoteReactionCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteZaps(note: Note): State<NoteState?> {
|
||||
fun observeNoteZaps(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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<NoteState?> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteReposts(note: Note): State<NoteState?> {
|
||||
fun observeNoteReposts(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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<NoteState?> {
|
||||
fun observeNoteRepostsBy(
|
||||
note: Note,
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// 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<Int> {
|
||||
fun observeNoteRepostCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteReferences(note: Note): State<Boolean> {
|
||||
fun observeNoteReferences(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// 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<Boolean> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteOts(note: Note): State<NoteState?> {
|
||||
fun observeNoteOts(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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<NoteState?> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeNoteEdits(note: Note): State<NoteState?> {
|
||||
fun observeNoteEdits(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// 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
|
||||
|
||||
+5
-2
@@ -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(
|
||||
|
||||
+120
-49
@@ -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<UserState?> {
|
||||
fun observeUser(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserName(user: User): State<String> {
|
||||
fun observeUserName(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String> {
|
||||
// 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<String> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserNip05(user: User): State<String?> {
|
||||
fun observeUserNip05(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String?> {
|
||||
// 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<String?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserAboutMe(user: User): State<String> {
|
||||
fun observeUserAboutMe(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String> {
|
||||
// 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<String> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserInfo(user: User): State<UserMetadata?> {
|
||||
fun observeUserInfo(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserMetadata?> {
|
||||
// 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<UserMetadata?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserBanner(user: User): State<String?> {
|
||||
fun observeUserBanner(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String?> {
|
||||
// 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<String?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserPicture(user: User): State<String?> {
|
||||
fun observeUserPicture(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String?> {
|
||||
// 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<String?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserShortName(user: User): State<String> {
|
||||
fun observeUserShortName(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String> {
|
||||
// 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<String> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserFollows(user: User): State<UserState?> {
|
||||
fun observeUserFollows(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserFollowCount(user: User): State<Int> {
|
||||
fun observeUserFollowCount(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserTagFollows(user: User): State<Int> {
|
||||
fun observeUserTagFollows(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserBookmarks(user: User): State<UserState?> {
|
||||
fun observeUserBookmarks(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserBookmarkCount(user: User): State<Int> {
|
||||
fun observeUserBookmarkCount(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserFollowers(user: User): State<UserState?> {
|
||||
fun observeUserFollowers(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserFollowerCount(user: User): State<Int> {
|
||||
fun observeUserFollowerCount(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
fun observeUserIsFollowing(
|
||||
user1: User,
|
||||
user2: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// 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<Boolean> {
|
||||
// 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<Boolean> {
|
||||
// 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<Boolean> {
|
||||
// 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<Boolean> {
|
||||
// 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<UserState?> {
|
||||
fun observeUserZaps(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserZapAmount(user: User): State<BigDecimal> {
|
||||
fun observeUserZapAmount(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<BigDecimal> {
|
||||
// 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<BigDecimal> {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun observeUserReports(user: User): State<UserState?> {
|
||||
fun observeUserReports(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserState?> {
|
||||
// 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<UserState?> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserReportCount(user: User): State<Int> {
|
||||
fun observeUserReportCount(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// 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<Int> {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeUserStatuses(user: User): State<ImmutableList<AddressableNote>> {
|
||||
fun observeUserStatuses(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<ImmutableList<AddressableNote>> {
|
||||
// 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<ImmutableList<AddressableNote>> {
|
||||
fun observeUserRelayIntoList(
|
||||
user: User,
|
||||
relayUrl: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// 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<String?> {
|
||||
// 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<RelayUsage> {
|
||||
fun observeUserRelaysUsing(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<RelayUsage> {
|
||||
// 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 =
|
||||
|
||||
+5
-2
@@ -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(
|
||||
|
||||
+5
-2
@@ -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(
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ class MarkdownMediaRenderer(
|
||||
) {
|
||||
renderInvisible(richTextStringBuilder) {
|
||||
// Preloads note if not loaded yet.
|
||||
EventFinderFilterAssemblerSubscription(baseNote)
|
||||
EventFinderFilterAssemblerSubscription(baseNote, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
+8
-12
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
+9
-4
@@ -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<FeedDefinition>,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,8 @@ fun HiddenNote(
|
||||
NoteAuthorPicture(
|
||||
baseNote = it,
|
||||
size = Size35dp,
|
||||
nav = nav,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<User>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ fun LoadStatuses(
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (ImmutableList<AddressableNote>) -> Unit,
|
||||
) {
|
||||
val userStatuses by observeUserStatuses(user)
|
||||
val userStatuses by observeUserStatuses(user, accountViewModel)
|
||||
|
||||
content(userStatuses)
|
||||
}
|
||||
@@ -158,7 +158,7 @@ fun LoadOts(
|
||||
) {
|
||||
var earliestDate: GenericLoadable<Long> 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 ->
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+10
-7
@@ -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<AddressableNote>) {
|
||||
fun ObserveAllStatusesToAvoidSwitchigAllTheTime(
|
||||
statuses: ImmutableList<AddressableNote>,
|
||||
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)
|
||||
|
||||
@@ -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<DraftEvent>(note)
|
||||
val noteEvent by observeNoteEvent<DraftEvent>(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,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -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<Color>(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")
|
||||
|
||||
@@ -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<Note>?,
|
||||
mentions: ImmutableList<User>?,
|
||||
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()}",
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ fun UserCompose(
|
||||
UsernameDisplay(baseUser, accountViewModel = accountViewModel)
|
||||
}
|
||||
|
||||
AboutDisplay(baseUser)
|
||||
AboutDisplay(baseUser, accountViewModel)
|
||||
}
|
||||
|
||||
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<User?>(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<String?>(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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ fun ObserveRelayListForDMs(
|
||||
accountViewModel,
|
||||
) { relayList ->
|
||||
if (relayList != null) {
|
||||
val relayListEvent by observeNoteEvent<ChatMessageRelayListEvent>(relayList)
|
||||
val relayListEvent by observeNoteEvent<ChatMessageRelayListEvent>(relayList, accountViewModel)
|
||||
|
||||
inner(relayListEvent)
|
||||
}
|
||||
|
||||
+4
-3
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<String>(
|
||||
showAmount(baseReward.amount),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() },
|
||||
|
||||
+1
-1
@@ -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)) {
|
||||
|
||||
@@ -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<BadgeDefinitionEvent>(baseNote)
|
||||
fun BadgeDisplay(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val badgeData by observeNoteEvent<BadgeDefinitionEvent>(baseNote, accountViewModel)
|
||||
|
||||
badgeData?.let {
|
||||
RenderBadge(
|
||||
@@ -174,6 +177,6 @@ fun RenderBadgeAward(
|
||||
}
|
||||
|
||||
note.replyTo?.firstOrNull()?.let {
|
||||
BadgeDisplay(baseNote = it)
|
||||
BadgeDisplay(baseNote = it, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ fun LongCommunityHeader(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent by observeNoteEvent<CommunityDefinitionEvent>(baseNote)
|
||||
val noteEvent by observeNoteEvent<CommunityDefinitionEvent>(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<CommunityDefinitionEvent>(baseNote)
|
||||
val noteEvent by observeNoteEvent<CommunityDefinitionEvent>(baseNote, accountViewModel)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
noteEvent?.image()?.let {
|
||||
|
||||
@@ -70,7 +70,7 @@ public fun RenderEmojiPack(
|
||||
accountViewModel: AccountViewModel,
|
||||
onClick: ((EmojiUrlTag) -> Unit)? = null,
|
||||
) {
|
||||
val noteEvent by observeNoteEvent<EmojiPackEvent>(baseNote)
|
||||
val noteEvent by observeNoteEvent<EmojiPackEvent>(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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -100,7 +100,7 @@ private fun RenderShortRepositoryHeader(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent by observeNoteEvent<GitRepositoryEvent>(baseNote)
|
||||
val noteEvent by observeNoteEvent<GitRepositoryEvent>(baseNote, accountViewModel)
|
||||
|
||||
Column(
|
||||
modifier = MaterialTheme.colorScheme.replyModifier.padding(10.dp),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<InteractiveStoryReadingStateEvent>(readingStateNote)
|
||||
val readingState by observeNoteEvent<InteractiveStoryReadingStateEvent>(readingStateNote, accountViewModel)
|
||||
|
||||
val currentScene = readingState?.currentScene()
|
||||
|
||||
if (currentScene != null && currentScene != rootEvent.address()) {
|
||||
LoadAddressableNote(currentScene, accountViewModel) { currentSceneBaseNote ->
|
||||
currentSceneBaseNote?.let {
|
||||
val currentSceneEvent by observeNoteEvent<InteractiveStoryBaseEvent>(it)
|
||||
val currentSceneEvent by observeNoteEvent<InteractiveStoryBaseEvent>(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) },
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -223,7 +223,7 @@ fun ShortTorrentHeader(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent by observeNoteEvent<TorrentEvent>(baseNote)
|
||||
val noteEvent by observeNoteEvent<TorrentEvent>(baseNote, accountViewModel)
|
||||
|
||||
ShortTorrentHeader(
|
||||
title = noteEvent?.title() ?: TorrentEvent.ALT_DESCRIPTION,
|
||||
|
||||
+7
-5
@@ -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)
|
||||
|
||||
+31
-27
@@ -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<User>) -> 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<String>): List<User> =
|
||||
hexList
|
||||
.mapNotNull { hex -> checkGetOrCreateUser(hex) }
|
||||
.sortedBy { account.isFollowing(it) }
|
||||
.reversed()
|
||||
|
||||
fun loadUsers(
|
||||
event: GeneralListEvent,
|
||||
onReady: (ImmutableList<User>) -> 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 <T : ViewModel> create(modelClass: Class<T>): T = AccountViewModel(accountSettings, settings) as T
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): 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<HexKey> =
|
||||
runBlocking {
|
||||
@@ -1802,6 +1804,7 @@ fun mockAccountViewModel(): AccountViewModel {
|
||||
),
|
||||
),
|
||||
sharedPreferencesViewModel.sharedPrefs,
|
||||
Amethyst(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1819,5 +1822,6 @@ fun mockVitorAccountViewModel(): AccountViewModel {
|
||||
),
|
||||
),
|
||||
sharedPreferencesViewModel.sharedPrefs,
|
||||
Amethyst(),
|
||||
)
|
||||
}
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
+8
-4
@@ -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<User>?,
|
||||
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()}" },
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ private fun WatchAndDisplayUser(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val userState by observeUserInfo(author)
|
||||
val userState by observeUserInfo(author, accountViewModel)
|
||||
|
||||
UserDisplayNameLayout(
|
||||
picture = {
|
||||
|
||||
+3
-3
@@ -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(
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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 =
|
||||
|
||||
+5
-5
@@ -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 =
|
||||
|
||||
+219
@@ -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<Color>? = 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<Color>? = 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<Color>? = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -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()
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ fun DiscoveryTopBar(
|
||||
FollowListWithoutRoutes(
|
||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||
listName = list,
|
||||
accountViewModel,
|
||||
) { listName ->
|
||||
accountViewModel.account.settings.changeDefaultDiscoveryFollowList(listName.code)
|
||||
}
|
||||
|
||||
+21
@@ -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<TypedFilter> {
|
||||
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(
|
||||
|
||||
+4
-4
@@ -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<Note> {
|
||||
val params = buildFilterParams(account)
|
||||
@@ -67,7 +67,7 @@ open class DiscoverChatFeedFilter(
|
||||
override fun applyFilter(collection: Set<Note>): Set<Note> = 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,
|
||||
+198
@@ -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<ImmutableList<User>>(
|
||||
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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+76
@@ -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<Note>() {
|
||||
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<Note> {
|
||||
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<Note>): Set<Note> = 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<Note>): Set<Note> {
|
||||
val params = buildFilterParams(account)
|
||||
|
||||
return collection.filterTo(HashSet()) {
|
||||
val noteEvent = it.event
|
||||
noteEvent is FollowListEvent && params.match(noteEvent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
|
||||
}
|
||||
+204
@@ -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<User>,
|
||||
)
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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<Note> {
|
||||
val allChannelNotes = LocalCache.channels.mapNotNull { _, channel -> LocalCache.getNoteIfExists(channel.idHex) }
|
||||
@@ -55,7 +55,7 @@ open class DiscoverLiveFeedFilter(
|
||||
|
||||
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||
val filterParams =
|
||||
FilterByListParams.create(
|
||||
FilterByListParams.Companion.create(
|
||||
userHex = account.userProfile().pubkeyHex,
|
||||
selectedListName = account.settings.defaultDiscoveryFollowList.value,
|
||||
followLists = account.liveDiscoveryFollowLists.value,
|
||||
+255
@@ -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<ParticipantTag>,
|
||||
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<ParticipantTag>,
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
inner: @Composable (ImmutableList<User>) -> Unit,
|
||||
) {
|
||||
var participantUsers by remember {
|
||||
mutableStateOf<ImmutableList<User>>(
|
||||
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<User>())
|
||||
|
||||
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)
|
||||
}
|
||||
+225
@@ -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<HexKey>,
|
||||
)
|
||||
|
||||
@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<String>,
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (ImmutableList<User>) -> Unit,
|
||||
) {
|
||||
var participantUsers by remember {
|
||||
mutableStateOf<ImmutableList<User>>(
|
||||
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)
|
||||
}
|
||||
+7
-7
@@ -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<Note> {
|
||||
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<Note>): Set<Note> {
|
||||
// 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<Note>): List<Note> {
|
||||
val lastNote =
|
||||
+219
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+4
-4
@@ -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<Note> {
|
||||
val notes =
|
||||
@@ -56,7 +56,7 @@ open class DiscoverNIP89FeedFilter(
|
||||
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
|
||||
|
||||
fun buildFilterParams(account: Account): FilterByListParams =
|
||||
FilterByListParams.create(
|
||||
FilterByListParams.Companion.create(
|
||||
account.userProfile().pubkeyHex,
|
||||
account.settings.defaultDiscoveryFollowList.value,
|
||||
account.liveDiscoveryFollowLists.value,
|
||||
+164
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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<Note> {
|
||||
val params = buildFilterParams(account)
|
||||
@@ -55,7 +55,7 @@ open class DiscoverMarketplaceFeedFilter(
|
||||
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
|
||||
|
||||
fun buildFilterParams(account: Account): FilterByListParams =
|
||||
FilterByListParams.create(
|
||||
FilterByListParams.Companion.create(
|
||||
account.userProfile().pubkeyHex,
|
||||
account.settings.defaultDiscoveryFollowList.value,
|
||||
account.liveDiscoveryFollowLists.value,
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+12
-9
@@ -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
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ fun HomeTopBar(
|
||||
FollowListWithRoutes(
|
||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||
listName = list,
|
||||
accountViewModel = accountViewModel,
|
||||
) { listName ->
|
||||
if (listName.route != null) {
|
||||
nav.nav(listName.route)
|
||||
|
||||
+5
-5
@@ -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)
|
||||
}
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ fun NotificationTopBar(
|
||||
FollowListWithoutRoutes(
|
||||
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||
listName = list,
|
||||
accountViewModel,
|
||||
) { listName ->
|
||||
accountViewModel.account.settings.changeDefaultNotificationFollowList(listName.code)
|
||||
}
|
||||
|
||||
+9
-7
@@ -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 ->
|
||||
|
||||
+6
-2
@@ -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)}")
|
||||
}
|
||||
|
||||
+6
-2
@@ -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) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user