From e8eea4be259aa0b1157dbe7057e46a33f37af44e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 21 Jun 2023 12:47:53 -0400 Subject: [PATCH] Removes old image proxy classes --- .../amethyst/ui/components/AsyncImageProxy.kt | 83 -------- .../ui/components/RobohashAsyncImage.kt | 14 +- .../ui/navigation/AccountSwitchBottomSheet.kt | 3 +- .../amethyst/ui/navigation/AppTopBar.kt | 3 +- .../amethyst/ui/navigation/DrawerContent.kt | 3 +- .../amethyst/ui/note/ChatroomCompose.kt | 196 ++++++++++-------- .../ui/note/ChatroomMessageCompose.kt | 7 +- .../amethyst/ui/note/NoteCompose.kt | 19 +- .../amethyst/ui/note/UsernameDisplay.kt | 130 +++++++----- .../amethyst/ui/qrcode/ShowQRDialog.kt | 3 +- .../ui/screen/ChatroomListFeedView.kt | 83 ++++---- .../amethyst/ui/screen/FeedView.kt | 18 +- .../ui/screen/loggedIn/ChannelScreen.kt | 3 +- .../ui/screen/loggedIn/ProfileScreen.kt | 49 +++-- .../vitorpamplona/amethyst/ui/theme/Shape.kt | 3 +- .../vitorpamplona/amethyst/ui/theme/Theme.kt | 6 + 16 files changed, 300 insertions(+), 323 deletions(-) delete mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt deleted file mode 100644 index 32b825186..000000000 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/AsyncImageProxy.kt +++ /dev/null @@ -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 - ) - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index 148a3087b..628798d05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -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() @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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index 1cffeee46..a79e7dbe2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -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() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index b98896764..c8619c40c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 3ba5a23e8..15263dbee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt index a69d60e13..f5ae56f8f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt @@ -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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 9d59c5fba..f5001d4ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 2ce53d58e..4342e5486 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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 ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 3a2bf2322..ddd23dbb7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -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?, 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?, + 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?, + 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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt index 31d6d3d59..361711dba 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt @@ -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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index 6dabc785a..649aaead0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -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 +) { + RefresheableView(viewModel, true) { + CorssFadeState(viewModel, accountViewModel, nav, markAsRead) + } +} + +@Composable +private fun CorssFadeState( + viewModel: FeedViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, + markAsRead: MutableState ) { 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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 221776e6a..734746736 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -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) + } + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index f8c613de8..43c89d4c4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index be9194142..0279de643 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -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 + } } - } + } ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 18581e910..734899c69 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index c3f15ccf1..4cb643ddb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -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