mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-08-08 17:51:57 +02:00
Improves rendering performance of Chat Screens
This commit is contained in:
@@ -275,9 +275,10 @@ private fun RenderUserAsClickableText(
|
|||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = it,
|
clickablePart = it,
|
||||||
suffix = addedCharts,
|
suffix = addedCharts,
|
||||||
tags = userTags,
|
maxLines = 1,
|
||||||
route = route,
|
route = route,
|
||||||
nav = nav
|
nav = nav,
|
||||||
|
tags = userTags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,24 +287,38 @@ private fun RenderUserAsClickableText(
|
|||||||
fun CreateClickableText(
|
fun CreateClickableText(
|
||||||
clickablePart: String,
|
clickablePart: String,
|
||||||
suffix: String,
|
suffix: String,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
overrideColor: Color? = null,
|
overrideColor: Color? = null,
|
||||||
fontWeight: FontWeight = FontWeight.Normal,
|
fontWeight: FontWeight = FontWeight.Normal,
|
||||||
route: String,
|
route: String,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
ClickableText(
|
val currentStyle = LocalTextStyle.current
|
||||||
text = buildAnnotatedString {
|
val primaryColor = MaterialTheme.colors.primary
|
||||||
withStyle(
|
val onBackgroundColor = MaterialTheme.colors.onBackground
|
||||||
LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()
|
|
||||||
) {
|
val clickablePartStyle = remember(primaryColor, overrideColor) {
|
||||||
|
currentStyle.copy(color = overrideColor ?: primaryColor, fontWeight = fontWeight).toSpanStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
val nonClickablePartStyle = remember(onBackgroundColor, overrideColor) {
|
||||||
|
currentStyle.copy(color = overrideColor ?: onBackgroundColor, fontWeight = fontWeight).toSpanStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = remember(clickablePartStyle, nonClickablePartStyle, clickablePart, suffix) {
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(clickablePartStyle) {
|
||||||
append(clickablePart)
|
append(clickablePart)
|
||||||
}
|
}
|
||||||
withStyle(
|
withStyle(nonClickablePartStyle) {
|
||||||
LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.onBackground, fontWeight = fontWeight).toSpanStyle()
|
|
||||||
) {
|
|
||||||
append(suffix)
|
append(suffix)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text = text,
|
||||||
|
maxLines = maxLines,
|
||||||
onClick = { nav(route) }
|
onClick = { nav(route) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -410,14 +425,17 @@ fun CreateTextWithEmoji(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val style = LocalTextStyle.current.merge(
|
val currentStyle = LocalTextStyle.current
|
||||||
TextStyle(
|
val style = remember(currentStyle) {
|
||||||
color = textColor,
|
currentStyle.merge(
|
||||||
textAlign = textAlign,
|
TextStyle(
|
||||||
fontWeight = fontWeight,
|
color = textColor,
|
||||||
fontSize = fontSize
|
textAlign = textAlign,
|
||||||
)
|
fontWeight = fontWeight,
|
||||||
).toSpanStyle()
|
fontSize = fontSize
|
||||||
|
)
|
||||||
|
).toSpanStyle()
|
||||||
|
}
|
||||||
|
|
||||||
InLineIconRenderer(emojiList, style, maxLines, overflow, modifier)
|
InLineIconRenderer(emojiList, style, maxLines, overflow, modifier)
|
||||||
}
|
}
|
||||||
@@ -426,6 +444,7 @@ fun CreateTextWithEmoji(
|
|||||||
@Composable
|
@Composable
|
||||||
fun CreateClickableTextWithEmoji(
|
fun CreateClickableTextWithEmoji(
|
||||||
clickablePart: String,
|
clickablePart: String,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
tags: ImmutableListOfLists<String>?,
|
tags: ImmutableListOfLists<String>?,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
onClick: (Int) -> Unit
|
onClick: (Int) -> Unit
|
||||||
@@ -448,29 +467,37 @@ fun CreateClickableTextWithEmoji(
|
|||||||
|
|
||||||
if (emojiList.isEmpty()) {
|
if (emojiList.isEmpty()) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
AnnotatedString(clickablePart),
|
text = AnnotatedString(clickablePart),
|
||||||
style = style,
|
style = style,
|
||||||
|
maxLines = maxLines,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ClickableInLineIconRenderer(emojiList, style.toSpanStyle()) {
|
ClickableInLineIconRenderer(emojiList, maxLines, style.toSpanStyle()) {
|
||||||
onClick(it)
|
onClick(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class DoubleEmojiList(
|
||||||
|
val part1: ImmutableList<Renderable>,
|
||||||
|
val part2: ImmutableList<Renderable>
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateClickableTextWithEmoji(
|
fun CreateClickableTextWithEmoji(
|
||||||
clickablePart: String,
|
clickablePart: String,
|
||||||
suffix: String,
|
suffix: String,
|
||||||
tags: ImmutableListOfLists<String>?,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
overrideColor: Color? = null,
|
overrideColor: Color? = null,
|
||||||
fontWeight: FontWeight = FontWeight.Normal,
|
fontWeight: FontWeight = FontWeight.Normal,
|
||||||
route: String,
|
route: String,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit,
|
||||||
|
tags: ImmutableListOfLists<String>?
|
||||||
) {
|
) {
|
||||||
var emojiLists by remember(clickablePart) {
|
var emojiLists by remember(clickablePart) {
|
||||||
mutableStateOf<Pair<ImmutableList<Renderable>, ImmutableList<Renderable>>?>(null)
|
mutableStateOf<DoubleEmojiList?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = clickablePart) {
|
LaunchedEffect(key1 = clickablePart) {
|
||||||
@@ -483,20 +510,28 @@ fun CreateClickableTextWithEmoji(
|
|||||||
val newEmojiList2 = assembleAnnotatedList(suffix, emojis)
|
val newEmojiList2 = assembleAnnotatedList(suffix, emojis)
|
||||||
|
|
||||||
if (newEmojiList1.isNotEmpty() || newEmojiList2.isNotEmpty()) {
|
if (newEmojiList1.isNotEmpty() || newEmojiList2.isNotEmpty()) {
|
||||||
emojiLists = Pair(newEmojiList1.toImmutableList(), newEmojiList2.toImmutableList())
|
emojiLists = DoubleEmojiList(newEmojiList1.toImmutableList(), newEmojiList2.toImmutableList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emojiLists == null) {
|
if (emojiLists == null) {
|
||||||
CreateClickableText(clickablePart, suffix, overrideColor, fontWeight, route, nav)
|
CreateClickableText(clickablePart, suffix, maxLines, overrideColor, fontWeight, route, nav)
|
||||||
} else {
|
} else {
|
||||||
ClickableInLineIconRenderer(emojiLists!!.first, LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()) {
|
ClickableInLineIconRenderer(
|
||||||
|
emojiLists!!.part1,
|
||||||
|
maxLines,
|
||||||
|
LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.primary, fontWeight = fontWeight).toSpanStyle()
|
||||||
|
) {
|
||||||
nav(route)
|
nav(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
InLineIconRenderer(emojiLists!!.second, LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.onBackground, fontWeight = fontWeight).toSpanStyle())
|
InLineIconRenderer(
|
||||||
|
emojiLists!!.part2,
|
||||||
|
LocalTextStyle.current.copy(color = overrideColor ?: MaterialTheme.colors.onBackground, fontWeight = fontWeight).toSpanStyle(),
|
||||||
|
maxLines
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +556,12 @@ class TextType(val text: String) : Renderable()
|
|||||||
class ImageUrlType(val url: String) : Renderable()
|
class ImageUrlType(val url: String) : Renderable()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClickableInLineIconRenderer(wordsInOrder: ImmutableList<Renderable>, style: SpanStyle, onClick: (Int) -> Unit) {
|
fun ClickableInLineIconRenderer(
|
||||||
|
wordsInOrder: ImmutableList<Renderable>,
|
||||||
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
|
style: SpanStyle,
|
||||||
|
onClick: (Int) -> Unit
|
||||||
|
) {
|
||||||
val inlineContent = wordsInOrder.mapIndexedNotNull { idx, value ->
|
val inlineContent = wordsInOrder.mapIndexedNotNull { idx, value ->
|
||||||
if (value is ImageUrlType) {
|
if (value is ImageUrlType) {
|
||||||
Pair(
|
Pair(
|
||||||
@@ -574,6 +614,7 @@ fun ClickableInLineIconRenderer(wordsInOrder: ImmutableList<Renderable>, style:
|
|||||||
text = annotatedText,
|
text = annotatedText,
|
||||||
modifier = pressIndicator,
|
modifier = pressIndicator,
|
||||||
inlineContent = inlineContent,
|
inlineContent = inlineContent,
|
||||||
|
maxLines = maxLines,
|
||||||
onTextLayout = {
|
onTextLayout = {
|
||||||
layoutResult.value = it
|
layoutResult.value = it
|
||||||
}
|
}
|
||||||
|
@@ -1041,8 +1041,9 @@ private fun DisplayUserFromTag(
|
|||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = displayName,
|
clickablePart = displayName,
|
||||||
suffix = remember { "$addedChars " },
|
suffix = remember { "$addedChars " },
|
||||||
tags = userTags,
|
maxLines = 1,
|
||||||
route = route,
|
route = route,
|
||||||
nav = nav
|
nav = nav,
|
||||||
|
tags = userTags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -52,6 +52,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
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.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
@@ -72,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size13dp
|
import com.vitorpamplona.amethyst.ui.theme.Size13dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
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.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
@@ -261,11 +263,11 @@ fun NormalChatNote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = routeForLastRead) {
|
if (routeForLastRead != null) {
|
||||||
routeForLastRead?.let {
|
LaunchedEffect(key1 = routeForLastRead) {
|
||||||
val createdAt = note.createdAt()
|
val createdAt = note.createdAt()
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
accountViewModel.account.markAsRead(it, createdAt)
|
accountViewModel.account.markAsRead(routeForLastRead, createdAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,43 +371,72 @@ private fun RenderBubble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = bubbleModifier) {
|
Column(modifier = bubbleModifier) {
|
||||||
if (drawAuthorInfo) {
|
MessageBubbleLines(
|
||||||
DrawAuthorInfo(
|
drawAuthorInfo,
|
||||||
baseNote,
|
baseNote,
|
||||||
alignment,
|
alignment,
|
||||||
nav
|
nav,
|
||||||
)
|
innerQuote,
|
||||||
} else {
|
backgroundBubbleColor,
|
||||||
Spacer(modifier = StdVertSpacer)
|
accountViewModel,
|
||||||
}
|
onWantsToReply,
|
||||||
|
canPreview,
|
||||||
|
bubbleSize,
|
||||||
|
availableBubbleSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderReplyRow(
|
@Composable
|
||||||
note = baseNote,
|
private fun MessageBubbleLines(
|
||||||
innerQuote = innerQuote,
|
drawAuthorInfo: Boolean,
|
||||||
backgroundBubbleColor = backgroundBubbleColor,
|
baseNote: Note,
|
||||||
|
alignment: Arrangement.Horizontal,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
innerQuote: Boolean,
|
||||||
|
backgroundBubbleColor: MutableState<Color>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
onWantsToReply: (Note) -> Unit,
|
||||||
|
canPreview: Boolean,
|
||||||
|
bubbleSize: MutableState<IntSize>,
|
||||||
|
availableBubbleSize: MutableState<IntSize>
|
||||||
|
) {
|
||||||
|
if (drawAuthorInfo) {
|
||||||
|
DrawAuthorInfo(
|
||||||
|
baseNote,
|
||||||
|
alignment,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderReplyRow(
|
||||||
|
note = baseNote,
|
||||||
|
innerQuote = innerQuote,
|
||||||
|
backgroundBubbleColor = backgroundBubbleColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
onWantsToReply = onWantsToReply
|
||||||
|
)
|
||||||
|
|
||||||
|
NoteRow(
|
||||||
|
note = baseNote,
|
||||||
|
canPreview = canPreview,
|
||||||
|
backgroundBubbleColor = backgroundBubbleColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
|
||||||
|
ConstrainedStatusRow(
|
||||||
|
bubbleSize = bubbleSize,
|
||||||
|
availableBubbleSize = availableBubbleSize
|
||||||
|
) {
|
||||||
|
StatusRow(
|
||||||
|
baseNote = baseNote,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
|
||||||
onWantsToReply = onWantsToReply
|
onWantsToReply = onWantsToReply
|
||||||
)
|
)
|
||||||
|
|
||||||
NoteRow(
|
|
||||||
note = baseNote,
|
|
||||||
canPreview = canPreview,
|
|
||||||
backgroundBubbleColor = backgroundBubbleColor,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav
|
|
||||||
)
|
|
||||||
|
|
||||||
ConstrainedStatusRow(
|
|
||||||
bubbleSize = bubbleSize,
|
|
||||||
availableBubbleSize = availableBubbleSize
|
|
||||||
) {
|
|
||||||
StatusRow(
|
|
||||||
baseNote = baseNote,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
onWantsToReply = onWantsToReply
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,25 +449,42 @@ private fun RenderReplyRow(
|
|||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onWantsToReply: (Note) -> Unit
|
onWantsToReply: (Note) -> Unit
|
||||||
) {
|
) {
|
||||||
val replyTo by remember {
|
val hasReply by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
note.replyTo?.lastOrNull()
|
innerQuote && note.replyTo?.lastOrNull() != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!innerQuote && replyTo != null) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
if (hasReply) {
|
||||||
replyTo?.let { note ->
|
RenderReply(note, backgroundBubbleColor, accountViewModel, nav, onWantsToReply)
|
||||||
ChatroomMessageCompose(
|
}
|
||||||
note,
|
}
|
||||||
null,
|
|
||||||
innerQuote = true,
|
@Composable
|
||||||
parentBackgroundColor = backgroundBubbleColor,
|
private fun RenderReply(
|
||||||
accountViewModel = accountViewModel,
|
note: Note,
|
||||||
nav = nav,
|
backgroundBubbleColor: MutableState<Color>,
|
||||||
onWantsToReply = onWantsToReply
|
accountViewModel: AccountViewModel,
|
||||||
)
|
nav: (String) -> Unit,
|
||||||
|
onWantsToReply: (Note) -> Unit
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
val replyTo by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
note.replyTo?.lastOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
replyTo?.let { note ->
|
||||||
|
ChatroomMessageCompose(
|
||||||
|
note,
|
||||||
|
null,
|
||||||
|
innerQuote = true,
|
||||||
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
onWantsToReply = onWantsToReply
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,9 +549,7 @@ private fun StatusRow(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onWantsToReply: (Note) -> Unit
|
onWantsToReply: (Note) -> Unit
|
||||||
) {
|
) {
|
||||||
val grayTint = MaterialTheme.colors.placeholderText
|
Column {
|
||||||
|
|
||||||
Column() {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
ChatTimeAgo(baseNote)
|
ChatTimeAgo(baseNote)
|
||||||
RelayBadges(baseNote)
|
RelayBadges(baseNote)
|
||||||
@@ -511,16 +557,16 @@ private fun StatusRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column() {
|
Column {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
LikeReaction(baseNote, grayTint, accountViewModel)
|
LikeReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
ZapReaction(baseNote, grayTint, accountViewModel)
|
ZapReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
ReplyReaction(
|
ReplyReaction(
|
||||||
baseNote,
|
baseNote = baseNote,
|
||||||
grayTint,
|
grayTint = MaterialTheme.colors.placeholderText,
|
||||||
accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
showCounter = false,
|
showCounter = false,
|
||||||
iconSize = Size16dp
|
iconSize = Size16dp
|
||||||
) {
|
) {
|
||||||
@@ -644,51 +690,107 @@ private fun DrawAuthorInfo(
|
|||||||
alignment: Arrangement.Horizontal,
|
alignment: Arrangement.Horizontal,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val userState by baseNote.author!!.live().metadata.observeAsState()
|
|
||||||
|
|
||||||
val pubkeyHex = remember { baseNote.author?.pubkeyHex } ?: return
|
|
||||||
val route = remember { "User/$pubkeyHex" }
|
|
||||||
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
|
||||||
val userProfilePicture = remember(userState) { ResizeImage(userState?.user?.profilePicture(), 25.dp) }
|
|
||||||
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = alignment,
|
horizontalArrangement = alignment,
|
||||||
modifier = Modifier.padding(top = 5.dp)
|
modifier = Modifier.padding(top = 5.dp)
|
||||||
) {
|
) {
|
||||||
RobohashAsyncImageProxy(
|
DisplayAndWatchNoteAuthor(baseNote, nav)
|
||||||
robot = pubkeyHex,
|
}
|
||||||
model = userProfilePicture,
|
}
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
|
||||||
modifier = remember {
|
|
||||||
Modifier
|
|
||||||
.width(25.dp)
|
|
||||||
.height(25.dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.clickable(onClick = {
|
|
||||||
nav(route)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
userDisplayName?.let {
|
@Composable
|
||||||
Spacer(modifier = StdHorzSpacer)
|
private fun DisplayAndWatchNoteAuthor(
|
||||||
|
baseNote: Note,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val author = remember {
|
||||||
|
baseNote.author
|
||||||
|
}
|
||||||
|
author?.let {
|
||||||
|
WatchAndDisplayUser(it, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CreateClickableTextWithEmoji(
|
@Composable
|
||||||
clickablePart = it,
|
private fun WatchAndDisplayUser(
|
||||||
suffix = "",
|
author: User,
|
||||||
tags = userTags,
|
nav: (String) -> Unit
|
||||||
fontWeight = FontWeight.Bold,
|
) {
|
||||||
overrideColor = MaterialTheme.colors.onBackground,
|
val pubkeyHex = remember { author.pubkeyHex }
|
||||||
route = route,
|
val route = remember { "User/${author.pubkeyHex}" }
|
||||||
nav = nav
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = StdHorzSpacer)
|
val userState by author.live().metadata.observeAsState()
|
||||||
DrawPlayName(it)
|
|
||||||
|
val userDisplayName by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
userState?.user?.toBestDisplayName()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val userProfilePicture by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
ResizeImage(userState?.user?.profilePicture(), Size25dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val userTags by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UserIcon(pubkeyHex, userProfilePicture, nav, route)
|
||||||
|
|
||||||
|
userDisplayName?.let {
|
||||||
|
DisplayMessageUsername(it, userTags, route, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UserIcon(
|
||||||
|
pubkeyHex: String,
|
||||||
|
userProfilePicture: ResizeImage,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
route: String
|
||||||
|
) {
|
||||||
|
RobohashAsyncImageProxy(
|
||||||
|
robot = pubkeyHex,
|
||||||
|
model = userProfilePicture,
|
||||||
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(Size25dp)
|
||||||
|
.height(Size25dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.clickable(onClick = {
|
||||||
|
nav(route)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DisplayMessageUsername(
|
||||||
|
userDisplayName: String,
|
||||||
|
userTags: ImmutableListOfLists<String>?,
|
||||||
|
route: String,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
CreateClickableTextWithEmoji(
|
||||||
|
clickablePart = userDisplayName,
|
||||||
|
suffix = "",
|
||||||
|
maxLines = 1,
|
||||||
|
tags = userTags,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
overrideColor = MaterialTheme.colors.onBackground, // we do not want clickable names in purple here.
|
||||||
|
route = route,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
DrawPlayName(userDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
@@ -2062,9 +2062,10 @@ private fun LoadAndDisplayUser(
|
|||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = userDisplayName,
|
clickablePart = userDisplayName,
|
||||||
suffix = " ",
|
suffix = " ",
|
||||||
tags = userTags,
|
maxLines = 1,
|
||||||
route = route,
|
route = route,
|
||||||
nav = nav
|
nav = nav,
|
||||||
|
tags = userTags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -65,14 +65,16 @@ private fun UserNameDisplay(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
CreateTextWithEmoji(
|
if (bestDisplayName != bestUserName) {
|
||||||
text = remember { "@$bestUserName" },
|
CreateTextWithEmoji(
|
||||||
tags = tags,
|
text = remember { "@$bestUserName" },
|
||||||
color = MaterialTheme.colors.placeholderText,
|
tags = tags,
|
||||||
maxLines = 1,
|
color = MaterialTheme.colors.placeholderText,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
modifier = modifier
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
Spacer(StdHorzSpacer)
|
Spacer(StdHorzSpacer)
|
||||||
DrawPlayName(bestDisplayName)
|
DrawPlayName(bestDisplayName)
|
||||||
} else if (bestDisplayName != null) {
|
} else if (bestDisplayName != null) {
|
||||||
@@ -88,7 +90,7 @@ private fun UserNameDisplay(
|
|||||||
DrawPlayName(bestDisplayName)
|
DrawPlayName(bestDisplayName)
|
||||||
} else if (bestUserName != null) {
|
} else if (bestUserName != null) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = remember { "@$bestUserName" },
|
text = bestUserName,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -99,7 +101,7 @@ private fun UserNameDisplay(
|
|||||||
DrawPlayName(bestUserName)
|
DrawPlayName(bestUserName)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
npubDisplay,
|
text = npubDisplay,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
@@ -113,8 +115,15 @@ fun DrawPlayName(name: String) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
DrawPlayNameIcon {
|
||||||
|
speak(name, context, lifecycleOwner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DrawPlayNameIcon(onClick: () -> Unit) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { speak(name, context, lifecycleOwner) },
|
onClick = onClick,
|
||||||
modifier = StdButtonSizeModifier
|
modifier = StdButtonSizeModifier
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@@ -99,6 +99,7 @@ import com.vitorpamplona.amethyst.ui.note.ZapReaction
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
|
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.DoubleVertSpacer
|
||||||
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.SmallBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
@@ -236,7 +237,7 @@ fun ChannelScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
|
||||||
replyTo.value?.let {
|
replyTo.value?.let {
|
||||||
DisplayReplyingToNote(it, accountViewModel, nav) {
|
DisplayReplyingToNote(it, accountViewModel, nav) {
|
||||||
|
@@ -26,10 +26,12 @@ val StdButtonSizeModifier = Modifier.size(20.dp)
|
|||||||
val StdHorzSpacer = Modifier.width(5.dp)
|
val StdHorzSpacer = Modifier.width(5.dp)
|
||||||
val StdVertSpacer = Modifier.height(5.dp)
|
val StdVertSpacer = Modifier.height(5.dp)
|
||||||
val DoubleHorzSpacer = Modifier.width(10.dp)
|
val DoubleHorzSpacer = Modifier.width(10.dp)
|
||||||
|
val DoubleVertSpacer = Modifier.width(10.dp)
|
||||||
|
|
||||||
val Size35dp = 35.dp
|
|
||||||
val Size13dp = 13.dp
|
val Size13dp = 13.dp
|
||||||
val Size16dp = 16.dp
|
val Size16dp = 16.dp
|
||||||
|
val Size25dp = 25.dp
|
||||||
|
val Size35dp = 35.dp
|
||||||
|
|
||||||
val StdPadding = Modifier.padding(10.dp)
|
val StdPadding = Modifier.padding(10.dp)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user