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
This commit is contained in:
Ivan Iskandar 2021-07-17 23:06:15 +07:00 committed by GitHub
parent c0647c3110
commit 05e7b0dc22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 144 additions and 13 deletions

View File

@ -147,6 +147,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

@ -37,7 +37,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

@ -31,6 +31,8 @@ import eu.kanade.tachiyomi.ui.browse.BrowseController
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -81,6 +83,9 @@ class SourceController :
// 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

@ -14,9 +14,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding import eu.kanade.tachiyomi.databinding.LibraryCategoryBinding
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 kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@ -106,6 +108,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

@ -277,6 +277,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,18 +1,27 @@
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.view.Gravity import android.view.Gravity
import android.view.View 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 kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -80,7 +90,13 @@ 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
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) Migrations.upgrade(preferences) else false val didMigration = if (savedInstanceState == null) Migrations.upgrade(preferences) else false
@ -114,13 +130,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)
@ -255,6 +270,79 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
} }
/**
* 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)
@ -355,6 +443,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
} }
} }
ready = true
isHandlingShortcut = false isHandlingShortcut = false
return true return true
} }
@ -526,6 +615,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() {
get() = binding.bottomNav ?: binding.sideNav!! get() = binding.bottomNav ?: binding.sideNav!!
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

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splash" />
<item <item
android:width="72dp" android:width="72dp"
android:height="72dp" android:height="72dp"
android:drawable="@drawable/ic_tachi" android:drawable="@drawable/ic_tachi"
android:gravity="center" /> android:gravity="center" />
</layer-list> </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>