diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 534d0af3dc..a47c049207 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -165,6 +165,7 @@ dependencies { implementation(androidx.palette) implementation(androidx.activity) implementation(androidx.core) + implementation(androidx.core.splashscreen) implementation(libs.flexbox) implementation(androidx.window) implementation(androidx.swiperefreshlayout) diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc5f..0000000000 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc5f..0000000000 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf8a00ba63..2e0348e25f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,7 +54,7 @@ android:name=".ui.main.MainActivity" android:windowSoftInputMode="adjustNothing" android:label="@string/app_short_name" - android:theme="@style/Theme.Splash" + android:theme="@style/Theme.Tachiyomi.SplashScreen" android:exported="true"> @@ -74,7 +74,7 @@ @@ -95,7 +95,7 @@ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 0446bd0265..a490a358ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -1129,6 +1129,7 @@ open class LibraryController( emptyList() }, ) + (activity as? MainActivity)?.ready = true } adapter.setItems(mangaMap) if (binding.libraryGridRecycler.recycler.translationX != 0f) { 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 4a67943703..356ff83cbe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -43,6 +43,8 @@ import androidx.core.app.ActivityCompat import androidx.core.content.getSystemService import androidx.core.graphics.ColorUtils import androidx.core.net.toUri +import androidx.core.splashscreen.SplashScreen +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat @@ -53,6 +55,8 @@ import androidx.core.view.forEach import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -236,7 +240,11 @@ open class MainActivity : BaseActivity() { } } + var ready = false + override fun onCreate(savedInstanceState: Bundle?) { + 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( @@ -262,6 +270,7 @@ open class MainActivity : BaseActivity() { window.sharedElementsUseOverlay = false super.onCreate(savedInstanceState) + backPressedCallback = object : OnBackPressedCallback(enabled = true) { var startTime: Long = 0 var lastX: Float = 0f @@ -386,6 +395,21 @@ open class MainActivity : BaseActivity() { val container: ViewGroup = binding.controllerContainer val content: ViewGroup = binding.mainContent + + if (savedInstanceState == null && this !is SearchActivity) { + // Reset Incognito Mode on relaunch + preferences.incognitoMode().set(false) + + // Show changelog if needed + if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) { + if (!BuildConfig.DEBUG) { + content.post { + whatsNewSheet().show() + } + } + } + } + DownloadJob.downloadFlow.onEach(::downloadStatusChanged).launchIn(lifecycleScope) lifecycleScope WindowCompat.setDecorFitsSystemWindows(window, false) @@ -629,19 +653,13 @@ open class MainActivity : BaseActivity() { (router.backstack.lastOrNull()?.controller as? BaseLegacyController<*>)?.setTitle() (router.backstack.lastOrNull()?.controller as? SettingsController)?.setTitle() - if (savedInstanceState == null && this !is SearchActivity) { - // Reset Incognito Mode on relaunch - preferences.incognitoMode().set(false) - - // Show changelog if needed - if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) { - if (!BuildConfig.DEBUG) { - content.post { - whatsNewSheet().show() - } - } - } + val startTime = System.currentTimeMillis() + splashScreen?.setKeepOnScreenCondition { + val elapsed = System.currentTimeMillis() - startTime + elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION) } + setSplashScreenExitAnimation(splashScreen) + getExtensionUpdates(true) preferences.extensionUpdatesCount() @@ -684,6 +702,43 @@ open class MainActivity : BaseActivity() { } } + private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) { + val root = findViewById(android.R.id.content) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) { + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + + splashScreen.setOnExitAnimationListener { splashProvider -> + // For some reason the SplashScreen applies (incorrect) Y translation to the iconView + splashProvider.iconView.translationY = 0F + + val activityAnim = ValueAnimator.ofFloat(1F, 0F).apply { + interpolator = LinearOutSlowInInterpolator() + duration = SPLASH_EXIT_ANIM_DURATION + addUpdateListener { va -> + val value = va.animatedValue as Float + root.translationY = value * 16.dpToPx + } + } + + val splashAnim = ValueAnimator.ofFloat(1F, 0F).apply { + interpolator = FastOutSlowInInterpolator() + duration = SPLASH_EXIT_ANIM_DURATION + addUpdateListener { va -> + val value = va.animatedValue as Float + splashProvider.view.alpha = value + } + doOnEnd { + splashProvider.remove() + } + } + + activityAnim.start() + splashAnim.start() + } + } + } + fun reEnableBackPressedCallBack() { val returnToStart = preferences.backReturnsToStart().get() && this !is SearchActivity backPressedCallback?.isEnabled = actionMode != null || @@ -1568,6 +1623,11 @@ open class MainActivity : BaseActivity() { const val INTENT_SEARCH_QUERY = "query" const val INTENT_SEARCH_FILTER = "filter" + // Splash screen + private const val SPLASH_MIN_DURATION = 500 // ms + private const val SPLASH_MAX_DURATION = 5000 // ms + private const val SPLASH_EXIT_ANIM_DURATION = 400L // ms + var chapterIdToExitTo = 0L var backVelocity = 0f } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index d3726adc12..20f77a6263 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -78,6 +78,7 @@ import eu.kanade.tachiyomi.util.view.isControllerVisible import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isHidden import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener @@ -170,6 +171,9 @@ class RecentsController(bundle: Bundle? = null) : binding.recycler.setHasFixedSize(true) binding.recycler.recycledViewPool.setMaxRecycledViews(0, 0) binding.recycler.addItemDecoration(RecentMangaDivider(view.context)) + binding.recycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } adapter.isSwipeEnabled = true adapter.itemTouchHelperCallback.setSwipeFlags( if (view.resources.isLTR) ItemTouchHelper.LEFT else ItemTouchHelper.RIGHT, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt index 3e5af7cfb7..9ac866e281 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/BrowseController.kt @@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isCompose import eu.kanade.tachiyomi.util.view.isControllerVisible +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener @@ -141,6 +142,9 @@ class BrowseController : binding.sourceRecycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context) binding.sourceRecycler.adapter = adapter + binding.sourceRecycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } adapter?.isSwipeEnabled = true adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY scrollViewWith( diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 3a414ac5f3..010f32ecef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -586,3 +586,20 @@ fun View?.isVisibleOnScreen(): Boolean { val screen = Rect(0, 0, Resources.getSystem().displayMetrics.widthPixels, Resources.getSystem().displayMetrics.heightPixels) return actualPosition.intersect(screen) } + +/** + * Callback will be run immediately when no animation running + */ +fun RecyclerView.onAnimationsFinished(callback: (RecyclerView) -> Unit) = post( + object : Runnable { + override fun run() { + if (isAnimating) { + itemAnimator?.isRunning { + post(this) + } + } else { + callback(this@onAnimationsFinished) + } + } + } +) diff --git a/app/src/main/res/drawable/ic_yokai_splash.xml b/app/src/main/res/drawable/ic_yokai_splash.xml index 1746ea616e..7135071e6d 100644 --- a/app/src/main/res/drawable/ic_yokai_splash.xml +++ b/app/src/main/res/drawable/ic_yokai_splash.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_background.xml b/app/src/main/res/drawable/splash_background.xml deleted file mode 100644 index 546572247c..0000000000 --- a/app/src/main/res/drawable/splash_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc5f..43bed53c2a 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc5f..43bed53c2a 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 80ce0395f2..0dec72d47c 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -36,7 +36,6 @@ #1F3399FF #3399FF #cce5ff - #212121 @color/md_white_1000_38 @color/md_black_1000_87 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7ac1f930aa..aedd2bc051 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -31,7 +31,7 @@ #54759E @color/md_white_1000 #78bcff - @color/primaryTachiyomi + @color/yokai_background @color/md_blue_A400 #1F2979FF #0d2f68 @@ -99,6 +99,9 @@ #CCA6CFD5 #CC7D1128 + + #0E1418 + #a149bf #cd95e0 @@ -178,4 +181,4 @@ #1F000000 #000000 @color/md_white_1000 - + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b0fb2d8e4a..92bd844d1d 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -10,9 +10,6 @@ false false true - @color/background - @drawable/ic_yokai_splash - 775 @color/primaryTachiyomi @color/primaryInverseTachiyomi @@ -219,12 +216,16 @@ -