From 4f949bcd79b51ecebcf4758f66f281f23a8b5732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 24 Jul 2024 01:21:24 +0300 Subject: [PATCH] wip lottifiles example --- .../src/jvmMain/kotlin/main.desktop.kt | 2 +- example/shared/src/commonMain/kotlin/App.kt | 9 +- .../kotlin/lottiefiles/LottieDetails.kt | 445 ++++++++++++++++++ .../kotlin/lottiefiles/LottieFilesScreen.kt | 344 +++----------- 4 files changed, 507 insertions(+), 293 deletions(-) create mode 100644 example/shared/src/commonMain/kotlin/lottiefiles/LottieDetails.kt diff --git a/example/desktopApp/src/jvmMain/kotlin/main.desktop.kt b/example/desktopApp/src/jvmMain/kotlin/main.desktop.kt index f5f778f1..e4a48526 100644 --- a/example/desktopApp/src/jvmMain/kotlin/main.desktop.kt +++ b/example/desktopApp/src/jvmMain/kotlin/main.desktop.kt @@ -7,7 +7,7 @@ fun main() { singleWindowApplication( title = "Compottie 2.0 Example", state = WindowState( - size = DpSize(1295.dp, 500.dp) + size = DpSize(800.dp, 600.dp) ) ) { App() diff --git a/example/shared/src/commonMain/kotlin/App.kt b/example/shared/src/commonMain/kotlin/App.kt index 5d14fe8f..25006b1d 100644 --- a/example/shared/src/commonMain/kotlin/App.kt +++ b/example/shared/src/commonMain/kotlin/App.kt @@ -16,7 +16,9 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -33,6 +35,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import io.github.alexzhirkevich.compottie.Compottie @@ -42,6 +45,7 @@ import io.github.alexzhirkevich.compottie.ExperimentalCompottieApi import io.github.alexzhirkevich.compottie.LottieClipSpec import io.github.alexzhirkevich.compottie.LottieComposition import io.github.alexzhirkevich.compottie.LottieCompositionSpec +import io.github.alexzhirkevich.compottie.Url import io.github.alexzhirkevich.compottie.animateLottieCompositionAsState import io.github.alexzhirkevich.compottie.dynamic.rememberLottieDynamicProperties import io.github.alexzhirkevich.compottie.rememberLottieComposition @@ -130,6 +134,7 @@ public suspend fun LottieCompositionSpec.Companion.ResourceString( public fun App() { // return LottieFontExample() +// return AllExamples() return LottieFilesScreen() // return LottieList() @@ -142,10 +147,10 @@ public fun App() { // LottieCompositionSpec.ResourceString("expr/move_horizontal.json") // LottieCompositionSpec.ResourceString("expr/wiggle.json") // LottieCompositionSpec.ResourceString("expr/noise.json") - LottieCompositionSpec.ResourceString(WONDERS) +// LottieCompositionSpec.ResourceString(WONDERS) // // LottieCompositionSpec.Url( -// "https://assets-v2.lottiefiles.com/a/a63d8606-1166-11ee-a7f8-83d9759dd8ff/hCTtJKM3Tu.lottie" +// "https://assets-v2.lottiefiles.com/a/9286b092-117a-11ee-b857-2712bc869389/WSepKUr5be.lottie" // "https://assets-v2.lottiefiles.com/a/d5654818-1168-11ee-a43f-870f05952f24/m5LUOQBrz9.lottie" // radial gr with angle // "https://assets-v2.lottiefiles.com/a/27cd3f04-1180-11ee-852d-8b2f8ce04afa/SB3d1oChh6.lottie", // rotationXYZ envelope // "https://assets-v2.lottiefiles.com/a/4a2c7f7e-1171-11ee-ae37-d7b32f8315b2/7qw6O5kPfv.lottie", // broken text pos diff --git a/example/shared/src/commonMain/kotlin/lottiefiles/LottieDetails.kt b/example/shared/src/commonMain/kotlin/lottiefiles/LottieDetails.kt new file mode 100644 index 00000000..3da7e067 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/lottiefiles/LottieDetails.kt @@ -0,0 +1,445 @@ +package lottiefiles + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Repeat +import androidx.compose.material.icons.filled.RepeatOne +import androidx.compose.material.icons.outlined.DoorFront +import androidx.compose.material.icons.outlined.FileDownload +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach +import io.github.alexzhirkevich.compottie.Compottie +import io.github.alexzhirkevich.compottie.LottieCompositionSpec +import io.github.alexzhirkevich.compottie.Url +import io.github.alexzhirkevich.compottie.rememberLottieAnimatable +import io.github.alexzhirkevich.compottie.rememberLottieComposition +import io.github.alexzhirkevich.compottie.rememberLottiePainter +import kotlinx.coroutines.launch + +@OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) +@Composable +internal fun LottieDetails( + modifier: Modifier = Modifier, + onDismiss : () -> Unit, + file: LottieFile, +) { + val composition by rememberLottieComposition { + LottieCompositionSpec.Url(file.lottieSource ?: file.jsonSource ?: "") + } + + val animatable = rememberLottieAnimatable() + + var isPlaying by rememberSaveable { mutableStateOf(true) } + var isLooping by rememberSaveable { mutableStateOf(true) } + + var speedIndex by rememberSaveable { + mutableStateOf(0) + } + + LaunchedEffect( + composition, + isPlaying, + isLooping, + speedIndex + ) { + if (!isPlaying) return@LaunchedEffect + + animatable.animate( + composition = composition, + iterations = if (isLooping) Compottie.IterateForever else 1, + initialProgress = if (animatable.progress == 1f) 0f else animatable.progress, + speed = Speed[speedIndex].first, + continueFromPreviousAnimate = false, + ) + + if (composition != null) { + isPlaying = false + } + } + + val coroutineScope = rememberCoroutineScope() + + Card( + modifier = modifier, + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.background + ) + ) { + BoxWithConstraints { + + val isWideScreen = constraints.maxWidth > LocalDensity.current.run { 500.dp.toPx() } + + Column( + modifier = Modifier + .layout { measurable, constraints -> + val w = (constraints.maxWidth * .9).toInt() + val shrinkedConstraints = constraints + .copy(maxWidth = w, minWidth = w) + val placeable = measurable.measure(shrinkedConstraints) + layout(constraints.maxWidth, placeable.height) { + placeable.place((constraints.maxWidth - w) / 2, 0) + } + } + .verticalScroll(rememberScrollState()) + .padding(vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + UserAvatar( + user = file.user, + size = 36.dp + ) + + Column( + modifier = Modifier.weight(1f) + ) { + if (file.name != null) { + Text( + modifier = Modifier.basicMarquee(), + text = file.name, + maxLines = 1, + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.bodyLarge + ) + } + + if (file.user.name != null) { + Text( + text = file.user.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall + ) + } + } + + DownloadButton( + file = file, + compact = !isWideScreen + ) + + IconButton( + onClick = onDismiss, + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "close" + ) + } + } + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f), + colors = CardDefaults.elevatedCardColors( + containerColor = file.bgColor?.let(::parseColorValue) ?: Color.White + ) + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = rememberLottiePainter( + composition = composition, + progress = animatable::value + ), + contentDescription = file.name + ) + } + + if (isWideScreen) { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + PlayButton( + isPlaying = isPlaying, + onPlaying = { isPlaying = it } + ) + Slider( + modifier = Modifier.weight(1f), + value = animatable.value, + onValueChange = { + isPlaying = false + coroutineScope.launch { + animatable.snapTo(progress = it) + } + }, + onValueChangeFinished = { + isPlaying = true + } + ) + + composition?.let { + Text( + "${(it.durationFrames * animatable.value).toInt()} / ${it.durationFrames.toInt()}" + ) + } + + RepeatButton( + isLooping = isLooping, + onClick = { + isLooping = !isLooping + isPlaying = true + } + ) + + SpeedButton( + speedIndex = speedIndex, + onSpeedIndexChange = { + speedIndex = it + } + ) + } + } else { + Column( + modifier = Modifier.padding(top = 32.dp, bottom = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(32.dp) + ) { + Slider( + modifier = Modifier.weight(1f), + value = animatable.value, + onValueChange = { + isPlaying = false + coroutineScope.launch { + animatable.snapTo(progress = it) + } + }, + onValueChangeFinished = { + isPlaying = true + } + ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + RepeatButton( + isLooping = isLooping, + onClick = { + isLooping = !isLooping + isPlaying = true + } + ) + + PlayButton( + isPlaying = isPlaying, + onPlaying = { isPlaying = it } + ) + + SpeedButton( + speedIndex = speedIndex, + onSpeedIndexChange = { + speedIndex = it + } + ) + } + } + } + + + Text( + text = "Tags", + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.labelLarge + ) + + ProvideTextStyle( + MaterialTheme.typography.labelMedium.let { + it.copy( + lineHeight = it.fontSize, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.Both + ) + ) + } + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + file.tags.fastForEach { + Text( + text = it, + modifier = Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondaryContainer) + .padding( + vertical = 8.dp, + horizontal = 16.dp + ) + ) + } + } + } + } + } + } +} + +private val Speed = listOf( + 1f to "1x", + 1.5f to "1.5x", + 2f to "2x", + .25f to ".25x", + .5f to ".5x", + .75f to ".75x", +) + +@Composable +private fun DownloadButton( + modifier: Modifier = Modifier, + file: LottieFile, + compact : Boolean +) { + val uriHandler = LocalUriHandler.current + + if (compact){ + FilledIconButton( + modifier = modifier, + onClick = { + (file.lottieSource ?: file.jsonSource)?.let { + uriHandler.openUri(it) + } + } + ) { + Icon( + imageVector = Icons.Outlined.FileDownload, + contentDescription = null + ) + } + } else { + Button( + modifier = modifier, + onClick = { + (file.lottieSource ?: file.jsonSource)?.let { + uriHandler.openUri(it) + } + } + ) { + Icon( + imageVector = Icons.Outlined.FileDownload, + contentDescription = null + ) + Text("Download") + } + } +} + +@Composable +private fun RepeatButton( + modifier: Modifier = Modifier, + isLooping : Boolean, + onClick : () -> Unit, +) { + IconButton( + modifier = modifier, + onClick = onClick + ) { + Icon( + imageVector = if (isLooping) + Icons.Default.Repeat + else Icons.Default.RepeatOne, + contentDescription = null + ) + } +} + +@Composable +private fun SpeedButton( + modifier: Modifier = Modifier, + speedIndex : Int, + onSpeedIndexChange : (Int) -> Unit +) { + IconButton( + modifier = modifier, + onClick = { + onSpeedIndexChange( + if (speedIndex == Speed.lastIndex) { + 0 + } else { + speedIndex + 1 + } + ) + } + ) { + Text( + text = Speed[speedIndex].second, + fontWeight = FontWeight.Bold, + lineHeight = LocalTextStyle.current.fontSize + ) + } +} + +@Composable +private fun PlayButton( + modifier: Modifier = Modifier, + isPlaying : Boolean, + onPlaying : (Boolean) -> Unit +) { + + IconButton( + modifier = modifier, + onClick = { onPlaying(!isPlaying) } + ) { + Icon( + imageVector = if (isPlaying) + Icons.Default.Pause + else Icons.Default.PlayArrow, + contentDescription = null + ) + } +} \ No newline at end of file diff --git a/example/shared/src/commonMain/kotlin/lottiefiles/LottieFilesScreen.kt b/example/shared/src/commonMain/kotlin/lottiefiles/LottieFilesScreen.kt index cacd2048..95615453 100644 --- a/example/shared/src/commonMain/kotlin/lottiefiles/LottieFilesScreen.kt +++ b/example/shared/src/commonMain/kotlin/lottiefiles/LottieFilesScreen.kt @@ -1,16 +1,17 @@ package lottiefiles -import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -23,21 +24,15 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Repeat -import androidx.compose.material.icons.filled.RepeatOne import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.rounded.ArrowBackIos import androidx.compose.material.icons.rounded.ArrowForwardIos -import androidx.compose.material3.AssistChip import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard @@ -49,18 +44,15 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.SearchBar -import androidx.compose.material3.Slider +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -69,15 +61,11 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.SubcomposeLayout -import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastSumBy import androidx.compose.ui.window.Dialog @@ -86,10 +74,8 @@ import coil3.compose.AsyncImage import io.github.alexzhirkevich.compottie.Compottie import io.github.alexzhirkevich.compottie.LottieCompositionSpec import io.github.alexzhirkevich.compottie.Url -import io.github.alexzhirkevich.compottie.rememberLottieAnimatable import io.github.alexzhirkevich.compottie.rememberLottieComposition import io.github.alexzhirkevich.compottie.rememberLottiePainter -import kotlinx.coroutines.launch import kotlin.math.abs @Composable @@ -97,9 +83,9 @@ internal fun LottieFilesScreen( modifier: Modifier = Modifier.fillMaxSize(), viewModel: LottieFilesViewModel = viewModel { LottieFilesViewModel() } ) { - DisposableEffect(0){ + DisposableEffect(0) { val l = Compottie.logger - Compottie.logger = null +// Compottie.logger = null onDispose { Compottie.logger = l } @@ -122,45 +108,58 @@ internal fun LottieFilesScreen( } } - BoxWithConstraints { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally - ) { - SearchBar( - viewModel = viewModel - ) + Surface { + BoxWithConstraints { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + SearchBar( + viewModel = viewModel + ) - val files = viewModel.files.collectAsState().value + val files = viewModel.files.collectAsState().value - LazyVerticalGrid( - modifier = Modifier.weight(1f), - contentPadding = PaddingValues(24.dp), - columns = GridCells.Adaptive(200.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - items( - items = files, - key = LottieFile::id + val gridState = rememberLazyGridState() + + LazyVerticalGrid( + state = gridState, + modifier = Modifier.weight(1f), + contentPadding = PaddingValues(24.dp), + columns = GridCells.Adaptive(200.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + items( + items = files, + key = LottieFile::id + ) { + LottieCard( + file = it, + onClick = { viewModel.onFileSelected(it) }, + modifier = Modifier + .fillMaxWidth() + ) + } + } + + val pageCount by viewModel.pageCount.collectAsState() + + AnimatedVisibility( + visible = pageCount > 1, + enter = slideInVertically { it } + expandVertically(), + exit = slideOutVertically { it } + shrinkVertically() ) { - LottieCard( - file = it, - onClick = { viewModel.onFileSelected(it) }, + PageSelector( + page = viewModel.page.collectAsState().value, + pageCount = pageCount, + onPageSelected = viewModel::onPageSelected, modifier = Modifier .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 12.dp), ) } } - - PageSelector( - page = viewModel.page.collectAsState().value, - pageCount = viewModel.pageCount.collectAsState().value, - onPageSelected = viewModel::onPageSelected, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 12.dp), - ) } } } @@ -459,7 +458,7 @@ private fun LottieCard( } @Composable -private fun UserAvatar(user: User, size : Dp) { +internal fun UserAvatar(user: User, size : Dp) { val placeholder = rememberVectorPainter(Icons.Default.Person) AsyncImage( @@ -473,243 +472,8 @@ private fun UserAvatar(user: User, size : Dp) { ) } -@OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) -@Composable -private fun LottieDetails( - modifier: Modifier = Modifier, - onDismiss : () -> Unit, - file: LottieFile, -) { - val composition by rememberLottieComposition { - LottieCompositionSpec.Url(file.lottieSource ?: file.jsonSource ?: "") - } - - val animatable = rememberLottieAnimatable() - - var isPlaying by rememberSaveable { mutableStateOf(true) } - var isLooping by rememberSaveable { mutableStateOf(true) } - - var speedIndex by rememberSaveable { - mutableStateOf(0) - } - - LaunchedEffect( - composition, - isPlaying, - isLooping, - speedIndex - ) { - if (!isPlaying) return@LaunchedEffect - - animatable.animate( - composition = composition, - iterations = if (isLooping) Compottie.IterateForever else 1, - initialProgress = if (animatable.progress == 1f) 0f else animatable.progress, - speed = Speed[speedIndex].first, - continueFromPreviousAnimate = false, - ) - - if (composition != null) { - isPlaying = false - } - } - - val coroutineScope = rememberCoroutineScope() - - Card( - modifier = modifier, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.background - ) - ) { - Column( - modifier = Modifier - .layout { measurable, constraints -> - val w = (constraints.maxWidth * .9).toInt() - val shrinkedConstraints = constraints - .copy(maxWidth = w, minWidth = w) - val placeable = measurable.measure(shrinkedConstraints) - layout(constraints.maxWidth, placeable.height){ - placeable.place((constraints.maxWidth - w)/2,0) - } - } - .verticalScroll(rememberScrollState()) - .padding(vertical = 12.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - UserAvatar( - user = file.user, - size = 36.dp - ) - - Column( - modifier.weight(1f) - ) { - if (file.name != null) { - Text( - modifier = Modifier.basicMarquee(), - text = file.name, - maxLines = 1, - fontWeight = FontWeight.Bold, - style = MaterialTheme.typography.bodyLarge - ) - } - - if (file.user.name != null) { - Text( - text = file.user.name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall - ) - } - } - IconButton( - onClick = onDismiss, - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "close" - ) - } - } - ElevatedCard( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1f), - colors = CardDefaults.elevatedCardColors( - containerColor = file.bgColor?.let(::parseColorValue) ?: Color.White - ) - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = rememberLottiePainter( - composition = composition, - progress = animatable::value - ), - contentDescription = file.name - ) - } - - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically - ) { - IconButton( - onClick = { - isPlaying = !isPlaying - } - ) { - Icon( - imageVector = if (isPlaying) - Icons.Default.Pause - else Icons.Default.PlayArrow, - contentDescription = null - ) - } - Slider( - modifier = Modifier.weight(1f), - value = animatable.value, - onValueChange = { - isPlaying = false - coroutineScope.launch { - animatable.snapTo(progress = it) - } - }, - onValueChangeFinished = { - isPlaying = true - } - ) - - composition?.let { - Text( - "${(it.durationFrames * animatable.value).toInt()} / ${it.durationFrames.toInt()}" - ) - } - - IconButton( - onClick = { - isLooping = !isLooping - isPlaying = true - } - ) { - Icon( - imageVector = if (isLooping) - Icons.Default.Repeat - else Icons.Default.RepeatOne, - contentDescription = null - ) - } - - IconButton( - onClick = { - speedIndex = if (speedIndex == Speed.lastIndex) { - 0 - } else { - speedIndex + 1 - } - } - ) { - Text( - text = Speed[speedIndex].second, - fontWeight = FontWeight.Bold, - lineHeight = LocalTextStyle.current.fontSize - ) - } - } - - Text( - text = "Tags", - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.labelLarge - ) - - ProvideTextStyle( - MaterialTheme.typography.labelMedium.let { - it.copy( - lineHeight = it.fontSize, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.Both - ) - ) - } - ) { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - file.tags.fastForEach { - Text( - text = it, - modifier = Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.secondaryContainer) - .padding( - horizontal = 12.dp, - vertical = 4.dp - ) - ) - } - } - } - } - } -} -private val Speed = listOf( - 1f to "1x", - 1.5f to "1.5x", - 2f to "2x", - .25f to ".25x", - .5f to ".5x", - .75f to ".75x", -) private const val ALPHA_MASK = 0xFF000000.toInt()