refactor: Backport Android 12 SplashScreen to older Android versions

Fixes GH-20
This commit is contained in:
ziro 2024-02-07 11:52:25 +07:00
parent 27284d00f6
commit 04078084bd
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
17 changed files with 123 additions and 54 deletions

View file

@ -165,6 +165,7 @@ dependencies {
implementation(androidx.palette) implementation(androidx.palette)
implementation(androidx.activity) implementation(androidx.activity)
implementation(androidx.core) implementation(androidx.core)
implementation(androidx.core.splashscreen)
implementation(libs.flexbox) implementation(libs.flexbox)
implementation(androidx.window) implementation(androidx.window)
implementation(androidx.swiperefreshlayout) implementation(androidx.swiperefreshlayout)

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -54,7 +54,7 @@
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:windowSoftInputMode="adjustNothing" android:windowSoftInputMode="adjustNothing"
android:label="@string/app_short_name" android:label="@string/app_short_name"
android:theme="@style/Theme.Splash" android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -74,7 +74,7 @@
</activity> </activity>
<activity <activity
android:name=".ui.main.SearchActivity" android:name=".ui.main.SearchActivity"
android:theme="@style/Theme.Splash" android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:label="@string/label_global_search" android:label="@string/label_global_search"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -95,7 +95,7 @@
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity> </activity>
<activity android:name=".ui.reader.ReaderActivity" <activity android:name=".ui.reader.ReaderActivity"
android:theme="@style/Theme.Splash" android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View file

@ -1129,6 +1129,7 @@ open class LibraryController(
emptyList() emptyList()
}, },
) )
(activity as? MainActivity)?.ready = true
} }
adapter.setItems(mangaMap) adapter.setItems(mangaMap)
if (binding.libraryGridRecycler.recycler.translationX != 0f) { if (binding.libraryGridRecycler.recycler.translationX != 0f) {

View file

@ -43,6 +43,8 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri 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.GestureDetectorCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
@ -53,6 +55,8 @@ import androidx.core.view.forEach
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@ -236,7 +240,11 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
} }
} }
var ready = false
override fun onCreate(savedInstanceState: Bundle?) { 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 // Set up shared element transition and disable overlay so views don't show above system bars
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
setExitSharedElementCallback( setExitSharedElementCallback(
@ -262,6 +270,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
window.sharedElementsUseOverlay = false window.sharedElementsUseOverlay = false
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
backPressedCallback = object : OnBackPressedCallback(enabled = true) { backPressedCallback = object : OnBackPressedCallback(enabled = true) {
var startTime: Long = 0 var startTime: Long = 0
var lastX: Float = 0f var lastX: Float = 0f
@ -386,6 +395,21 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
val container: ViewGroup = binding.controllerContainer val container: ViewGroup = binding.controllerContainer
val content: ViewGroup = binding.mainContent 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) DownloadJob.downloadFlow.onEach(::downloadStatusChanged).launchIn(lifecycleScope)
lifecycleScope lifecycleScope
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@ -629,19 +653,13 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
(router.backstack.lastOrNull()?.controller as? BaseLegacyController<*>)?.setTitle() (router.backstack.lastOrNull()?.controller as? BaseLegacyController<*>)?.setTitle()
(router.backstack.lastOrNull()?.controller as? SettingsController)?.setTitle() (router.backstack.lastOrNull()?.controller as? SettingsController)?.setTitle()
if (savedInstanceState == null && this !is SearchActivity) { val startTime = System.currentTimeMillis()
// Reset Incognito Mode on relaunch splashScreen?.setKeepOnScreenCondition {
preferences.incognitoMode().set(false) val elapsed = System.currentTimeMillis() - startTime
elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
// Show changelog if needed
if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) {
if (!BuildConfig.DEBUG) {
content.post {
whatsNewSheet().show()
}
}
}
} }
setSplashScreenExitAnimation(splashScreen)
getExtensionUpdates(true) getExtensionUpdates(true)
preferences.extensionUpdatesCount() preferences.extensionUpdatesCount()
@ -684,6 +702,43 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
} }
} }
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
val root = findViewById<View>(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() { fun reEnableBackPressedCallBack() {
val returnToStart = preferences.backReturnsToStart().get() && this !is SearchActivity val returnToStart = preferences.backReturnsToStart().get() && this !is SearchActivity
backPressedCallback?.isEnabled = actionMode != null || backPressedCallback?.isEnabled = actionMode != null ||
@ -1568,6 +1623,11 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
const val INTENT_SEARCH_QUERY = "query" const val INTENT_SEARCH_QUERY = "query"
const val INTENT_SEARCH_FILTER = "filter" 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 chapterIdToExitTo = 0L
var backVelocity = 0f var backVelocity = 0f
} }

View file

@ -78,6 +78,7 @@ import eu.kanade.tachiyomi.util.view.isControllerVisible
import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.isHidden import eu.kanade.tachiyomi.util.view.isHidden
import eu.kanade.tachiyomi.util.view.moveRecyclerViewUp 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.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
@ -170,6 +171,9 @@ class RecentsController(bundle: Bundle? = null) :
binding.recycler.setHasFixedSize(true) binding.recycler.setHasFixedSize(true)
binding.recycler.recycledViewPool.setMaxRecycledViews(0, 0) binding.recycler.recycledViewPool.setMaxRecycledViews(0, 0)
binding.recycler.addItemDecoration(RecentMangaDivider(view.context)) binding.recycler.addItemDecoration(RecentMangaDivider(view.context))
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
adapter.isSwipeEnabled = true adapter.isSwipeEnabled = true
adapter.itemTouchHelperCallback.setSwipeFlags( adapter.itemTouchHelperCallback.setSwipeFlags(
if (view.resources.isLTR) ItemTouchHelper.LEFT else ItemTouchHelper.RIGHT, if (view.resources.isLTR) ItemTouchHelper.LEFT else ItemTouchHelper.RIGHT,

View file

@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isCollapsed
import eu.kanade.tachiyomi.util.view.isCompose import eu.kanade.tachiyomi.util.view.isCompose
import eu.kanade.tachiyomi.util.view.isControllerVisible 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.requestFilePermissionsSafe
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
@ -141,6 +142,9 @@ class BrowseController :
binding.sourceRecycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context) binding.sourceRecycler.layoutManager = LinearLayoutManagerAccurateOffset(view.context)
binding.sourceRecycler.adapter = adapter binding.sourceRecycler.adapter = adapter
binding.sourceRecycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
adapter?.isSwipeEnabled = true adapter?.isSwipeEnabled = true
adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY adapter?.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
scrollViewWith( scrollViewWith(

View file

@ -586,3 +586,20 @@ fun View?.isVisibleOnScreen(): Boolean {
val screen = Rect(0, 0, Resources.getSystem().displayMetrics.widthPixels, Resources.getSystem().displayMetrics.heightPixels) val screen = Rect(0, 0, Resources.getSystem().displayMetrics.widthPixels, Resources.getSystem().displayMetrics.heightPixels)
return actualPosition.intersect(screen) 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)
}
}
}
)

