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

View File

@ -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
)
}
}

View File

@ -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,

View File

@ -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()
}
}

View File

@ -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,

View File

@ -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() }

View File

@ -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(

View File

@ -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,

View File

@ -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
)

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)
}
)
}
}
}

View File

@ -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)

View File

@ -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
}
}
}
}
)
}

View File

@ -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)

View File

@ -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