diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7d85de75a..b2a175af2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -230,7 +230,7 @@ dependencies { implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") { exclude(group = "androidx.viewpager", module = "viewpager") } - implementation("dev.chrisbanes.insetter:insetter:0.6.0") + implementation("dev.chrisbanes.insetter:insetter:0.6.1") // Conductor val conductorVersion = "3.0.0" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 019ff8a7a..cf76d9945 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -10,6 +10,7 @@ import android.os.Looper import android.view.Gravity import android.view.Menu import android.view.ViewGroup +import android.view.Window import android.widget.Toast import androidx.appcompat.view.ActionMode import androidx.core.animation.doOnEnd @@ -30,6 +31,7 @@ import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.Router import com.google.android.material.appbar.AppBarLayout import com.google.android.material.navigation.NavigationBarView +import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import dev.chrisbanes.insetter.applyInsetter import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R @@ -126,6 +128,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { // 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) EXHMigrations.upgrade(preferences) else false @@ -144,6 +151,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { // Draw edge-to-edge WindowCompat.setDecorFitsSystemWindows(window, false) binding.fabLayout.rootFab.applyInsetter { + ignoreVisibility(true) type(navigationBars = true) { margin() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index b2f90154f..2999c1ad2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -1,8 +1,7 @@ package eu.kanade.tachiyomi.ui.manga -import android.animation.Animator -import android.animation.AnimatorListenerAdapter import android.app.Activity +import android.app.ActivityOptions import android.content.Context import android.content.Intent import android.graphics.Bitmap @@ -98,7 +97,6 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast -import eu.kanade.tachiyomi.util.view.getCoordinates import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView @@ -450,18 +448,7 @@ class MangaController : fab.setOnClickListener { val item = presenter.getNextUnreadChapter() if (item != null) { - // Get coordinates and start animation - actionFab?.getCoordinates()?.let { coordinates -> - binding.revealView.showRevealEffect( - coordinates.x, - coordinates.y, - object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator?) { - openChapter(item.chapter, true) - } - } - ) - } + openChapter(item.chapter, it) } } } @@ -494,20 +481,6 @@ class MangaController : super.onDestroyView(view) } - override fun onActivityResumed(activity: Activity) { - if (view == null) return - - // Check if animation view is visible - if (binding.revealView.isVisible) { - // Show the unreveal effect - actionFab?.getCoordinates()?.let { coordinates -> - binding.revealView.hideRevealEffect(coordinates.x, coordinates.y, 1920) - } - } - - super.onActivityResumed(activity) - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.manga, menu) } @@ -1133,13 +1106,21 @@ class MangaController : } } - fun openChapter(chapter: Chapter, hasAnimation: Boolean = false) { + private fun openChapter(chapter: Chapter, sharedElement: View? = null) { val activity = activity ?: return val intent = ReaderActivity.newIntent(activity, presenter.manga, chapter) - if (hasAnimation) { - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) + activity.apply { + if (sharedElement != null) { + val activityOptions = ActivityOptions.makeSceneTransitionAnimation( + activity, + sharedElement, + ReaderActivity.SHARED_ELEMENT_NAME + ) + startActivity(intent, activityOptions.toBundle()) + } else { + startActivity(intent) + } } - startActivity(intent) } override fun onItemClick(view: View?, position: Int): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 7d039ba79..0866c37a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -22,7 +22,9 @@ import android.view.HapticFeedbackConstants import android.view.KeyEvent import android.view.Menu 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 @@ -42,6 +44,8 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter @@ -138,6 +142,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() private const val ENABLED_BUTTON_IMAGE_ALPHA = 255 private const val DISABLED_BUTTON_IMAGE_ALPHA = 64 + + const val SHARED_ELEMENT_NAME = "reader_shared_element_root" } private val preferences: PreferencesHelper by injectLazy() @@ -195,6 +201,17 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() */ override fun onCreate(savedInstanceState: Bundle?) { applyAppTheme(preferences) + + // Setup shared element transitions + window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) + findViewById<View>(android.R.id.content).transitionName = SHARED_ELEMENT_NAME + setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback()) + window.sharedElementEnterTransition = buildContainerTransform(true) + window.sharedElementReturnTransition = buildContainerTransform(false) + + // Postpone custom transition until manga ready + postponeEnterTransition() + super.onCreate(savedInstanceState) binding = ReaderActivityBinding.inflate(layoutInflater) @@ -392,6 +409,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() return handled || super.dispatchGenericMotionEvent(event) } + private fun buildContainerTransform(entering: Boolean): MaterialContainerTransform { + return MaterialContainerTransform(this, entering).apply { + addTarget(android.R.id.content) + } + } + /** * Initializes the reader menu. It sets up click listeners and the initial visibility. */ @@ -1122,6 +1145,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() } } binding.readerContainer.addView(loadingIndicator) + + startPostponedEnterTransition() } private fun showReadingModeToast(mode: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt deleted file mode 100755 index 6bd1546f1..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/RevealAnimationView.kt +++ /dev/null @@ -1,73 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.animation.Animator -import android.content.Context -import android.util.AttributeSet -import android.view.View -import android.view.ViewAnimationUtils -import androidx.core.animation.doOnEnd -import androidx.core.view.isInvisible -import androidx.core.view.isVisible - -class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - View(context, attrs) { - - /** - * Hides the animation view with a animation - * - * @param centerX x starting point - * @param centerY y starting point - * @param initialRadius size of radius of animation - */ - fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) { - // Make the view visible. - this.isVisible = true - - // Create the animation (the final radius is zero). - val anim = ViewAnimationUtils.createCircularReveal( - this, - centerX, - centerY, - initialRadius.toFloat(), - 0f - ) - - // Set duration of animation. - anim.duration = 500 - - // make the view invisible when the animation is done - anim.doOnEnd { - this@RevealAnimationView.isInvisible = true - } - - anim.start() - } - - /** - * Fills the animation view with a animation - * - * @param centerX x starting point - * @param centerY y starting point - * @param listener animation listener - */ - fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener) { - this.isVisible = true - - val height = this.height - - // Create animation - val anim = ViewAnimationUtils.createCircularReveal( - this, - centerX, - centerY, - 0f, - height.toFloat() - ) - - // Set duration of animation - anim.duration = 350 - - anim.addListener(listener) - anim.start() - } -} diff --git a/app/src/main/res/layout-sw720dp/manga_controller.xml b/app/src/main/res/layout-sw720dp/manga_controller.xml index 3a727142b..e4fa42b14 100644 --- a/app/src/main/res/layout-sw720dp/manga_controller.xml +++ b/app/src/main/res/layout-sw720dp/manga_controller.xml @@ -6,14 +6,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - <eu.kanade.tachiyomi.widget.RevealAnimationView - android:id="@+id/reveal_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/colorAccent" - android:elevation="5dp" - android:visibility="invisible" /> - <eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout android:id="@+id/swipe_refresh" android:layout_width="match_parent" diff --git a/app/src/main/res/layout/manga_controller.xml b/app/src/main/res/layout/manga_controller.xml index 89d945a96..adad3b699 100755 --- a/app/src/main/res/layout/manga_controller.xml +++ b/app/src/main/res/layout/manga_controller.xml @@ -6,14 +6,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - <eu.kanade.tachiyomi.widget.RevealAnimationView - android:id="@+id/reveal_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?attr/colorAccent" - android:elevation="5dp" - android:visibility="invisible" /> - <eu.kanade.tachiyomi.widget.ThemedSwipeRefreshLayout android:id="@+id/swipe_refresh" android:layout_width="match_parent"