refactor(media): simplify GIF rendering and wire autoplay setting
Replaces the video-style play/pause controls on GIFs with normal image rendering. When Auto-play Videos is off, GIFs pause on the first frame (feed GIFs additionally show a small "gif" label in the bottom-left); tapping still opens the fullscreen dialog. Profile-picture GIFs now respect the same setting. The key fix is bypassing ProfilePictureFetcher (which serves a static JPEG thumbnail and prevents animation); GIF avatars load via the raw URL so Coil's animated decoder runs. Autoplay is read reactively from a new autoPlayVideosFlow StateFlow so toggling the setting immediately starts/stops animation in the drawer, top bar, and every other avatar.
This commit is contained in:
@@ -21,44 +21,40 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Pause
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.material.icons.outlined.OpenInFull
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import coil3.asDrawable
|
import coil3.asDrawable
|
||||||
import coil3.compose.AsyncImagePainter
|
import coil3.compose.AsyncImagePainter
|
||||||
import coil3.compose.SubcomposeAsyncImage
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
import coil3.compose.SubcomposeAsyncImageContent
|
import coil3.compose.SubcomposeAsyncImageContent
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
import com.vitorpamplona.amethyst.ui.theme.Font10SP
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GifVideoView(
|
fun GifVideoView(
|
||||||
@@ -73,24 +69,17 @@ fun GifVideoView(
|
|||||||
thumbhash: String? = null,
|
thumbhash: String? = null,
|
||||||
) {
|
) {
|
||||||
val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
|
val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
|
||||||
val automaticallyStartPlayback = remember { mutableStateOf(accountViewModel.settings.autoPlayVideos()) }
|
val autoPlay = accountViewModel.settings.autoPlayVideos()
|
||||||
val controllerVisible = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
LaunchedEffect(controllerVisible.value, automaticallyStartPlayback.value) {
|
|
||||||
if (controllerVisible.value && automaticallyStartPlayback.value) {
|
|
||||||
delay(3000)
|
|
||||||
controllerVisible.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val borderModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier
|
val borderModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val containerModifier =
|
||||||
|
(if (ratio != null) borderModifier.aspectRatio(ratio) else borderModifier)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.let { if (onDialog != null) it.clickable { onDialog() } else it }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier = containerModifier,
|
||||||
(if (ratio != null) borderModifier.aspectRatio(ratio) else borderModifier)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { controllerVisible.value = !controllerVisible.value },
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
@@ -104,13 +93,9 @@ fun GifVideoView(
|
|||||||
val successState = state as? AsyncImagePainter.State.Success
|
val successState = state as? AsyncImagePainter.State.Success
|
||||||
val drawable = successState?.result?.image?.asDrawable(context.resources)
|
val drawable = successState?.result?.image?.asDrawable(context.resources)
|
||||||
|
|
||||||
LaunchedEffect(automaticallyStartPlayback.value, drawable) {
|
LaunchedEffect(autoPlay, drawable) {
|
||||||
if (drawable is Animatable) {
|
if (drawable is Animatable) {
|
||||||
if (automaticallyStartPlayback.value) {
|
if (autoPlay) drawable.start() else drawable.stop()
|
||||||
drawable.start()
|
|
||||||
} else {
|
|
||||||
drawable.stop()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +112,11 @@ fun GifVideoView(
|
|||||||
|
|
||||||
is AsyncImagePainter.State.Success -> {
|
is AsyncImagePainter.State.Success -> {
|
||||||
SubcomposeAsyncImageContent(Modifier.fillMaxSize())
|
SubcomposeAsyncImageContent(Modifier.fillMaxSize())
|
||||||
|
|
||||||
|
SideEffect {
|
||||||
|
val image = (state as AsyncImagePainter.State.Success).result.image
|
||||||
|
MediaAspectRatioCache.add(videoUri, image.width, image.height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AsyncImagePainter.State.Error -> {
|
is AsyncImagePainter.State.Error -> {
|
||||||
@@ -137,49 +127,21 @@ fun GifVideoView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zoom button
|
if (!autoPlay) {
|
||||||
AnimatedVisibility(
|
Text(
|
||||||
visible = controllerVisible.value,
|
text = stringResource(R.string.gif),
|
||||||
modifier = Modifier.align(Alignment.TopEnd),
|
color = Color.White,
|
||||||
enter = fadeIn(),
|
fontWeight = FontWeight.Bold,
|
||||||
exit = fadeOut(),
|
fontSize = Font10SP,
|
||||||
) {
|
lineHeight = Font10SP,
|
||||||
IconButton(
|
|
||||||
onClick = { onDialog?.invoke() },
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.OpenInFull,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play/Pause button
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = controllerVisible.value || !automaticallyStartPlayback.value,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.align(Alignment.BottomStart)
|
||||||
.background(if (!automaticallyStartPlayback.value) Color.Black.copy(alpha = 0.3f) else Color.Transparent),
|
.padding(6.dp)
|
||||||
contentAlignment = Alignment.Center,
|
.clip(SmallBorder)
|
||||||
) {
|
.background(Color.Black.copy(alpha = 0.6f))
|
||||||
IconButton(
|
.padding(horizontal = 4.dp, vertical = 1.dp),
|
||||||
modifier = Modifier.size(Size75dp),
|
)
|
||||||
onClick = { automaticallyStartPlayback.value = !automaticallyStartPlayback.value },
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = if (automaticallyStartPlayback.value) Icons.Default.Pause else Icons.Default.PlayArrow,
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White,
|
|
||||||
modifier = Modifier.size(Size75dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-1
@@ -20,9 +20,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
@@ -31,7 +37,12 @@ import androidx.compose.ui.graphics.FilterQuality
|
|||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import coil3.asDrawable
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import coil3.compose.AsyncImagePainter
|
||||||
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
|
import coil3.compose.SubcomposeAsyncImageContent
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter
|
import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter
|
||||||
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
|
import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
|
||||||
@@ -75,8 +86,18 @@ fun RobohashFallbackAsyncImage(
|
|||||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
|
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
loadRobohash: Boolean,
|
loadRobohash: Boolean,
|
||||||
|
autoPlayGif: Boolean = true,
|
||||||
) {
|
) {
|
||||||
if (model != null && loadProfilePicture) {
|
if (model != null && loadProfilePicture && isGifUrl(model)) {
|
||||||
|
GifProfilePicture(
|
||||||
|
userHex = robot,
|
||||||
|
userPicture = model,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier,
|
||||||
|
loadRobohash = loadRobohash,
|
||||||
|
autoPlay = autoPlayGif,
|
||||||
|
)
|
||||||
|
} else if (model != null && loadProfilePicture) {
|
||||||
val painter =
|
val painter =
|
||||||
if (loadRobohash) {
|
if (loadRobohash) {
|
||||||
rememberVectorPainter(
|
rememberVectorPainter(
|
||||||
@@ -124,3 +145,58 @@ fun RobohashFallbackAsyncImage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GifProfilePicture(
|
||||||
|
userHex: String,
|
||||||
|
userPicture: String,
|
||||||
|
contentDescription: String?,
|
||||||
|
modifier: Modifier,
|
||||||
|
loadRobohash: Boolean,
|
||||||
|
autoPlay: Boolean,
|
||||||
|
) {
|
||||||
|
val resources = LocalContext.current.resources
|
||||||
|
val fallbackPainter =
|
||||||
|
if (loadRobohash) {
|
||||||
|
rememberVectorPainter(image = CachedRobohash.get(userHex, MaterialTheme.colorScheme.isLight))
|
||||||
|
} else {
|
||||||
|
forwardingPainter(
|
||||||
|
painter = rememberMaterialSymbolPainter(MaterialSymbols.Face),
|
||||||
|
colorFilter = MaterialTheme.colorScheme.onBackgroundColorFilter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
SubcomposeAsyncImage(
|
||||||
|
model = userPicture,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
val state by painter.state.collectAsState()
|
||||||
|
val successState = state as? AsyncImagePainter.State.Success
|
||||||
|
val drawable = successState?.result?.image?.asDrawable(resources)
|
||||||
|
|
||||||
|
LaunchedEffect(drawable, autoPlay) {
|
||||||
|
if (drawable is Animatable) {
|
||||||
|
if (autoPlay) drawable.start() else drawable.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (state) {
|
||||||
|
is AsyncImagePainter.State.Success -> {
|
||||||
|
SubcomposeAsyncImageContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Image(
|
||||||
|
painter = fallbackPainter,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
@@ -225,6 +225,10 @@ private fun AccountPicture(
|
|||||||
modifier = AccountPictureModifier,
|
modifier = AccountPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -259,6 +259,10 @@ fun ProfileContentTemplate(
|
|||||||
.clickable(onClick = onClick),
|
.clickable(onClick = onClick),
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (bestDisplayName != null) {
|
if (bestDisplayName != null) {
|
||||||
|
|||||||
+5
@@ -31,6 +31,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
@@ -87,6 +88,10 @@ private fun LoggedInUserPictureDrawer(
|
|||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||||
@@ -623,6 +624,10 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
|
|||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
@@ -1818,6 +1819,10 @@ private fun ChannelNotePicture(
|
|||||||
modifier = MaterialTheme.colorScheme.channelNotePictureModifier,
|
modifier = MaterialTheme.colorScheme.channelNotePictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -637,6 +637,10 @@ fun InnerUserPicture(
|
|||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -328,6 +328,10 @@ fun ShortCommunityHeader(
|
|||||||
modifier = HeaderPictureModifier,
|
modifier = HeaderPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +382,10 @@ fun ShortCommunityHeaderNoActions(
|
|||||||
modifier = HeaderPictureModifier,
|
modifier = HeaderPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -140,5 +141,14 @@ class UiSettingsState(
|
|||||||
|
|
||||||
fun autoPlayVideos() = uiSettingsFlow.automaticallyPlayVideos.value == BooleanType.ALWAYS
|
fun autoPlayVideos() = uiSettingsFlow.automaticallyPlayVideos.value == BooleanType.ALWAYS
|
||||||
|
|
||||||
|
val autoPlayVideosFlow: StateFlow<Boolean> =
|
||||||
|
uiSettingsFlow.automaticallyPlayVideos
|
||||||
|
.map { it == BooleanType.ALWAYS }
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
uiSettingsFlow.automaticallyPlayVideos.value == BooleanType.ALWAYS,
|
||||||
|
)
|
||||||
|
|
||||||
fun showImages() = showImages.value
|
fun showImages() = showImages.value
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -47,6 +47,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
import com.vitorpamplona.amethyst.commons.defaults.Constants
|
||||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||||
@@ -162,6 +163,10 @@ fun RenderChannelData(
|
|||||||
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||||
@@ -103,6 +104,10 @@ private fun DrawRelayIcon(
|
|||||||
modifier = HeaderPictureModifier,
|
modifier = HeaderPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
@@ -89,6 +90,10 @@ fun LongPublicChatChannelHeader(
|
|||||||
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
@@ -72,6 +73,10 @@ fun ShortPublicChatChannelHeader(
|
|||||||
modifier = HeaderPictureModifier,
|
modifier = HeaderPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
@@ -33,6 +33,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
@@ -73,6 +74,10 @@ fun ChannelFileUploadDialog(
|
|||||||
modifier = HeaderPictureModifier,
|
modifier = HeaderPictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
@@ -209,6 +209,10 @@ private fun ChannelRoomCompose(
|
|||||||
hasNewMessages = (noteEvent?.createdAt ?: Long.MIN_VALUE) > lastReadTime,
|
hasNewMessages = (noteEvent?.createdAt ?: Long.MIN_VALUE) > lastReadTime,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(routeFor(channel)) },
|
onClick = { nav.nav(routeFor(channel)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -241,6 +245,10 @@ private fun ChannelRoomCompose(
|
|||||||
hasNewMessages = (noteEvent?.createdAt ?: Long.MIN_VALUE) > lastReadTime,
|
hasNewMessages = (noteEvent?.createdAt ?: Long.MIN_VALUE) > lastReadTime,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(routeFor(channel)) },
|
onClick = { nav.nav(routeFor(channel)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -276,6 +284,10 @@ private fun MarmotGroupRoomCompose(
|
|||||||
hasNewMessages = unread > 0,
|
hasNewMessages = unread > 0,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(Route.MarmotGroupChat(chatroom.nostrGroupId)) },
|
onClick = { nav.nav(Route.MarmotGroupChat(chatroom.nostrGroupId)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -399,6 +411,7 @@ fun ChannelName(
|
|||||||
hasNewMessages: Boolean,
|
hasNewMessages: Boolean,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
loadRobohash: Boolean,
|
loadRobohash: Boolean,
|
||||||
|
autoPlayGif: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ChannelName(
|
ChannelName(
|
||||||
@@ -410,6 +423,7 @@ fun ChannelName(
|
|||||||
modifier = AccountPictureModifier,
|
modifier = AccountPictureModifier,
|
||||||
loadProfilePicture = loadProfilePicture,
|
loadProfilePicture = loadProfilePicture,
|
||||||
loadRobohash = loadRobohash,
|
loadRobohash = loadRobohash,
|
||||||
|
autoPlayGif = autoPlayGif,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
channelTitle,
|
channelTitle,
|
||||||
|
|||||||
+4
@@ -416,6 +416,10 @@ private fun RenderBadgeImage(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -232,6 +232,10 @@ fun RenderName(
|
|||||||
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
modifier = MaterialTheme.colorScheme.largeProfilePictureModifier,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
+12
@@ -568,6 +568,10 @@ private fun DisplaySearchResults(
|
|||||||
hasNewMessages = false,
|
hasNewMessages = false,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(routeFor(item)) },
|
onClick = { nav.nav(routeFor(item)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -597,6 +601,10 @@ private fun DisplaySearchResults(
|
|||||||
hasNewMessages = false,
|
hasNewMessages = false,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(routeFor(item)) },
|
onClick = { nav.nav(routeFor(item)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -624,6 +632,10 @@ private fun DisplaySearchResults(
|
|||||||
hasNewMessages = false,
|
hasNewMessages = false,
|
||||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||||
|
autoPlayGif =
|
||||||
|
accountViewModel.settings.autoPlayVideosFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
.value,
|
||||||
onClick = { nav.nav(routeFor(item)) },
|
onClick = { nav.nav(routeFor(item)) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2506,4 +2506,5 @@
|
|||||||
<string name="emoji_private_badge">Private emoji</string>
|
<string name="emoji_private_badge">Private emoji</string>
|
||||||
<string name="emoji_public_explainer">Public emojis are visible to everyone and appear in your reaction menu and \":\" autocomplete picker when this pack is in your emoji list.</string>
|
<string name="emoji_public_explainer">Public emojis are visible to everyone and appear in your reaction menu and \":\" autocomplete picker when this pack is in your emoji list.</string>
|
||||||
<string name="emoji_private_explainer">Private emojis are stored encrypted on relays and visible only to you. They appear in your reaction menu and \":\" autocomplete just like public ones.</string>
|
<string name="emoji_private_explainer">Private emojis are stored encrypted on relays and visible only to you. They appear in your reaction menu and \":\" autocomplete just like public ones.</string>
|
||||||
|
<string name="gif">Gif</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user