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
This commit is contained in:
Ivan Iskandar 2021-07-17 23:06:15 +07:00 committed by Jobobby04
parent ec7f70e71c
commit f2bd785c57
12 changed files with 153 additions and 22 deletions

View File

@ -138,6 +138,7 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta02") implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta02")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0") implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
implementation("androidx.core:core-ktx:1.7.0-alpha01") 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.multidex:multidex:2.0.1")
implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.2.1") implementation("androidx.recyclerview:recyclerview:1.2.1")

View File

@ -38,7 +38,7 @@
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/Theme.Splash"> android:theme="@style/Theme.Tachiyomi.SplashScreen">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -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.index.IndexController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.category.sources.ChangeSourceCategoriesDialog 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.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import exh.ui.smartsearch.SmartSearchController import exh.ui.smartsearch.SmartSearchController
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -102,6 +104,9 @@ class SourceController(bundle: Bundle? = null) :
// Create recycler and set adapter. // Create recycler and set adapter.
binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter binding.recycler.adapter = adapter
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
adapter?.fastScroller = binding.fastScroller adapter?.fastScroller = binding.fastScroller
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 301)

View File

@ -20,9 +20,11 @@ import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
import eu.kanade.tachiyomi.ui.category.CategoryAdapter import eu.kanade.tachiyomi.ui.category.CategoryAdapter
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting 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.lang.plusAssign
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import exh.ui.LoadingHandle import exh.ui.LoadingHandle
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -128,6 +130,10 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
} }
.launchIn(scope) .launchIn(scope)
recycler.onAnimationsFinished {
(controller.activity as? MainActivity)?.ready = true
}
// Double the distance required to trigger sync // Double the distance required to trigger sync
binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt()) binding.swipeRefresh.setDistanceToTriggerSync((2 * 64 * resources.displayMetrics.density).toInt())
binding.swipeRefresh.refreshes() binding.swipeRefresh.refreshes()

View File

@ -316,6 +316,7 @@ class LibraryController(
binding.emptyView.hide() binding.emptyView.hide()
} else { } else {
binding.emptyView.show(R.string.information_empty_library) binding.emptyView.show(R.string.information_empty_library)
(activity as? MainActivity)?.ready = true
} }
// Get the current active category. // Get the current active category.

View File

@ -1,7 +1,10 @@
package eu.kanade.tachiyomi.ui.main package eu.kanade.tachiyomi.ui.main
import android.animation.ValueAnimator
import android.app.SearchManager import android.app.SearchManager
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.view.Gravity import android.view.Gravity
@ -10,11 +13,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout 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.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceDialogController import androidx.preference.PreferenceDialogController
import com.bluelinelabs.conductor.Conductor 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.ui.recent.updates.UpdatesController
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI 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.system.toast
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import exh.EXHMigrations import exh.EXHMigrations
@ -87,6 +97,9 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>() private var fixedViewsToBottom = mutableMapOf<View, AppBarLayout.OnOffsetChangedListener>()
// To be checked by splash screen. If true then splash screen will be removed.
var ready = false
// SY --> // SY -->
// Idle-until-urgent // Idle-until-urgent
private var firstPaint = false private var firstPaint = false
@ -107,6 +120,9 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
// SY <-- // SY <--
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Prevent splash screen showing up on configuration changes
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val didMigration = if (savedInstanceState == null) EXHMigrations.upgrade(preferences) else false val didMigration = if (savedInstanceState == null) EXHMigrations.upgrade(preferences) else false
@ -140,13 +156,12 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
} }
} }
// Make sure navigation bar is on bottom before we modify it val startTime = System.currentTimeMillis()
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> splashScreen?.setKeepVisibleCondition {
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { val elapsed = System.currentTimeMillis() - startTime
window.setNavigationBarTransparentCompat(this) elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
}
insets
} }
setSplashScreenExitAnimation(splashScreen)
tabAnimator = ViewHeightAnimator(binding.tabs, 0L) tabAnimator = ViewHeightAnimator(binding.tabs, 0L)
@ -308,6 +323,79 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
// SY <-- // 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) { override fun onNewIntent(intent: Intent) {
if (!handleIntentAction(intent)) { if (!handleIntentAction(intent)) {
super.onNewIntent(intent) super.onNewIntent(intent)
@ -408,6 +496,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
} }
} }
ready = true
isHandlingShortcut = false isHandlingShortcut = false
return true return true
} }
@ -532,7 +621,6 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
updateNavMenu(it.menu) updateNavMenu(it.menu)
// SY <-- // SY <--
} }
bottomViewNavigationBehavior?.slideUp(it) bottomViewNavigationBehavior?.slideUp(it)
} else { } else {
if (collapse) { if (collapse) {
@ -601,6 +689,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
} }
companion object { 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 // Shortcut actions
const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY" const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED" const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"

View File

@ -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.RootController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem 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.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -110,6 +112,9 @@ class HistoryController :
} else { } else {
adapter?.onLoadMoreComplete(mangaHistory) adapter?.onLoadMoreComplete(mangaHistory)
} }
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
} }
/** /**

View File

@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChaptersAdapter
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.recyclerview.scrollStateChanges import reactivecircus.flowbinding.recyclerview.scrollStateChanges
@ -224,6 +225,9 @@ class UpdatesController :
fun onNextRecentChapters(chapters: List<IFlexible<*>>) { fun onNextRecentChapters(chapters: List<IFlexible<*>>) {
destroyActionModeIfNeeded() destroyActionModeIfNeeded()
adapter?.updateDataSet(chapters) adapter?.updateDataSet(chapters)
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
} }
override fun onUpdateEmptyView(size: Int) { override fun onUpdateEmptyView(size: Int) {

View File

@ -197,3 +197,20 @@ inline fun TextView.setMaxLinesAndEllipsize(_ellipsize: TextUtils.TruncateAt = T
maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight
ellipsize = _ellipsize 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)
}
}
}
)

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:maxHeight="72dp"
android:drawable="@drawable/splash_icon"
android:gravity="center" />
</layer-list>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/splash_icon" />
</item>
</layer-list>

View File

@ -178,8 +178,10 @@
<!--===============--> <!--===============-->
<!--== Splash Theme ==--> <!--== Splash Theme ==-->
<style name="Theme.Splash" parent="Theme.Tachiyomi"> <style name="Theme.Tachiyomi.SplashScreen" parent="Theme.SplashScreen">
<item name="android:windowBackground">@drawable/splash_background</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_tachi_splash</item>
<item name="windowSplashScreenBackground">@color/splash</item>
<item name="postSplashScreenTheme">@style/Theme.Tachiyomi</item>
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item> <item name="android:windowLightStatusBar">false</item>