Adds live activities to the main feed.
This commit is contained in:
@@ -852,8 +852,6 @@ object LocalCache {
|
|||||||
|
|
||||||
note.loadEvent(event, author, replyTo)
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
Log.d("CM", "AAA REPLY TO ${event.content} ${event.taggedEvents()}")
|
|
||||||
|
|
||||||
// Counts the replies
|
// Counts the replies
|
||||||
replyTo.forEach {
|
replyTo.forEach {
|
||||||
it.addReply(note)
|
it.addReply(note)
|
||||||
|
|||||||
@@ -375,7 +375,14 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isNewThread(): Boolean {
|
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 {
|
fun hasZapped(loggedIn: User): Boolean {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.vitorpamplona.amethyst.model.UserState
|
|||||||
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
|
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
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.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
@@ -68,7 +70,9 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
|
|||||||
PollNoteEvent.kind,
|
PollNoteEvent.kind,
|
||||||
HighlightEvent.kind,
|
HighlightEvent.kind,
|
||||||
AudioTrackEvent.kind,
|
AudioTrackEvent.kind,
|
||||||
PinListEvent.kind
|
PinListEvent.kind,
|
||||||
|
LiveActivitiesChatMessageEvent.kind,
|
||||||
|
LiveActivitiesEvent.kind
|
||||||
),
|
),
|
||||||
authors = followSet,
|
authors = followSet,
|
||||||
limit = 400,
|
limit = 400,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.vitorpamplona.amethyst.ui.dal
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
return collection
|
return collection
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter {
|
.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)) &&
|
(it.author?.pubkeyHex in followingKeySet || (it.event?.isTaggedHashes(followingTagSet) ?: false)) &&
|
||||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||||
it.author?.let { !account.isHidden(it) } ?: true &&
|
it.author?.let { !account.isHidden(it) } ?: true &&
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
|
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.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
@@ -38,6 +39,7 @@ import kotlinx.coroutines.launch
|
|||||||
fun AppNavigation(
|
fun AppNavigation(
|
||||||
homeFeedViewModel: NostrHomeFeedViewModel,
|
homeFeedViewModel: NostrHomeFeedViewModel,
|
||||||
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
|
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
|
||||||
|
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
|
||||||
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
|
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
|
||||||
newFeedViewModel: NostrChatroomListNewFeedViewModel,
|
newFeedViewModel: NostrChatroomListNewFeedViewModel,
|
||||||
searchFeedViewModel: NostrGlobalFeedViewModel,
|
searchFeedViewModel: NostrGlobalFeedViewModel,
|
||||||
@@ -67,6 +69,7 @@ fun AppNavigation(
|
|||||||
HomeScreen(
|
HomeScreen(
|
||||||
homeFeedViewModel = homeFeedViewModel,
|
homeFeedViewModel = homeFeedViewModel,
|
||||||
repliesFeedViewModel = repliesFeedViewModel,
|
repliesFeedViewModel = repliesFeedViewModel,
|
||||||
|
liveActivitiesViewModel = liveActivitiesViewModel,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
nip47 = nip47
|
nip47 = nip47
|
||||||
|
|||||||
@@ -255,7 +255,6 @@ fun ChatroomMessageCompose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val replyTo = note.replyTo
|
val replyTo = note.replyTo
|
||||||
println("AAA replyTo ${replyTo?.lastOrNull()}")
|
|
||||||
if (!innerQuote && !replyTo.isNullOrEmpty()) {
|
if (!innerQuote && !replyTo.isNullOrEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
replyTo.lastOrNull()?.let { note ->
|
replyTo.lastOrNull()?.let { note ->
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent
|
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.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
@@ -379,7 +380,7 @@ fun NormalNote(
|
|||||||
val channelHex = remember { baseNote.channelHex() }
|
val channelHex = remember { baseNote.channelHex() }
|
||||||
|
|
||||||
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && channelHex != null) {
|
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) {
|
} else if (noteEvent is BadgeDefinitionEvent) {
|
||||||
BadgeDisplay(baseNote = baseNote)
|
BadgeDisplay(baseNote = baseNote)
|
||||||
} else if (noteEvent is FileHeaderEvent) {
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
@@ -1648,7 +1649,8 @@ private fun ReplyRow(
|
|||||||
|
|
||||||
val showChannelReply by remember {
|
val showChannelReply by remember {
|
||||||
derivedStateOf {
|
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) {
|
} else if (showChannelReply) {
|
||||||
val channelHex = note.channelHex()
|
val channelHex = note.channelHex()
|
||||||
channelHex?.let {
|
channelHex?.let {
|
||||||
val replies = remember { note.replyTo?.toImmutableList() }
|
ChannelHeader(
|
||||||
val mentions = remember { (note.event as? ChannelMessageEvent)?.mentions()?.toImmutableList() ?: persistentListOf() }
|
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))
|
Spacer(modifier = Modifier.height(5.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ private fun ReplyInformation(
|
|||||||
fun ReplyInformationChannel(
|
fun ReplyInformationChannel(
|
||||||
replyTo: ImmutableList<Note>?,
|
replyTo: ImmutableList<Note>?,
|
||||||
mentions: ImmutableList<String>,
|
mentions: ImmutableList<String>,
|
||||||
channelHex: String,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -145,67 +144,25 @@ fun ReplyInformationChannel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sortedMentions != null) {
|
if (sortedMentions != null) {
|
||||||
LoadChannel(channelHex) { channel ->
|
ReplyInformationChannel(
|
||||||
ReplyInformationChannel(
|
replyTo,
|
||||||
replyTo,
|
sortedMentions,
|
||||||
sortedMentions,
|
onUserTagClick = {
|
||||||
channel,
|
nav("User/${it.pubkeyHex}")
|
||||||
onUserTagClick = {
|
}
|
||||||
nav("User/${it.pubkeyHex}")
|
)
|
||||||
},
|
|
||||||
onChannelTagClick = {
|
|
||||||
nav("Channel/${it.idHex}")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplyInformationChannel(
|
fun ReplyInformationChannel(
|
||||||
replyTo: ImmutableList<Note>?,
|
replyTo: ImmutableList<Note>?,
|
||||||
mentions: ImmutableList<User>?,
|
mentions: ImmutableList<User>?,
|
||||||
baseChannel: Channel,
|
|
||||||
prefix: String = "",
|
prefix: String = "",
|
||||||
onUserTagClick: (User) -> Unit,
|
onUserTagClick: (User) -> Unit
|
||||||
onChannelTagClick: (Channel) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
|
||||||
val channel = channelState?.channel ?: return
|
|
||||||
|
|
||||||
val channelName = remember(channelState) {
|
|
||||||
AnnotatedString("${channel.toBestDisplayName()} ")
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowRow() {
|
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 (mentions != null && mentions.isNotEmpty()) {
|
||||||
if (replyTo != null && replyTo.isNotEmpty()) {
|
if (replyTo != null && replyTo.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
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.HomeNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter
|
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
|
@Stable
|
||||||
class NostrHomeFeedViewModel(val account: Account) : FeedViewModel(HomeNewThreadFeedFilter(account)) {
|
class NostrHomeFeedViewModel(val account: Account) : FeedViewModel(HomeNewThreadFeedFilter(account)) {
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ fun NoteMaster(
|
|||||||
) {
|
) {
|
||||||
Column() {
|
Column() {
|
||||||
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && note.channelHex() != null) {
|
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) {
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
FileHeaderDisplay(baseNote)
|
FileHeaderDisplay(baseNote)
|
||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
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.screen.RefreshingChatroomFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
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 com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -203,8 +207,9 @@ fun ChannelScreen(
|
|||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
ChannelHeader(
|
ChannelHeader(
|
||||||
channel,
|
baseChannel = channel,
|
||||||
accountViewModel,
|
showVideo = true,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -472,8 +477,14 @@ fun MyTextField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(channelHex: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ChannelHeader(
|
||||||
var baseChannel by remember { mutableStateOf<Channel?>(null) }
|
channelHex: String,
|
||||||
|
showVideo: Boolean,
|
||||||
|
modifier: Modifier = StdPadding,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
var baseChannel by remember { mutableStateOf<Channel?>(LocalCache.channels[channelHex]) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(key1 = channelHex) {
|
LaunchedEffect(key1 = channelHex) {
|
||||||
@@ -483,12 +494,24 @@ fun ChannelHeader(channelHex: String, accountViewModel: AccountViewModel, nav: (
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseChannel?.let {
|
baseChannel?.let {
|
||||||
ChannelHeader(it, accountViewModel, nav)
|
ChannelHeader(
|
||||||
|
it,
|
||||||
|
showVideo,
|
||||||
|
modifier,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 channelState by baseChannel.live.observeAsState()
|
||||||
val channel = remember(channelState) { channelState?.channel } ?: return
|
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) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
VideoView(
|
VideoView(
|
||||||
videoUri = streamingUrl!!,
|
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) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = channel.idHex,
|
robot = channel.idHex,
|
||||||
@@ -624,12 +647,31 @@ private fun LiveChannelActionOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val status by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
channel.info?.status()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
// EditButton(accountViewModel, channel)
|
// EditButton(accountViewModel, channel)
|
||||||
} else {
|
} else {
|
||||||
LocalCache.addressables[channel.idHex]?.let {
|
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)
|
LikeReaction(it, MaterialTheme.colors.onSurface, accountViewModel)
|
||||||
Spacer(modifier = Modifier.width(5.dp))
|
Spacer(modifier = StdHorzSpacer)
|
||||||
ZapReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel)
|
ZapReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
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.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.padding
|
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.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
@@ -14,6 +20,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -30,7 +37,9 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog
|
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.FeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
|
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
|
||||||
@@ -47,6 +56,7 @@ import kotlinx.coroutines.launch
|
|||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
homeFeedViewModel: NostrHomeFeedViewModel,
|
homeFeedViewModel: NostrHomeFeedViewModel,
|
||||||
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
|
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
|
||||||
|
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
nip47: String? = null
|
nip47: String? = null
|
||||||
@@ -88,7 +98,7 @@ fun HomeScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
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(
|
private fun HomePages(
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
tabs: ImmutableList<TabItem>,
|
tabs: ImmutableList<TabItem>,
|
||||||
|
liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -120,6 +131,12 @@ private fun HomePages(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveActivities(
|
||||||
|
liveActivitiesViewModel = liveActivitiesViewModel,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
|
||||||
HorizontalPager(pageCount = 2, state = pagerState) { page ->
|
HorizontalPager(pageCount = 2, state = pagerState) { page ->
|
||||||
RefresheableFeedView(
|
RefresheableFeedView(
|
||||||
viewModel = tabs[page].viewModel,
|
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
|
@Composable
|
||||||
fun WatchAccountForHomeScreen(
|
fun WatchAccountForHomeScreen(
|
||||||
homeFeedViewModel: NostrHomeFeedViewModel,
|
homeFeedViewModel: NostrHomeFeedViewModel,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
|
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.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
@@ -89,6 +90,11 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
factory = NostrHomeRepliesFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrHomeRepliesFeedViewModel.Factory(accountViewModel.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel = viewModel(
|
||||||
|
key = accountViewModel.userProfile().pubkeyHex + "NostrHomeLiveActivitiesFeedViewModel",
|
||||||
|
factory = NostrHomeFeedLiveActivitiesViewModel.Factory(accountViewModel.account)
|
||||||
|
)
|
||||||
|
|
||||||
val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel(
|
val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel(
|
||||||
key = accountViewModel.userProfile().pubkeyHex + "NostrGlobalFeedViewModel",
|
key = accountViewModel.userProfile().pubkeyHex + "NostrGlobalFeedViewModel",
|
||||||
factory = NostrGlobalFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrGlobalFeedViewModel.Factory(accountViewModel.account)
|
||||||
@@ -184,6 +190,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
AppNavigation(
|
AppNavigation(
|
||||||
homeFeedViewModel,
|
homeFeedViewModel,
|
||||||
repliesFeedViewModel,
|
repliesFeedViewModel,
|
||||||
|
liveActivitiesViewModel,
|
||||||
knownFeedViewModel,
|
knownFeedViewModel,
|
||||||
newFeedViewModel,
|
newFeedViewModel,
|
||||||
searchFeedViewModel,
|
searchFeedViewModel,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.theme
|
package com.vitorpamplona.amethyst.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -25,3 +26,5 @@ val StdHorzSpacer = Modifier.width(5.dp)
|
|||||||
val DoubleHorzSpacer = Modifier.width(10.dp)
|
val DoubleHorzSpacer = Modifier.width(10.dp)
|
||||||
|
|
||||||
val Size35dp = 35.dp
|
val Size35dp = 35.dp
|
||||||
|
|
||||||
|
val StdPadding = Modifier.padding(10.dp)
|
||||||
|
|||||||
Reference in New Issue
Block a user