View file

@ -2,7 +2,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:drawable="@mipmap/ic_launcher_round" android:drawable="@mipmap/ic_launcher_round"
android:width="160dp" android:width="72dp"
android:height="160dp" android:height="72dp"
android:gravity="center" /> android:gravity="center" />
</layer-list> </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/splashBackground" />
<item
android:width="160dp"
android:height="160dp"
android:drawable="@mipmap/ic_launcher_round"
android:gravity="center" />
</layer-list>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/yokai_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/yokai_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View file

@ -36,7 +36,6 @@
<color name="rippleColorTachiyomi">#1F3399FF</color> <color name="rippleColorTachiyomi">#1F3399FF</color>
<color name="secondaryTachiyomi">#3399FF</color> <color name="secondaryTachiyomi">#3399FF</color>
<color name="secondaryVariantTachiyomi">#cce5ff</color> <color name="secondaryVariantTachiyomi">#cce5ff</color>
<color name="splashBackground">#212121</color>
<color name="actionModeShadow">@color/md_white_1000_38</color> <color name="actionModeShadow">@color/md_white_1000_38</color>
<color name="textColorPrimaryInverse">@color/md_black_1000_87</color> <color name="textColorPrimaryInverse">@color/md_black_1000_87</color>

View file

@ -31,7 +31,7 @@
<color name="primaryTachiyomi">#54759E</color> <color name="primaryTachiyomi">#54759E</color>
<color name="onPrimaryTachiyomi">@color/md_white_1000</color> <color name="onPrimaryTachiyomi">@color/md_white_1000</color>
<color name="primaryInverseTachiyomi">#78bcff</color> <color name="primaryInverseTachiyomi">#78bcff</color>
<color name="splashBackground">@color/primaryTachiyomi</color> <color name="splashBackground">@color/yokai_background</color>
<color name="secondaryTachiyomi">@color/md_blue_A400</color> <color name="secondaryTachiyomi">@color/md_blue_A400</color>
<color name="rippleColorTachiyomi">#1F2979FF</color> <color name="rippleColorTachiyomi">#1F2979FF</color>
<color name="secondaryVariantTachiyomi">#0d2f68</color> <color name="secondaryVariantTachiyomi">#0d2f68</color>
@ -99,6 +99,9 @@
<color name="navigation_next">#CCA6CFD5</color> <color name="navigation_next">#CCA6CFD5</color>
<color name="navigation_prev">#CC7D1128</color> <color name="navigation_prev">#CC7D1128</color>
<!-- Yokai -->
<color name="yokai_background">#0E1418</color>
<!-- Spring Blossom --> <!-- Spring Blossom -->
<color name="primaryDuskDawn">#a149bf</color> <color name="primaryDuskDawn">#a149bf</color>
<color name="primaryInverseDuskDawn">#cd95e0</color> <color name="primaryInverseDuskDawn">#cd95e0</color>
@ -178,4 +181,4 @@
<color name="rippleColorYinYang">#1F000000</color> <color name="rippleColorYinYang">#1F000000</color>
<color name="secondaryVariantYinYang">#000000</color> <color name="secondaryVariantYinYang">#000000</color>
<color name="onSecondaryYinYang">@color/md_white_1000</color> <color name="onSecondaryYinYang">@color/md_white_1000</color>
</resources> </resources>

