diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 976caee65..b935ce564 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -1134,7 +1134,7 @@ class Account( user.countReportAuthorsBy(followingKeySet()) < 5 } - fun isAcceptableDirect(note: Note): Boolean { + private fun isAcceptableDirect(note: Note): Boolean { if (!warnAboutPostsWithReports) { return note.reportsBy(userProfile()).isEmpty() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt index 64608e1ef..d84817b8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt @@ -33,7 +33,7 @@ class PublicChatChannel(idHex: String) : Channel(idHex) { } override fun profilePicture(): String? { - if (info.picture.isNullOrBlank()) return null + if (info.picture.isNullOrBlank()) return super.profilePicture() return info.picture ?: super.profilePicture() } @@ -65,7 +65,7 @@ class LiveActivitiesChannel(val address: ATag) : Channel(address.toTag()) { } override fun profilePicture(): String? { - return info?.image()?.ifBlank { null } ?: super.profilePicture() + return info?.image()?.ifBlank { null } } override fun anyNameStartsWith(prefix: String): Boolean { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 11825e078..0494fe832 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -12,6 +12,8 @@ import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.relays.EOSETime 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.note.toShortenHex import fr.acinq.secp256k1.Hex @@ -237,6 +239,7 @@ class User(val pubkeyHex: String) { info = newUserInfo info?.latestMetadata = latestMetadata info?.updatedMetadataAt = latestMetadata.createdAt + info?.tags = latestMetadata.tags.toImmutableListOfLists() if (newUserInfo.lud16.isNullOrBlank() && newUserInfo.lud06?.lowercase()?.startsWith("lnurl") == true) { try { @@ -421,6 +424,7 @@ class UserMetadata { var updatedMetadataAt: Long = 0 var latestMetadata: MetadataEvent? = null + var tags: ImmutableListOfLists? = null fun anyName(): String? { return display_name ?: displayName ?: name ?: username @@ -434,6 +438,23 @@ class UserMetadata { fun lnAddress(): String? { 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(user)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 0804769ee..20c084d51 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel +import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel @@ -389,7 +390,7 @@ fun UserComposeForChat( ), verticalAlignment = Alignment.CenterVertically ) { - ClickableUserPicture(baseUser, 55.dp, accountViewModel) + ClickableUserPicture(baseUser, Size55dp, accountViewModel) Column( modifier = Modifier diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index bc43fefef..ee4822039 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.components import android.util.Log import android.util.LruCache import android.util.Patterns +import androidx.compose.animation.Crossfade import androidx.compose.foundation.border import androidx.compose.foundation.layout.* 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.dp 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.MarkdownParseOptions 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.service.nip19.Nip19 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.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle @@ -751,7 +753,11 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: MutableState Unit) { } if (myTag != null) { - tagSuffixPair = Pair(myTag, mySuffix) + launch(Dispatchers.Main) { + tagSuffixPair = Pair(myTag, mySuffix) + } } } } @@ -922,11 +930,15 @@ fun TagLink(word: String, tags: ImmutableListOfLists, canPreview: Boolea if (tag.size > 1) { if (tag[0] == "p") { LocalCache.checkGetOrCreateUser(tag[1])?.let { - loadedTag = LoadedTag(it, null, suffix) + launch(Dispatchers.Main) { + loadedTag = LoadedTag(it, null, suffix) + } } } else if (tag[0] == "e" || tag[0] == "a") { LocalCache.checkGetOrCreateNote(tag[1])?.let { - loadedTag = LoadedTag(null, it, suffix) + launch(Dispatchers.Main) { + loadedTag = LoadedTag(null, it, suffix) + } } } } @@ -995,25 +1007,23 @@ private fun DisplayUserFromTag( ) { val route = remember { "User/${baseUser.pubkeyHex}" } val suffix = remember { "$addedChars " } + val hex = remember { baseUser.pubkeyDisplayHex() } - val innerUserState by baseUser.live().metadata.observeAsState() - val displayName by remember(innerUserState) { - derivedStateOf { - innerUserState?.user?.toBestDisplayName() ?: "" - } - } - val userTags by remember(innerUserState) { - derivedStateOf { - innerUserState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() - } - } + val meta by baseUser.live().metadata.map { + it.user.info + }.distinctUntilChanged().observeAsState(baseUser.info) - CreateClickableTextWithEmoji( - clickablePart = displayName, - suffix = suffix, - maxLines = 1, - route = route, - nav = nav, - tags = userTags - ) + Crossfade(targetState = meta) { + val displayName = remember(it) { + it?.bestDisplayName() ?: hex + } + CreateClickableTextWithEmoji( + clickablePart = displayName, + suffix = suffix, + maxLines = 1, + route = route, + nav = nav, + tags = it?.tags ?: ImmutableListOfLists() + ) + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index c65547431..0879cb365 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -50,7 +50,7 @@ fun RobohashAsyncImage( @Composable fun RobohashFallbackAsyncImage( robot: String, - model: String, + model: String?, contentDescription: String?, modifier: Modifier = Modifier, alignment: Alignment = Alignment.Center, @@ -91,28 +91,15 @@ fun RobohashAsyncImageProxy( colorFilter: ColorFilter? = null, 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( - robot = robot, - model = model, - contentDescription = contentDescription, - modifier = modifier, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter, - filterQuality = filterQuality - ) - } + RobohashFallbackAsyncImage( + robot = robot, + model = model, + contentDescription = contentDescription, + modifier = modifier, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality + ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index e793da24a..21867fec3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.components +import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User @@ -109,21 +111,16 @@ fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifie @Composable fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) { - val userState by baseUser.live().metadata.observeAsState() - val isValidNIP05 by remember(userState) { - derivedStateOf { - userState?.user?.nip05()?.split("@")?.size == 2 - } - } - val nip05 by remember(userState) { - derivedStateOf { - userState?.user?.nip05() - } - } + val nip05 by baseUser.live().metadata.map { + it.user.nip05() + }.observeAsState() - if (isValidNIP05) { - nip05?.let { - DisplayNIP05Line(it, baseUser, columnModifier) + Crossfade(targetState = nip05, modifier = columnModifier) { + if (it != null) { + val isValid = it.split("@").size == 2 + if (isValid) { + DisplayNIP05Line(it, baseUser, columnModifier) + } } } } @@ -131,9 +128,11 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie @Composable private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modifier = Modifier) { Column(modifier = columnModifier) { - Row(verticalAlignment = Alignment.CenterVertically) { - val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex) - DisplayNIP05(nip05, nip05Verified) + val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex) + Crossfade(targetState = nip05Verified) { + Row(verticalAlignment = Alignment.CenterVertically) { + DisplayNIP05(nip05, it) + } } } } @@ -150,7 +149,7 @@ private fun DisplayNIP05( if (user != "_") { Text( - text = AnnotatedString(user), + text = remember(nip05) { AnnotatedString(user) }, color = MaterialTheme.colors.placeholderText, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -160,7 +159,7 @@ private fun DisplayNIP05( NIP05VerifiedSymbol(nip05Verified) ClickableText( - text = AnnotatedString(domain), + text = remember(nip05) { AnnotatedString(domain) }, onClick = { runCatching { uri.openUri("https://$domain") } }, style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(0.52f)), maxLines = 1, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 82e657227..e6fd9c7e8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -83,6 +83,7 @@ import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.get +import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import coil.compose.AsyncImage 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.ShowMoreRelaysButtonIconButtonModifier 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.Size30Modifier 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.StdHorzSpacer 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.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor import com.vitorpamplona.amethyst.ui.theme.placeholderText 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.subtleBorder import kotlinx.collections.immutable.ImmutableList @@ -195,7 +202,7 @@ import java.util.Locale fun NoteCompose( baseNote: Note, routeForLastRead: String? = null, - modifier: Modifier = remember { Modifier }, + modifier: Modifier = Modifier, isBoostedNote: Boolean = false, isQuotedNote: Boolean = false, unPackReply: Boolean = true, @@ -207,34 +214,36 @@ fun NoteCompose( ) { val isBlank by baseNote.live().metadata.map { it.note.event == null - }.observeAsState(baseNote.event == null) + }.distinctUntilChanged().observeAsState(baseNote.event == null) - if (isBlank) { - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - BlankNote( - remember { - modifier.combinedClickable( - onClick = { }, - onLongClick = showPopup - ) - }, - isBoostedNote || isQuotedNote + Crossfade(targetState = isBlank) { + if (it) { + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + BlankNote( + remember { + modifier.combinedClickable( + onClick = { }, + onLongClick = showPopup + ) + }, + isBoostedNote || isQuotedNote + ) + } + } else { + CheckHiddenNoteCompose( + baseNote, + routeForLastRead, + modifier, + isBoostedNote, + isQuotedNote, + unPackReply, + makeItShort, + addMarginTop, + parentBackgroundColor, + accountViewModel, + nav ) } - } else { - CheckHiddenNoteCompose( - baseNote, - routeForLastRead, - modifier, - isBoostedNote, - isQuotedNote, - unPackReply, - makeItShort, - addMarginTop, - parentBackgroundColor, - accountViewModel, - nav - ) } } @@ -261,20 +270,22 @@ fun CheckHiddenNoteCompose( } } - if (!isHidden) { - LoadedNoteCompose( - note, - routeForLastRead, - modifier, - isBoostedNote, - isQuotedNote, - unPackReply, - makeItShort, - addMarginTop, - parentBackgroundColor, - accountViewModel, - nav - ) + Crossfade(targetState = isHidden) { + if (!it) { + LoadedNoteCompose( + note, + routeForLastRead, + modifier, + isBoostedNote, + isQuotedNote, + unPackReply, + makeItShort, + addMarginTop, + parentBackgroundColor, + accountViewModel, + nav + ) + } } } @@ -309,37 +320,19 @@ fun LoadedNoteCompose( ) } + val scope = rememberCoroutineScope() + WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports -> if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) { - state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports) - } - } - - var showReportedNote by remember { mutableStateOf(false) } - - val showHiddenNote by remember(state, showReportedNote) { - derivedStateOf { - !state.isAcceptable && !showReportedNote - } - } - - if (showHiddenNote) { - HiddenNote( - state.relevantReports, - accountViewModel, - modifier, - isBoostedNote, - nav, - onClick = { showReportedNote = true } - ) - } else { - val canPreview by remember(state, showReportedNote) { - derivedStateOf { - (!state.isAcceptable && showReportedNote) || state.canPreview + scope.launch(Dispatchers.Main) { + state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports) } } + } - NormalNote( + Crossfade(targetState = state) { + RenderReportState( + it, note, routeForLastRead, modifier, @@ -348,7 +341,6 @@ fun LoadedNoteCompose( unPackReply, makeItShort, addMarginTop, - canPreview, parentBackgroundColor, accountViewModel, nav @@ -356,6 +348,54 @@ fun LoadedNoteCompose( } } +@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? = null, + accountViewModel: AccountViewModel, + nav: (String) -> Unit +) { + var showReportedNote by remember { mutableStateOf(false) } + + Crossfade(targetState = !state.isAcceptable && !showReportedNote) { showHiddenNote -> + if (showHiddenNote) { + HiddenNote( + state.relevantReports, + accountViewModel, + modifier, + isBoostedNote, + nav, + onClick = { showReportedNote = true } + ) + } else { + val canPreview = (!state.isAcceptable && showReportedNote) || state.canPreview + + NormalNote( + note, + routeForLastRead, + modifier, + isBoostedNote, + isQuotedNote, + unPackReply, + makeItShort, + addMarginTop, + canPreview, + parentBackgroundColor, + accountViewModel, + nav + ) + } + } +} + @Composable fun WatchForReports( note: Note, @@ -368,18 +408,30 @@ fun WatchForReports( LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) { launch(Dispatchers.Default) { accountViewModel.account.let { loggedIn -> - val newCanPreview = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex || - (note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true) || - noteReportsState?.note?.hasAnyReports() != true + val isFromLoggedIn = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex + val isFromLoggedInFollow = note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true - val newIsAcceptable = noteReportsState?.note?.let { - loggedIn.isAcceptable(it) - } ?: true - val newRelevantReports = noteReportsState?.note?.let { - loggedIn.getRelevantReports(it) - } ?: emptySet() + if (isFromLoggedIn || isFromLoggedInFollow) { + // No need to process if from trusted people + onChange(true, true, persistentSetOf()) + } else { + val newCanPreview = noteReportsState?.note?.hasAnyReports() != true - onChange(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet()) + val newIsAcceptable = noteReportsState?.note?.let { + loggedIn.isAcceptable(it) + } ?: true + + if (newCanPreview && newIsAcceptable) { + // No need to process reports if nothing is wrong + onChange(true, true, persistentSetOf()) + } else { + val newRelevantReports = noteReportsState?.note?.let { + loggedIn.getRelevantReports(it) + } ?: emptySet() + + onChange(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet()) + } + } } } } @@ -400,46 +452,35 @@ fun NormalNote( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val noteEvent = remember { baseNote.event } - - val isChannelHeader by remember { - derivedStateOf { - (baseNote.event is ChannelCreateEvent || baseNote.event is ChannelMetadataEvent) && baseNote.channelHex() != null - } - } - - if (isChannelHeader) { - ChannelHeader( + when (baseNote.event) { + is ChannelCreateEvent, is ChannelMetadataEvent -> ChannelHeader( channelNote = baseNote, showVideo = !makeItShort, showBottomDiviser = true, accountViewModel = accountViewModel, nav = nav ) - } else if (noteEvent is BadgeDefinitionEvent) { - BadgeDisplay(baseNote = baseNote) - } else if (noteEvent is FileHeaderEvent) { - FileHeaderDisplay(baseNote) - } else if (noteEvent is FileStorageHeaderEvent) { - FileStorageHeaderDisplay(baseNote) - } else { - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - CheckNewAndRenderNote( - baseNote, - routeForLastRead, - modifier, - isBoostedNote, - isQuotedNote, - unPackReply, - makeItShort, - addMarginTop, - canPreview, - parentBackgroundColor, - accountViewModel, - showPopup, - nav - ) - } + is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) + is FileHeaderEvent -> FileHeaderDisplay(baseNote) + is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote) + else -> + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + CheckNewAndRenderNote( + baseNote, + routeForLastRead, + modifier, + isBoostedNote, + isQuotedNote, + unPackReply, + makeItShort, + addMarginTop, + canPreview, + parentBackgroundColor, + accountViewModel, + showPopup, + nav + ) + } } } @@ -534,7 +575,7 @@ private fun ClickableNote( ) { val scope = rememberCoroutineScope() - val updatedModifier = remember(backgroundColor.value) { + val updatedModifier = remember(backgroundColor) { modifier .combinedClickable( onClick = { @@ -569,17 +610,8 @@ fun InnerNoteWithReactions( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val notBoostedNorQuote by remember { - derivedStateOf { - !isBoostedNote && !isQuotedNote - } - } - - val showSecondRow by remember { - derivedStateOf { - baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote - } - } + val notBoostedNorQuote = !isBoostedNote && !isQuotedNote + val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote Row( modifier = remember { @@ -593,29 +625,29 @@ fun InnerNoteWithReactions( } ) { if (notBoostedNorQuote) { - AuthorAndRelayInformation(baseNote, accountViewModel, nav) + Column(WidthAuthorPictureModifier) { + AuthorAndRelayInformation(baseNote, accountViewModel, nav) + } Spacer(modifier = DoubleHorzSpacer) } - NoteBody( - baseNote = baseNote, - showAuthorPicture = isQuotedNote, - unPackReply = unPackReply, - makeItShort = makeItShort, - canPreview = canPreview, - showSecondRow = showSecondRow, - backgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav - ) - } - - val isNotRepost by remember { - derivedStateOf { - baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent + Column(Modifier.fillMaxWidth()) { + NoteBody( + baseNote = baseNote, + showAuthorPicture = isQuotedNote, + unPackReply = unPackReply, + makeItShort = makeItShort, + canPreview = canPreview, + showSecondRow = showSecondRow, + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav + ) } } + val isNotRepost = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent + if (isNotRepost) { if (makeItShort) { if (isBoostedNote) { @@ -651,43 +683,41 @@ private fun NoteBody( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - Column(Modifier.fillMaxWidth()) { - FirstUserInfoRow( - baseNote = baseNote, - showAuthorPicture = showAuthorPicture, - accountViewModel = accountViewModel, - nav = nav - ) + FirstUserInfoRow( + baseNote = baseNote, + showAuthorPicture = showAuthorPicture, + accountViewModel = accountViewModel, + nav = nav + ) - if (showSecondRow) { - SecondUserInfoRow( - baseNote, - accountViewModel, - nav - ) - } - - Spacer(modifier = HalfVertSpacer) - - if (!makeItShort) { - ReplyRow( - baseNote, - unPackReply, - backgroundColor, - accountViewModel, - nav - ) - } - - RenderNoteRow( + if (showSecondRow) { + SecondUserInfoRow( baseNote, - backgroundColor, - makeItShort, - canPreview, accountViewModel, nav ) } + + Spacer(modifier = HalfVertSpacer) + + if (!makeItShort) { + ReplyRow( + baseNote, + unPackReply, + backgroundColor, + accountViewModel, + nav + ) + } + + RenderNoteRow( + baseNote, + backgroundColor, + makeItShort, + canPreview, + accountViewModel, + nav + ) } @Composable @@ -1711,37 +1741,39 @@ private fun ReplyRow( } } - val showChannelReply by remember { - derivedStateOf { - (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) || - (noteEvent is LiveActivitiesChatMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) - } - } - if (showReply) { val replyingDirectlyTo = remember { note.replyTo?.lastOrNull() } if (replyingDirectlyTo != null && unPackReply) { ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav) - Spacer(modifier = Modifier.height(5.dp)) + Spacer(modifier = StdVertSpacer) } else { // ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav) } - } else if (showChannelReply) { - val channelHex = note.channelHex() - channelHex?.let { - ChannelHeader( - channelHex = channelHex, - showVideo = false, - showBottomDiviser = false, - modifier = remember { Modifier.padding(vertical = 5.dp) }, - accountViewModel = accountViewModel, - nav = nav - ) + } else { + val showChannelReply by remember { + derivedStateOf { + (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) || + (noteEvent is LiveActivitiesChatMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) + } + } - val replies = remember { note.replyTo?.toImmutableList() } - val mentions = remember { (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf() } + if (showChannelReply) { + val channelHex = note.channelHex() + channelHex?.let { + ChannelHeader( + channelHex = channelHex, + showVideo = false, + showBottomDiviser = false, + modifier = remember { Modifier.padding(vertical = 5.dp) }, + accountViewModel = accountViewModel, + nav = nav + ) - ReplyInformationChannel(replies, mentions, accountViewModel, nav) + val replies = remember { note.replyTo?.toImmutableList() } + val mentions = remember { (note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf() } + + ReplyInformationChannel(replies, mentions, accountViewModel, nav) + } } } } @@ -1759,7 +1791,7 @@ private fun ReplyNoteComposition( val defaultReplyBackground = MaterialTheme.colors.replyBackground LaunchedEffect(key1 = backgroundColor.value, key2 = defaultReplyBackground) { - launch(Dispatchers.IO) { + launch(Dispatchers.Default) { val newReplyBackgroundColor = defaultReplyBackground.compositeOver(backgroundColor.value) if (replyBackgroundColor.value != newReplyBackgroundColor) { @@ -1768,22 +1800,10 @@ private fun ReplyNoteComposition( } } - val borderColor = MaterialTheme.colors.subtleBorder - NoteCompose( baseNote = replyingDirectlyTo, isQuotedNote = true, - modifier = remember { - Modifier - .padding(top = 5.dp) - .fillMaxWidth() - .clip(shape = QuoteBorder) - .border( - 1.dp, - borderColor, - QuoteBorder - ) - }, + modifier = MaterialTheme.colors.replyModifier, unPackReply = false, makeItShort = true, parentBackgroundColor = replyBackgroundColor, @@ -1801,7 +1821,7 @@ private fun SecondUserInfoRow( val noteEvent = remember { note.event } ?: return val noteAuthor = remember { note.author } ?: return - Row(verticalAlignment = Alignment.CenterVertically) { + Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) { ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) }) val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } } @@ -1823,7 +1843,7 @@ private fun FirstUserInfoRow( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth()) { + Row(verticalAlignment = CenterVertically, modifier = remember { UserNameRowHeight }) { val isRepost by remember { derivedStateOf { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent @@ -1869,13 +1889,13 @@ private fun MoreOptionsButton( var moreActionsExpanded by remember { mutableStateOf(false) } IconButton( - modifier = remember { Modifier.size(24.dp) }, + modifier = Size24Modifier, onClick = { moreActionsExpanded = true } ) { Icon( imageVector = Icons.Default.MoreVert, null, - modifier = remember { Modifier.size(15.dp) }, + modifier = Size15Modifier, tint = MaterialTheme.colors.placeholderText ) @@ -1908,14 +1928,12 @@ fun TimeAgo(time: Long) { @Composable private fun AuthorAndRelayInformation(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - Column(WidthAuthorPictureModifier) { - // Draws the boosted picture outside the boosted card. - Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) { - RenderAuthorImages(baseNote, nav, accountViewModel) - } - - BadgeBox(baseNote, accountViewModel, nav) + // Draws the boosted picture outside the boosted card. + Box(modifier = Size55Modifier, contentAlignment = Alignment.BottomEnd) { + RenderAuthorImages(baseNote, nav, accountViewModel) } + + BadgeBox(baseNote, accountViewModel, nav) } @Composable @@ -1950,11 +1968,7 @@ private fun RenderAuthorImages( nav: (String) -> Unit, accountViewModel: AccountViewModel ) { - val isRepost by remember { - derivedStateOf { - baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent - } - } + val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp) @@ -1962,11 +1976,7 @@ private fun RenderAuthorImages( RepostNoteAuthorPicture(baseNote, accountViewModel, nav) } - val isChannel by remember { - derivedStateOf { - baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null - } - } + val isChannel = baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null if (isChannel) { val baseChannelHex = remember { baseNote.channelHex() } @@ -2001,7 +2011,7 @@ fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit) private fun ChannelNotePicture(baseChannel: Channel) { val model by baseChannel.live.map { it.channel.profilePicture() - }.observeAsState() + }.distinctUntilChanged().observeAsState() val backgroundColor = MaterialTheme.colors.background @@ -2879,16 +2889,20 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: mutableStateOf(lazyRelayList.take(3).toImmutableList()) } - WatchRelayLists(baseNote) { relayList -> - if (!equalImmutableLists(relayList, lazyRelayList)) { - lazyRelayList = relayList - shortRelayList = relayList.take(3).toImmutableList() - } + val scope = rememberCoroutineScope() - val nextShowMore = relayList.size > 3 - if (nextShowMore != showShowMore) { - // only triggers recomposition when actually different - showShowMore = nextShowMore + WatchRelayLists(baseNote) { relayList -> + scope.launch(Dispatchers.Main) { + if (!equalImmutableLists(relayList, lazyRelayList)) { + lazyRelayList = relayList + shortRelayList = relayList.take(3).toImmutableList() + } + + val nextShowMore = relayList.size > 3 + if (nextShowMore != showShowMore) { + // only triggers recomposition when actually different + showShowMore = nextShowMore + } } } @@ -2982,12 +2996,14 @@ fun NoteAuthorPicture( ) { val author by baseNote.live().metadata.map { it.note.author - }.observeAsState() + }.distinctUntilChanged().observeAsState(baseNote.author) - if (author == null) { - DisplayBlankAuthor(size, modifier) - } else { - ClickableUserPicture(author!!, size, accountViewModel, modifier, onClick) + Crossfade(targetState = author) { + if (it == null) { + DisplayBlankAuthor(size, modifier) + } else { + ClickableUserPicture(it, size, accountViewModel, modifier, onClick) + } } } @@ -3111,15 +3127,23 @@ fun BaseUserPicture( val userProfile by baseUser.live().metadata.map { it.user.profilePicture() - }.observeAsState() + }.distinctUntilChanged().observeAsState(baseUser.profilePicture()) - PictureAndFollowingMark( - userHex = userPubkey, - userPicture = userProfile, - size = size, - modifier = modifier, - accountViewModel = accountViewModel - ) + val myBoxModifier = remember { + Modifier.size(size) + } + + Crossfade(targetState = userProfile) { + Box(myBoxModifier, contentAlignment = TopEnd) { + PictureAndFollowingMark( + userHex = userPubkey, + userPicture = it, + size = size, + modifier = modifier, + accountViewModel = accountViewModel + ) + } + } } @Composable @@ -3157,12 +3181,16 @@ fun PictureAndFollowingMark( private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) { val showFollowingMark by accountViewModel.userFollows.map { it.user.isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex) - }.observeAsState( + }.distinctUntilChanged().observeAsState( accountViewModel.account.userProfile().isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex) ) - if (showFollowingMark) { - FollowingIcon(iconSize) + Crossfade(targetState = showFollowingMark) { + if (it) { + Box(contentAlignment = TopEnd) { + FollowingIcon(iconSize) + } + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 1fb6d116c..620d6c5af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.Size55dp @Composable fun UserCompose( @@ -38,9 +39,9 @@ fun UserCompose( modifier = overallModifier, 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) { UsernameDisplay(baseUser) } @@ -48,7 +49,7 @@ fun UserCompose( AboutDisplay(baseUser) } - Column(modifier = Modifier.padding(start = 10.dp)) { + Column(modifier = remember { Modifier.padding(start = 10.dp) }) { UserActionOptions(baseUser, accountViewModel) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 70a1b81e8..34c567cb2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -2,6 +2,8 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Context import android.util.Log +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.material.Icon 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.style.TextOverflow import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.tts.TextToSpeechHelper 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.theme.StdButtonSizeModifier 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) { val authorState by baseNote.live().metadata.map { it.note.author - }.distinctUntilChanged().observeAsState() + }.observeAsState(baseNote.author) - authorState?.let { - UsernameDisplay(it, weight) + Crossfade(targetState = authorState, modifier = weight) { + it?.let { + UsernameDisplay(it, weight) + } } } @Composable 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 { derivedStateOf { baseUser.pubkeyDisplayHex() } } - val tags by remember(userState) { - derivedStateOf { - userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() + + 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) } } - - UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, tags, weight) } @Composable @@ -106,16 +102,18 @@ private fun UserDisplay( tags: ImmutableListOfLists?, modifier: Modifier ) { - CreateTextWithEmoji( - text = bestDisplayName, - tags = tags, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = modifier - ) - Spacer(StdHorzSpacer) - DrawPlayName(bestDisplayName) + Row(modifier = modifier) { + CreateTextWithEmoji( + text = bestDisplayName, + tags = tags, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = modifier + ) + Spacer(StdHorzSpacer) + DrawPlayName(bestDisplayName) + } } @Composable @@ -125,22 +123,24 @@ private fun UserAndUsernameDisplay( bestUserName: String, modifier: Modifier ) { - CreateTextWithEmoji( - text = bestDisplayName, - tags = tags, - fontWeight = FontWeight.Bold, - maxLines = 1 - ) - CreateTextWithEmoji( - text = remember { "@$bestUserName" }, - tags = tags, - color = MaterialTheme.colors.placeholderText, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = modifier - ) - Spacer(StdHorzSpacer) - DrawPlayName(bestDisplayName) + Row(modifier = modifier) { + CreateTextWithEmoji( + text = bestDisplayName, + tags = tags, + fontWeight = FontWeight.Bold, + maxLines = 1 + ) + CreateTextWithEmoji( + text = remember { "@$bestUserName" }, + tags = tags, + color = MaterialTheme.colors.placeholderText, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = modifier + ) + Spacer(StdHorzSpacer) + DrawPlayName(bestDisplayName) + } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 82b3f4193..d12a4c411 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -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.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange +import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -106,7 +107,7 @@ private fun RenderZapNote( }, verticalAlignment = Alignment.CenterVertically ) { - UserPicture(baseAuthor, 55.dp, accountViewModel = accountViewModel, nav = nav) + UserPicture(baseAuthor, Size55dp, accountViewModel = accountViewModel, nav = nav) Column( modifier = remember { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 734746736..fb1f482cb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -197,8 +197,7 @@ private fun FeedLoaded( ) { itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> val defaultModifier = remember { - Modifier - .fillMaxWidth().animateItemPlacement() + Modifier.fillMaxWidth().animateItemPlacement() } Row(defaultModifier) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 4f59bc703..789f4bf33 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -586,16 +586,18 @@ fun ChannelHeader( ) } - RobohashAsyncImageProxy( - robot = channel.idHex, - model = channel.profilePicture(), - contentDescription = stringResource(R.string.profile_image), - modifier = Modifier - .width(Size35dp) - .height(Size35dp) - .padding(start = 10.dp) - .clip(shape = CircleShape) - ) + channel.profilePicture()?.let { + RobohashAsyncImageProxy( + robot = channel.idHex, + model = it, + contentDescription = stringResource(R.string.profile_image), + modifier = Modifier + .width(Size35dp) + .height(Size35dp) + .padding(start = 10.dp) + .clip(shape = CircleShape) + ) + } Column( modifier = Modifier diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index bfc3cf89b..e753dfdf7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.theme import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -62,6 +63,7 @@ val StdPadding = Modifier.padding(10.dp) val Size15Modifier = Modifier.size(15.dp) val Size20Modifier = Modifier.size(20.dp) val Size22Modifier = Modifier.size(22.dp) +val Size24Modifier = Modifier.size(24.dp) val Size30Modifier = Modifier.size(30.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 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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 71669c223..a3a6805d1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -121,6 +121,26 @@ val LightImageModifier = Modifier 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( paragraphSpacing = DefaultParagraphSpacing, headingStyle = DefaultHeadingStyle, @@ -243,6 +263,9 @@ val Colors.repostProfileBorder: Modifier val Colors.imageModifier: Modifier get() = if (isLight) LightImageModifier else DarkImageModifier +val Colors.replyModifier: Modifier + get() = if (isLight) LightReplyBorderModifier else DarkReplyBorderModifier + @Composable fun AmethystTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colors = if (darkTheme) {