Performance Improvements

This commit is contained in:
Vitor Pamplona 2023-06-26 14:25:46 -04:00
parent 743a4c9d87
commit f9d652f849
15 changed files with 465 additions and 389 deletions

View File

@ -1134,7 +1134,7 @@ class Account(
user.countReportAuthorsBy(followingKeySet()) < 5 user.countReportAuthorsBy(followingKeySet()) < 5
} }
fun isAcceptableDirect(note: Note): Boolean { private fun isAcceptableDirect(note: Note): Boolean {
if (!warnAboutPostsWithReports) { if (!warnAboutPostsWithReports) {
return note.reportsBy(userProfile()).isEmpty() return note.reportsBy(userProfile()).isEmpty()
} }

View File

@ -33,7 +33,7 @@ class PublicChatChannel(idHex: String) : Channel(idHex) {
} }
override fun profilePicture(): String? { override fun profilePicture(): String? {
if (info.picture.isNullOrBlank()) return null if (info.picture.isNullOrBlank()) return super.profilePicture()
return info.picture ?: super.profilePicture() return info.picture ?: super.profilePicture()
} }
@ -65,7 +65,7 @@ class LiveActivitiesChannel(val address: ATag) : Channel(address.toTag()) {
} }
override fun profilePicture(): String? { override fun profilePicture(): String? {
return info?.image()?.ifBlank { null } ?: super.profilePicture() return info?.image()?.ifBlank { null }
} }
override fun anyNameStartsWith(prefix: String): Boolean { override fun anyNameStartsWith(prefix: String): Boolean {

View File

@ -12,6 +12,8 @@ import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.EOSETime
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
@ -237,6 +239,7 @@ class User(val pubkeyHex: String) {
info = newUserInfo info = newUserInfo
info?.latestMetadata = latestMetadata info?.latestMetadata = latestMetadata
info?.updatedMetadataAt = latestMetadata.createdAt info?.updatedMetadataAt = latestMetadata.createdAt
info?.tags = latestMetadata.tags.toImmutableListOfLists()
if (newUserInfo.lud16.isNullOrBlank() && newUserInfo.lud06?.lowercase()?.startsWith("lnurl") == true) { if (newUserInfo.lud16.isNullOrBlank() && newUserInfo.lud06?.lowercase()?.startsWith("lnurl") == true) {
try { try {
@ -421,6 +424,7 @@ class UserMetadata {
var updatedMetadataAt: Long = 0 var updatedMetadataAt: Long = 0
var latestMetadata: MetadataEvent? = null var latestMetadata: MetadataEvent? = null
var tags: ImmutableListOfLists<String>? = null
fun anyName(): String? { fun anyName(): String? {
return display_name ?: displayName ?: name ?: username return display_name ?: displayName ?: name ?: username
@ -434,6 +438,23 @@ class UserMetadata {
fun lnAddress(): String? { fun lnAddress(): String? {
return (lud16?.trim() ?: lud06?.trim())?.ifBlank { null } return (lud16?.trim() ?: lud06?.trim())?.ifBlank { null }
} }
fun bestUsername(): String? {
return name?.ifBlank { null } ?: username?.ifBlank { null }
}
fun bestDisplayName(): String? {
return displayName?.ifBlank { null } ?: display_name?.ifBlank { null }
}
fun nip05(): String? {
return nip05?.ifBlank { null }
}
fun profilePicture(): String? {
if (picture.isNullOrBlank()) picture = null
return picture
}
} }
class UserLiveData(val user: User) : LiveData<UserState>(UserState(user)) { class UserLiveData(val user: User) : LiveData<UserState>(UserState(user)) {

View File

@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel
import com.vitorpamplona.amethyst.ui.theme.Size55dp
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.channels.Channel import kotlinx.coroutines.channels.Channel
@ -389,7 +390,7 @@ fun UserComposeForChat(
), ),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
ClickableUserPicture(baseUser, 55.dp, accountViewModel) ClickableUserPicture(baseUser, Size55dp, accountViewModel)
Column( Column(
modifier = Modifier modifier = Modifier

View File

@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.components
import android.util.Log import android.util.Log
import android.util.LruCache import android.util.LruCache
import android.util.Patterns import android.util.Patterns
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.ClickableText
@ -26,6 +27,8 @@ import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.HeadingStyle import com.halilibo.richtext.ui.HeadingStyle
@ -40,7 +43,6 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon import com.vitorpamplona.amethyst.model.checkForHashtagWithIcon
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.nip19.Nip19
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle
@ -751,7 +753,11 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: MutableState<Co
} }
} }
loadedLink = LoadedBechLink(returningNote, it) val newLink = LoadedBechLink(returningNote, it)
launch(Dispatchers.Main) {
loadedLink = newLink
}
} }
} }
} }
@ -838,10 +844,12 @@ fun HashTag(word: String, nav: (String) -> Unit) {
} }
if (myTag != null) { if (myTag != null) {
launch(Dispatchers.Main) {
tagSuffixPair = Pair(myTag, mySuffix) tagSuffixPair = Pair(myTag, mySuffix)
} }
} }
} }
}
tagSuffixPair?.let { tagPair -> tagSuffixPair?.let { tagPair ->
val hashtagIcon = remember(tagPair.first) { checkForHashtagWithIcon(tagPair.first) } val hashtagIcon = remember(tagPair.first) { checkForHashtagWithIcon(tagPair.first) }
@ -922,10 +930,13 @@ fun TagLink(word: String, tags: ImmutableListOfLists<String>, canPreview: Boolea
if (tag.size > 1) { if (tag.size > 1) {
if (tag[0] == "p") { if (tag[0] == "p") {
LocalCache.checkGetOrCreateUser(tag[1])?.let { LocalCache.checkGetOrCreateUser(tag[1])?.let {
launch(Dispatchers.Main) {
loadedTag = LoadedTag(it, null, suffix) loadedTag = LoadedTag(it, null, suffix)
} }
}
} else if (tag[0] == "e" || tag[0] == "a") { } else if (tag[0] == "e" || tag[0] == "a") {
LocalCache.checkGetOrCreateNote(tag[1])?.let { LocalCache.checkGetOrCreateNote(tag[1])?.let {
launch(Dispatchers.Main) {
loadedTag = LoadedTag(null, it, suffix) loadedTag = LoadedTag(null, it, suffix)
} }
} }
@ -934,6 +945,7 @@ fun TagLink(word: String, tags: ImmutableListOfLists<String>, canPreview: Boolea
} }
} }
} }
}
if (loadedTag == null) { if (loadedTag == null) {
Text( Text(
@ -995,25 +1007,23 @@ private fun DisplayUserFromTag(
) { ) {
val route = remember { "User/${baseUser.pubkeyHex}" } val route = remember { "User/${baseUser.pubkeyHex}" }
val suffix = remember { "$addedChars " } val suffix = remember { "$addedChars " }
val hex = remember { baseUser.pubkeyDisplayHex() }
val innerUserState by baseUser.live().metadata.observeAsState() val meta by baseUser.live().metadata.map {
val displayName by remember(innerUserState) { it.user.info
derivedStateOf { }.distinctUntilChanged().observeAsState(baseUser.info)
innerUserState?.user?.toBestDisplayName() ?: ""
}
}
val userTags by remember(innerUserState) {
derivedStateOf {
innerUserState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists()
}
}
Crossfade(targetState = meta) {
val displayName = remember(it) {
it?.bestDisplayName() ?: hex
}
CreateClickableTextWithEmoji( CreateClickableTextWithEmoji(
clickablePart = displayName, clickablePart = displayName,
suffix = suffix, suffix = suffix,
maxLines = 1, maxLines = 1,
route = route, route = route,
nav = nav, nav = nav,
tags = userTags tags = it?.tags ?: ImmutableListOfLists()
) )
} }
}

View File

@ -50,7 +50,7 @@ fun RobohashAsyncImage(
@Composable @Composable
fun RobohashFallbackAsyncImage( fun RobohashFallbackAsyncImage(
robot: String, robot: String,
model: String, model: String?,
contentDescription: String?, contentDescription: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center, alignment: Alignment = Alignment.Center,
@ -91,18 +91,6 @@ fun RobohashAsyncImageProxy(
colorFilter: ColorFilter? = null, colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) { ) {
if (model == null) {
RobohashAsyncImage(
robot = robot,
contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality
)
} else {
RobohashFallbackAsyncImage( RobohashFallbackAsyncImage(
robot = robot, robot = robot,
model = model, model = model,
@ -115,4 +103,3 @@ fun RobohashAsyncImageProxy(
filterQuality = filterQuality filterQuality = filterQuality
) )
} }
}

View File

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -28,6 +29,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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
@ -109,31 +111,28 @@ fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifie
@Composable @Composable
fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) { fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) {
val userState by baseUser.live().metadata.observeAsState() val nip05 by baseUser.live().metadata.map {
val isValidNIP05 by remember(userState) { it.user.nip05()
derivedStateOf { }.observeAsState()
userState?.user?.nip05()?.split("@")?.size == 2
}
}
val nip05 by remember(userState) {
derivedStateOf {
userState?.user?.nip05()
}
}
if (isValidNIP05) { Crossfade(targetState = nip05, modifier = columnModifier) {
nip05?.let { if (it != null) {
val isValid = it.split("@").size == 2
if (isValid) {
DisplayNIP05Line(it, baseUser, columnModifier) DisplayNIP05Line(it, baseUser, columnModifier)
} }
} }
} }
}
@Composable @Composable
private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modifier = Modifier) { private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modifier = Modifier) {
Column(modifier = columnModifier) { Column(modifier = columnModifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex) val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex)
DisplayNIP05(nip05, nip05Verified) Crossfade(targetState = nip05Verified) {
Row(verticalAlignment = Alignment.CenterVertically) {
DisplayNIP05(nip05, it)
}
} }
} }
} }
@ -150,7 +149,7 @@ private fun DisplayNIP05(
if (user != "_") { if (user != "_") {
Text( Text(
text = AnnotatedString(user), text = remember(nip05) { AnnotatedString(user) },
color = MaterialTheme.colors.placeholderText, color = MaterialTheme.colors.placeholderText,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
@ -160,7 +159,7 @@ private fun DisplayNIP05(
NIP05VerifiedSymbol(nip05Verified) NIP05VerifiedSymbol(nip05Verified)
ClickableText( ClickableText(
text = AnnotatedString(domain), text = remember(nip05) { AnnotatedString(domain) },
onClick = { runCatching { uri.openUri("https://$domain") } }, onClick = { runCatching { uri.openUri("https://$domain") } },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)), style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)),
maxLines = 1, maxLines = 1,

View File

@ -83,6 +83,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.get import androidx.core.graphics.get
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map import androidx.lifecycle.map
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
@ -160,6 +161,8 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifier import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifier
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.Size25dp import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
import com.vitorpamplona.amethyst.ui.theme.Size30dp import com.vitorpamplona.amethyst.ui.theme.Size30dp
@ -168,12 +171,16 @@ import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.UserNameMaxRowHeight
import com.vitorpamplona.amethyst.ui.theme.UserNameRowHeight
import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.replyBackground import com.vitorpamplona.amethyst.ui.theme.replyBackground
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.amethyst.ui.theme.repostProfileBorder import com.vitorpamplona.amethyst.ui.theme.repostProfileBorder
import com.vitorpamplona.amethyst.ui.theme.subtleBorder import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -195,7 +202,7 @@ import java.util.Locale
fun NoteCompose( fun NoteCompose(
baseNote: Note, baseNote: Note,
routeForLastRead: String? = null, routeForLastRead: String? = null,
modifier: Modifier = remember { Modifier }, modifier: Modifier = Modifier,
isBoostedNote: Boolean = false, isBoostedNote: Boolean = false,
isQuotedNote: Boolean = false, isQuotedNote: Boolean = false,
unPackReply: Boolean = true, unPackReply: Boolean = true,
@ -207,9 +214,10 @@ fun NoteCompose(
) { ) {
val isBlank by baseNote.live().metadata.map { val isBlank by baseNote.live().metadata.map {
it.note.event == null it.note.event == null
}.observeAsState(baseNote.event == null) }.distinctUntilChanged().observeAsState(baseNote.event == null)
if (isBlank) { Crossfade(targetState = isBlank) {
if (it) {
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
BlankNote( BlankNote(
remember { remember {
@ -237,6 +245,7 @@ fun NoteCompose(
) )
} }
} }
}
@Composable @Composable
fun CheckHiddenNoteCompose( fun CheckHiddenNoteCompose(
@ -261,7 +270,8 @@ fun CheckHiddenNoteCompose(
} }
} }
if (!isHidden) { Crossfade(targetState = isHidden) {
if (!it) {
LoadedNoteCompose( LoadedNoteCompose(
note, note,
routeForLastRead, routeForLastRead,
@ -277,6 +287,7 @@ fun CheckHiddenNoteCompose(
) )
} }
} }
}
@Immutable @Immutable
data class NoteComposeReportState( data class NoteComposeReportState(
@ -309,20 +320,52 @@ fun LoadedNoteCompose(
) )
} }
val scope = rememberCoroutineScope()
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports -> WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) { if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
scope.launch(Dispatchers.Main) {
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports) state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
} }
} }
}
Crossfade(targetState = state) {
RenderReportState(
it,
note,
routeForLastRead,
modifier,
isBoostedNote,
isQuotedNote,
unPackReply,
makeItShort,
addMarginTop,
parentBackgroundColor,
accountViewModel,
nav
)
}
}
@Composable
fun RenderReportState(
state: NoteComposeReportState,
note: Note,
routeForLastRead: String? = null,
modifier: Modifier = Modifier,
isBoostedNote: Boolean = false,
isQuotedNote: Boolean = false,
unPackReply: Boolean = true,
makeItShort: Boolean = false,
addMarginTop: Boolean = true,
parentBackgroundColor: MutableState<Color>? = null,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
var showReportedNote by remember { mutableStateOf(false) } var showReportedNote by remember { mutableStateOf(false) }
val showHiddenNote by remember(state, showReportedNote) { Crossfade(targetState = !state.isAcceptable && !showReportedNote) { showHiddenNote ->
derivedStateOf {
!state.isAcceptable && !showReportedNote
}
}
if (showHiddenNote) { if (showHiddenNote) {
HiddenNote( HiddenNote(
state.relevantReports, state.relevantReports,
@ -333,11 +376,7 @@ fun LoadedNoteCompose(
onClick = { showReportedNote = true } onClick = { showReportedNote = true }
) )
} else { } else {
val canPreview by remember(state, showReportedNote) { val canPreview = (!state.isAcceptable && showReportedNote) || state.canPreview
derivedStateOf {
(!state.isAcceptable && showReportedNote) || state.canPreview
}
}
NormalNote( NormalNote(
note, note,
@ -355,6 +394,7 @@ fun LoadedNoteCompose(
) )
} }
} }
}
@Composable @Composable
fun WatchForReports( fun WatchForReports(
@ -368,13 +408,23 @@ fun WatchForReports(
LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) { LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) {
launch(Dispatchers.Default) { launch(Dispatchers.Default) {
accountViewModel.account.let { loggedIn -> accountViewModel.account.let { loggedIn ->
val newCanPreview = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex || val isFromLoggedIn = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex
(note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true) || val isFromLoggedInFollow = note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true
noteReportsState?.note?.hasAnyReports() != true
if (isFromLoggedIn || isFromLoggedInFollow) {
// No need to process if from trusted people
onChange(true, true, persistentSetOf())
} else {
val newCanPreview = noteReportsState?.note?.hasAnyReports() != true
val newIsAcceptable = noteReportsState?.note?.let { val newIsAcceptable = noteReportsState?.note?.let {
loggedIn.isAcceptable(it) loggedIn.isAcceptable(it)
} ?: true } ?: true
if (newCanPreview && newIsAcceptable) {
// No need to process reports if nothing is wrong
onChange(true, true, persistentSetOf())
} else {
val newRelevantReports = noteReportsState?.note?.let { val newRelevantReports = noteReportsState?.note?.let {
loggedIn.getRelevantReports(it) loggedIn.getRelevantReports(it)
} ?: emptySet() } ?: emptySet()
@ -384,6 +434,8 @@ fun WatchForReports(
} }
} }
} }
}
}
@Composable @Composable
fun NormalNote( fun NormalNote(
@ -400,29 +452,18 @@ fun NormalNote(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
val noteEvent = remember { baseNote.event } when (baseNote.event) {
is ChannelCreateEvent, is ChannelMetadataEvent -> ChannelHeader(
val isChannelHeader by remember {
derivedStateOf {
(baseNote.event is ChannelCreateEvent || baseNote.event is ChannelMetadataEvent) && baseNote.channelHex() != null
}
}
if (isChannelHeader) {
ChannelHeader(
channelNote = baseNote, channelNote = baseNote,
showVideo = !makeItShort, showVideo = !makeItShort,
showBottomDiviser = true, showBottomDiviser = true,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav nav = nav
) )
} else if (noteEvent is BadgeDefinitionEvent) { is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
BadgeDisplay(baseNote = baseNote) is FileHeaderEvent -> FileHeaderDisplay(baseNote)
} else if (noteEvent is FileHeaderEvent) { is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote)
FileHeaderDisplay(baseNote) else ->
} else if (noteEvent is FileStorageHeaderEvent) {
FileStorageHeaderDisplay(baseNote)
} else {
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
CheckNewAndRenderNote( CheckNewAndRenderNote(
baseNote, baseNote,
@ -534,7 +575,7 @@ private fun ClickableNote(
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val updatedModifier = remember(backgroundColor.value) { val updatedModifier = remember(backgroundColor) {
modifier modifier
.combinedClickable( .combinedClickable(
onClick = { onClick = {
@ -569,17 +610,8 @@ fun InnerNoteWithReactions(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
val notBoostedNorQuote by remember { val notBoostedNorQuote = !isBoostedNote && !isQuotedNote
derivedStateOf { val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
!isBoostedNote && !isQuotedNote
}
}
val showSecondRow by remember {
derivedStateOf {
baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
}
}
Row( Row(
modifier = remember { modifier = remember {
@ -593,10 +625,13 @@ fun InnerNoteWithReactions(
} }
) { ) {
if (notBoostedNorQuote) { if (notBoostedNorQuote) {
Column(WidthAuthorPictureModifier) {
AuthorAndRelayInformation(baseNote, accountViewModel, nav) AuthorAndRelayInformation(baseNote, accountViewModel, nav)
}
Spacer(modifier = DoubleHorzSpacer) Spacer(modifier = DoubleHorzSpacer)
} }
Column(Modifier.fillMaxWidth()) {
NoteBody( NoteBody(
baseNote = baseNote, baseNote = baseNote,
showAuthorPicture = isQuotedNote, showAuthorPicture = isQuotedNote,
@ -609,12 +644,9 @@ fun InnerNoteWithReactions(
nav = nav nav = nav
) )
} }
}
val isNotRepost by remember { val isNotRepost = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent
derivedStateOf {
baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent
}
}
if (isNotRepost) { if (isNotRepost) {
if (makeItShort) { if (makeItShort) {
@ -651,7 +683,6 @@ private fun NoteBody(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
Column(Modifier.fillMaxWidth()) {
FirstUserInfoRow( FirstUserInfoRow(
baseNote = baseNote, baseNote = baseNote,
showAuthorPicture = showAuthorPicture, showAuthorPicture = showAuthorPicture,
@ -688,7 +719,6 @@ private fun NoteBody(
nav nav
) )
} }
}
@Composable @Composable
private fun RenderNoteRow( private fun RenderNoteRow(
@ -1711,6 +1741,15 @@ private fun ReplyRow(
} }
} }
if (showReply) {
val replyingDirectlyTo = remember { note.replyTo?.lastOrNull() }
if (replyingDirectlyTo != null && unPackReply) {
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
Spacer(modifier = StdVertSpacer)
} else {
// ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav)
}
} else {
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())) ||
@ -1718,15 +1757,7 @@ private fun ReplyRow(
} }
} }
if (showReply) { if (showChannelReply) {
val replyingDirectlyTo = remember { note.replyTo?.lastOrNull() }
if (replyingDirectlyTo != null && unPackReply) {
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
Spacer(modifier = Modifier.height(5.dp))
} else {
// ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav)
}
} else if (showChannelReply) {
val channelHex = note.channelHex() val channelHex = note.channelHex()
channelHex?.let { channelHex?.let {
ChannelHeader( ChannelHeader(
@ -1745,6 +1776,7 @@ private fun ReplyRow(
} }
} }
} }
}
@Composable @Composable
private fun ReplyNoteComposition( private fun ReplyNoteComposition(
@ -1759,7 +1791,7 @@ private fun ReplyNoteComposition(
val defaultReplyBackground = MaterialTheme.colors.replyBackground val defaultReplyBackground = MaterialTheme.colors.replyBackground
LaunchedEffect(key1 = backgroundColor.value, key2 = defaultReplyBackground) { LaunchedEffect(key1 = backgroundColor.value, key2 = defaultReplyBackground) {
launch(Dispatchers.IO) { launch(Dispatchers.Default) {
val newReplyBackgroundColor = val newReplyBackgroundColor =
defaultReplyBackground.compositeOver(backgroundColor.value) defaultReplyBackground.compositeOver(backgroundColor.value)
if (replyBackgroundColor.value != newReplyBackgroundColor) { if (replyBackgroundColor.value != newReplyBackgroundColor) {
@ -1768,22 +1800,10 @@ private fun ReplyNoteComposition(
} }
} }
val borderColor = MaterialTheme.colors.subtleBorder
NoteCompose( NoteCompose(
baseNote = replyingDirectlyTo, baseNote = replyingDirectlyTo,
isQuotedNote = true, isQuotedNote = true,
modifier = remember { modifier = MaterialTheme.colors.replyModifier,
Modifier
.padding(top = 5.dp)
.fillMaxWidth()
.clip(shape = QuoteBorder)
.border(
1.dp,
borderColor,
QuoteBorder
)
},
unPackReply = false, unPackReply = false,
makeItShort = true, makeItShort = true,
parentBackgroundColor = replyBackgroundColor, parentBackgroundColor = replyBackgroundColor,
@ -1801,7 +1821,7 @@ private fun SecondUserInfoRow(
val noteEvent = remember { note.event } ?: return val noteEvent = remember { note.event } ?: return
val noteAuthor = remember { note.author } ?: return val noteAuthor = remember { note.author } ?: return
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) }) ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } } val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
@ -1823,7 +1843,7 @@ private fun FirstUserInfoRow(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit nav: (String) -> Unit
) { ) {
Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(verticalAlignment = CenterVertically, modifier = remember { UserNameRowHeight }) {
val isRepost by remember { val isRepost by remember {
derivedStateOf { derivedStateOf {
baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
@ -1869,13 +1889,13 @@ private fun MoreOptionsButton(
var moreActionsExpanded by remember { mutableStateOf(false) } var moreActionsExpanded by remember { mutableStateOf(false) }
IconButton( IconButton(
modifier = remember { Modifier.size(24.dp) }, modifier = Size24Modifier,
onClick = { moreActionsExpanded = true } onClick = { moreActionsExpanded = true }
) { ) {
Icon( Icon(
imageVector = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
null, null,
modifier = remember { Modifier.size(15.dp) }, modifier = Size15Modifier,
tint = MaterialTheme.colors.placeholderText tint = MaterialTheme.colors.placeholderText
) )
@ -1908,7 +1928,6 @@ fun TimeAgo(time: Long) {
@Composable @Composable
private fun AuthorAndRelayInformation(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { private fun AuthorAndRelayInformation(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
Column(WidthAuthorPictureModifier) {
// Draws the boosted picture outside the boosted card. // Draws the boosted picture outside the boosted card.
Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) { Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) {
RenderAuthorImages(baseNote, nav, accountViewModel) RenderAuthorImages(baseNote, nav, accountViewModel)
@ -1916,7 +1935,6 @@ private fun AuthorAndRelayInformation(baseNote: Note, accountViewModel: AccountV
BadgeBox(baseNote, accountViewModel, nav) BadgeBox(baseNote, accountViewModel, nav)
} }
}
@Composable @Composable
private fun BadgeBox( private fun BadgeBox(
@ -1950,11 +1968,7 @@ private fun RenderAuthorImages(
nav: (String) -> Unit, nav: (String) -> Unit,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val isRepost by remember { val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
derivedStateOf {
baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
}
}
NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp) NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp)
@ -1962,11 +1976,7 @@ private fun RenderAuthorImages(
RepostNoteAuthorPicture(baseNote, accountViewModel, nav) RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
} }
val isChannel by remember { val isChannel = baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null
derivedStateOf {
baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null
}
}
if (isChannel) { if (isChannel) {
val baseChannelHex = remember { baseNote.channelHex() } val baseChannelHex = remember { baseNote.channelHex() }
@ -2001,7 +2011,7 @@ fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit)
private fun ChannelNotePicture(baseChannel: Channel) { private fun ChannelNotePicture(baseChannel: Channel) {
val model by baseChannel.live.map { val model by baseChannel.live.map {
it.channel.profilePicture() it.channel.profilePicture()
}.observeAsState() }.distinctUntilChanged().observeAsState()
val backgroundColor = MaterialTheme.colors.background val backgroundColor = MaterialTheme.colors.background
@ -2879,7 +2889,10 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
mutableStateOf(lazyRelayList.take(3).toImmutableList()) mutableStateOf(lazyRelayList.take(3).toImmutableList())
} }
val scope = rememberCoroutineScope()
WatchRelayLists(baseNote) { relayList -> WatchRelayLists(baseNote) { relayList ->
scope.launch(Dispatchers.Main) {
if (!equalImmutableLists(relayList, lazyRelayList)) { if (!equalImmutableLists(relayList, lazyRelayList)) {
lazyRelayList = relayList lazyRelayList = relayList
shortRelayList = relayList.take(3).toImmutableList() shortRelayList = relayList.take(3).toImmutableList()
@ -2891,6 +2904,7 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
showShowMore = nextShowMore showShowMore = nextShowMore
} }
} }
}
Spacer(DoubleVertSpacer) Spacer(DoubleVertSpacer)
@ -2982,12 +2996,14 @@ fun NoteAuthorPicture(
) { ) {
val author by baseNote.live().metadata.map { val author by baseNote.live().metadata.map {
it.note.author it.note.author
}.observeAsState() }.distinctUntilChanged().observeAsState(baseNote.author)
if (author == null) { Crossfade(targetState = author) {
if (it == null) {
DisplayBlankAuthor(size, modifier) DisplayBlankAuthor(size, modifier)
} else { } else {
ClickableUserPicture(author!!, size, accountViewModel, modifier, onClick) ClickableUserPicture(it, size, accountViewModel, modifier, onClick)
}
} }
} }
@ -3111,16 +3127,24 @@ fun BaseUserPicture(
val userProfile by baseUser.live().metadata.map { val userProfile by baseUser.live().metadata.map {
it.user.profilePicture() it.user.profilePicture()
}.observeAsState() }.distinctUntilChanged().observeAsState(baseUser.profilePicture())
val myBoxModifier = remember {
Modifier.size(size)
}
Crossfade(targetState = userProfile) {
Box(myBoxModifier, contentAlignment = TopEnd) {
PictureAndFollowingMark( PictureAndFollowingMark(
userHex = userPubkey, userHex = userPubkey,
userPicture = userProfile, userPicture = it,
size = size, size = size,
modifier = modifier, modifier = modifier,
accountViewModel = accountViewModel accountViewModel = accountViewModel
) )
} }
}
}
@Composable @Composable
fun PictureAndFollowingMark( fun PictureAndFollowingMark(
@ -3157,14 +3181,18 @@ fun PictureAndFollowingMark(
private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) { private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
val showFollowingMark by accountViewModel.userFollows.map { val showFollowingMark by accountViewModel.userFollows.map {
it.user.isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex) it.user.isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
}.observeAsState( }.distinctUntilChanged().observeAsState(
accountViewModel.account.userProfile().isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex) accountViewModel.account.userProfile().isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
) )
if (showFollowingMark) { Crossfade(targetState = showFollowingMark) {
if (it) {
Box(contentAlignment = TopEnd) {
FollowingIcon(iconSize) FollowingIcon(iconSize)
} }
} }
}
}
@Composable @Composable
private fun FollowingIcon(iconSize: Dp) { private fun FollowingIcon(iconSize: Dp) {

View File

@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Size55dp
@Composable @Composable
fun UserCompose( fun UserCompose(
@ -38,9 +39,9 @@ fun UserCompose(
modifier = overallModifier, modifier = overallModifier,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
UserPicture(baseUser, 55.dp, accountViewModel = accountViewModel, nav = nav) UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav)
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
UsernameDisplay(baseUser) UsernameDisplay(baseUser)
} }
@ -48,7 +49,7 @@ fun UserCompose(
AboutDisplay(baseUser) AboutDisplay(baseUser)
} }
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
UserActionOptions(baseUser, accountViewModel) UserActionOptions(baseUser, accountViewModel)
} }
} }

View File

@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.ui.note
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
@ -20,13 +22,11 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map import androidx.lifecycle.map
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.service.tts.TextToSpeechHelper import com.vitorpamplona.amethyst.service.tts.TextToSpeechHelper
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.theme.StdButtonSizeModifier import com.vitorpamplona.amethyst.ui.theme.StdButtonSizeModifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@ -36,38 +36,34 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) { fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
val authorState by baseNote.live().metadata.map { val authorState by baseNote.live().metadata.map {
it.note.author it.note.author
}.distinctUntilChanged().observeAsState() }.observeAsState(baseNote.author)
authorState?.let { Crossfade(targetState = authorState, modifier = weight) {
it?.let {
UsernameDisplay(it, weight) UsernameDisplay(it, weight)
} }
} }
}
@Composable @Composable
fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) { fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
val userState by baseUser.live().metadata.observeAsState()
val bestUserName by remember(userState) {
derivedStateOf {
userState?.user?.bestUsername()
}
}
val bestDisplayName by remember(userState) {
derivedStateOf {
userState?.user?.bestDisplayName()
}
}
val npubDisplay by remember { val npubDisplay by remember {
derivedStateOf { derivedStateOf {
baseUser.pubkeyDisplayHex() baseUser.pubkeyDisplayHex()
} }
} }
val tags by remember(userState) {
derivedStateOf {
userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists()
}
}
UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, tags, weight) val userMetadata by baseUser.live().metadata.map {
it.user.info
}.observeAsState(baseUser.info)
Crossfade(targetState = userMetadata, modifier = weight) {
if (it != null) {
UserNameDisplay(it.bestUsername(), it.bestDisplayName(), npubDisplay, it.tags, weight)
} else {
NPubDisplay(npubDisplay, weight)
}
}
} }
@Composable @Composable
@ -106,6 +102,7 @@ private fun UserDisplay(
tags: ImmutableListOfLists<String>?, tags: ImmutableListOfLists<String>?,
modifier: Modifier modifier: Modifier
) { ) {
Row(modifier = modifier) {
CreateTextWithEmoji( CreateTextWithEmoji(
text = bestDisplayName, text = bestDisplayName,
tags = tags, tags = tags,
@ -117,6 +114,7 @@ private fun UserDisplay(
Spacer(StdHorzSpacer) Spacer(StdHorzSpacer)
DrawPlayName(bestDisplayName) DrawPlayName(bestDisplayName)
} }
}
@Composable @Composable
private fun UserAndUsernameDisplay( private fun UserAndUsernameDisplay(
@ -125,6 +123,7 @@ private fun UserAndUsernameDisplay(
bestUserName: String, bestUserName: String,
modifier: Modifier modifier: Modifier
) { ) {
Row(modifier = modifier) {
CreateTextWithEmoji( CreateTextWithEmoji(
text = bestDisplayName, text = bestDisplayName,
tags = tags, tags = tags,
@ -142,6 +141,7 @@ private fun UserAndUsernameDisplay(
Spacer(StdHorzSpacer) Spacer(StdHorzSpacer)
DrawPlayName(bestDisplayName) DrawPlayName(bestDisplayName)
} }
}
@Composable @Composable
fun DrawPlayName(name: String) { fun DrawPlayName(name: String) {

View File

@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Size55dp
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
@ -106,7 +107,7 @@ private fun RenderZapNote(
}, },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
UserPicture(baseAuthor, 55.dp, accountViewModel = accountViewModel, nav = nav) UserPicture(baseAuthor, Size55dp, accountViewModel = accountViewModel, nav = nav)
Column( Column(
modifier = remember { modifier = remember {

View File

@ -197,8 +197,7 @@ private fun FeedLoaded(
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
val defaultModifier = remember { val defaultModifier = remember {
Modifier Modifier.fillMaxWidth().animateItemPlacement()
.fillMaxWidth().animateItemPlacement()
} }
Row(defaultModifier) { Row(defaultModifier) {

View File

@ -586,9 +586,10 @@ fun ChannelHeader(
) )
} }
channel.profilePicture()?.let {
RobohashAsyncImageProxy( RobohashAsyncImageProxy(
robot = channel.idHex, robot = channel.idHex,
model = channel.profilePicture(), model = it,
contentDescription = stringResource(R.string.profile_image), contentDescription = stringResource(R.string.profile_image),
modifier = Modifier modifier = Modifier
.width(Size35dp) .width(Size35dp)
@ -596,6 +597,7 @@ fun ChannelHeader(
.padding(start = 10.dp) .padding(start = 10.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
) )
}
Column( Column(
modifier = Modifier modifier = Modifier

View File

@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.theme
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
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.layout.width
@ -62,6 +63,7 @@ val StdPadding = Modifier.padding(10.dp)
val Size15Modifier = Modifier.size(15.dp) val Size15Modifier = Modifier.size(15.dp)
val Size20Modifier = Modifier.size(20.dp) val Size20Modifier = Modifier.size(20.dp)
val Size22Modifier = Modifier.size(22.dp) val Size22Modifier = Modifier.size(22.dp)
val Size24Modifier = Modifier.size(24.dp)
val Size30Modifier = Modifier.size(30.dp) val Size30Modifier = Modifier.size(30.dp)
val Size55Modifier = Modifier.size(55.dp) val Size55Modifier = Modifier.size(55.dp)
@ -76,6 +78,8 @@ val DiviserThickness = 0.25.dp
val ReactionRowHeight = Modifier.height(24.dp).padding(start = 10.dp) val ReactionRowHeight = Modifier.height(24.dp).padding(start = 10.dp)
val ReactionRowHeightChat = Modifier.height(25.dp) val ReactionRowHeightChat = Modifier.height(25.dp)
val UserNameRowHeight = Modifier.height(22.dp).fillMaxWidth()
val UserNameMaxRowHeight = Modifier.heightIn(max = 22.dp).fillMaxWidth()
val Height4dpModifier = Modifier.height(4.dp) val Height4dpModifier = Modifier.height(4.dp)

View File

@ -121,6 +121,26 @@ val LightImageModifier = Modifier
QuoteBorder QuoteBorder
) )
val DarkReplyBorderModifier = Modifier
.padding(top = 5.dp)
.fillMaxWidth()
.clip(shape = QuoteBorder)
.border(
1.dp,
DarkSubtleBorder,
QuoteBorder
)
val LightReplyBorderModifier = Modifier
.padding(top = 5.dp)
.fillMaxWidth()
.clip(shape = QuoteBorder)
.border(
1.dp,
LightSubtleBorder,
QuoteBorder
)
val MarkDownStyleOnDark = richTextDefaults.copy( val MarkDownStyleOnDark = richTextDefaults.copy(
paragraphSpacing = DefaultParagraphSpacing, paragraphSpacing = DefaultParagraphSpacing,
headingStyle = DefaultHeadingStyle, headingStyle = DefaultHeadingStyle,
@ -243,6 +263,9 @@ val Colors.repostProfileBorder: Modifier
val Colors.imageModifier: Modifier val Colors.imageModifier: Modifier
get() = if (isLight) LightImageModifier else DarkImageModifier get() = if (isLight) LightImageModifier else DarkImageModifier
val Colors.replyModifier: Modifier
get() = if (isLight) LightReplyBorderModifier else DarkReplyBorderModifier
@Composable @Composable
fun AmethystTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { fun AmethystTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val colors = if (darkTheme) { val colors = if (darkTheme) {