View file

@ -10,9 +10,6 @@
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item> <item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">false</item> <item name="android:enforceNavigationBarContrast" tools:targetApi="29">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowSplashScreenBackground" tools:targetApi="31">@color/background</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="31">@drawable/ic_yokai_splash</item>
<item name="android:windowSplashScreenAnimationDuration" tools:targetApi="31">775</item>
<!--Standard Material colors --> <!--Standard Material colors -->
<item name="colorPrimary">@color/primaryTachiyomi</item> <item name="colorPrimary">@color/primaryTachiyomi</item>
<item name="colorPrimaryInverse">@color/primaryInverseTachiyomi</item> <item name="colorPrimaryInverse">@color/primaryInverseTachiyomi</item>
@ -219,12 +216,16 @@
<!--===============--> <!--===============-->
<!-- Launch Screen --> <!-- Launch Screen -->
<!--===============--> <!--===============-->
<style name="Theme.Splash" parent="Theme.Tachiyomi">
<item name="android:windowBackground">@drawable/splash_background</item> <!-- Splash -->
<item name="android:statusBarColor">@color/splashBackground</item> <style name="Theme.Tachiyomi.SplashScreen" parent="Theme.SplashScreen">
<item name="android:navigationBarColor">@color/splashBackground</item> <item name="windowSplashScreenBackground">@color/splashBackground</item>
<item name="android:windowLightStatusBar">false</item> <item name="windowSplashScreenAnimatedIcon">@drawable/ic_yokai_splash</item>
<item name="android:windowLightNavigationBar" tools:targetApi="27">false</item> <item name="postSplashScreenTheme">@style/Theme.Tachiyomi</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
</style> </style>
<style name="Theme.OSS" parent="Theme.Tachiyomi.Monet"> <style name="Theme.OSS" parent="Theme.Tachiyomi.Monet">

View file

@ -11,6 +11,7 @@ browser = { module = "androidx.browser:browser", version = "1.6.0" }
biometric = { module = "androidx.biometric:biometric", version = "1.1.0" } biometric = { module = "androidx.biometric:biometric", version = "1.1.0" }
cardview = { module = "androidx.cardview:cardview", version = "1.0.0" } cardview = { module = "androidx.cardview:cardview", version = "1.0.0" }
core = { module = "androidx.core:core-ktx", version = "1.12.0" } core = { module = "androidx.core:core-ktx", version = "1.12.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" }
glance-appwidget = { module = "androidx.glance:glance-appwidget", version = "1.0.0" } glance-appwidget = { module = "androidx.glance:glance-appwidget", version = "1.0.0" }
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }