diff --git a/androidApp/src/main/assets/app_icon.png b/androidApp/src/main/assets/app_icon.png index 53fc536..d4491c3 100644 Binary files a/androidApp/src/main/assets/app_icon.png and b/androidApp/src/main/assets/app_icon.png differ diff --git a/androidApp/src/main/res/drawable/ic_launcher_background.xml b/androidApp/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index e93e11a..0000000 --- a/androidApp/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/androidApp/src/main/res/drawable/ic_launcher_foreground.xml b/androidApp/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/androidApp/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cf..0000000 --- a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cf..0000000 --- a/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png index a571e60..9c67d11 100644 Binary files a/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png and b/androidApp/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png index 61da551..9c67d11 100644 Binary files a/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/androidApp/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png index c41dd28..8c16c78 100644 Binary files a/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png and b/androidApp/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png index db5080a..8c16c78 100644 Binary files a/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/androidApp/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png index 6dba46d..333be02 100644 Binary files a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png and b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png index da31a87..333be02 100644 Binary files a/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/androidApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png index 15ac681..78b044c 100644 Binary files a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index b216f2d..78b044c 100644 Binary files a/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/androidApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png index f25a419..9a9fedb 100644 Binary files a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index e96783c..9a9fedb 100644 Binary files a/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 997fd04..3b1fe4e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ material3 = "1.10.0-alpha05" kotlinx-datetime = "0.8.0" tyme4kt = "1.4.5" sketch = "4.4.0" +navigationevent = "1.0.1" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -34,6 +35,7 @@ tyme4kt = { module = "cn.6tail:tyme4kt", version.ref = "tyme4kt" } sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" } sketch-animated-gif = { module = "io.github.panpf.sketch4:sketch-animated-gif", version.ref = "sketch" } sketch-compose-resources = { module = "io.github.panpf.sketch4:sketch-compose-resources", version.ref = "sketch" } +navigationevent-compose = { module = "org.jetbrains.androidx.navigationevent:navigationevent-compose", version.ref = "navigationevent" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index e087e53..b8312ec 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -49,6 +49,7 @@ kotlin { implementation(libs.sketch.compose) implementation(libs.sketch.animated.gif) implementation(libs.sketch.compose.resources) + implementation(libs.navigationevent.compose) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/shared/src/androidMain/kotlin/plus/rua/project/Platform.android.kt b/shared/src/androidMain/kotlin/plus/rua/project/Platform.android.kt index 6b4d872..f1ae397 100644 --- a/shared/src/androidMain/kotlin/plus/rua/project/Platform.android.kt +++ b/shared/src/androidMain/kotlin/plus/rua/project/Platform.android.kt @@ -2,8 +2,11 @@ package plus.rua.project import android.os.Build import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope -import kotlinx.coroutines.CancellationException +import androidx.compose.runtime.LaunchedEffect +import androidx.navigationevent.NavigationEventInfo +import androidx.navigationevent.NavigationEventTransitionState +import androidx.navigationevent.compose.NavigationBackHandler +import androidx.navigationevent.compose.rememberNavigationEventState class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" @@ -22,19 +25,19 @@ actual fun PredictiveBackHandler( onBack: () -> Unit, onCancel: () -> Unit ) { - if (Build.VERSION.SDK_INT >= 34) { - rememberCoroutineScope() - androidx.activity.compose.PredictiveBackHandler(enabled) { progress -> - try { - progress.collect { backEvent -> - onProgress(backEvent.progress) - } - onBack() - } catch (e: CancellationException) { - onCancel() - } + val navState = rememberNavigationEventState(NavigationEventInfo.None) + + NavigationBackHandler( + state = navState, + isBackEnabled = enabled, + onBackCancelled = onCancel, + onBackCompleted = onBack + ) + + LaunchedEffect(navState.transitionState) { + val ts = navState.transitionState + if (ts is NavigationEventTransitionState.InProgress) { + onProgress(ts.latestEvent.progress) } - } else { - androidx.activity.compose.BackHandler(enabled = enabled, onBack = onBack) } } diff --git a/shared/src/commonMain/composeResources/files/app_icon.png b/shared/src/commonMain/composeResources/files/app_icon.png index 53fc536..d4491c3 100644 Binary files a/shared/src/commonMain/composeResources/files/app_icon.png and b/shared/src/commonMain/composeResources/files/app_icon.png differ diff --git a/shared/src/commonMain/kotlin/plus/rua/project/App.kt b/shared/src/commonMain/kotlin/plus/rua/project/App.kt index 58956fe..8e14e37 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/App.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/App.kt @@ -21,6 +21,7 @@ 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 @@ -32,6 +33,35 @@ 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 * 0.8f + 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 并管理页面导航。 * @@ -59,7 +89,7 @@ fun App() { scope.launch { backAnimProgress.snapTo(backProgress) backProgress = 0f - backAnimProgress.animateTo(1f, tween(200, easing = FastOutSlowInEasing)) + backAnimProgress.animateTo(1f, tween(250, easing = FastOutSlowInEasing)) currentScreen = when (currentScreen) { Screen.About -> Screen.Main Screen.Licenses -> Screen.About @@ -96,14 +126,11 @@ fun App() { CalendarMonthView( modifier = Modifier.graphicsLayer { if (currentScreen != Screen.Main) { - val baseScale = 0.92f + 0.08f * effectiveBackProgress - val scale = if (forwardTarget != null) { - lerp(1f, baseScale, forwardProgress.value) - } else { - baseScale - } - scaleX = scale - scaleY = scale + applyRevealTransform( + effectiveBackProgress, + forwardProgress.value, + forwardTarget != null + ) } }, onNavigateToAbout = { navigateTo(Screen.About) } @@ -120,34 +147,18 @@ fun App() { }, modifier = Modifier.graphicsLayer { when (currentScreen) { - Screen.Licenses -> { - val baseScale = 0.92f + 0.08f * effectiveBackProgress - val scale = if (forwardTarget == Screen.Licenses) { - lerp(1f, baseScale, forwardProgress.value) - } else { - baseScale - } - scaleX = scale - scaleY = scale - } + Screen.Licenses -> applyRevealTransform( + effectiveBackProgress, + forwardProgress.value, + forwardTarget == Screen.Licenses + ) Screen.About -> { val bp = effectiveBackProgress val fp = forwardProgress.value when { - bp > 0.001f -> { - translationX = bp * size.width * 0.3f - scaleX = 1f - bp * 0.05f - scaleY = 1f - bp * 0.05f - shadowElevation = 32.dp.toPx() * bp - shape = RoundedCornerShape(28.dp * bp) - clip = bp > 0.01f - } - - fp < 0.999f && forwardTarget == Screen.About -> { - translationX = (1f - fp) * size.width - alpha = fp - } + bp > 0.001f -> applyDismissTransform(bp) + fp < 0.999f && forwardTarget == Screen.About -> applyEnterTransform(fp) } } @@ -165,19 +176,8 @@ fun App() { val bp = effectiveBackProgress val fp = forwardProgress.value when { - bp > 0.001f -> { - translationX = bp * size.width * 0.3f - scaleX = 1f - bp * 0.05f - scaleY = 1f - bp * 0.05f - shadowElevation = 32.dp.toPx() * bp - shape = RoundedCornerShape(28.dp * bp) - clip = bp > 0.01f - } - - fp < 0.999f && forwardTarget == Screen.Licenses -> { - translationX = (1f - fp) * size.width - alpha = fp - } + bp > 0.001f -> applyDismissTransform(bp) + fp < 0.999f && forwardTarget == Screen.Licenses -> applyEnterTransform(fp) } } )