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