Forráskód Böngészése

Adjust screen transitions (#8707)

* Fade transition between main navigation tabs
* Shared axis X between screen stacks

Activity transition is using a "close enough" shared axis X xml animation
Ivan Iskandar 2 éve
szülő
commit
82a3a98a5a

+ 1 - 0
app/build.gradle.kts

@@ -268,6 +268,7 @@ dependencies {
     implementation(libs.cascade)
     implementation(libs.bundles.voyager)
     implementation(libs.wheelpicker)
+    implementation(libs.materialmotion.core)
 
     // Logging
     implementation(libs.logcat)

+ 0 - 17
app/src/main/java/eu/kanade/presentation/util/Constants.kt

@@ -1,10 +1,5 @@
 package eu.kanade.presentation.util
 
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.with
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.ui.unit.dp
@@ -29,15 +24,3 @@ class Padding {
 
 val MaterialTheme.padding: Padding
     get() = Padding()
-
-object Transition {
-
-    /**
-     * Mimics [eu.kanade.tachiyomi.ui.base.controller.OneWayFadeChangeHandler]
-     */
-    val OneWayFade = fadeIn(
-        animationSpec = tween(
-            easing = LinearEasing,
-        ),
-    ) with ExitTransition.None
-}

+ 19 - 0
app/src/main/java/eu/kanade/presentation/util/Navigator.kt

@@ -1,8 +1,13 @@
 package eu.kanade.presentation.util
 
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ProvidableCompositionLocal
 import androidx.compose.runtime.staticCompositionLocalOf
+import cafe.adriel.voyager.core.stack.StackEvent
 import cafe.adriel.voyager.navigator.Navigator
+import cafe.adriel.voyager.transitions.ScreenTransition
+import soup.compose.material.motion.animation.materialSharedAxisX
+import soup.compose.material.motion.animation.rememberSlideDistance
 
 /**
  * For invoking back press to the parent activity
@@ -12,3 +17,17 @@ val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositio
 interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
     suspend fun onReselect(navigator: Navigator) {}
 }
+
+@Composable
+fun DefaultNavigatorScreenTransition(navigator: Navigator) {
+    val slideDistance = rememberSlideDistance()
+    ScreenTransition(
+        navigator = navigator,
+        transition = {
+            materialSharedAxisX(
+                forward = navigator.lastEvent != StackEvent.Pop,
+                slideDistance = slideDistance,
+            )
+        },
+    )
+}

+ 9 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt

@@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.with
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
@@ -41,7 +42,6 @@ import eu.kanade.domain.source.service.SourcePreferences
 import eu.kanade.presentation.components.NavigationBar
 import eu.kanade.presentation.components.NavigationRail
 import eu.kanade.presentation.components.Scaffold
-import eu.kanade.presentation.util.Transition
 import eu.kanade.presentation.util.isTabletUi
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.browse.BrowseTab
@@ -55,6 +55,8 @@ import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
+import soup.compose.material.motion.animation.materialFadeThroughIn
+import soup.compose.material.motion.animation.materialFadeThroughOut
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 
@@ -64,6 +66,8 @@ object HomeScreen : Screen {
     private val openTabEvent = Channel<Tab>()
     private val showBottomNavEvent = Channel<Boolean>()
 
+    private const val TabFadeDuration = 200
+
     private val tabs = listOf(
         LibraryTab,
         UpdatesTab,
@@ -116,7 +120,10 @@ object HomeScreen : Screen {
                         ) {
                             AnimatedContent(
                                 targetState = tabNavigator.current,
-                                transitionSpec = { Transition.OneWayFade },
+                                transitionSpec = {
+                                    materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) with
+                                        materialFadeThroughOut(durationMillis = TabFadeDuration)
+                                },
                                 content = {
                                     tabNavigator.saveableState(key = "currentTab", it) {
                                         it.Content()

+ 2 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -7,7 +7,6 @@ import android.graphics.Color
 import android.os.Build
 import android.os.Bundle
 import android.view.View
-import android.view.Window
 import android.widget.Toast
 import androidx.activity.compose.BackHandler
 import androidx.compose.foundation.isSystemInDarkTheme
@@ -42,16 +41,14 @@ import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.Navigator
 import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
 import cafe.adriel.voyager.navigator.currentOrThrow
-import cafe.adriel.voyager.transitions.ScreenTransition
 import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
 import eu.kanade.domain.base.BasePreferences
 import eu.kanade.domain.category.model.Category
 import eu.kanade.domain.library.service.LibraryPreferences
 import eu.kanade.domain.source.service.SourcePreferences
 import eu.kanade.domain.ui.UiPreferences
 import eu.kanade.presentation.components.AppStateBanners
-import eu.kanade.presentation.util.Transition
+import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
 import eu.kanade.presentation.util.collectAsState
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.Migrations
@@ -118,11 +115,6 @@ class MainActivity : BaseActivity() {
         // Prevent splash screen showing up on configuration changes
         val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
 
-        // Set up shared element transition and disable overlay so views don't show above system bars
-        window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
-        setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
-        window.sharedElementsUseOverlay = false
-
         super.onCreate(savedInstanceState)
 
         val didMigration = if (savedInstanceState == null) {
@@ -196,7 +188,7 @@ class MainActivity : BaseActivity() {
                     }
                     Box(modifier = boxModifier) {
                         // Shows current screen
-                        ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade })
+                        DefaultNavigatorScreenTransition(navigator = navigator)
                     }
 
                     // Pop source-related screens when incognito mode is turned off

+ 2 - 21
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -21,9 +21,7 @@ import android.view.KeyEvent
 import android.view.Menu
 import android.view.MenuItem
 import android.view.MotionEvent
-import android.view.View
 import android.view.View.LAYER_TYPE_HARDWARE
-import android.view.Window
 import android.view.WindowManager
 import android.view.animation.Animation
 import android.view.animation.AnimationUtils
@@ -43,11 +41,9 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
 import com.google.android.material.shape.MaterialShapeDrawable
 import com.google.android.material.slider.Slider
 import com.google.android.material.transition.platform.MaterialContainerTransform
-import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
 import dev.chrisbanes.insetter.applyInsetter
 import eu.kanade.domain.base.BasePreferences
 import eu.kanade.domain.manga.model.Manga
-import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.notification.NotificationReceiver
 import eu.kanade.tachiyomi.data.notification.Notifications
@@ -115,9 +111,6 @@ class ReaderActivity : BaseActivity() {
 
         private const val ENABLED_BUTTON_IMAGE_ALPHA = 255
         private const val DISABLED_BUTTON_IMAGE_ALPHA = 64
-
-        const val EXTRA_IS_TRANSITION = "${BuildConfig.APPLICATION_ID}.READER_IS_TRANSITION"
-        const val SHARED_ELEMENT_NAME = "reader_shared_element_root"
     }
 
     private val readerPreferences: ReaderPreferences by injectLazy()
@@ -168,20 +161,7 @@ class ReaderActivity : BaseActivity() {
      */
     override fun onCreate(savedInstanceState: Bundle?) {
         registerSecureActivity(this)
-
-        // Setup shared element transitions
-        if (intent.extras?.getBoolean(EXTRA_IS_TRANSITION) == true) {
-            window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
-            findViewById<View>(android.R.id.content)?.let { contentView ->
-                contentView.transitionName = SHARED_ELEMENT_NAME
-                setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
-                window.sharedElementEnterTransition = buildContainerTransform(true)
-                window.sharedElementReturnTransition = buildContainerTransform(false)
-
-                // Postpone custom transition until manga ready
-                postponeEnterTransition()
-            }
-        }
+        overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit)
 
         super.onCreate(savedInstanceState)
 
@@ -357,6 +337,7 @@ class ReaderActivity : BaseActivity() {
     override fun finish() {
         viewModel.onActivityFinish()
         super.finish()
+        overridePendingTransition(R.anim.shared_axis_x_pop_enter, R.anim.shared_axis_x_pop_exit)
     }
 
     override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {

+ 3 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt

@@ -6,14 +6,13 @@ import cafe.adriel.voyager.core.screen.Screen
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.Navigator
 import cafe.adriel.voyager.navigator.currentOrThrow
-import cafe.adriel.voyager.transitions.ScreenTransition
 import eu.kanade.presentation.components.TwoPanelBox
 import eu.kanade.presentation.more.settings.screen.AboutScreen
 import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
 import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
 import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
+import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
 import eu.kanade.presentation.util.LocalBackPress
-import eu.kanade.presentation.util.Transition
 import eu.kanade.presentation.util.isTabletUi
 
 class SettingsScreen private constructor(
@@ -42,10 +41,7 @@ class SettingsScreen private constructor(
                         }
                     }
                     CompositionLocalProvider(LocalBackPress provides pop) {
-                        ScreenTransition(
-                            navigator = it,
-                            transition = { Transition.OneWayFade },
-                        )
+                        DefaultNavigatorScreenTransition(navigator = it)
                     }
                 },
             )
@@ -65,12 +61,7 @@ class SettingsScreen private constructor(
                             SettingsMainScreen.Content(twoPane = true)
                         }
                     },
-                    endContent = {
-                        ScreenTransition(
-                            navigator = it,
-                            transition = { Transition.OneWayFade },
-                        )
-                    },
+                    endContent = { DefaultNavigatorScreenTransition(navigator = it) },
                 )
             }
         }

