mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	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:
		| @@ -37,7 +37,7 @@ | ||||
|         <activity | ||||
|             android:name=".ui.main.MainActivity" | ||||
|             android:launchMode="singleTop" | ||||
|             android:theme="@style/Theme.Splash"> | ||||
|             android:theme="@style/Theme.Tachiyomi.SplashScreen"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|   | ||||
| @@ -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.globalsearch.GlobalSearchController | ||||
| 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.api.get | ||||
|  | ||||
| @@ -81,6 +83,9 @@ class SourceController : | ||||
|         // 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) | ||||
|   | ||||
| @@ -14,9 +14,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.library.LibraryUpdateService | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| 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.system.toast | ||||
| import eu.kanade.tachiyomi.util.view.inflate | ||||
| import eu.kanade.tachiyomi.util.view.onAnimationsFinished | ||||
| import eu.kanade.tachiyomi.widget.AutofitRecyclerView | ||||
| import kotlinx.coroutines.MainScope | ||||
| import kotlinx.coroutines.cancel | ||||
| @@ -106,6 +108,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() | ||||
|   | ||||
| @@ -277,6 +277,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. | ||||
|   | ||||
| @@ -1,18 +1,27 @@ | ||||
| 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.view.Gravity | ||||
| 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 kotlinx.coroutines.delay | ||||
| @@ -80,7 +90,13 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { | ||||
|  | ||||
|     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?) { | ||||
|         // Prevent splash screen showing up on configuration changes | ||||
|         val splashScreen = if (savedInstanceState == null) installSplashScreen() else null | ||||
|  | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         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 | ||||
|         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) | ||||
|  | ||||
| @@ -255,6 +270,79 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { | ||||
|             .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) { | ||||
|         if (!handleIntentAction(intent)) { | ||||
|             super.onNewIntent(intent) | ||||
| @@ -355,6 +443,7 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         ready = true | ||||
|         isHandlingShortcut = false | ||||
|         return true | ||||
|     } | ||||
| @@ -526,6 +615,11 @@ class MainActivity : BaseViewBindingActivity<MainActivityBinding>() { | ||||
|         get() = binding.bottomNav ?: binding.sideNav!! | ||||
|  | ||||
|     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" | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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<IFlexible<*>>) { | ||||
|         destroyActionModeIfNeeded() | ||||
|         adapter?.updateDataSet(chapters) | ||||
|         binding.recycler.onAnimationsFinished { | ||||
|             (activity as? MainActivity)?.ready = true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onUpdateEmptyView(size: Int) { | ||||
|   | ||||
| @@ -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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| ) | ||||
|   | ||||
| @@ -1,12 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
| 
 | ||||
|     <item android:drawable="@color/splash" /> | ||||
| 
 | ||||
|     <item | ||||
|         android:width="72dp" | ||||
|         android:height="72dp" | ||||
|         android:drawable="@drawable/ic_tachi" | ||||
|         android:gravity="center" /> | ||||
| 
 | ||||
| </layer-list> | ||||
| @@ -178,8 +178,10 @@ | ||||
|     <!--===============--> | ||||
|  | ||||
|     <!--== Splash Theme ==--> | ||||
|     <style name="Theme.Splash" parent="Theme.Tachiyomi"> | ||||
|         <item name="android:windowBackground">@drawable/splash_background</item> | ||||
|     <style name="Theme.Tachiyomi.SplashScreen" parent="Theme.SplashScreen"> | ||||
|         <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:navigationBarColor">@android:color/transparent</item> | ||||
|         <item name="android:windowLightStatusBar">false</item> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user