Adds simplified views to the video and chat feeds.

This commit is contained in:
Vitor Pamplona
2024-03-14 11:11:38 -04:00
parent 1014e29289
commit 837865a699
3 changed files with 112 additions and 121 deletions
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
@@ -33,7 +34,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -41,6 +41,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -51,7 +52,6 @@ import androidx.compose.runtime.remember
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.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.compositeOver
@@ -66,7 +66,6 @@ import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
@@ -84,10 +83,9 @@ import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.chatAuthorImage
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
@@ -306,12 +304,16 @@ fun NormalChatNote(
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
val showDetails = remember { mutableStateOf(false) }
val clickableModifier =
remember {
Modifier.combinedClickable(
onClick = {
if (note.event is ChannelCreateEvent) {
nav("Channel/${note.idHex}")
} else {
showDetails.value = !showDetails.value
}
},
onLongClick = { popupExpanded = true },
@@ -341,6 +343,7 @@ fun NormalChatNote(
onWantsToReply,
canPreview,
availableBubbleSize,
showDetails,
accountViewModel,
nav,
)
@@ -367,6 +370,7 @@ private fun RenderBubble(
onWantsToReply: (Note) -> Unit,
canPreview: Boolean,
availableBubbleSize: MutableState<Int>,
showDetails: State<Boolean>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
@@ -375,12 +379,13 @@ private fun RenderBubble(
val bubbleModifier =
remember {
Modifier
.padding(start = 10.dp, end = 5.dp, bottom = 5.dp)
.padding(start = 10.dp, end = 10.dp, bottom = 5.dp)
.onSizeChanged {
if (bubbleSize.intValue != it.width) {
bubbleSize.intValue = it.width
}
}
.animateContentSize()
}
Column(modifier = bubbleModifier) {
@@ -388,14 +393,15 @@ private fun RenderBubble(
drawAuthorInfo,
baseNote,
alignment,
nav,
availableBubbleSize,
innerQuote,
backgroundBubbleColor,
accountViewModel,
bubbleSize,
onWantsToReply,
canPreview,
bubbleSize,
availableBubbleSize,
showDetails,
accountViewModel,
nav,
)
}
}
@@ -405,14 +411,15 @@ private fun MessageBubbleLines(
drawAuthorInfo: Boolean,
baseNote: Note,
alignment: Arrangement.Horizontal,
nav: (String) -> Unit,
availableBubbleSize: MutableState<Int>,
innerQuote: Boolean,
backgroundBubbleColor: MutableState<Color>,
accountViewModel: AccountViewModel,
bubbleSize: MutableState<Int>,
onWantsToReply: (Note) -> Unit,
canPreview: Boolean,
bubbleSize: MutableState<Int>,
availableBubbleSize: MutableState<Int>,
showDetails: State<Boolean>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
if (drawAuthorInfo) {
DrawAuthorInfo(
@@ -421,8 +428,6 @@ private fun MessageBubbleLines(
accountViewModel.settings.showProfilePictures.value,
nav,
)
} else {
Spacer(modifier = StdVertSpacer)
}
RenderReplyRow(
@@ -442,32 +447,34 @@ private fun MessageBubbleLines(
nav = nav,
)
ConstrainedStatusRow(
bubbleSize = bubbleSize,
availableBubbleSize = availableBubbleSize,
firstColumn = {
IncognitoBadge(baseNote)
ChatTimeAgo(baseNote)
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer)
},
secondColumn = {
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
Spacer(modifier = StdHorzSpacer)
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer)
ReplyReaction(
baseNote = baseNote,
grayTint = MaterialTheme.colorScheme.placeholderText,
accountViewModel = accountViewModel,
showCounter = false,
iconSizeModifier = Size15Modifier,
) {
onWantsToReply(baseNote)
}
Spacer(modifier = StdHorzSpacer)
},
)
if (showDetails.value || baseNote.zaps.isNotEmpty() || baseNote.zapPayments.isNotEmpty() || baseNote.reactions.isNotEmpty()) {
ConstrainedStatusRow(
bubbleSize = bubbleSize,
availableBubbleSize = availableBubbleSize,
firstColumn = {
IncognitoBadge(baseNote)
ChatTimeAgo(baseNote)
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer)
},
secondColumn = {
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
Spacer(modifier = StdHorzSpacer)
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
Spacer(modifier = DoubleHorzSpacer)
ReplyReaction(
baseNote = baseNote,
grayTint = MaterialTheme.colorScheme.placeholderText,
accountViewModel = accountViewModel,
showCounter = false,
iconSizeModifier = Size15Modifier,
) {
onWantsToReply(baseNote)
}
Spacer(modifier = StdHorzSpacer)
},
)
}
}
@Composable
@@ -479,9 +486,7 @@ private fun RenderReplyRow(
nav: (String) -> Unit,
onWantsToReply: (Note) -> Unit,
) {
val hasReply by remember { derivedStateOf { !innerQuote && note.replyTo?.lastOrNull() != null } }
if (hasReply) {
if (!innerQuote && note.replyTo?.lastOrNull() != null) {
RenderReply(note, backgroundBubbleColor, accountViewModel, nav, onWantsToReply)
}
}
@@ -504,8 +509,8 @@ private fun RenderReply(
replyTo.value?.let { note ->
ChatroomMessageCompose(
note,
null,
baseNote = note,
routeForLastRead = null,
innerQuote = true,
parentBackgroundColor = backgroundBubbleColor,
accountViewModel = accountViewModel,
@@ -525,7 +530,7 @@ private fun NoteRow(
nav: (String) -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
when (remember(note) { note.event }) {
when (note.event) {
is ChannelCreateEvent -> {
RenderCreateChannelNote(note)
}
@@ -719,41 +724,36 @@ private fun DrawAuthorInfo(
loadProfilePicture: Boolean,
nav: (String) -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = alignment,
modifier = Modifier.padding(top = Size10dp),
) {
DisplayAndWatchNoteAuthor(baseNote, loadProfilePicture, nav)
baseNote.author?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = alignment,
modifier =
Modifier
.padding(top = Size10dp)
.clickable {
nav("User/${baseNote.author?.pubkeyHex}")
},
) {
WatchAndDisplayUser(it, loadProfilePicture, nav)
}
}
}
@Composable
private fun DisplayAndWatchNoteAuthor(
baseNote: Note,
loadProfilePicture: Boolean,
nav: (String) -> Unit,
) {
val author = remember { baseNote.author }
author?.let { WatchAndDisplayUser(it, loadProfilePicture, nav) }
}
@Composable
private fun WatchAndDisplayUser(
author: User,
loadProfilePicture: Boolean,
nav: (String) -> Unit,
) {
val route = "User/${author.pubkeyHex}"
val userState by author.live().userMetadataInfo.observeAsState()
UserIcon(author.pubkeyHex, userState?.picture, loadProfilePicture, nav, route)
UserIcon(author.pubkeyHex, userState?.picture, loadProfilePicture)
userState?.let {
it.bestName()?.let { name ->
DisplayMessageUsername(name, it.tags, route, nav)
}
if (userState != null) {
DisplayMessageUsername(userState?.bestName() ?: author.pubkeyDisplayHex(), userState?.tags ?: EmptyTagList)
} else {
DisplayMessageUsername(author.pubkeyDisplayHex(), EmptyTagList)
}
}
@@ -762,40 +762,26 @@ private fun UserIcon(
pubkeyHex: String,
userProfilePicture: String?,
loadProfilePicture: Boolean,
nav: (String) -> Unit,
route: String,
) {
RobohashFallbackAsyncImage(
robot = pubkeyHex,
model = userProfilePicture,
contentDescription = stringResource(id = R.string.profile_image),
loadProfilePicture = loadProfilePicture,
modifier =
remember {
Modifier
.width(Size25dp)
.height(Size25dp)
.clip(shape = CircleShape)
.clickable(onClick = { nav(route) })
},
modifier = chatAuthorImage,
)
}
@Composable
private fun DisplayMessageUsername(
userDisplayName: String,
userTags: ImmutableListOfLists<String>?,
route: String,
nav: (String) -> Unit,
userTags: ImmutableListOfLists<String>,
) {
Spacer(modifier = StdHorzSpacer)
CreateClickableTextWithEmoji(
clickablePart = userDisplayName,
maxLines = 1,
CreateTextWithEmoji(
text = userDisplayName,
tags = userTags,
maxLines = 1,
fontWeight = FontWeight.Bold,
overrideColor = MaterialTheme.colorScheme.onBackground,
route = route,
nav = nav,
)
}
@@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -64,6 +63,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
import com.vitorpamplona.amethyst.ui.actions.NewPostView
@@ -91,11 +91,14 @@ import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
import com.vitorpamplona.amethyst.ui.theme.AuthorInfoVideoFeed
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.Size35Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.Size39Modifier
import com.vitorpamplona.amethyst.ui.theme.Size40Modifier
import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.onBackgroundColorFilter
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.FileHeaderEvent
@@ -321,8 +324,8 @@ private fun RenderVideoOrPictureNote(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
Column(remember { Modifier.fillMaxSize(1f) }, verticalArrangement = Arrangement.Center) {
Row(remember { Modifier.weight(1f) }, verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.fillMaxSize(1f), verticalArrangement = Arrangement.Center) {
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
val noteEvent = remember { note.event }
if (noteEvent is FileHeaderEvent) {
FileHeaderDisplay(note, false, accountViewModel)
@@ -332,18 +335,17 @@ private fun RenderVideoOrPictureNote(
}
}
Row(verticalAlignment = Alignment.Bottom, modifier = remember { Modifier.fillMaxSize(1f) }) {
Column(remember { Modifier.weight(1f) }) {
Row(modifier = Modifier.fillMaxSize(1f), verticalAlignment = Alignment.Bottom) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.Center) {
RenderAuthorInformation(note, nav, accountViewModel)
}
Column(
remember { Modifier.width(65.dp).padding(bottom = 10.dp) },
modifier = AuthorInfoVideoFeed,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(horizontalArrangement = Arrangement.Center) {
ReactionsColumn(note, accountViewModel, nav)
}
ReactionsColumn(note, accountViewModel, nav)
}
}
}
@@ -354,32 +356,34 @@ private fun RenderAuthorInformation(
nav: (String) -> Unit,
accountViewModel: AccountViewModel,
) {
Row(remember { Modifier.padding(10.dp) }, verticalAlignment = Alignment.Bottom) {
Column(remember { Modifier.size(55.dp) }, verticalArrangement = Arrangement.Center) {
NoteAuthorPicture(note, nav, accountViewModel, 55.dp)
}
Row(modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp), verticalAlignment = Alignment.CenterVertically) {
NoteAuthorPicture(note, nav, accountViewModel, Size55dp)
Spacer(modifier = DoubleHorzSpacer)
Column(
remember { Modifier.padding(start = 10.dp, end = 10.dp).height(65.dp).weight(1f) },
Modifier.height(65.dp).weight(1f),
verticalArrangement = Arrangement.Center,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(note, remember { Modifier.weight(1f) })
VideoUserOptionAction(note, accountViewModel, nav)
}
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
note.author!!,
Modifier.weight(1f),
accountViewModel,
nav = nav,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp),
) {
RelayBadges(baseNote = note, accountViewModel, nav)
if (accountViewModel.settings.featureSet != FeatureSetType.SIMPLIFIED) {
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
note.author!!,
Modifier.weight(1f),
accountViewModel,
nav = nav,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp),
) {
RelayBadges(baseNote = note, accountViewModel, nav)
}
}
}
}
@@ -457,11 +461,9 @@ fun ReactionsColumn(
)
}
Spacer(modifier = Modifier.height(8.dp))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(bottom = 75.dp, end = 20.dp),
modifier = Modifier.padding(bottom = 75.dp, end = 10.dp),
) {
ReplyReaction(
baseNote = baseNote,
@@ -175,7 +175,7 @@ val ZeroPadding = PaddingValues(0.dp)
val FeedPadding = PaddingValues(top = 10.dp, bottom = 10.dp)
val ButtonPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
val ChatPaddingInnerQuoteModifier = Modifier.padding(top = 10.dp, end = 5.dp)
val ChatPaddingInnerQuoteModifier = Modifier.padding(top = 10.dp)
val ChatPaddingModifier =
Modifier.fillMaxWidth(1f)
.padding(
@@ -228,3 +228,6 @@ val liveStreamTag =
.clip(SmallBorder)
.background(Color.Black)
.padding(horizontal = Size5dp)
val chatAuthorImage = Modifier.size(20.dp).clip(shape = CircleShape)
val AuthorInfoVideoFeed = Modifier.width(75.dp)