+ 6 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt

@@ -30,6 +30,7 @@ class WebViewActivity : BaseActivity() {
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
+        overridePendingTransition(R.anim.shared_axis_x_push_enter, R.anim.shared_axis_x_push_exit)
         super.onCreate(savedInstanceState)
 
         if (!WebViewUtil.supportsWebView(this)) {
@@ -58,6 +59,11 @@ class WebViewActivity : BaseActivity() {
         }
     }
 
+    override fun finish() {
+        super.finish()
+        overridePendingTransition(R.anim.shared_axis_x_pop_enter, R.anim.shared_axis_x_pop_exit)
+    }
+
     private fun shareWebpage(url: String) {
         try {
             startActivity(url.toUri().toShareIntent(this, type = "text/plain"))

+ 13 - 0
app/src/main/res/anim-v33/shared_axis_x_pop_enter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="-30dp"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="0" />
+    <alpha
+        android:duration="195"
+        android:fromAlpha="0"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:toAlpha="1" />
+</set>

+ 14 - 0
app/src/main/res/anim-v33/shared_axis_x_pop_exit.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="0"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="30dp" />
+
+    <alpha
+        android:duration="195"
+        android:fromAlpha="1"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:toAlpha="0" />
+</set>

+ 13 - 0
app/src/main/res/anim-v33/shared_axis_x_push_enter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="30dp"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="0" />
+    <alpha
+        android:duration="195"
+        android:fromAlpha="0"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:toAlpha="1" />
+</set>

+ 14 - 0
app/src/main/res/anim-v33/shared_axis_x_push_exit.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="0"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="-30dp" />
+
+    <alpha
+        android:duration="195"
+        android:fromAlpha="1"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:toAlpha="0" />
+</set>

+ 13 - 0
app/src/main/res/anim/shared_axis_x_pop_enter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="-5%p"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="0" />
+    <alpha
+        android:duration="195"
+        android:fromAlpha="0"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:toAlpha="1" />
+</set>

+ 14 - 0
app/src/main/res/anim/shared_axis_x_pop_exit.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="0"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="5%p" />
+
+    <alpha
+        android:duration="195"
+        android:fromAlpha="1"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:toAlpha="0" />
+</set>

+ 13 - 0
app/src/main/res/anim/shared_axis_x_push_enter.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="5%p"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="0" />
+    <alpha
+        android:duration="195"
+        android:fromAlpha="0"
+        android:interpolator="@android:interpolator/linear_out_slow_in"
+        android:toAlpha="1" />
+</set>

+ 14 - 0
app/src/main/res/anim/shared_axis_x_push_exit.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:duration="300"
+        android:fromXDelta="0"
+        android:interpolator="@android:interpolator/fast_out_slow_in"
+        android:toXDelta="-5%p" />
+
+    <alpha
+        android:duration="195"
+        android:fromAlpha="1"
+        android:interpolator="@android:interpolator/fast_out_linear_in"
+        android:toAlpha="0" />
+</set>

+ 1 - 0
gradle/libs.versions.toml

@@ -62,6 +62,7 @@ directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
 insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
 cascade = "me.saket.cascade:cascade-compose:2.0.0-beta1"
 wheelpicker = "com.github.commandiron:WheelPickerCompose:1.0.11"
+materialmotion-core = "io.github.fornewid:material-motion-compose-core:0.10.3"
 
 logcat = "com.squareup.logcat:logcat:0.1"