mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Breaks down Compose components in the Discovery tab.
This commit is contained in:
parent
188ef3762d
commit
cec204b7ae
@ -19,6 +19,7 @@ import androidx.compose.material3.Divider
|
||||
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.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@ -30,7 +31,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.BottomStart
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Alignment.Companion.TopEnd
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -50,7 +50,6 @@ 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.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -75,6 +74,7 @@ import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_ENDED
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED
|
||||
import com.vitorpamplona.quartz.events.Participant
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@ -372,6 +372,18 @@ private fun RenderNoteRow(
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class LiveActivityCard(
|
||||
val name: String,
|
||||
val cover: String?,
|
||||
val media: String?,
|
||||
val subject: String?,
|
||||
val content: String?,
|
||||
val participants: ImmutableList<Participant>,
|
||||
val status: String?,
|
||||
val starts: Long?
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderLiveActivityThumb(
|
||||
baseNote: Note,
|
||||
@ -380,38 +392,279 @@ fun RenderLiveActivityThumb(
|
||||
) {
|
||||
val noteEvent = baseNote.event as? LiveActivitiesEvent ?: return
|
||||
|
||||
val eventUpdates by baseNote.live().metadata.observeAsState()
|
||||
val card by baseNote.live().metadata.map {
|
||||
val noteEvent = it.note.event as? LiveActivitiesEvent
|
||||
|
||||
val media = remember(eventUpdates) { noteEvent.streaming() }
|
||||
val cover by remember(eventUpdates) {
|
||||
derivedStateOf {
|
||||
noteEvent.image()?.ifBlank { null }
|
||||
}
|
||||
}
|
||||
val subject = remember(eventUpdates) { noteEvent.title()?.ifBlank { null } }
|
||||
val content = remember(eventUpdates) { noteEvent.summary() }
|
||||
val participants = remember(eventUpdates) { noteEvent.participants() }
|
||||
val status = remember(eventUpdates) { noteEvent.status() }
|
||||
val starts = remember(eventUpdates) { noteEvent.starts() }
|
||||
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()
|
||||
)
|
||||
}.distinctUntilChanged().observeAsState(
|
||||
LiveActivityCard(
|
||||
name = noteEvent.dTag(),
|
||||
cover = noteEvent.image()?.ifBlank { null },
|
||||
media = noteEvent.streaming(),
|
||||
subject = noteEvent.title()?.ifBlank { null },
|
||||
content = noteEvent.summary(),
|
||||
participants = noteEvent.participants().toImmutableList(),
|
||||
status = noteEvent.status(),
|
||||
starts = noteEvent.starts()
|
||||
)
|
||||
)
|
||||
|
||||
var isOnline by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(key1 = media) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newIsOnline = OnlineChecker.isOnline(media)
|
||||
LaunchedEffect(key1 = card.media) {
|
||||
accountViewModel.checkIsOnline(card.media) { newIsOnline ->
|
||||
if (isOnline != newIsOnline) {
|
||||
isOnline = newIsOnline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
baseNote.author?.let {
|
||||
DisplayAuthorBanner(it)
|
||||
}
|
||||
}
|
||||
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
Crossfade(targetState = card.status) {
|
||||
when (it) {
|
||||
STATUS_LIVE -> {
|
||||
if (card.media.isNullOrBlank()) {
|
||||
LiveFlag()
|
||||
} else if (isOnline) {
|
||||
LiveFlag()
|
||||
} else {
|
||||
OfflineFlag()
|
||||
}
|
||||
}
|
||||
STATUS_ENDED -> {
|
||||
EndedFlag()
|
||||
}
|
||||
STATUS_PLANNED -> {
|
||||
ScheduledFlag(card.starts)
|
||||
}
|
||||
else -> {
|
||||
EndedFlag()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadParticipants(card.participants, baseNote, accountViewModel) { participantUsers ->
|
||||
Box(
|
||||
Modifier
|
||||
.padding(10.dp)
|
||||
.align(BottomStart)
|
||||
) {
|
||||
if (participantUsers.isNotEmpty()) {
|
||||
Gallery(participantUsers, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
ChannelHeader(
|
||||
channelHex = remember { baseNote.idHex },
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
showFlag = false,
|
||||
sendToChannel = true,
|
||||
modifier = remember {
|
||||
Modifier.padding(start = 0.dp, end = 0.dp, top = 5.dp, bottom = 5.dp)
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class CommunityCard(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val cover: String?,
|
||||
val moderators: ImmutableList<Participant>
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderCommunitiesThumb(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val noteEvent = baseNote.event as? CommunityDefinitionEvent ?: return
|
||||
|
||||
val card by baseNote.live().metadata.map {
|
||||
val noteEvent = it.note.event as? CommunityDefinitionEvent
|
||||
|
||||
CommunityCard(
|
||||
name = noteEvent?.dTag() ?: "",
|
||||
description = noteEvent?.description(),
|
||||
cover = noteEvent?.image()?.ifBlank { null },
|
||||
moderators = noteEvent?.moderators()?.toImmutableList() ?: persistentListOf()
|
||||
)
|
||||
}.distinctUntilChanged().observeAsState(
|
||||
CommunityCard(
|
||||
name = noteEvent.dTag(),
|
||||
description = noteEvent.description(),
|
||||
cover = noteEvent.image()?.ifBlank { null },
|
||||
moderators = noteEvent.moderators().toImmutableList()
|
||||
)
|
||||
)
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.3f)
|
||||
.aspectRatio(ratio = 1f)
|
||||
) {
|
||||
card.cover?.let {
|
||||
Box(contentAlignment = BottomStart) {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(QuoteBorder)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
baseNote.author?.let {
|
||||
DisplayAuthorBanner(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
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)
|
||||
}
|
||||
|
||||
card.description?.let {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row() {
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LoadModerators(card.moderators, baseNote, accountViewModel) { participantUsers ->
|
||||
if (participantUsers.isNotEmpty()) {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Gallery(participantUsers, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadModerators(
|
||||
moderators: ImmutableList<Participant>,
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (ImmutableList<User>) -> Unit
|
||||
) {
|
||||
var participantUsers by remember {
|
||||
mutableStateOf<ImmutableList<User>>(
|
||||
persistentListOf()
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = eventUpdates) {
|
||||
LaunchedEffect(key1 = moderators) {
|
||||
launch(Dispatchers.IO) {
|
||||
val hosts = moderators.mapNotNull { part ->
|
||||
if (part.key != baseNote.author?.pubkeyHex) {
|
||||
LocalCache.checkGetOrCreateUser(part.key)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val followingKeySet = accountViewModel.account.selectedUsersFollowList(accountViewModel.account.defaultDiscoveryFollowList)
|
||||
val allParticipants = ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
|
||||
|
||||
val newParticipantUsers = if (followingKeySet == null) {
|
||||
val allFollows = accountViewModel.account.selectedUsersFollowList(KIND3_FOLLOWS)
|
||||
val followingParticipants = ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts)
|
||||
|
||||
(hosts + followingParticipants + (allParticipants - followingParticipants)).toImmutableList()
|
||||
} else {
|
||||
(hosts + allParticipants).toImmutableList()
|
||||
}
|
||||
|
||||
if (!equalImmutableLists(newParticipantUsers, participantUsers)) {
|
||||
participantUsers = newParticipantUsers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content(participantUsers)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadParticipants(
|
||||
participants: ImmutableList<Participant>,
|
||||
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.key != baseNote.author?.pubkeyHex) {
|
||||
@ -445,198 +698,7 @@ fun RenderLiveActivityThumb(
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = TopEnd,
|
||||
modifier = Modifier
|
||||
.aspectRatio(ratio = 16f / 9f)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
cover?.let {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(QuoteBorder)
|
||||
)
|
||||
} ?: run {
|
||||
baseNote.author?.let {
|
||||
DisplayAuthorBanner(it)
|
||||
}
|
||||
}
|
||||
|
||||
Box(Modifier.padding(10.dp)) {
|
||||
Crossfade(targetState = status) {
|
||||
when (it) {
|
||||
STATUS_LIVE -> {
|
||||
if (media.isNullOrBlank()) {
|
||||
LiveFlag()
|
||||
} else if (isOnline) {
|
||||
LiveFlag()
|
||||
} else {
|
||||
OfflineFlag()
|
||||
}
|
||||
}
|
||||
STATUS_ENDED -> {
|
||||
EndedFlag()
|
||||
}
|
||||
STATUS_PLANNED -> {
|
||||
ScheduledFlag(starts)
|
||||
}
|
||||
else -> {
|
||||
EndedFlag()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.padding(10.dp)
|
||||
.align(BottomStart)
|
||||
) {
|
||||
if (participantUsers.isNotEmpty()) {
|
||||
Gallery(participantUsers, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
|
||||
ChannelHeader(
|
||||
channelHex = remember { baseNote.idHex },
|
||||
showVideo = false,
|
||||
showBottomDiviser = false,
|
||||
showFlag = false,
|
||||
sendToChannel = true,
|
||||
modifier = remember {
|
||||
Modifier.padding(start = 0.dp, end = 0.dp, top = 5.dp, bottom = 5.dp)
|
||||
},
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderCommunitiesThumb(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||
val noteEvent = baseNote.event as? CommunityDefinitionEvent ?: return
|
||||
|
||||
val eventUpdates by baseNote.live().metadata.observeAsState()
|
||||
|
||||
val name = remember(eventUpdates) { noteEvent.dTag() }
|
||||
val description = remember(eventUpdates) { noteEvent.description() }
|
||||
val cover by remember(eventUpdates) {
|
||||
derivedStateOf {
|
||||
noteEvent.image()?.ifBlank { null }
|
||||
}
|
||||
}
|
||||
val moderators = remember(eventUpdates) { noteEvent.moderators() }
|
||||
|
||||
var participantUsers by remember {
|
||||
mutableStateOf<ImmutableList<User>>(
|
||||
persistentListOf()
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = eventUpdates) {
|
||||
launch(Dispatchers.IO) {
|
||||
val hosts = moderators.mapNotNull { part ->
|
||||
if (part.key != baseNote.author?.pubkeyHex) {
|
||||
LocalCache.checkGetOrCreateUser(part.key)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val followingKeySet = accountViewModel.account.selectedUsersFollowList(accountViewModel.account.defaultDiscoveryFollowList)
|
||||
val allParticipants = ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
|
||||
|
||||
val newParticipantUsers = if (followingKeySet == null) {
|
||||
val allFollows = accountViewModel.account.selectedUsersFollowList(KIND3_FOLLOWS)
|
||||
val followingParticipants = ParticipantListBuilder().followsThatParticipateOn(baseNote, allFollows).minus(hosts)
|
||||
|
||||
(hosts + followingParticipants + (allParticipants - followingParticipants)).toImmutableList()
|
||||
} else {
|
||||
(hosts + allParticipants).toImmutableList()
|
||||
}
|
||||
|
||||
if (!equalImmutableLists(newParticipantUsers, participantUsers)) {
|
||||
participantUsers = newParticipantUsers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.3f)
|
||||
.aspectRatio(ratio = 1f)
|
||||
) {
|
||||
cover?.let {
|
||||
Box(contentAlignment = BottomStart) {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(QuoteBorder)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
baseNote.author?.let {
|
||||
DisplayAuthorBanner(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = 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)
|
||||
}
|
||||
|
||||
description?.let {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row() {
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (participantUsers.isNotEmpty()) {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Gallery(participantUsers, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inner(participantUsers)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
Loading…
Reference in New Issue
Block a user