From f2bd785c57e6fa7dc0a3562f8f63aada965d6288 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 17 Jul 2021 23:06:15 +0700 Subject: [PATCH] Fix splash screen icon on Android 12 (#5565) * Use Core Splashscreen for splashscreen stuff * Keep splash screen until activity ready Ready as in the data inside starting screen is finished showing * Use custom splash screen exit animation on older android version * Add splash screen minimum duration to prevent exit jank * Fix broken AMOLED theme * Improvements (cherry picked from commit 05e7b0dc22dbd9d180e3410fdfa8c3fa1759e57d) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt # app/src/main/res/drawable/ic_tachi_splash.xml --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 2 +- .../ui/browse/source/SourceController.kt | 5 + .../ui/library/LibraryCategoryView.kt | 6 + .../tachiyomi/ui/library/LibraryController.kt | 1 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 107 ++++++++++++++++-- .../ui/recent/history/HistoryController.kt | 5 + .../ui/recent/updates/UpdatesController.kt | 4 + .../tachiyomi/util/view/ViewExtensions.kt | 17 +++ app/src/main/res/drawable/ic_tachi_splash.xml | 9 ++ .../main/res/drawable/splash_background.xml | 12 -- app/src/main/res/values/themes.xml | 6 +- 12 files changed, 153 insertions(+), 22 deletions(-) create mode 100644 app/src/main/res/drawable/ic_tachi_splash.xml delete mode 100644 app/src/main/res/drawable/splash_background.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d80b4e794..248d5e131 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -138,6 +138,7 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta02") implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0") implementation("androidx.core:core-ktx:1.7.0-alpha01") + implementation("androidx.core:core-splashscreen:1.0.0-alpha01") implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.recyclerview:recyclerview:1.2.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 02f12e32a..898f8b6bc 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,7 +38,7 @@ + android:theme="@style/Theme.Tachiyomi.SplashScreen"> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt index 43a39dc85..83c5c22e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt @@ -35,7 +35,9 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.browse.source.index.IndexController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.category.sources.ChangeSourceCategoriesDialog +import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import exh.ui.smartsearch.SmartSearchController import kotlinx.parcelize.Parcelize import uy.kohesive.injekt.Injekt @@ -102,6 +104,9 @@ class SourceController(bundle: Bundle? = null) : // Create recycler and set adapter. binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.adapter = adapter + binding.recycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } adapter?.fastScroller = binding.fastScroller requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt index a9dff00e9..434843eab 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt @@ -20,9 +20,11 @@ import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding import eu.kanade.tachiyomi.ui.category.CategoryAdapter import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting +import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.inflate +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import eu.kanade.tachiyomi.widget.AutofitRecyclerView import exh.ui.LoadingHandle import kotlinx.coroutines.CoroutineScope @@ -128,6 +130,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att } .launchIn(scope) + recycler.onAnimationsFinished { + (controller.activity as? MainActivity)?.ready = true + } + // Double the distance required to trigger sync binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) binding.swipeRefresh.refreshes() 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 7a116c82b..e7ee34673 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 @@ -316,6 +316,7 @@ class LibraryController( binding.emptyView.hide() } else { binding.emptyView.show(R.string.information_empty_library) + (activity as? MainActivity)?.ready = true } // Get the current active category. 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 739a9d8b9..84143c3cc 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 @@ -1,7 +1,10 @@ package eu.kanade.tachiyomi.ui.main +import android.animation.ValueAnimator import android.app.SearchManager import android.content.Intent +import android.graphics.Color +import android.os.Build import android.os.Bundle import android.os.Looper import android.view.Gravity @@ -10,11 +13,17 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.animation.doOnEnd +import androidx.core.splashscreen.SplashScreen +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceDialogController import com.bluelinelabs.conductor.Conductor @@ -49,6 +58,7 @@ import eu.kanade.tachiyomi.ui.recent.history.HistoryController import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI +import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat import exh.EXHMigrations @@ -87,6 +97,9 @@ class MainActivity : BaseViewBindingActivity() { private var fixedViewsToBottom = mutableMapOf() + // To be checked by splash screen. If true then splash screen will be removed. + var ready = false + // SY --> // Idle-until-urgent private var firstPaint = false @@ -107,6 +120,9 @@ class MainActivity : BaseViewBindingActivity() { // SY <-- override fun onCreate(savedInstanceState: Bundle?) { + // Prevent splash screen showing up on configuration changes + val splashScreen = if (savedInstanceState == null) installSplashScreen() else null + super.onCreate(savedInstanceState) val didMigration = if (savedInstanceState == null) EXHMigrations.upgrade(preferences) else false @@ -140,13 +156,12 @@ class MainActivity : BaseViewBindingActivity() { } } - // Make sure navigation bar is on bottom before we modify it - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> - if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { - window.setNavigationBarTransparentCompat(this) - } - insets + val startTime = System.currentTimeMillis() + splashScreen?.setKeepVisibleCondition { + val elapsed = System.currentTimeMillis() - startTime + elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION) } + setSplashScreenExitAnimation(splashScreen) tabAnimator = ViewHeightAnimator(binding.tabs, 0L) @@ -308,6 +323,79 @@ class MainActivity : BaseViewBindingActivity() { // SY <-- } + /** + * Sets custom splash screen exit animation on devices prior to Android 12. + * + * When custom animation is used, status and navigation bar color will be set to transparent and will be restored + * after the animation is finished. + */ + private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) { + val setNavbarScrim = { + // Make sure navigation bar is on bottom before we modify it + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { + window.setNavigationBarTransparentCompat(this@MainActivity) + } + insets + } + ViewCompat.requestApplyInsets(binding.root) + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + val oldStatusColor = window.statusBarColor + val oldNavigationColor = window.navigationBarColor + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + + val wicc = WindowInsetsControllerCompat(window, window.decorView) + val isLightStatusBars = wicc.isAppearanceLightStatusBars + val isLightNavigationBars = wicc.isAppearanceLightNavigationBars + wicc.isAppearanceLightStatusBars = false + wicc.isAppearanceLightNavigationBars = false + + 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 + binding.root.translationY = value * 16.dpToPx + } + } + + var barColorRestored = false + 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 + + if (!barColorRestored && value <= 0.5F) { + barColorRestored = true + wicc.isAppearanceLightStatusBars = isLightStatusBars + wicc.isAppearanceLightNavigationBars = isLightNavigationBars + } + } + doOnEnd { + splashProvider.remove() + window.statusBarColor = oldStatusColor + window.navigationBarColor = oldNavigationColor + setNavbarScrim() + } + } + + activityAnim.start() + splashAnim.start() + } + } else { + setNavbarScrim() + } + } + override fun onNewIntent(intent: Intent) { if (!handleIntentAction(intent)) { super.onNewIntent(intent) @@ -408,6 +496,7 @@ class MainActivity : BaseViewBindingActivity() { } } + ready = true isHandlingShortcut = false return true } @@ -532,7 +621,6 @@ class MainActivity : BaseViewBindingActivity() { updateNavMenu(it.menu) // SY <-- } - bottomViewNavigationBehavior?.slideUp(it) } else { if (collapse) { @@ -601,6 +689,11 @@ class MainActivity : BaseViewBindingActivity() { } companion object { + // 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 + // Shortcut actions const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY" const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index a4dcd26e5..14347c9eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -23,9 +23,11 @@ import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem +import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -110,6 +112,9 @@ class HistoryController : } else { adapter?.onLoadMoreComplete(mangaHistory) } + binding.recycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt index edf04ede6..2a917c0b6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt @@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.onAnimationsFinished import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import reactivecircus.flowbinding.recyclerview.scrollStateChanges @@ -224,6 +225,9 @@ class UpdatesController : fun onNextRecentChapters(chapters: List>) { destroyActionModeIfNeeded() adapter?.updateDataSet(chapters) + binding.recycler.onAnimationsFinished { + (activity as? MainActivity)?.ready = true + } } override fun onUpdateEmptyView(size: Int) { 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 cfd6a6d3d..d455d7f60 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 @@ -197,3 +197,20 @@ inline fun TextView.setMaxLinesAndEllipsize(_ellipsize: TextUtils.TruncateAt = T maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight ellipsize = _ellipsize } + +/** + * 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_tachi_splash.xml b/app/src/main/res/drawable/ic_tachi_splash.xml new file mode 100644 index 000000000..fe41a2871 --- /dev/null +++ b/app/src/main/res/drawable/ic_tachi_splash.xml @@ -0,0 +1,9 @@ + + + + + + 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 c819fcb9a..000000000 --- a/app/src/main/res/drawable/splash_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b784ce3d4..e1ae64e4b 100755 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -178,8 +178,10 @@ -