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.activity)
implementation(androidx.core)
implementation(androidx.core.splashscreen)
implementation(libs.flexbox)
implementation(androidx.window)
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:windowSoftInputMode="adjustNothing"
android:label="@string/app_short_name"
android:theme="@style/Theme.Splash"
android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -74,7 +74,7 @@
</activity>
<activity
android:name=".ui.main.SearchActivity"
android:theme="@style/Theme.Splash"
android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:label="@string/label_global_search"
android:exported="true">
<intent-filter>
@ -95,7 +95,7 @@
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>
</activity>
<activity android:name=".ui.reader.ReaderActivity"
android:theme="@style/Theme.Splash"
android:theme="@style/Theme.Tachiyomi.SplashScreen"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

View file

@ -1129,6 +1129,7 @@ open class LibraryController(
emptyList()
},
)
(activity as? MainActivity)?.ready = true
}
adapter.setItems(mangaMap)
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.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<MainActivityBinding>() {
}
}
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<MainActivityBinding>() {
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<MainActivityBinding>() {
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<MainActivityBinding>() {
(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<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() {
val returnToStart = preferences.backReturnsToStart().get() && this !is SearchActivity
backPressedCallback?.isEnabled = actionMode != null ||
@ -1568,6 +1623,11 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
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
}

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.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,

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.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(

View file

@ -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)
}
}
}
)

View file

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

View file

@ -1,5 +1,5 @@
<?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"/>
<background android:drawable="@color/yokai_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

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

View file

@ -31,7 +31,7 @@
<color name="primaryTachiyomi">#54759E</color>
<color name="onPrimaryTachiyomi">@color/md_white_1000</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="rippleColorTachiyomi">#1F2979FF</color>
<color name="secondaryVariantTachiyomi">#0d2f68</color>
@ -99,6 +99,9 @@
<color name="navigation_next">#CCA6CFD5</color>
<color name="navigation_prev">#CC7D1128</color>
<!-- Yokai -->
<color name="yokai_background">#0E1418</color>
<!-- Spring Blossom -->
<color name="primaryDuskDawn">#a149bf</color>
<color name="primaryInverseDuskDawn">#cd95e0</color>
@ -178,4 +181,4 @@
<color name="rippleColorYinYang">#1F000000</color>
<color name="secondaryVariantYinYang">#000000</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:enforceNavigationBarContrast" tools:targetApi="29">false</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 -->
<item name="colorPrimary">@color/primaryTachiyomi</item>
<item name="colorPrimaryInverse">@color/primaryInverseTachiyomi</item>
@ -219,12 +216,16 @@
<!--===============-->
<!-- Launch Screen -->
<!--===============-->
<style name="Theme.Splash" parent="Theme.Tachiyomi">
<item name="android:windowBackground">@drawable/splash_background</item>
<item name="android:statusBarColor">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
<!-- Splash -->
<style name="Theme.Tachiyomi.SplashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splashBackground</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_yokai_splash</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 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" }
cardview = { module = "androidx.cardview:cardview", version = "1.0.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" }
glance-appwidget = { module = "androidx.glance:glance-appwidget", version = "1.0.0" }
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }