mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-11 13:32:03 +02:00
Removes old image proxy classes
This commit is contained in:
parent
cce9d6cf68
commit
e8eea4be25
@ -1,83 +0,0 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.DefaultAlpha
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.AsyncImagePainter
|
||||
import java.util.Base64
|
||||
|
||||
@Immutable
|
||||
data class ResizeImage(val url: String?, val size: Dp) {
|
||||
fun proxyUrl(): String? {
|
||||
if (url == null) return null
|
||||
|
||||
// Fixes Image size to reduce pings to servers for each size used in the app
|
||||
val imgPx = 200 // with(LocalDensity.current) { model.size.toPx().toInt() }
|
||||
val base64 = Base64.getUrlEncoder().encodeToString(url.toByteArray())
|
||||
|
||||
return url // "https://d12fidohs5rlxk.cloudfront.net/preset:sharp/rs:fit:$imgPx:$imgPx:0/gravity:sm/$base64"
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AsyncImageProxy(
|
||||
model: ResizeImage,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
placeholder: Painter? = null,
|
||||
error: Painter? = null,
|
||||
fallback: Painter? = error,
|
||||
onLoading: ((AsyncImagePainter.State.Loading) -> Unit)? = null,
|
||||
onSuccess: ((AsyncImagePainter.State.Success) -> Unit)? = null,
|
||||
onError: ((AsyncImagePainter.State.Error) -> Unit)? = null,
|
||||
alignment: Alignment = Alignment.Center,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
alpha: Float = DefaultAlpha,
|
||||
colorFilter: ColorFilter? = null,
|
||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
||||
) {
|
||||
if (model.url == null) {
|
||||
AsyncImage(
|
||||
model = model.url,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
placeholder = placeholder,
|
||||
error = error,
|
||||
fallback = fallback,
|
||||
onLoading = onLoading,
|
||||
onSuccess = onSuccess,
|
||||
onError = onError,
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
filterQuality = filterQuality
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = model.proxyUrl(),
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
placeholder = placeholder,
|
||||
error = error,
|
||||
fallback = fallback,
|
||||
onLoading = onLoading,
|
||||
onSuccess = onSuccess,
|
||||
onError = onError,
|
||||
alignment = alignment,
|
||||
contentScale = contentScale,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
filterQuality = filterQuality
|
||||
)
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import coil.compose.AsyncImage
|
||||
import coil.compose.AsyncImagePainter
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
@ -19,7 +18,6 @@ import java.util.Date
|
||||
@Composable
|
||||
fun RobohashAsyncImage(
|
||||
robot: String,
|
||||
robotSize: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null,
|
||||
transform: (AsyncImagePainter.State) -> AsyncImagePainter.State = AsyncImagePainter.DefaultTransform,
|
||||
@ -55,7 +53,6 @@ var imageErrors = mapOf<String, Long>()
|
||||
@Composable
|
||||
fun RobohashFallbackAsyncImage(
|
||||
robot: String,
|
||||
robotSize: Dp,
|
||||
model: String,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
@ -70,7 +67,6 @@ fun RobohashFallbackAsyncImage(
|
||||
if (errorCache != null && (Date().time / 1000) - errorCache < (60 * 5)) {
|
||||
RobohashAsyncImage(
|
||||
robot = robot,
|
||||
robotSize = robotSize,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
alignment = alignment,
|
||||
@ -107,7 +103,7 @@ fun RobohashFallbackAsyncImage(
|
||||
@Composable
|
||||
fun RobohashAsyncImageProxy(
|
||||
robot: String,
|
||||
model: ResizeImage,
|
||||
model: String?,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
alignment: Alignment = Alignment.Center,
|
||||
@ -116,12 +112,9 @@ fun RobohashAsyncImageProxy(
|
||||
colorFilter: ColorFilter? = null,
|
||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
||||
) {
|
||||
val proxy = remember(model) { model.proxyUrl() }
|
||||
|
||||
if (proxy == null) {
|
||||
if (model == null) {
|
||||
RobohashAsyncImage(
|
||||
robot = robot,
|
||||
robotSize = model.size,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
alignment = alignment,
|
||||
@ -133,8 +126,7 @@ fun RobohashAsyncImageProxy(
|
||||
} else {
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = robot,
|
||||
robotSize = model.size,
|
||||
model = proxy,
|
||||
model = model,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier,
|
||||
alignment = alignment,
|
||||
|
@ -52,7 +52,6 @@ import com.vitorpamplona.amethyst.model.decodePublicKey
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
@ -200,7 +199,7 @@ private fun AccountPicture(user: User) {
|
||||
val userState by user.live().metadata.observeAsState()
|
||||
val profilePicture by remember(userState) {
|
||||
derivedStateOf {
|
||||
ResizeImage(userState?.user?.profilePicture(), 55.dp)
|
||||
userState?.user?.profilePicture()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,6 @@ import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||
@ -342,7 +341,7 @@ private fun LoggedInUserPictureDrawer(
|
||||
val accountUserState by accountViewModel.account.userProfile().live().metadata.observeAsState()
|
||||
|
||||
val pubkeyHex = remember { accountUserState?.user?.pubkeyHex ?: "" }
|
||||
val profilePicture = remember(accountUserState) { ResizeImage(accountUserState?.user?.profilePicture(), 34.dp) }
|
||||
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture() }
|
||||
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
|
@ -62,7 +62,6 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.HttpClient
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -127,7 +126,7 @@ fun ProfileContent(
|
||||
val profilePubHex = remember(accountUserState) { accountUserState?.user?.pubkeyHex } ?: return
|
||||
|
||||
val profileBanner = remember(accountUserState) { accountUserState?.user?.info?.banner?.ifBlank { null } }
|
||||
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } }
|
||||
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture() }
|
||||
val bestUserName = remember(accountUserState) { accountUserState?.user?.bestUsername() }
|
||||
val bestDisplayName = remember(accountUserState) { accountUserState?.user?.bestDisplayName() }
|
||||
val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
||||
|
@ -47,9 +47,10 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -68,20 +69,23 @@ fun ChatroomCompose(
|
||||
noteState?.note?.channelHex()
|
||||
}
|
||||
}
|
||||
val isBlank = remember(noteState) {
|
||||
note?.event == null
|
||||
}
|
||||
|
||||
if (note?.event == null) {
|
||||
if (isBlank) {
|
||||
BlankNote(Modifier)
|
||||
} else if (channelHex != null) {
|
||||
LoadChannel(baseChannelHex = channelHex!!) { channel ->
|
||||
ChannelRoomCompose(note, channel, accountViewModel, nav)
|
||||
ChannelRoomCompose(baseNote, channel, accountViewModel, nav)
|
||||
}
|
||||
} else {
|
||||
val userRoomHex = remember(noteState, accountViewModel) {
|
||||
(note.event as? PrivateDmEvent)?.talkingWith(accountViewModel.userProfile().pubkeyHex)
|
||||
(baseNote.event as? PrivateDmEvent)?.talkingWith(accountViewModel.userProfile().pubkeyHex)
|
||||
} ?: return
|
||||
|
||||
LoadUser(userRoomHex) { user ->
|
||||
UserRoomCompose(note, user, accountViewModel, nav)
|
||||
UserRoomCompose(baseNote, user, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,29 +142,7 @@ private fun ChannelRoomCompose(
|
||||
channelIdHex = chanHex,
|
||||
channelPicture = channelPicture,
|
||||
channelTitle = { modifier ->
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
) {
|
||||
append(channelName)
|
||||
}
|
||||
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
) {
|
||||
append(" ${stringResource(id = R.string.public_chat)}")
|
||||
}
|
||||
},
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = modifier,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
ChannelTitleWithBoostInfo(channelName, modifier)
|
||||
},
|
||||
channelLastTime = remember(note) { note.createdAt() },
|
||||
channelLastContent = remember(note) { "$authorName: $description" },
|
||||
@ -169,6 +151,39 @@ private fun ChannelRoomCompose(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelTitleWithBoostInfo(channelName: String, modifier: Modifier) {
|
||||
val boosted = stringResource(id = R.string.public_chat)
|
||||
val placeHolderColor = MaterialTheme.colors.placeholderText
|
||||
val channelNameAndBoostInfo = remember {
|
||||
buildAnnotatedString {
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
) {
|
||||
append(channelName)
|
||||
}
|
||||
|
||||
withStyle(
|
||||
SpanStyle(
|
||||
color = placeHolderColor,
|
||||
fontWeight = FontWeight.Normal
|
||||
)
|
||||
) {
|
||||
append(" $boosted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = channelNameAndBoostInfo,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = modifier,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserRoomCompose(
|
||||
note: Note,
|
||||
@ -191,9 +206,9 @@ private fun UserRoomCompose(
|
||||
ChannelName(
|
||||
channelPicture = {
|
||||
UserPicture(
|
||||
user,
|
||||
baseUser = user,
|
||||
accountViewModel = accountViewModel,
|
||||
size = 55.dp
|
||||
size = Size55dp
|
||||
)
|
||||
},
|
||||
channelTitle = { UsernameDisplay(user, it) },
|
||||
@ -256,12 +271,14 @@ fun ChannelName(
|
||||
channelPicture = {
|
||||
RobohashAsyncImageProxy(
|
||||
robot = channelIdHex,
|
||||
model = ResizeImage(channelPicture, 55.dp),
|
||||
model = channelPicture,
|
||||
contentDescription = stringResource(R.string.channel_image),
|
||||
modifier = Modifier
|
||||
.width(55.dp)
|
||||
.height(55.dp)
|
||||
.clip(shape = CircleShape)
|
||||
modifier = remember {
|
||||
Modifier
|
||||
.width(Size55dp)
|
||||
.height(Size55dp)
|
||||
.clip(shape = CircleShape)
|
||||
}
|
||||
)
|
||||
},
|
||||
channelTitle,
|
||||
@ -281,68 +298,85 @@ fun ChannelName(
|
||||
hasNewMessages: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(modifier = Modifier.clickable(onClick = onClick)) {
|
||||
Column(modifier = remember { Modifier.clickable(onClick = onClick) }) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)
|
||||
modifier = remember { Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp) }
|
||||
) {
|
||||
channelPicture()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
modifier = remember { Modifier.padding(start = 10.dp) },
|
||||
verticalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
) {
|
||||
channelTitle(Modifier.weight(1f))
|
||||
|
||||
channelLastTime?.let {
|
||||
Text(
|
||||
timeAgo(channelLastTime, context),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (channelLastContent != null) {
|
||||
Text(
|
||||
channelLastContent,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
stringResource(R.string.referenced_event_not_found),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.52f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
if (hasNewMessages) {
|
||||
NewItemsBubble()
|
||||
}
|
||||
}
|
||||
FirstRow(channelTitle, channelLastTime)
|
||||
SecondRow(channelLastContent, hasNewMessages)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
modifier = remember { Modifier.padding(top = 10.dp) },
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecondRow(channelLastContent: String?, hasNewMessages: Boolean) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (channelLastContent != null) {
|
||||
Text(
|
||||
channelLastContent,
|
||||
color = MaterialTheme.colors.grayText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
stringResource(R.string.referenced_event_not_found),
|
||||
color = MaterialTheme.colors.grayText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
|
||||
if (hasNewMessages) {
|
||||
NewItemsBubble()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FirstRow(
|
||||
channelTitle: @Composable (Modifier) -> Unit,
|
||||
channelLastTime: Long?
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = remember { Modifier.padding(bottom = 4.dp) }
|
||||
) {
|
||||
channelTitle(
|
||||
remember {
|
||||
Modifier.weight(1f)
|
||||
}
|
||||
)
|
||||
|
||||
channelLastTime?.let {
|
||||
val context = LocalContext.current
|
||||
val timeAgo = remember(channelLastTime) { timeAgo(channelLastTime, context) }
|
||||
Text(
|
||||
timeAgo,
|
||||
color = MaterialTheme.colors.grayText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NewItemsBubble() {
|
||||
Box(
|
||||
|
@ -60,7 +60,6 @@ import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
@ -70,7 +69,6 @@ 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.Size16dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||
@ -730,7 +728,7 @@ private fun WatchAndDisplayUser(
|
||||
|
||||
val userProfilePicture by remember(userState) {
|
||||
derivedStateOf {
|
||||
ResizeImage(userState?.user?.profilePicture(), Size25dp)
|
||||
userState?.user?.profilePicture()
|
||||
}
|
||||
}
|
||||
|
||||
@ -750,7 +748,7 @@ private fun WatchAndDisplayUser(
|
||||
@Composable
|
||||
private fun UserIcon(
|
||||
pubkeyHex: String,
|
||||
userProfilePicture: ResizeImage,
|
||||
userProfilePicture: String?,
|
||||
nav: (String) -> Unit,
|
||||
route: String
|
||||
) {
|
||||
@ -875,7 +873,6 @@ fun RenderRelay(dirtyUrl: String) {
|
||||
) {
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = iconUrl,
|
||||
robotSize = Size13dp,
|
||||
model = iconUrl,
|
||||
contentDescription = stringResource(id = R.string.relay_icon),
|
||||
colorFilter = RelayIconFilter,
|
||||
|
@ -122,7 +122,6 @@ import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
@ -143,6 +142,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
@ -1925,7 +1925,7 @@ private fun ChannelNotePicture(baseChannel: Channel) {
|
||||
}
|
||||
|
||||
val model = remember(channelState) {
|
||||
ResizeImage(channel.profilePicture(), 30.dp)
|
||||
channel.profilePicture()
|
||||
}
|
||||
|
||||
Box(boxModifier) {
|
||||
@ -2710,7 +2710,7 @@ private fun RelayBadges(baseNote: Note) {
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(remember { Modifier.height(10.dp) })
|
||||
Spacer(DoubleVertSpacer)
|
||||
|
||||
if (expanded) {
|
||||
VerticalRelayPanelWithFlow(lazyRelayList)
|
||||
@ -2819,7 +2819,6 @@ fun NoteAuthorPicture(
|
||||
|
||||
RobohashAsyncImage(
|
||||
robot = "authornotfound",
|
||||
robotSize = size,
|
||||
contentDescription = stringResource(R.string.unknown_author),
|
||||
modifier = nullModifier
|
||||
)
|
||||
@ -2836,8 +2835,14 @@ fun UserPicture(
|
||||
size: Dp,
|
||||
pictureModifier: Modifier = Modifier
|
||||
) {
|
||||
val route by remember {
|
||||
derivedStateOf {
|
||||
"User/${user.pubkeyHex}"
|
||||
}
|
||||
}
|
||||
|
||||
UserPicture(user, size, accountViewModel, pictureModifier) {
|
||||
nav("User/${it.pubkeyHex}")
|
||||
nav(route)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2914,9 +2919,7 @@ fun UserPicture(
|
||||
Box(myBoxModifier, contentAlignment = TopEnd) {
|
||||
RobohashAsyncImageProxy(
|
||||
robot = userHex,
|
||||
model = remember(userPicture) {
|
||||
ResizeImage(userPicture, size)
|
||||
},
|
||||
model = userPicture,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
modifier = myImageModifier
|
||||
)
|
||||
|
@ -10,6 +10,7 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.PlayCircle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
@ -42,10 +43,26 @@ fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
||||
@Composable
|
||||
fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
|
||||
val userState by baseUser.live().metadata.observeAsState()
|
||||
val bestUserName = remember(userState) { userState?.user?.bestUsername() }
|
||||
val bestDisplayName = remember(userState) { userState?.user?.bestDisplayName() }
|
||||
val npubDisplay = remember { baseUser.pubkeyDisplayHex() }
|
||||
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
|
||||
val bestUserName by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.bestUsername()
|
||||
}
|
||||
}
|
||||
val bestDisplayName by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.bestDisplayName()
|
||||
}
|
||||
}
|
||||
val npubDisplay by remember {
|
||||
derivedStateOf {
|
||||
baseUser.pubkeyDisplayHex()
|
||||
}
|
||||
}
|
||||
val tags by remember(userState) {
|
||||
derivedStateOf {
|
||||
userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists()
|
||||
}
|
||||
}
|
||||
|
||||
UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, tags, weight)
|
||||
}
|
||||
@ -58,58 +75,71 @@ private fun UserNameDisplay(
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
modifier: Modifier
|
||||
) {
|
||||
if (bestUserName != null && bestDisplayName != null) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestDisplayName,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1
|
||||
)
|
||||
if (bestDisplayName != bestUserName) {
|
||||
CreateTextWithEmoji(
|
||||
text = remember { "@$bestUserName" },
|
||||
tags = tags,
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
Spacer(StdHorzSpacer)
|
||||
DrawPlayName(bestDisplayName)
|
||||
if (bestUserName != null && bestDisplayName != null && bestDisplayName != bestUserName) {
|
||||
UserAndUsernameDisplay(bestDisplayName, tags, bestUserName, modifier)
|
||||
} else if (bestDisplayName != null) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestDisplayName,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
DrawPlayName(bestDisplayName)
|
||||
UserDisplay(bestDisplayName, tags, modifier)
|
||||
} else if (bestUserName != null) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestUserName,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
DrawPlayName(bestUserName)
|
||||
UserDisplay(bestUserName, tags, modifier)
|
||||
} else {
|
||||
Text(
|
||||
text = npubDisplay,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
NPubDisplay(npubDisplay, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NPubDisplay(npubDisplay: String, modifier: Modifier) {
|
||||
Text(
|
||||
text = npubDisplay,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserDisplay(
|
||||
bestDisplayName: String,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
modifier: Modifier
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestDisplayName,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
DrawPlayName(bestDisplayName)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserAndUsernameDisplay(
|
||||
bestDisplayName: String,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
bestUserName: String,
|
||||
modifier: Modifier
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestDisplayName,
|
||||
tags = tags,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1
|
||||
)
|
||||
CreateTextWithEmoji(
|
||||
text = remember { "@$bestUserName" },
|
||||
tags = tags,
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
DrawPlayName(bestDisplayName)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawPlayName(name: String) {
|
||||
val context = LocalContext.current
|
||||
|
@ -36,7 +36,6 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.qrcode.NIP19QrCodeScanner
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
@ -76,7 +75,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
|
||||
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
|
||||
RobohashAsyncImageProxy(
|
||||
robot = user.pubkeyHex,
|
||||
model = ResizeImage(user.profilePicture(), 100.dp),
|
||||
model = user.profilePicture(),
|
||||
contentDescription = stringResource(R.string.profile_image),
|
||||
modifier = Modifier
|
||||
.width(100.dp)
|
||||
|
@ -2,27 +2,17 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -30,54 +20,51 @@ import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ChatroomListFeedView(
|
||||
viewModel: FeedViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
markAsRead: MutableState<Boolean>
|
||||
) {
|
||||
RefresheableView(viewModel, true) {
|
||||
CorssFadeState(viewModel, accountViewModel, nav, markAsRead)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CorssFadeState(
|
||||
viewModel: FeedViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
markAsRead: MutableState<Boolean>
|
||||
) {
|
||||
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
|
||||
|
||||
var refreshing by remember { mutableStateOf(false) }
|
||||
val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false }
|
||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||
|
||||
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||
Column() {
|
||||
Crossfade(
|
||||
targetState = feedState,
|
||||
animationSpec = tween(durationMillis = 100)
|
||||
) { state ->
|
||||
when (state) {
|
||||
is FeedState.Empty -> {
|
||||
FeedEmpty {
|
||||
refreshing = true
|
||||
}
|
||||
}
|
||||
|
||||
is FeedState.FeedError -> {
|
||||
FeedError(state.errorMessage) {
|
||||
refreshing = true
|
||||
}
|
||||
}
|
||||
|
||||
is FeedState.Loaded -> {
|
||||
if (refreshing) {
|
||||
refreshing = false
|
||||
}
|
||||
FeedLoaded(state, accountViewModel, nav, markAsRead)
|
||||
}
|
||||
|
||||
FeedState.Loading -> {
|
||||
LoadingFeed()
|
||||
}
|
||||
Crossfade(
|
||||
targetState = feedState,
|
||||
animationSpec = tween(durationMillis = 100)
|
||||
) { state ->
|
||||
when (state) {
|
||||
is FeedState.Empty -> {
|
||||
FeedEmpty {
|
||||
viewModel.invalidateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||
is FeedState.FeedError -> {
|
||||
FeedError(state.errorMessage) {
|
||||
viewModel.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
is FeedState.Loaded -> {
|
||||
FeedLoaded(state, accountViewModel, nav, markAsRead)
|
||||
}
|
||||
|
||||
FeedState.Loading -> {
|
||||
LoadingFeed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +107,7 @@ private fun FeedLoaded(
|
||||
state.feed.value,
|
||||
key = { index, item -> if (index == 0) index else item.idHex }
|
||||
) { _, item ->
|
||||
Row(Modifier.fillMaxWidth().defaultMinSize(minHeight = 75.dp)) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
ChatroomCompose(
|
||||
item,
|
||||
accountViewModel = accountViewModel,
|
||||
|
@ -61,10 +61,12 @@ fun RefresheableView(
|
||||
val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false }
|
||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||
|
||||
val modifier = if (enablePullRefresh) {
|
||||
Modifier.pullRefresh(pullRefreshState)
|
||||
} else {
|
||||
Modifier
|
||||
val modifier = remember {
|
||||
if (enablePullRefresh) {
|
||||
Modifier.pullRefresh(pullRefreshState)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier) {
|
||||
@ -73,7 +75,13 @@ fun RefresheableView(
|
||||
}
|
||||
|
||||
if (enablePullRefresh) {
|
||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
||||
PullRefreshIndicator(
|
||||
refreshing = refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = remember {
|
||||
Modifier.align(Alignment.TopCenter)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.ServersAvailable
|
||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||
@ -549,7 +548,7 @@ fun ChannelHeader(
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RobohashAsyncImageProxy(
|
||||
robot = channel.idHex,
|
||||
model = ResizeImage(channel.profilePicture(), Size35dp),
|
||||
model = channel.profilePicture(),
|
||||
contentDescription = context.getString(R.string.profile_image),
|
||||
modifier = Modifier
|
||||
.width(Size35dp)
|
||||
|
@ -25,6 +25,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
@ -1040,39 +1041,45 @@ private fun WatchAndRenderBadgeImage(
|
||||
) {
|
||||
val noteState by baseNote.live().metadata.observeAsState()
|
||||
val eventId = remember(noteState) { noteState?.note?.idHex } ?: return
|
||||
val image = remember(noteState) {
|
||||
val event = noteState?.note?.event as? BadgeDefinitionEvent
|
||||
event?.thumb()?.ifBlank { null } ?: event?.image()?.ifBlank { null }
|
||||
val image by remember(noteState) {
|
||||
derivedStateOf {
|
||||
val event = noteState?.note?.event as? BadgeDefinitionEvent
|
||||
event?.thumb()?.ifBlank { null } ?: event?.image()?.ifBlank { null }
|
||||
}
|
||||
}
|
||||
|
||||
val bgColor = MaterialTheme.colors.background
|
||||
|
||||
if (image == null) {
|
||||
RobohashAsyncImage(
|
||||
robot = "authornotfound",
|
||||
robotSize = size,
|
||||
contentDescription = stringResource(R.string.unknown_author),
|
||||
modifier = pictureModifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
.background(MaterialTheme.colors.background)
|
||||
modifier = remember {
|
||||
pictureModifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
.drawBehind { drawRect(bgColor) }
|
||||
}
|
||||
)
|
||||
} else {
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = eventId,
|
||||
robotSize = size,
|
||||
model = image,
|
||||
model = image!!,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
modifier = pictureModifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
.clip(shape = CircleShape)
|
||||
.background(MaterialTheme.colors.background)
|
||||
.run {
|
||||
if (onClick != null) {
|
||||
this.clickable(onClick = { onClick(eventId) })
|
||||
} else {
|
||||
this
|
||||
modifier = remember {
|
||||
pictureModifier
|
||||
.width(size)
|
||||
.height(size)
|
||||
.clip(shape = CircleShape)
|
||||
.drawBehind { drawRect(bgColor) }
|
||||
.run {
|
||||
if (onClick != null) {
|
||||
this.clickable(onClick = { onClick(eventId) })
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
@ -26,12 +26,13 @@ val StdButtonSizeModifier = Modifier.size(20.dp)
|
||||
val StdHorzSpacer = Modifier.width(5.dp)
|
||||
val StdVertSpacer = Modifier.height(5.dp)
|
||||
val DoubleHorzSpacer = Modifier.width(10.dp)
|
||||
val DoubleVertSpacer = Modifier.width(10.dp)
|
||||
val DoubleVertSpacer = Modifier.height(10.dp)
|
||||
|
||||
val Size13dp = 13.dp
|
||||
val Size16dp = 16.dp
|
||||
val Size25dp = 25.dp
|
||||
val Size35dp = 35.dp
|
||||
val Size55dp = 55.dp
|
||||
|
||||
val StdPadding = Modifier.padding(10.dp)
|
||||
|
||||
|
@ -48,6 +48,9 @@ private val LightMediumImportantLink = LightColorPalette.primary.copy(alpha = 0.
|
||||
private val DarkVeryImportantLink = DarkColorPalette.primary.copy(alpha = 0.12f)
|
||||
private val LightVeryImportantLink = LightColorPalette.primary.copy(alpha = 0.12f)
|
||||
|
||||
private val DarkGrayText = DarkColorPalette.onSurface.copy(alpha = 0.52f)
|
||||
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)
|
||||
|
||||
@ -89,6 +92,9 @@ val Colors.veryImportantLink: Color
|
||||
val Colors.placeholderText: Color
|
||||
get() = if (isLight) LightPlaceholderText else DarkPlaceholderText
|
||||
|
||||
val Colors.grayText: Color
|
||||
get() = if (isLight) LightGrayText else DarkGrayText
|
||||
|
||||
val Colors.subtleBorder: Color
|
||||
get() = if (isLight) LightSubtleBorder else DarkSubtleBorder
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user