mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-11 13:32:03 +02:00
Minor performance adjustments across the app.
This commit is contained in:
parent
c24ba18207
commit
887fc33073
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user