Minor performance adjustments across the app.

This commit is contained in:
Vitor Pamplona 2023-06-23 12:02:27 -04:00
parent c24ba18207
commit 887fc33073
17 changed files with 807 additions and 565 deletions

View File

@ -33,6 +33,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@ -60,7 +61,7 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.note.ChannelName
import com.vitorpamplona.amethyst.ui.note.UserPicture
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
@ -347,6 +348,10 @@ private fun RenderChannel(
item: com.vitorpamplona.amethyst.model.Channel,
onClick: () -> Unit
) {
val hasNewMessages = remember {
mutableStateOf(false)
}
ChannelName(
channelIdHex = item.idHex,
channelPicture = item.profilePicture(),
@ -358,7 +363,7 @@ private fun RenderChannel(
},
channelLastTime = null,
channelLastContent = item.summary(),
false,
hasNewMessages,
onClick = onClick
)
}
@ -384,7 +389,7 @@ fun UserComposeForChat(
),
verticalAlignment = Alignment.CenterVertically
) {
UserPicture(baseUser, 55.dp, accountViewModel)
ClickableUserPicture(baseUser, 55.dp, accountViewModel)
Column(
modifier = Modifier

View File

@ -13,7 +13,6 @@ import androidx.compose.ui.platform.LocalContext
import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter
import java.util.Date
@Composable
fun RobohashAsyncImage(
@ -48,8 +47,6 @@ fun RobohashAsyncImage(
)
}
var imageErrors = mapOf<String, Long>()
@Composable
fun RobohashFallbackAsyncImage(
robot: String,
@ -62,42 +59,24 @@ fun RobohashFallbackAsyncImage(
colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) {
val errorCache = remember(imageErrors) { imageErrors[model] }
val context = LocalContext.current
val painter = rememberAsyncImagePainter(
model = Robohash.imageRequest(context, robot)
)
if (errorCache != null && (Date().time / 1000) - errorCache < (60 * 5)) {
RobohashAsyncImage(
robot = robot,
contentDescription = contentDescription,
modifier = modifier,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality
)
} else {
val context = LocalContext.current
val painter = rememberAsyncImagePainter(
model = Robohash.imageRequest(context, robot)
)
AsyncImage(
model = model,
contentDescription = contentDescription,
modifier = modifier,
placeholder = painter,
fallback = painter,
error = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality,
onError = {
imageErrors = imageErrors + Pair(model, Date().time / 1000)
}
)
}
AsyncImage(
model = model,
contentDescription = contentDescription,
modifier = modifier,
placeholder = painter,
fallback = painter,
error = painter,
alignment = alignment,
contentScale = contentScale,
alpha = alpha,
colorFilter = colorFilter,
filterQuality = filterQuality
)
}
@Composable

View File

@ -34,9 +34,9 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
val publicChannels = account.followingChannels().map { it ->
it.notes.values
.filter { account.isAcceptable(it) }
.filter { account.isAcceptable(it) && it.event != null }
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.lastOrNull { it.event != null }
.lastOrNull()
}
return (privateMessages + publicChannels)

View File

@ -18,6 +18,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@ -41,6 +42,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@ -62,34 +64,68 @@ fun ChatroomCompose(
nav: (String) -> Unit
) {
val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note
val channelHex by remember(noteState) {
derivedStateOf {
noteState?.note?.channelHex()
}
}
val isBlank = remember(noteState) {
note?.event == null
noteState?.note?.event == null
}
if (isBlank) {
BlankNote(Modifier)
} else if (channelHex != null) {
LoadChannel(baseChannelHex = channelHex!!) { channel ->
ChannelRoomCompose(baseNote, channel, accountViewModel, nav)
}
} else {
val userRoomHex = remember(noteState, accountViewModel) {
(baseNote.event as? PrivateDmEvent)?.talkingWith(accountViewModel.userProfile().pubkeyHex)
} ?: return
ChatroomComposeChannelOrUser(baseNote, accountViewModel, nav)
}
}
LoadUser(userRoomHex) { user ->
@Composable
fun ChatroomComposeChannelOrUser(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val channelHex by remember {
derivedStateOf {
baseNote.channelHex()
}
}
if (channelHex != null) {
ChatroomChannel(channelHex, baseNote, accountViewModel, nav)
} else {
ChatroomDirectMessage(baseNote, accountViewModel, nav)
}
}
@Composable
private fun ChatroomDirectMessage(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val userRoomHex by remember {
derivedStateOf {
(baseNote.event as? PrivateDmEvent)?.talkingWith(accountViewModel.userProfile().pubkeyHex)
}
}
userRoomHex?.let {
LoadUser(it) { user ->
UserRoomCompose(baseNote, user, accountViewModel, nav)
}
}
}
@Composable
private fun ChatroomChannel(
channelHex: HexKey?,
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
LoadChannel(baseChannelHex = channelHex!!) { channel ->
ChannelRoomCompose(baseNote, channel, accountViewModel, nav)
}
}
@Composable
private fun ChannelRoomCompose(
note: Note,
@ -130,11 +166,11 @@ private fun ChannelRoomCompose(
noteEvent?.content()
}
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
var hasNewMessages = remember { mutableStateOf<Boolean>(false) }
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
if (hasNewMessages != newHasNewMessages) {
hasNewMessages = newHasNewMessages
if (hasNewMessages.value != newHasNewMessages) {
hasNewMessages.value = newHasNewMessages
}
}
@ -191,21 +227,21 @@ private fun UserRoomCompose(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
var hasNewMessages = remember { mutableStateOf<Boolean>(false) }
val route = remember(user) {
"Room/${user.pubkeyHex}"
}
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
if (hasNewMessages != newHasNewMessages) {
hasNewMessages = newHasNewMessages
if (hasNewMessages.value != newHasNewMessages) {
hasNewMessages.value = newHasNewMessages
}
}
ChannelName(
channelPicture = {
UserPicture(
NonClickableUserPicture(
baseUser = user,
accountViewModel = accountViewModel,
size = Size55dp
@ -264,7 +300,7 @@ fun ChannelName(
channelTitle: @Composable (Modifier) -> Unit,
channelLastTime: Long?,
channelLastContent: String?,
hasNewMessages: Boolean,
hasNewMessages: MutableState<Boolean>,
onClick: () -> Unit
) {
ChannelName(
@ -295,17 +331,19 @@ fun ChannelName(
channelTitle: @Composable (Modifier) -> Unit,
channelLastTime: Long?,
channelLastContent: String?,
hasNewMessages: Boolean,
hasNewMessages: MutableState<Boolean>,
onClick: () -> Unit
) {
Column(modifier = remember { Modifier.clickable(onClick = onClick) }) {
Row(
modifier = remember { Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp) }
) {
channelPicture()
Column(remember { Modifier.width(Size55dp) }) {
channelPicture()
}
Column(
modifier = remember { Modifier.padding(start = 10.dp) },
modifier = remember { Modifier.padding(start = 10.dp).fillMaxWidth() },
verticalArrangement = Arrangement.SpaceAround
) {
FirstRow(channelTitle, channelLastTime)
@ -321,7 +359,7 @@ fun ChannelName(
}
@Composable
private fun SecondRow(channelLastContent: String?, hasNewMessages: Boolean) {
private fun SecondRow(channelLastContent: String?, hasNewMessages: MutableState<Boolean>) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
@ -333,7 +371,7 @@ private fun SecondRow(channelLastContent: String?, hasNewMessages: Boolean) {
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
modifier = Modifier.weight(1f)
modifier = remember { Modifier.weight(1f) }
)
} else {
Text(
@ -341,11 +379,11 @@ private fun SecondRow(channelLastContent: String?, hasNewMessages: Boolean) {
color = MaterialTheme.colors.grayText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
modifier = remember { Modifier.weight(1f) }
)
}
if (hasNewMessages) {
if (hasNewMessages.value) {
NewItemsBubble()
}
}

View File

@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -24,6 +25,7 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
@ -46,6 +48,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
@ -72,7 +75,9 @@ import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeMe
import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeThem
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
import com.vitorpamplona.amethyst.ui.theme.Size13dp
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size15dp
import com.vitorpamplona.amethyst.ui.theme.Size16dp
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@ -845,9 +850,11 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
@Composable
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val iconUrl = remember(dirtyUrl) {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl/favicon.ico"
val iconUrl by remember(dirtyUrl) {
derivedStateOf {
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
"https://$cleanUrl/favicon.ico"
}
}
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
@ -865,36 +872,48 @@ fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (Stri
val context = LocalContext.current
val scope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = Size15dp)
val clickableModifier = remember(dirtyUrl) {
Modifier
.padding(1.dp)
.size(15.dp)
.clickable(onClick = {
loadRelayInfo(dirtyUrl, context, scope) {
relayInfo = it
.size(Size15dp)
.clickable(
role = Role.Button,
interactionSource = interactionSource,
indication = ripple,
onClick = {
loadRelayInfo(dirtyUrl, context, scope) {
relayInfo = it
}
}
})
}
val backgroundColor = MaterialTheme.colors.background
val iconModifier = remember(dirtyUrl) {
Modifier
.size(13.dp)
.clip(shape = CircleShape)
.drawBehind { drawRect(backgroundColor) }
)
}
Box(
modifier = clickableModifier
) {
RobohashFallbackAsyncImage(
robot = iconUrl,
model = iconUrl,
contentDescription = stringResource(id = R.string.relay_icon),
colorFilter = RelayIconFilter,
modifier = iconModifier
)
RenderRelayIcon(iconUrl)
}
}
@Composable
private fun RenderRelayIcon(iconUrl: String) {
val backgroundColor = MaterialTheme.colors.background
val iconModifier = remember {
Modifier
.size(Size13dp)
.clip(shape = CircleShape)
.drawBehind { drawRect(backgroundColor) }
}
RobohashFallbackAsyncImage(
robot = iconUrl,
model = iconUrl,
contentDescription = stringResource(id = R.string.relay_icon),
colorFilter = RelayIconFilter,
modifier = iconModifier
)
}

View File

@ -516,6 +516,22 @@ fun FastNoteAuthorPicture(
size: Dp,
pictureModifier: Modifier = Modifier,
accountViewModel: AccountViewModel
) {
val myBoxModifier = remember {
Modifier.size(size)
}
Box(myBoxModifier, contentAlignment = Alignment.TopEnd) {
WatchMetadataAndRenderPictureWithFollwingMark(author, size, pictureModifier, accountViewModel)
}
}
@Composable
private fun WatchMetadataAndRenderPictureWithFollwingMark(
author: User,
size: Dp,
pictureModifier: Modifier,
accountViewModel: AccountViewModel
) {
var profilePicture by remember {
mutableStateOf(author.info?.picture)
@ -531,7 +547,7 @@ fun FastNoteAuthorPicture(
}
}
UserPicture(
PictureAndFollowingMark(
userHex = authorPubKey,
userPicture = profilePicture,
size = size,

View File

@ -9,6 +9,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -41,6 +42,7 @@ import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.PushPin
import androidx.compose.material.lightColors
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
@ -69,6 +71,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -79,6 +82,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.map
import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter
import coil.request.SuccessResult
@ -193,14 +197,11 @@ fun NoteCompose(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val noteState by baseNote.live().metadata.observeAsState()
val missingEvent by remember(noteState) {
derivedStateOf {
noteState?.note?.event == null
}
}
val isBlank by baseNote.live().metadata.map {
it.note.event == null
}.observeAsState(true)
if (missingEvent) {
if (isBlank) {
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
BlankNote(
remember {
@ -248,7 +249,6 @@ fun CheckHiddenNoteCompose(
val isHidden by remember(accountState) {
derivedStateOf {
val isSensitive = note.event?.isSensitive() ?: false
accountState?.account?.isHidden(note.author!!) == true || (isSensitive && accountState?.account?.showSensitiveContent == false)
}
}
@ -303,7 +303,7 @@ fun LoadedNoteCompose(
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
}
}
@ -352,7 +352,7 @@ fun LoadedNoteCompose(
fun WatchForReports(
note: Note,
accountViewModel: AccountViewModel,
onChange: (Boolean, Boolean, Set<Note>) -> Unit
onChange: (Boolean, Boolean, ImmutableSet<Note>) -> Unit
) {
val userFollowsState by accountViewModel.userFollows.observeAsState()
val noteReportsState by note.live().reports.observeAsState()
@ -371,7 +371,7 @@ fun WatchForReports(
loggedIn.getRelevantReports(it)
} ?: emptySet()
onChange(newIsAcceptable, newCanPreview, newRelevantReports)
onChange(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
}
}
}
@ -490,47 +490,43 @@ private fun CheckNewAndRenderNote(
}
}
val updatedModifier = remember(backgroundColor.value) {
modifier.drawBehind {
drawRect(backgroundColor.value)
}
}
RenderNoteWithReactions(
modifier = updatedModifier,
backgroundColor = backgroundColor,
ClickableNote(
baseNote = baseNote,
isBoostedNote = isBoostedNote,
isQuotedNote = isQuotedNote,
addMarginTop = addMarginTop,
unPackReply = unPackReply,
makeItShort = makeItShort,
canPreview = canPreview,
backgroundColor = backgroundColor,
modifier = modifier,
accountViewModel = accountViewModel,
showPopup = showPopup,
nav = nav
)
) {
InnerNoteWithReactions(
baseNote = baseNote,
backgroundColor = backgroundColor,
isBoostedNote = isBoostedNote,
isQuotedNote = isQuotedNote,
addMarginTop = addMarginTop,
unPackReply = unPackReply,
makeItShort = makeItShort,
canPreview = canPreview,
accountViewModel = accountViewModel,
nav = nav
)
}
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
private fun RenderNoteWithReactions(
private fun ClickableNote(
baseNote: Note,
backgroundColor: MutableState<Color>,
modifier: Modifier,
isBoostedNote: Boolean,
isQuotedNote: Boolean,
addMarginTop: Boolean,
unPackReply: Boolean,
makeItShort: Boolean,
canPreview: Boolean,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
showPopup: () -> Unit,
nav: (String) -> Unit
nav: (String) -> Unit,
content: @Composable () -> Unit
) {
val scope = rememberCoroutineScope()
val columnModifier = remember(showPopup) {
val updatedModifier = remember(backgroundColor.value) {
modifier
.combinedClickable(
onClick = {
@ -542,8 +538,29 @@ private fun RenderNoteWithReactions(
},
onLongClick = showPopup
)
.drawBehind {
drawRect(backgroundColor.value)
}
}
Column(modifier = updatedModifier) {
content()
}
}
@Composable
fun InnerNoteWithReactions(
baseNote: Note,
backgroundColor: MutableState<Color>,
isBoostedNote: Boolean,
isQuotedNote: Boolean,
addMarginTop: Boolean,
unPackReply: Boolean,
makeItShort: Boolean,
canPreview: Boolean,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val notBoostedNorQuote by remember {
derivedStateOf {
!isBoostedNote && !isQuotedNote
@ -556,57 +573,61 @@ private fun RenderNoteWithReactions(
}
}
Column(modifier = columnModifier) {
Row(
modifier = remember {
Modifier
.padding(
start = if (!isBoostedNote) 12.dp else 0.dp,
end = if (!isBoostedNote) 12.dp else 0.dp,
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
// Don't add margin to the bottom because of the Divider down below
)
}
) {
if (notBoostedNorQuote) {
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
Spacer(modifier = DoubleHorzSpacer)
}
Row(
modifier = remember {
Modifier
.padding(
start = if (!isBoostedNote) 12.dp else 0.dp,
end = if (!isBoostedNote) 12.dp else 0.dp,
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
// Don't add margin to the bottom because of the Divider down below
)
}
) {
if (notBoostedNorQuote) {
AuthorAndRelayInformation(baseNote, accountViewModel, nav)
Spacer(modifier = DoubleHorzSpacer)
}
NoteBody(
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
}
}
if (isNotRepost) {
if (makeItShort) {
if (isBoostedNote) {
} else {
Spacer(modifier = DoubleVertSpacer)
}
} else {
ReactionsRow(
baseNote = baseNote,
showAuthorPicture = isQuotedNote,
unPackReply = unPackReply,
makeItShort = makeItShort,
canPreview = canPreview,
showSecondRow = showSecondRow,
backgroundColor = backgroundColor,
showReactionDetail = notBoostedNorQuote,
accountViewModel = accountViewModel,
nav = nav
)
}
}
val isRepost = remember { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent }
if (!isRepost) {
if (!makeItShort) {
ReactionsRow(
baseNote = baseNote,
showReactionDetail = notBoostedNorQuote,
accountViewModel = accountViewModel,
nav = nav
)
} else {
if (!isBoostedNote) {
Spacer(modifier = DoubleVertSpacer)
}
}
}
if (!isQuotedNote && !isBoostedNote) {
Divider(
thickness = DiviserThickness
)
}
if (notBoostedNorQuote) {
Divider(
thickness = DiviserThickness
)
}
}
@ -622,7 +643,7 @@ private fun NoteBody(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Column() {
Column(Modifier.fillMaxWidth()) {
FirstUserInfoRow(
baseNote = baseNote,
showAuthorPicture = showAuthorPicture,
@ -1410,7 +1431,7 @@ private fun RenderBadgeAward(
},
verticalAlignment = Alignment.CenterVertically
) {
UserPicture(
ClickableUserPicture(
baseUser = user,
accountViewModel = accountViewModel,
size = Size35dp
@ -1794,9 +1815,13 @@ private fun FirstUserInfoRow(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val isRepost = remember { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent }
Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth()) {
val isRepost by remember {
derivedStateOf {
baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
}
}
Row(verticalAlignment = CenterVertically) {
if (showAuthorPicture) {
NoteAuthorPicture(baseNote, nav, accountViewModel, Size25dp)
Spacer(HalfPadding)
@ -1891,7 +1916,11 @@ private fun BadgeBox(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val isRepost = remember { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent }
val isRepost by remember {
derivedStateOf {
baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
}
}
if (isRepost) {
val baseReply by remember {
@ -1913,8 +1942,11 @@ private fun RenderAuthorImages(
nav: (String) -> Unit,
accountViewModel: AccountViewModel
) {
val isRepost = remember { baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent }
val isChannel = remember { baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null }
val isRepost by remember {
derivedStateOf {
baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
}
}
NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp)
@ -1922,6 +1954,12 @@ private fun RenderAuthorImages(
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
}
val isChannel by remember {
derivedStateOf {
baseNote.event is ChannelMessageEvent && baseNote.channelHex() != null
}
}
if (isChannel) {
val baseChannelHex = remember { baseNote.channelHex() }
if (baseChannelHex != null) {
@ -1953,8 +1991,9 @@ fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit)
@Composable
private fun ChannelNotePicture(baseChannel: Channel) {
val channelState by baseChannel.live.observeAsState()
val channel = remember(channelState) { channelState?.channel } ?: return
val model by baseChannel.live.map {
it.channel.profilePicture()
}.observeAsState()
val backgroundColor = MaterialTheme.colors.background
@ -1971,19 +2010,9 @@ private fun ChannelNotePicture(baseChannel: Channel) {
)
}
val boxModifier = remember {
Modifier
.width(30.dp)
.height(30.dp)
}
val model = remember(channelState) {
channel.profilePicture()
}
Box(boxModifier) {
Box(Size30Modifier) {
RobohashAsyncImageProxy(
robot = channel.idHex,
robot = baseChannel.idHex,
model = model,
contentDescription = stringResource(R.string.group_picture),
modifier = modifier
@ -2016,7 +2045,6 @@ private fun RepostNoteAuthorPicture(
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DisplayHighlight(
highlight: String,
@ -2052,7 +2080,6 @@ fun DisplayHighlight(
}
@Composable
@OptIn(ExperimentalLayoutApi::class)
private fun DisplayQuoteAuthor(
authorHex: String,
url: String?,
@ -2101,11 +2128,11 @@ private fun LoadAndDisplayUser(
userBase: User,
nav: (String) -> Unit
) {
val userState by userBase.live().metadata.observeAsState()
val route = remember { "User/${userBase.pubkeyHex}" }
val userState by userBase.live().metadata.observeAsState()
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
val userTags =
remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
if (userDisplayName != null) {
CreateClickableTextWithEmoji(
@ -2141,27 +2168,32 @@ fun DisplayFollowingHashtagsInPost(
}
}
Column() {
Row(verticalAlignment = Alignment.CenterVertically) {
firstTag?.let {
val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") }
val route = remember(firstTag) { "Hashtag/$firstTag" }
ClickableText(
text = displayTag,
onClick = { nav(route) },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
),
maxLines = 1
)
firstTag?.let {
Column() {
Row(verticalAlignment = CenterVertically) {
DisplayTagList(it, nav)
}
}
}
}
@Composable
private fun DisplayTagList(firstTag: String, nav: (String) -> Unit) {
val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") }
val route = remember(firstTag) { "Hashtag/$firstTag" }
ClickableText(
text = displayTag,
onClick = { nav(route) },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
),
maxLines = 1
)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DisplayUncitedHashtags(
@ -2541,7 +2573,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, accountViewModel: AccountViewMo
nav("User/${it.second.pubkeyHex}")
}
) {
UserPicture(it.second, 25.dp, accountViewModel)
ClickableUserPicture(it.second, 25.dp, accountViewModel)
Spacer(Modifier.width(5.dp))
UsernameDisplay(it.second, Modifier.weight(1f))
Spacer(Modifier.width(5.dp))
@ -2622,7 +2654,7 @@ fun RenderLiveActivityEvent(baseNote: Note, accountViewModel: AccountViewModel,
nav("User/${it.second.pubkeyHex}")
}
) {
UserPicture(it.second, 25.dp, accountViewModel)
ClickableUserPicture(it.second, 25.dp, accountViewModel)
Spacer(Modifier.width(5.dp))
UsernameDisplay(it.second, Modifier.weight(1f))
Spacer(Modifier.width(5.dp))
@ -2753,12 +2785,20 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
var expanded by remember { mutableStateOf(false) }
var showShowMore by remember { mutableStateOf(false) }
var lazyRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
var shortRelayList by remember { mutableStateOf<ImmutableList<String>>(persistentListOf()) }
var lazyRelayList by remember {
val baseNumber = baseNote.relays.map {
it.removePrefix("wss://").removePrefix("ws://")
}.toImmutableList()
mutableStateOf(baseNumber)
}
var shortRelayList by remember {
mutableStateOf(lazyRelayList.take(3).toImmutableList())
}
WatchRelayLists(baseNote) { relayList ->
if (!equalImmutableLists(relayList, lazyRelayList)) {
lazyRelayList = relayList.toImmutableList()
lazyRelayList = relayList
shortRelayList = relayList.take(3).toImmutableList()
}
@ -2817,7 +2857,9 @@ private fun VerticalRelayPanelWithFlow(
val showMoreRelaysButtonIconButtonModifier = Modifier.size(24.dp)
val showMoreRelaysButtonIconModifier = Modifier.size(15.dp)
val showMoreRelaysButtonBoxModifer = Modifier.fillMaxWidth().height(25.dp)
val showMoreRelaysButtonBoxModifer = Modifier
.fillMaxWidth()
.height(25.dp)
@Composable
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
@ -2861,33 +2903,35 @@ fun NoteAuthorPicture(
modifier: Modifier = Modifier,
onClick: ((User) -> Unit)? = null
) {
val noteState by baseNote.live().metadata.observeAsState()
val author by remember(noteState) {
derivedStateOf {
noteState?.note?.author
}
}
val author by baseNote.live().metadata.map {
it.note.author
}.observeAsState()
if (author == null) {
val backgroundColor = MaterialTheme.colors.background
val nullModifier = remember {
modifier
.size(size)
.clip(shape = CircleShape)
.drawBehind { drawRect(backgroundColor) }
}
RobohashAsyncImage(
robot = "authornotfound",
contentDescription = stringResource(R.string.unknown_author),
modifier = nullModifier
)
DisplayBlankAuthor(size, modifier)
} else {
UserPicture(author!!, size, accountViewModel, modifier, onClick)
ClickableUserPicture(author!!, size, accountViewModel, modifier, onClick)
}
}
@Composable
private fun DisplayBlankAuthor(size: Dp, modifier: Modifier) {
val backgroundColor = MaterialTheme.colors.background
val nullModifier = remember {
modifier
.size(size)
.clip(shape = CircleShape)
.drawBehind { drawRect(backgroundColor) }
}
RobohashAsyncImage(
robot = "authornotfound",
contentDescription = stringResource(R.string.unknown_author),
modifier = nullModifier
)
}
@Composable
fun UserPicture(
user: User,
@ -2902,14 +2946,14 @@ fun UserPicture(
}
}
UserPicture(user, size, accountViewModel, pictureModifier) {
ClickableUserPicture(user, size, accountViewModel, pictureModifier) {
nav(route)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun UserPicture(
fun ClickableUserPicture(
baseUser: User,
size: Dp,
accountViewModel: AccountViewModel,
@ -2917,57 +2961,89 @@ fun UserPicture(
onClick: ((User) -> Unit)? = null,
onLongClick: ((User) -> Unit)? = null
) {
val userState by baseUser.live().metadata.observeAsState()
val userPubkey = remember {
baseUser.pubkeyHex
}
val userProfile by remember(userState) {
derivedStateOf {
userState?.user?.profilePicture()
}
}
val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = size)
// BaseUser is the same reference as accountState.user
val myModifier = remember {
if (onClick != null && onLongClick != null) {
Modifier.combinedClickable(
onClick = { onClick(baseUser) },
onLongClick = { onLongClick(baseUser) }
)
} else if (onClick != null) {
Modifier.clickable(onClick = { onClick(baseUser) })
} else {
Modifier
.size(size)
.combinedClickable(
onClick = { onClick(baseUser) },
onLongClick = { onLongClick(baseUser) },
role = Role.Button,
interactionSource = interactionSource,
indication = ripple
)
} else if (onClick != null) {
Modifier
.size(size)
.clickable(
onClick = { onClick(baseUser) },
role = Role.Button,
interactionSource = interactionSource,
indication = ripple
)
} else {
Modifier.size(size)
}
}
Row(modifier = myModifier) {
UserPicture(
userHex = userPubkey,
userPicture = userProfile,
size = size,
modifier = modifier,
accountViewModel = accountViewModel
)
Box(modifier = myModifier, contentAlignment = TopEnd) {
UserPicture(baseUser, size, accountViewModel, modifier)
}
}
@Composable
fun NonClickableUserPicture(
baseUser: User,
size: Dp,
accountViewModel: AccountViewModel,
modifier: Modifier = remember { Modifier }
) {
val myBoxModifier = remember {
Modifier.size(size)
}
Box(myBoxModifier, contentAlignment = TopEnd) {
UserPicture(baseUser, size, accountViewModel, modifier)
}
}
@Composable
fun UserPicture(
baseUser: User,
size: Dp,
accountViewModel: AccountViewModel,
modifier: Modifier = remember { Modifier }
) {
val userPubkey = remember {
baseUser.pubkeyHex
}
val userProfile by baseUser.live().metadata.map {
it.user.profilePicture()
}.observeAsState()
PictureAndFollowingMark(
userHex = userPubkey,
userPicture = userProfile,
size = size,
modifier = modifier,
accountViewModel = accountViewModel
)
}
@Composable
fun PictureAndFollowingMark(
userHex: String,
userPicture: String?,
size: Dp,
modifier: Modifier = Modifier,
modifier: Modifier,
accountViewModel: AccountViewModel
) {
val backgroundColor = MaterialTheme.colors.background
val myBoxModifier = remember {
Modifier.size(size)
}
val myImageModifier = remember {
modifier
.size(size)
@ -2975,18 +3051,19 @@ fun UserPicture(
.drawBehind { drawRect(backgroundColor) }
}
val myIconSize = remember(size) { size.div(3.5f) }
RobohashAsyncImageProxy(
robot = userHex,
model = userPicture,
contentDescription = stringResource(id = R.string.profile_image),
modifier = myImageModifier
)
Box(myBoxModifier, contentAlignment = TopEnd) {
RobohashAsyncImageProxy(
robot = userHex,
model = userPicture,
contentDescription = stringResource(id = R.string.profile_image),
modifier = myImageModifier
)
ObserveAndDisplayFollowingMark(userHex, myIconSize, accountViewModel)
val myIconSize by remember(size) {
derivedStateOf {
size.div(3.5f)
}
}
ObserveAndDisplayFollowingMark(userHex, myIconSize, accountViewModel)
}
@Composable

View File

@ -36,11 +36,10 @@ import androidx.compose.material.icons.filled.PersonRemove
import androidx.compose.material.icons.filled.Report
import androidx.compose.material.icons.filled.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@ -112,8 +111,8 @@ private fun VerticalDivider(color: Color) =
@Composable
fun LongPressToQuickAction(baseNote: Note, accountViewModel: AccountViewModel, content: @Composable (() -> Unit) -> Unit) {
var popupExpanded = remember { mutableStateOf(false) }
val showPopup = remember(popupExpanded) { { popupExpanded.value = true } }
val popupExpanded = remember { mutableStateOf(false) }
val showPopup = remember { { popupExpanded.value = true } }
content(showPopup)
@ -122,192 +121,241 @@ fun LongPressToQuickAction(baseNote: Note, accountViewModel: AccountViewModel, c
@Composable
fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
var showSelectTextDialog by remember(note) { mutableStateOf(false) }
var showDeleteAlertDialog by remember(note) { mutableStateOf(false) }
var showBlockAlertDialog by remember(note) { mutableStateOf(false) }
var showReportDialog by remember(note) { mutableStateOf(false) }
val showSelectTextDialog = remember { mutableStateOf(false) }
val showDeleteAlertDialog = remember { mutableStateOf(false) }
val showBlockAlertDialog = remember { mutableStateOf(false) }
val showReportDialog = remember { mutableStateOf(false) }
if (popupExpanded) {
val context = LocalContext.current
val primaryLight = lightenColor(MaterialTheme.colors.primary, 0.1f)
val cardShape = RoundedCornerShape(5.dp)
val clipboardManager = LocalClipboardManager.current
val scope = rememberCoroutineScope()
RenderMainPopup(
accountViewModel,
note,
onDismiss,
showBlockAlertDialog,
showDeleteAlertDialog,
showReportDialog
)
}
val backgroundColor = if (MaterialTheme.colors.isLight) {
MaterialTheme.colors.primary
} else {
MaterialTheme.colors.secondaryButtonBackground
if (showSelectTextDialog.value) {
accountViewModel.decrypt(note)?.let {
SelectTextDialog(it) { showSelectTextDialog.value = false }
}
}
val showToast = { stringResource: Int ->
scope.launch {
Toast.makeText(
context,
context.getString(stringResource),
Toast.LENGTH_SHORT
).show()
}
if (showDeleteAlertDialog.value) {
DeleteAlertDialog(note, accountViewModel) {
showDeleteAlertDialog.value = false
onDismiss()
}
}
val isOwnNote = accountViewModel.isLoggedUser(note.author)
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
if (showBlockAlertDialog.value) {
BlockAlertDialog(note, accountViewModel) {
showBlockAlertDialog.value = false
onDismiss()
}
}
Popup(onDismissRequest = onDismiss, alignment = Alignment.Center) {
Card(
modifier = Modifier.shadow(elevation = 6.dp, shape = cardShape),
shape = cardShape,
backgroundColor = backgroundColor
) {
Column(modifier = Modifier.width(IntrinsicSize.Min)) {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
NoteQuickActionItem(
icon = Icons.Default.ContentCopy,
label = stringResource(R.string.quick_action_copy_text)
) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(
AnnotatedString(
accountViewModel.decrypt(note) ?: ""
)
if (showReportDialog.value) {
ReportNoteDialog(note, accountViewModel) {
showReportDialog.value = false
onDismiss()
}
}
}
@Composable
private fun RenderMainPopup(
accountViewModel: AccountViewModel,
note: Note,
onDismiss: () -> Unit,
showBlockAlertDialog: MutableState<Boolean>,
showDeleteAlertDialog: MutableState<Boolean>,
showReportDialog: MutableState<Boolean>
) {
val context = LocalContext.current
val primaryLight = lightenColor(MaterialTheme.colors.primary, 0.1f)
val cardShape = RoundedCornerShape(5.dp)
val clipboardManager = LocalClipboardManager.current
val scope = rememberCoroutineScope()
val backgroundColor = if (MaterialTheme.colors.isLight) {
MaterialTheme.colors.primary
} else {
MaterialTheme.colors.secondaryButtonBackground
}
val showToast = { stringResource: Int ->
scope.launch {
Toast.makeText(
context,
context.getString(stringResource),
Toast.LENGTH_SHORT
).show()
}
}
val isOwnNote = accountViewModel.isLoggedUser(note.author)
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
Popup(onDismissRequest = onDismiss, alignment = Alignment.Center) {
Card(
modifier = Modifier.shadow(elevation = 6.dp, shape = cardShape),
shape = cardShape,
backgroundColor = backgroundColor
) {
Column(modifier = Modifier.width(IntrinsicSize.Min)) {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
NoteQuickActionItem(
icon = Icons.Default.ContentCopy,
label = stringResource(R.string.quick_action_copy_text)
) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(
AnnotatedString(
accountViewModel.decrypt(note) ?: ""
)
showToast(R.string.copied_note_text_to_clipboard)
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(Icons.Default.AlternateEmail, stringResource(R.string.quick_action_copy_user_id)) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
showToast(R.string.copied_user_id_to_clipboard)
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(Icons.Default.FormatQuote, stringResource(R.string.quick_action_copy_note_id)) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(AnnotatedString("nostr:${note.toNEvent()}"))
showToast(R.string.copied_note_id_to_clipboard)
onDismiss()
}
}
if (!isOwnNote) {
VerticalDivider(primaryLight)
NoteQuickActionItem(Icons.Default.Block, stringResource(R.string.quick_action_block)) {
if (accountViewModel.hideBlockAlertDialog) {
scope.launch(Dispatchers.IO) {
note.author?.let { accountViewModel.hide(it) }
onDismiss()
}
} else {
showBlockAlertDialog = true
}
}
}
}
Divider(
color = primaryLight,
modifier = Modifier
.fillMaxWidth()
.width(1.dp)
)
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
if (isOwnNote) {
NoteQuickActionItem(Icons.Default.Delete, stringResource(R.string.quick_action_delete)) {
if (accountViewModel.hideDeleteRequestDialog) {
scope.launch(Dispatchers.IO) {
accountViewModel.delete(note)
onDismiss()
}
} else {
showDeleteAlertDialog = true
}
}
} else if (isFollowingUser) {
NoteQuickActionItem(Icons.Default.PersonRemove, stringResource(R.string.quick_action_unfollow)) {
scope.launch(Dispatchers.IO) {
accountViewModel.unfollow(note.author!!)
onDismiss()
}
}
} else {
NoteQuickActionItem(Icons.Default.PersonAdd, stringResource(R.string.quick_action_follow)) {
scope.launch(Dispatchers.IO) {
accountViewModel.follow(note.author!!)
onDismiss()
}
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(
icon = ImageVector.vectorResource(id = R.drawable.relays),
label = stringResource(R.string.broadcast)
) {
scope.launch(Dispatchers.IO) {
accountViewModel.broadcast(note)
// showSelectTextDialog = true
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(icon = Icons.Default.Share, label = stringResource(R.string.quick_action_share)) {
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(
Intent.EXTRA_TEXT,
externalLinkForNote(note)
)
putExtra(Intent.EXTRA_TITLE, context.getString(R.string.quick_action_share_browser_link))
}
val shareIntent = Intent.createChooser(sendIntent, context.getString(R.string.quick_action_share))
ContextCompat.startActivity(context, shareIntent, null)
)
showToast(R.string.copied_note_text_to_clipboard)
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(
Icons.Default.AlternateEmail,
stringResource(R.string.quick_action_copy_user_id)
) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
showToast(R.string.copied_user_id_to_clipboard)
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(
Icons.Default.FormatQuote,
stringResource(R.string.quick_action_copy_note_id)
) {
scope.launch(Dispatchers.IO) {
clipboardManager.setText(AnnotatedString("nostr:${note.toNEvent()}"))
showToast(R.string.copied_note_id_to_clipboard)
onDismiss()
}
}
if (!isOwnNote) {
VerticalDivider(primaryLight)
if (!isOwnNote) {
VerticalDivider(primaryLight)
NoteQuickActionItem(Icons.Default.Report, stringResource(R.string.quick_action_report)) {
showReportDialog = true
NoteQuickActionItem(
Icons.Default.Block,
stringResource(R.string.quick_action_block)
) {
if (accountViewModel.hideBlockAlertDialog) {
scope.launch(Dispatchers.IO) {
note.author?.let { accountViewModel.hide(it) }
onDismiss()
}
} else {
showBlockAlertDialog.value = true
}
}
}
}
Divider(
color = primaryLight,
modifier = Modifier
.fillMaxWidth()
.width(1.dp)
)
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
if (isOwnNote) {
NoteQuickActionItem(
Icons.Default.Delete,
stringResource(R.string.quick_action_delete)
) {
if (accountViewModel.hideDeleteRequestDialog) {
scope.launch(Dispatchers.IO) {
accountViewModel.delete(note)
onDismiss()
}
} else {
showDeleteAlertDialog.value = true
}
}
} else if (isFollowingUser) {
NoteQuickActionItem(
Icons.Default.PersonRemove,
stringResource(R.string.quick_action_unfollow)
) {
scope.launch(Dispatchers.IO) {
accountViewModel.unfollow(note.author!!)
onDismiss()
}
}
} else {
NoteQuickActionItem(
Icons.Default.PersonAdd,
stringResource(R.string.quick_action_follow)
) {
scope.launch(Dispatchers.IO) {
accountViewModel.follow(note.author!!)
onDismiss()
}
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(
icon = ImageVector.vectorResource(id = R.drawable.relays),
label = stringResource(R.string.broadcast)
) {
scope.launch(Dispatchers.IO) {
accountViewModel.broadcast(note)
// showSelectTextDialog = true
onDismiss()
}
}
VerticalDivider(primaryLight)
NoteQuickActionItem(
icon = Icons.Default.Share,
label = stringResource(R.string.quick_action_share)
) {
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(
Intent.EXTRA_TEXT,
externalLinkForNote(note)
)
putExtra(
Intent.EXTRA_TITLE,
context.getString(R.string.quick_action_share_browser_link)
)
}
val shareIntent = Intent.createChooser(
sendIntent,
context.getString(R.string.quick_action_share)
)
ContextCompat.startActivity(context, shareIntent, null)
onDismiss()
}
if (!isOwnNote) {
VerticalDivider(primaryLight)
NoteQuickActionItem(
Icons.Default.Report,
stringResource(R.string.quick_action_report)
) {
showReportDialog.value = true
}
}
}
}
}
}
if (showSelectTextDialog) {
accountViewModel.decrypt(note)?.let {
SelectTextDialog(it) { showSelectTextDialog = false }
}
}
if (showDeleteAlertDialog) {
DeleteAlertDialog(note, accountViewModel) {
showDeleteAlertDialog = false
onDismiss()
}
}
if (showBlockAlertDialog) {
BlockAlertDialog(note, accountViewModel) {
showBlockAlertDialog = false
onDismiss()
}
}
if (showReportDialog) {
ReportNoteDialog(note, accountViewModel) {
showReportDialog = false
onDismiss()
}
}
}
@Composable

View File

@ -2,7 +2,12 @@ package com.vitorpamplona.amethyst.ui.note
import android.content.Context
import android.widget.Toast
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.with
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -13,7 +18,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -60,6 +64,8 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
@ -74,19 +80,24 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DarkerGreen
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
import com.vitorpamplona.amethyst.ui.theme.Height4dpModifier
import com.vitorpamplona.amethyst.ui.theme.NoSoTinyBorders
import com.vitorpamplona.amethyst.ui.theme.ReactionRowExpandButton
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeight
import com.vitorpamplona.amethyst.ui.theme.ReactionRowZapraiserSize
import com.vitorpamplona.amethyst.ui.theme.Size18dp
import com.vitorpamplona.amethyst.ui.theme.Size0dp
import com.vitorpamplona.amethyst.ui.theme.Size17dp
import com.vitorpamplona.amethyst.ui.theme.Size19dp
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size22Modifier
import com.vitorpamplona.amethyst.ui.theme.Size24dp
import com.vitorpamplona.amethyst.ui.theme.Size75dp
import com.vitorpamplona.amethyst.ui.theme.TinyBorders
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter
import com.vitorpamplona.amethyst.ui.theme.subtleButton
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
@ -136,10 +147,13 @@ private fun InnerReactionRow(
modifier = ReactionRowExpandButton
) {
Row(verticalAlignment = CenterVertically) {
ExpandButton(baseNote, wantsToSeeReactions)
ExpandButton(baseNote) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
}
}
}
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
@ -177,7 +191,11 @@ private fun InnerReactionRow(
modifier = remember { Modifier.weight(1f) }
) {
Row(verticalAlignment = CenterVertically) {
ViewCountReaction(baseNote.idHex, MaterialTheme.colors.placeholderText)
ViewCountReaction(
note = baseNote,
grayTint = MaterialTheme.colors.placeholderText,
viewCountColorFilter = MaterialTheme.colors.placeholderTextColorFilter
)
}
}
}
@ -197,13 +215,12 @@ private fun LoadAndDisplayZapraiser(
}
if (zapraiserAmount > 0) {
Spacer(modifier = remember { Modifier.height(4.dp) })
Spacer(modifier = Height4dpModifier)
Row(
verticalAlignment = CenterVertically,
modifier = remember {
Modifier
.fillMaxWidth()
.padding(start = if (showReactionDetail) 75.dp else 0.dp)
ReactionRowZapraiserSize
.padding(start = if (showReactionDetail) Size75dp else Size0dp)
},
horizontalArrangement = Arrangement.Start
) {
@ -253,37 +270,35 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
MaterialTheme.colors.mediumImportanceLink
}
Box(ReactionRowZapraiserSize) {
LinearProgressIndicator(
modifier = ReactionRowZapraiserSize,
color = color,
progress = zapraiserProgress
)
LinearProgressIndicator(
modifier = ReactionRowZapraiserSize,
color = color,
progress = zapraiserProgress
)
if (details) {
Row(
verticalAlignment = CenterVertically,
modifier = TinyBorders
) {
val totalPercentage by remember(zapraiserProgress) {
derivedStateOf {
"${(zapraiserProgress * 100).roundToInt()}%"
}
if (details) {
Box(
contentAlignment = Center,
modifier = TinyBorders
) {
val totalPercentage by remember(zapraiserProgress) {
derivedStateOf {
"${(zapraiserProgress * 100).roundToInt()}%"
}
Text(
text = stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserLeft),
modifier = NoSoTinyBorders,
color = MaterialTheme.colors.placeholderText,
fontSize = Font14SP
)
}
Text(
text = stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserLeft),
modifier = NoSoTinyBorders,
color = MaterialTheme.colors.placeholderText,
fontSize = Font14SP
)
}
}
}
@Composable
private fun ExpandButton(baseNote: Note, wantsToSeeReactions: MutableState<Boolean>) {
private fun ExpandButton(baseNote: Note, content: @Composable () -> Unit) {
val zapsState by baseNote.live().zaps.observeAsState()
val boostsState by baseNote.live().boosts.observeAsState()
val reactionsState by baseNote.live().reactions.observeAsState()
@ -297,7 +312,7 @@ private fun ExpandButton(baseNote: Note, wantsToSeeReactions: MutableState<Boole
}
if (hasReactions) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
content()
}
}
@ -440,7 +455,7 @@ fun ReplyReaction(
grayTint: Color,
accountViewModel: AccountViewModel,
showCounter: Boolean = true,
iconSize: Dp = Size18dp,
iconSize: Dp = Size17dp,
onPress: () -> Unit
) {
val context = LocalContext.current
@ -485,18 +500,55 @@ fun ReplyReaction(
@Composable
fun ReplyCounter(baseNote: Note, textColor: Color) {
val repliesState by baseNote.live().replies.observeAsState()
val replyCount by remember(repliesState) {
derivedStateOf {
" " + showCount(repliesState?.note?.replies?.size)
}
}
val repliesState by baseNote.live().replies.map {
it.note.replies.size
}.distinctUntilChanged().observeAsState()
Text(
text = replyCount,
fontSize = Font14SP,
color = textColor
)
SlidingAnimation(repliesState ?: 0, textColor)
}
@Composable
@OptIn(ExperimentalAnimationApi::class)
private fun SlidingAnimation(count: Int, textColor: Color) {
AnimatedContent<Int>(
targetState = count,
transitionSpec = {
if (targetState > initialState) {
slideInVertically { -it } with slideOutVertically { it }
} else {
slideInVertically { it } with slideOutVertically { -it }
}
}
) { count ->
Text(
text = showCount(count),
fontSize = Font14SP,
color = textColor,
modifier = HalfStartPadding
)
}
}
@Composable
@OptIn(ExperimentalAnimationApi::class)
private fun SlidingAnimation(amount: String, textColor: Color) {
AnimatedContent<String>(
targetState = amount,
transitionSpec = {
if (targetState > initialState) {
slideInVertically { -it } with slideOutVertically { it }
} else {
slideInVertically { it } with slideOutVertically { -it }
}
}
) { count ->
Text(
text = amount,
fontSize = Font14SP,
color = textColor,
modifier = HalfStartPadding
)
}
}
@Composable
@ -560,13 +612,9 @@ fun BoostReaction(
@Composable
fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountViewModel: AccountViewModel) {
val boostsState by baseNote.live().boosts.observeAsState()
val iconTint by remember(boostsState) {
derivedStateOf {
if (boostsState?.note?.isBoostedBy(accountViewModel.userProfile()) == true) Color.Unspecified else grayTint
}
}
val iconTint by baseNote.live().boosts.map {
if (it.note.isBoostedBy(accountViewModel.userProfile())) Color.Unspecified else grayTint
}.distinctUntilChanged().observeAsState(grayTint)
val iconModifier = remember {
Modifier.size(iconSize)
@ -582,18 +630,11 @@ fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountV
@Composable
fun BoostText(baseNote: Note, grayTint: Color) {
val boostsState by baseNote.live().boosts.observeAsState()
val boostCount by remember(boostsState) {
derivedStateOf {
" " + showCount(boostsState?.note?.boosts?.size)
}
}
val boostState by baseNote.live().boosts.map {
it.note.boosts.size
}.distinctUntilChanged().observeAsState(0)
Text(
text = boostCount,
fontSize = Font14SP,
color = grayTint
)
SlidingAnimation(boostState, grayTint)
}
@OptIn(ExperimentalFoundationApi::class)
@ -738,24 +779,20 @@ private fun RenderReactionType(
fun LikeText(baseNote: Note, grayTint: Color) {
val reactionsState by baseNote.live().reactions.observeAsState()
var reactionsCount by remember(reactionsState) {
mutableStateOf("")
var reactionsCount by remember(baseNote) {
mutableStateOf(0)
}
LaunchedEffect(key1 = reactionsState) {
launch(Dispatchers.Default) {
val newReactionsCount = " " + showCount(reactionsState?.note?.countReactions())
val newReactionsCount = reactionsState?.note?.countReactions() ?: 0
if (reactionsCount != newReactionsCount) {
reactionsCount = newReactionsCount
}
}
}
Text(
text = reactionsCount,
fontSize = Font14SP,
color = grayTint
)
SlidingAnimation(reactionsCount, grayTint)
}
private fun likeClick(
@ -1024,22 +1061,19 @@ private fun ZapAmountText(
}
}
Text(
text = zapAmountTxt,
fontSize = Font14SP,
color = grayTint
)
SlidingAnimation(zapAmountTxt, grayTint)
}
@Composable
fun ViewCountReaction(
idHex: String,
note: Note,
grayTint: Color,
barChartSize: Dp = Size19dp,
numberSize: Dp = Size24dp
numberSize: Dp = Size24dp,
viewCountColorFilter: ColorFilter
) {
DrawViewCountIcon(barChartSize, grayTint)
DrawViewCount(idHex, numberSize, grayTint)
DrawViewCount(note, numberSize, viewCountColorFilter)
}
@Composable
@ -1061,9 +1095,9 @@ private fun DrawViewCountIcon(
@Composable
private fun DrawViewCount(
idHex: String,
note: Note,
numberSize: Dp = Size24dp,
grayTint: Color
viewCountColorFilter: ColorFilter
) {
val context = LocalContext.current
@ -1071,21 +1105,17 @@ private fun DrawViewCount(
Modifier.height(numberSize)
}
val colorFilter = remember {
ColorFilter.tint(grayTint)
}
AsyncImage(
model = remember(idHex) {
model = remember(note) {
ImageRequest.Builder(context)
.data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000")
.data("https://counter.amethyst.social/${note.idHex}.svg?label=+&color=00000000")
.diskCachePolicy(CachePolicy.DISABLED)
.memoryCachePolicy(CachePolicy.ENABLED)
.build()
},
contentDescription = stringResource(R.string.view_count),
modifier = iconModifier,
colorFilter = colorFilter
colorFilter = viewCountColorFilter
)
}

View File

@ -20,6 +20,8 @@ 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
@ -32,12 +34,13 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
@Composable
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
val noteState by baseNote.live().metadata.observeAsState()
val author = remember(noteState) {
noteState?.note?.author
} ?: return
val authorState by baseNote.live().metadata.map {
it.note.author
}.distinctUntilChanged().observeAsState()
UsernameDisplay(author, weight)
authorState?.let {
UsernameDisplay(it, weight)
}
}
@Composable

View File

@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
@ -198,7 +198,7 @@ fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, nav: (Str
) {
Column(modifier = Modifier.padding(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
UserPicture(
ClickableUserPicture(
baseUser = baseUser,
accountViewModel = accountViewModel,
size = Size35dp

View File

@ -195,10 +195,12 @@ private fun FeedLoaded(
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
CheckIfOnline(item) {
ChannelHeader(
channelHex = item.idHex,
channelHex = remember { item.idHex },
showVideo = false,
showBottomDiviser = true,
modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 5.dp, bottom = 5.dp),
modifier = remember {
Modifier.padding(start = 10.dp, end = 10.dp, top = 5.dp, bottom = 5.dp)
},
accountViewModel = accountViewModel,
nav = nav
)

View File

@ -81,7 +81,7 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.screen.FeedState
import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView
import com.vitorpamplona.amethyst.ui.screen.NostrUserAppRecommendationsFeedViewModel
@ -511,7 +511,7 @@ private fun ProfileHeader(
) {
val clipboardManager = LocalClipboardManager.current
UserPicture(
ClickableUserPicture(
baseUser = baseUser,
accountViewModel = accountViewModel,
size = 100.dp,

View File

@ -62,9 +62,9 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.AboutDisplay
import com.vitorpamplona.amethyst.ui.note.ChannelName
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
@ -366,6 +366,10 @@ private fun DisplaySearchResults(
val channels by searchBarViewModel.searchResultsChannels.collectAsState()
val notes by searchBarViewModel.searchResultsNotes.collectAsState()
val hasNewMessages = remember {
mutableStateOf(false)
}
LazyColumn(
modifier = Modifier.fillMaxHeight(),
contentPadding = PaddingValues(
@ -405,7 +409,7 @@ private fun DisplaySearchResults(
},
channelLastTime = null,
channelLastContent = item.summary(),
false,
hasNewMessages = hasNewMessages,
onClick = { nav("Channel/${item.idHex}") }
)
}
@ -493,7 +497,7 @@ fun UserLine(
top = 10.dp
)
) {
UserPicture(baseUser, 55.dp, accountViewModel, Modifier, null)
ClickableUserPicture(baseUser, 55.dp, accountViewModel, Modifier, null)
Column(
modifier = Modifier

View File

@ -88,6 +88,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.onBackgroundColorFilter
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
@ -397,7 +398,7 @@ fun ReactionsColumn(baseNote: Note, accountViewModel: AccountViewModel, nav: (St
}*/
LikeReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, accountViewModel, iconSize = 40.dp, heartSize = Size35dp, 28.sp)
ZapReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, accountViewModel, iconSize = 40.dp, animationSize = Size35dp)
ViewCountReaction(baseNote.idHex, grayTint = MaterialTheme.colors.onBackground, barChartSize = 39.dp)
ViewCountReaction(baseNote, grayTint = MaterialTheme.colors.onBackground, barChartSize = 39.dp, viewCountColorFilter = MaterialTheme.colors.onBackgroundColorFilter)
}
}

View File

@ -36,8 +36,11 @@ val DoubleVertSpacer = Modifier.height(10.dp)
val HalfDoubleVertSpacer = Modifier.height(7.dp)
val Size0dp = 0.dp
val Size13dp = 13.dp
val Size15dp = 15.dp
val Size16dp = 16.dp
val Size17dp = 17.dp
val Size18dp = 18.dp
val Size19dp = 19.dp
val Size20dp = 20.dp
@ -46,7 +49,9 @@ val Size25dp = 25.dp
val Size30dp = 30.dp
val Size35dp = 35.dp
val Size55dp = 55.dp
val Size75dp = 75.dp
val HalfStartPadding = Modifier.padding(start = 5.dp)
val StdStartPadding = Modifier.padding(start = 10.dp)
val HalfPadding = Modifier.padding(5.dp)
@ -67,4 +72,6 @@ val WidthAuthorPictureModifier = Modifier.width(55.dp)
val DiviserThickness = 0.25.dp
val ReactionRowHeight = Modifier.padding(start = 10.dp)
val ReactionRowHeight = Modifier.height(24.dp).padding(start = 10.dp)
val Height4dpModifier = Modifier.height(4.dp)

View File

@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
@ -66,6 +67,12 @@ private val LightGrayText = LightColorPalette.onSurface.copy(alpha = 0.52f)
private val DarkPlaceholderText = DarkColorPalette.onSurface.copy(alpha = 0.32f)
private val LightPlaceholderText = LightColorPalette.onSurface.copy(alpha = 0.32f)
private val DarkPlaceholderTextColorFilter = ColorFilter.tint(DarkPlaceholderText)
private val LightPlaceholderTextColorFilter = ColorFilter.tint(LightPlaceholderText)
private val DarkOnBackgroundColorFilter = ColorFilter.tint(DarkColorPalette.onBackground)
private val LightOnBackgroundColorFilter = ColorFilter.tint(LightColorPalette.onBackground)
private val DarkSubtleButton = DarkColorPalette.onSurface.copy(alpha = 0.22f)
private val LightSubtleButton = LightColorPalette.onSurface.copy(alpha = 0.22f)
@ -206,6 +213,12 @@ val Colors.veryImportantLink: Color
val Colors.placeholderText: Color
get() = if (isLight) LightPlaceholderText else DarkPlaceholderText
val Colors.placeholderTextColorFilter: ColorFilter
get() = if (isLight) LightPlaceholderTextColorFilter else DarkPlaceholderTextColorFilter
val Colors.onBackgroundColorFilter: ColorFilter
get() = if (isLight) LightOnBackgroundColorFilter else DarkOnBackgroundColorFilter
val Colors.grayText: Color
get() = if (isLight) LightGrayText else DarkGrayText