Files
radiola-android/app/src/main/java/com/radiola/ui/components/FlipCover.kt
nk a46e437351 feat(player): 3D-переворот обложки при смене трека
Вместо простой смены — эффект переворота (как страница альбома/пластинка):
старая обложка улетает передней гранью (0–90°), новая прилетает задней
(90–180°, контр-вращение чтобы не зеркалилась). Компонент FlipCover,
подключён к обложке в плеере; срабатывает при смене coverUrl трека.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:19:10 +03:00

79 lines
2.9 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.radiola.ui.components
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import coil.compose.AsyncImage
/**
* Обложка с эффектом 3D-переворота при смене изображения — как будто
* перелистывается страница альбома / пластинка. Старая обложка «улетает»
* передней стороной (090°), новая «прилетает» задней (90180°).
*/
@Composable
fun FlipCover(
model: String?,
contentDescription: String?,
modifier: Modifier = Modifier,
fallback: @Composable () -> Unit,
) {
var current by remember { mutableStateOf(model) }
var previous by remember { mutableStateOf(model) }
val rotation = remember { Animatable(0f) }
LaunchedEffect(model) {
if (model != current) {
previous = current
current = model
rotation.snapTo(0f)
rotation.animateTo(180f, animationSpec = tween(620, easing = FastOutSlowInEasing))
// Оседаем: новая обложка становится «лицом», угол 0 — без рывка.
previous = current
rotation.snapTo(0f)
}
}
val angle = rotation.value
val showFront = angle <= 90f
val faceModel = if (showFront) previous else current
Box(
modifier = modifier.graphicsLayer {
rotationY = angle
cameraDistance = 16f * density
},
contentAlignment = Alignment.Center,
) {
// Заднюю грань контр-вращаем, чтобы изображение не было зеркальным.
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer { rotationY = if (showFront) 0f else 180f },
contentAlignment = Alignment.Center,
) {
if (!faceModel.isNullOrBlank()) {
AsyncImage(
model = faceModel,
contentDescription = contentDescription,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
)
} else {
fallback()
}
}
}
}