198 lines
7.6 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 plus.rua.project
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import plus.rua.project.ui.AboutScreen
import plus.rua.project.ui.CalendarMonthView
import plus.rua.project.ui.LicensesScreen
import plus.rua.project.ui.lerp
private enum class Screen { Main, About, Licenses }
/** 返回手势动画:顶层页面滑出 + 淡出 + 缩小 + 圆角阴影 */
private fun GraphicsLayerScope.applyDismissTransform(progress: Float) {
translationX = progress * size.width * 0.5f
scaleX = 1f - progress * 0.08f
scaleY = 1f - progress * 0.08f
alpha = 1f - progress
shadowElevation = 32.dp.toPx() * progress
shape = RoundedCornerShape(28.dp * progress)
clip = progress > 0.01f
}
/** 底层页面缩放:随返回进度从 baseScale 放大到 1.0 */
private fun GraphicsLayerScope.applyRevealTransform(
progress: Float,
forwardProgress: Float,
isForwardAnimating: Boolean
) {
val baseScale = 0.92f + 0.08f * progress
val scale = if (isForwardAnimating) lerp(1f, baseScale, forwardProgress) else baseScale
scaleX = scale
scaleY = scale
}
/** 前向导航动画:新页面从右侧滑入 */
private fun GraphicsLayerScope.applyEnterTransform(progress: Float) {
translationX = (1f - progress) * size.width
alpha = progress
}
/**
* 应用入口 Composable根据系统主题切换明暗 ColorScheme 并管理页面导航。
*
* 使用 Box 分层布局替代 AnimatedContent支持预测性返回手势
* - 底层页面始终组合(状态保持),缩放显现
* - 顶层页面在手势期间平滑位移、缩放、圆角、阴影
* - 前向导航从右侧滑入,返回导航跟手驱动
*/
@Composable
@Preview(name = "Calendar App")
fun App() {
var currentScreen by remember { mutableStateOf(Screen.Main) }
var backProgress by remember { mutableFloatStateOf(0f) }
val scope = rememberCoroutineScope()
val backAnimProgress = remember { Animatable(0f) }
val effectiveBackProgress by remember {
derivedStateOf { maxOf(backProgress, backAnimProgress.value) }
}
var forwardTarget by remember { mutableStateOf<Screen?>(null) }
val forwardProgress = remember { Animatable(1f) }
val handleBack: () -> Unit = {
scope.launch {
backAnimProgress.snapTo(backProgress)
backProgress = 0f
backAnimProgress.animateTo(1f, tween(250, easing = FastOutSlowInEasing))
currentScreen = when (currentScreen) {
Screen.About -> Screen.Main
Screen.Licenses -> Screen.About
else -> currentScreen
}
backAnimProgress.snapTo(0f)
}
}
val handleCancel: () -> Unit = {
scope.launch {
backAnimProgress.snapTo(backProgress)
backProgress = 0f
backAnimProgress.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow))
}
}
val navigateTo: (Screen) -> Unit = { target ->
if (forwardTarget == null) {
scope.launch {
forwardTarget = target
currentScreen = target
forwardProgress.snapTo(0f)
forwardProgress.animateTo(1f, tween(350, easing = FastOutSlowInEasing))
forwardTarget = null
}
}
}
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = colorScheme) {
Box(modifier = Modifier.fillMaxSize()) {
// Layer 0: CalendarMonthView始终组合以保持状态
CalendarMonthView(
modifier = Modifier.graphicsLayer {
if (currentScreen != Screen.Main) {
applyRevealTransform(
effectiveBackProgress,
forwardProgress.value,
forwardTarget != null
)
}
},
onNavigateToAbout = { navigateTo(Screen.About) }
)
// Layer 1: AboutScreenAbout 或 Licenses 页面时组合)
if (currentScreen == Screen.About || currentScreen == Screen.Licenses) {
AboutScreen(
onBack = {
if (currentScreen == Screen.About) handleBack()
},
onNavigateToLicenses = {
if (currentScreen == Screen.About) navigateTo(Screen.Licenses)
},
modifier = Modifier.graphicsLayer {
when (currentScreen) {
Screen.Licenses -> applyRevealTransform(
effectiveBackProgress,
forwardProgress.value,
forwardTarget == Screen.Licenses
)
Screen.About -> {
val bp = effectiveBackProgress
val fp = forwardProgress.value
when {
bp > 0.001f -> applyDismissTransform(bp)
fp < 0.999f && forwardTarget == Screen.About -> applyEnterTransform(fp)
}
}
else -> {}
}
}
)
}
// Layer 2: LicensesScreenLicenses 页面时组合)
if (currentScreen == Screen.Licenses) {
LicensesScreen(
onBack = handleBack,
modifier = Modifier.graphicsLayer {
val bp = effectiveBackProgress
val fp = forwardProgress.value
when {
bp > 0.001f -> applyDismissTransform(bp)
fp < 0.999f && forwardTarget == Screen.Licenses -> applyEnterTransform(fp)
}
}
)
}
// 预测性返回手势
if (currentScreen != Screen.Main) {
PredictiveBackHandler(
enabled = backProgress == 0f && !backAnimProgress.isRunning && forwardTarget == null,
onProgress = { backProgress = it },
onBack = handleBack,
onCancel = handleCancel
)
}
}
}
}