Adds live activities to the main feed.

This commit is contained in:
Vitor Pamplona 2023-06-20 13:54:47 -04:00
parent d1bbdef5c4
commit 8aae101ef6
15 changed files with 255 additions and 73 deletions

View File

@ -852,8 +852,6 @@ object LocalCache {
note.loadEvent(event, author, replyTo)
Log.d("CM", "AAA REPLY TO ${event.content} ${event.taggedEvents()}")
// Counts the replies
replyTo.forEach {
it.addReply(note)

View File

@ -375,7 +375,14 @@ open class Note(val idHex: String) {
}
fun isNewThread(): Boolean {
return event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo?.size == 0
return (
event is RepostEvent ||
event is GenericRepostEvent ||
replyTo == null ||
replyTo?.size == 0
) &&
event !is ChannelMessageEvent &&
event !is LiveActivitiesChatMessageEvent
}
fun hasZapped(loggedIn: User): Boolean {

View File

@ -5,6 +5,8 @@ import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PinListEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
@ -68,7 +70,9 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
PollNoteEvent.kind,
HighlightEvent.kind,
AudioTrackEvent.kind,
PinListEvent.kind
PinListEvent.kind,
LiveActivitiesChatMessageEvent.kind,
LiveActivitiesEvent.kind
),
authors = followSet,
limit = 400,

View File

@ -3,6 +3,8 @@ package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
@ -27,7 +29,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
return collection
.asSequence()
.filter {
(it.event is TextNoteEvent || it.event is PollNoteEvent) &&
(it.event is TextNoteEvent || it.event is PollNoteEvent || it.event is ChannelMessageEvent || it.event is LiveActivitiesChatMessageEvent) &&
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
it.author?.let { !account.isHidden(it) } ?: true &&

View File

@ -0,0 +1,69 @@
package com.vitorpamplona.amethyst.ui.dal
import android.util.Log
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent
import okhttp3.Request
import java.util.Date
class HomeLiveActivitiesFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultHomeFollowList
}
override fun feed(): List<Note> {
val longFormNotes = innerApplyFilter(LocalCache.addressables.values)
return sort(longFormNotes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
checkNotInMainThread()
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
val fortyEightHrs = (Date().time / 1000) - 60 * 60 * 48 // hrs
return collection
.asSequence()
.filter { it ->
val noteEvent = it.event
(noteEvent is LiveActivitiesEvent && noteEvent.createdAt > fortyEightHrs && noteEvent.status() == "live" && checkIfOnline(noteEvent.streaming())) &&
(it.author?.pubkeyHex in followingKeySet || (noteEvent.isTaggedHashes(followingTagSet))) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true
}
.toSet()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}
}
fun checkIfOnline(url: String?): Boolean {
if (url.isNullOrBlank()) return false
val request = Request.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.get()
.build()
return try {
HttpClient.getHttpClient().newCall(request).execute().code == 200
} catch (e: Exception) {
Log.e("LiveActivities", "Failed to check streaming url $url", e)
false
}
}

View File

@ -13,6 +13,7 @@ import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
@ -38,6 +39,7 @@ import kotlinx.coroutines.launch
fun AppNavigation(
homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
newFeedViewModel: NostrChatroomListNewFeedViewModel,
searchFeedViewModel: NostrGlobalFeedViewModel,
@ -67,6 +69,7 @@ fun AppNavigation(
HomeScreen(
homeFeedViewModel = homeFeedViewModel,
repliesFeedViewModel = repliesFeedViewModel,
liveActivitiesViewModel = liveActivitiesViewModel,
accountViewModel = accountViewModel,
nav = nav,
nip47 = nip47

View File

@ -255,7 +255,6 @@ fun ChatroomMessageCompose(
}
val replyTo = note.replyTo
println("AAA replyTo ${replyTo?.lastOrNull()}")
if (!innerQuote && !replyTo.isNullOrEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) {
replyTo.lastOrNull()?.let { note ->

View File

@ -92,6 +92,7 @@ import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
@ -379,7 +380,7 @@ fun NormalNote(
val channelHex = remember { baseNote.channelHex() }
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && channelHex != null) {
ChannelHeader(channelHex = channelHex, accountViewModel = accountViewModel, nav = nav)
ChannelHeader(channelHex = channelHex, showVideo = !makeItShort, accountViewModel = accountViewModel, nav = nav)
} else if (noteEvent is BadgeDefinitionEvent) {
BadgeDisplay(baseNote = baseNote)
} else if (noteEvent is FileHeaderEvent) {
@ -1648,7 +1649,8 @@ private fun ReplyRow(
val showChannelReply by remember {
derivedStateOf {
noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())
(noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) ||
(noteEvent is LiveActivitiesChatMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser()))
}
}
@ -1663,10 +1665,18 @@ private fun ReplyRow(
} else if (showChannelReply) {
val channelHex = note.channelHex()
channelHex?.let {
val replies = remember { note.replyTo?.toImmutableList() }
val mentions = remember { (note.event as? ChannelMessageEvent)?.mentions()?.toImmutableList() ?: persistentListOf() }
ChannelHeader(
channelHex = channelHex,
showVideo = false,
modifier = remember { Modifier.padding(vertical = 10.dp) },
accountViewModel = accountViewModel,
nav = nav
)
ReplyInformationChannel(replies, mentions, it, accountViewModel, nav)
val replies = remember { note.replyTo?.toImmutableList() }
val mentions = remember { (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf() }
ReplyInformationChannel(replies, mentions, accountViewModel, nav)
Spacer(modifier = Modifier.height(5.dp))
}
}

View File

@ -128,7 +128,6 @@ private fun ReplyInformation(
fun ReplyInformationChannel(
replyTo: ImmutableList<Note>?,
mentions: ImmutableList<String>,
channelHex: String,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
@ -145,67 +144,25 @@ fun ReplyInformationChannel(
}
if (sortedMentions != null) {
LoadChannel(channelHex) { channel ->
ReplyInformationChannel(
replyTo,
sortedMentions,
channel,
onUserTagClick = {
nav("User/${it.pubkeyHex}")
},
onChannelTagClick = {
nav("Channel/${it.idHex}")
}
)
}
ReplyInformationChannel(
replyTo,
sortedMentions,
onUserTagClick = {
nav("User/${it.pubkeyHex}")
}
)
}
}
@Composable
fun ReplyInformationChannel(replyTo: ImmutableList<Note>?, mentions: ImmutableList<User>?, channel: Channel, nav: (String) -> Unit) {
ReplyInformationChannel(
replyTo,
mentions,
channel,
onUserTagClick = {
nav("User/${it.pubkeyHex}")
},
onChannelTagClick = {
nav("Channel/${it.idHex}")
}
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ReplyInformationChannel(
replyTo: ImmutableList<Note>?,
mentions: ImmutableList<User>?,
baseChannel: Channel,
prefix: String = "",
onUserTagClick: (User) -> Unit,
onChannelTagClick: (Channel) -> Unit
onUserTagClick: (User) -> Unit
) {
val channelState by baseChannel.live.observeAsState()
val channel = channelState?.channel ?: return
val channelName = remember(channelState) {
AnnotatedString("${channel.toBestDisplayName()} ")
}
FlowRow() {
Text(
stringResource(R.string.in_channel),
fontSize = 13.sp,
color = MaterialTheme.colors.placeholderText
)
ClickableText(
text = channelName,
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.lessImportantLink, fontSize = 13.sp),
onClick = { onChannelTagClick(channel) }
)
if (mentions != null && mentions.isNotEmpty()) {
if (replyTo != null && replyTo.isNotEmpty()) {
Text(

View File

@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeLiveActivitiesFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter
@ -99,6 +100,15 @@ class NostrChatroomListNewFeedViewModel(val account: Account) : FeedViewModel(Ch
}
}
@Stable
class NostrHomeFeedLiveActivitiesViewModel(val account: Account) : FeedViewModel(HomeLiveActivitiesFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrHomeFeedLiveActivitiesViewModel : ViewModel> create(modelClass: Class<NostrHomeFeedLiveActivitiesViewModel>): NostrHomeFeedLiveActivitiesViewModel {
return NostrHomeFeedLiveActivitiesViewModel(account) as NostrHomeFeedLiveActivitiesViewModel
}
}
}
@Stable
class NostrHomeFeedViewModel(val account: Account) : FeedViewModel(HomeNewThreadFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {

View File

@ -378,7 +378,7 @@ fun NoteMaster(
) {
Column() {
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && note.channelHex() != null) {
ChannelHeader(channelHex = note.channelHex()!!, accountViewModel = accountViewModel, nav = nav)
ChannelHeader(channelHex = note.channelHex()!!, showVideo = true, accountViewModel = accountViewModel, nav = nav)
} else if (noteEvent is FileHeaderEvent) {
FileHeaderDisplay(baseNote)
} else if (noteEvent is FileStorageHeaderEvent) {

View File

@ -53,6 +53,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
@ -98,6 +99,9 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -203,8 +207,9 @@ fun ChannelScreen(
Column(Modifier.fillMaxHeight()) {
ChannelHeader(
channel,
accountViewModel,
baseChannel = channel,
showVideo = true,
accountViewModel = accountViewModel,
nav = nav
)
@ -472,8 +477,14 @@ fun MyTextField(
}
@Composable
fun ChannelHeader(channelHex: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var baseChannel by remember { mutableStateOf<Channel?>(null) }
fun ChannelHeader(
channelHex: String,
showVideo: Boolean,
modifier: Modifier = StdPadding,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
var baseChannel by remember { mutableStateOf<Channel?>(LocalCache.channels[channelHex]) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = channelHex) {
@ -483,12 +494,24 @@ fun ChannelHeader(channelHex: String, accountViewModel: AccountViewModel, nav: (
}
baseChannel?.let {
ChannelHeader(it, accountViewModel, nav)
ChannelHeader(
it,
showVideo,
modifier,
accountViewModel,
nav
)
}
}
@Composable
fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
fun ChannelHeader(
baseChannel: Channel,
showVideo: Boolean,
modifier: Modifier = StdPadding,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val channelState by baseChannel.live.observeAsState()
val channel = remember(channelState) { channelState?.channel } ?: return
@ -506,7 +529,7 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, nav:
}
}
if (streamingUrl != null) {
if (streamingUrl != null && showVideo) {
Row(verticalAlignment = Alignment.CenterVertically) {
VideoView(
videoUri = streamingUrl!!,
@ -516,7 +539,7 @@ fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, nav:
}
}
Column(modifier = Modifier.padding(12.dp)) {
Column(modifier = modifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
RobohashAsyncImageProxy(
robot = channel.idHex,
@ -624,12 +647,31 @@ private fun LiveChannelActionOptions(
}
}
val status by remember {
derivedStateOf {
channel.info?.status()
}
}
if (isMe) {
// EditButton(accountViewModel, channel)
} else {
LocalCache.addressables[channel.idHex]?.let {
if (status == "live") {
Text(
text = "LIVE",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier
.clip(SmallBorder)
.drawBehind { drawRect(Color.Red) }
.padding(horizontal = 5.dp)
)
Spacer(modifier = StdHorzSpacer)
}
LikeReaction(it, MaterialTheme.colors.onSurface, accountViewModel)
Spacer(modifier = Modifier.width(5.dp))
Spacer(modifier = StdHorzSpacer)
ZapReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel)
}
}

View File

@ -1,9 +1,15 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material.MaterialTheme
@ -14,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@ -30,7 +37,9 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog
import com.vitorpamplona.amethyst.ui.screen.FeedState
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
@ -47,6 +56,7 @@ import kotlinx.coroutines.launch
fun HomeScreen(
homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
nip47: String? = null
@ -88,7 +98,7 @@ fun HomeScreen(
Column(
modifier = Modifier.padding(vertical = 0.dp)
) {
HomePages(pagerState, tabs, accountViewModel, nav)
HomePages(pagerState, tabs, liveActivitiesViewModel, accountViewModel, nav)
}
}
}
@ -98,6 +108,7 @@ fun HomeScreen(
private fun HomePages(
pagerState: PagerState,
tabs: ImmutableList<TabItem>,
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
@ -120,6 +131,12 @@ private fun HomePages(
}
}
LiveActivities(
liveActivitiesViewModel = liveActivitiesViewModel,
accountViewModel = accountViewModel,
nav = nav
)
HorizontalPager(pageCount = 2, state = pagerState) { page ->
RefresheableFeedView(
viewModel = tabs[page].viewModel,
@ -131,6 +148,60 @@ private fun HomePages(
}
}
@Composable
fun LiveActivities(
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val feedState by liveActivitiesViewModel.feedContent.collectAsState()
Crossfade(
targetState = feedState,
animationSpec = tween(durationMillis = 100)
) { state ->
when (state) {
is FeedState.Loaded -> {
FeedLoaded(
state,
accountViewModel,
nav
)
}
else -> {
}
}
}
}
@Composable
private fun FeedLoaded(
state: FeedState.Loaded,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
ChannelHeader(
channelHex = item.idHex,
showVideo = false,
modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
accountViewModel = accountViewModel,
nav = nav
)
}
}
}
@Composable
fun WatchAccountForHomeScreen(
homeFeedViewModel: NostrHomeFeedViewModel,

View File

@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
@ -89,6 +90,11 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
factory = NostrHomeRepliesFeedViewModel.Factory(accountViewModel.account)
)
val liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel = viewModel(
key = accountViewModel.userProfile().pubkeyHex + "NostrHomeLiveActivitiesFeedViewModel",
factory = NostrHomeFeedLiveActivitiesViewModel.Factory(accountViewModel.account)
)
val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel(
key = accountViewModel.userProfile().pubkeyHex + "NostrGlobalFeedViewModel",
factory = NostrGlobalFeedViewModel.Factory(accountViewModel.account)
@ -184,6 +190,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
AppNavigation(
homeFeedViewModel,
repliesFeedViewModel,
liveActivitiesViewModel,
knownFeedViewModel,
newFeedViewModel,
searchFeedViewModel,

View File

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.theme
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
@ -25,3 +26,5 @@ val StdHorzSpacer = Modifier.width(5.dp)
val DoubleHorzSpacer = Modifier.width(10.dp)
val Size35dp = 35.dp
val StdPadding = Modifier.padding(10.dp)