Removes old image proxy classes

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