mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +01:00 
			
		
		
		
	Apply system animation scale to parts of Tachiyomi that don't respect it by default (#5794)
* Add initial code for scaling animations, apply scale to reader nav overlay * Rename extension function, apply system animator scale to ActionToolbar * Apply system animator scale to expanding manga cover animation * Apply system animator scale to image crossfade (also disables animated covers when browsing) * Add documentation, make MotionScene Transition comment a bit more clear * Disable animated covers in MangaInfoHeaderAdapter if animator duration scale is 0 * Disable animated covers in Library if animator duration scale is 0 * Convert loadAny listener to extension function
This commit is contained in:
		| @@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.data.preference.asImmediateFlow | ||||
| import eu.kanade.tachiyomi.network.NetworkHelper | ||||
| import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate | ||||
| import eu.kanade.tachiyomi.util.system.AuthenticatorUtil | ||||
| import eu.kanade.tachiyomi.util.system.animatorDurationScale | ||||
| import eu.kanade.tachiyomi.util.system.notification | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.onEach | ||||
| @@ -125,7 +126,7 @@ open class App : Application(), LifecycleObserver, ImageLoaderFactory { | ||||
|                 add(MangaCoverFetcher()) | ||||
|             } | ||||
|             okHttpClient(Injekt.get<NetworkHelper>().coilClient) | ||||
|             crossfade(300) | ||||
|             crossfade((300 * this@App.animatorDurationScale).toInt()) | ||||
|             allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice) | ||||
|         }.build() | ||||
|     } | ||||
|   | ||||
| @@ -4,11 +4,11 @@ import android.view.View | ||||
| import androidx.core.view.isVisible | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import coil.clear | ||||
| import coil.loadAny | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.davidea.flexibleadapter.items.IFlexible | ||||
| import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding | ||||
| import eu.kanade.tachiyomi.util.isLocal | ||||
| import eu.kanade.tachiyomi.util.view.loadAnyAutoPause | ||||
|  | ||||
| /** | ||||
|  * Class used to hold the displayed data of a manga in the library, like the cover or the title. | ||||
| @@ -57,6 +57,6 @@ class LibraryComfortableGridHolder( | ||||
|  | ||||
|         // Update the cover. | ||||
|         binding.thumbnail.clear() | ||||
|         binding.thumbnail.loadAny(item.manga) | ||||
|         binding.thumbnail.loadAnyAutoPause(item.manga) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.ui.library | ||||
| import android.view.View | ||||
| import androidx.core.view.isVisible | ||||
| import coil.clear | ||||
| import coil.loadAny | ||||
| import eu.davidea.flexibleadapter.FlexibleAdapter | ||||
| import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding | ||||
| import eu.kanade.tachiyomi.util.isLocal | ||||
| import eu.kanade.tachiyomi.util.view.loadAnyAutoPause | ||||
|  | ||||
| /** | ||||
|  * Class used to hold the displayed data of a manga in the library, like the cover or the title. | ||||
| @@ -55,6 +55,6 @@ open class LibraryCompactGridHolder( | ||||
|  | ||||
|         // Update the cover. | ||||
|         binding.thumbnail.clear() | ||||
|         binding.thumbnail.loadAny(item.manga) | ||||
|         binding.thumbnail.loadAnyAutoPause(item.manga) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import androidx.core.view.isVisible | ||||
| import androidx.core.view.updateLayoutParams | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import coil.loadAny | ||||
| import coil.target.ImageViewTarget | ||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||
| import eu.kanade.tachiyomi.R | ||||
| @@ -20,7 +19,9 @@ import eu.kanade.tachiyomi.source.model.SManga | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight | ||||
| import eu.kanade.tachiyomi.ui.manga.MangaController | ||||
| import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale | ||||
| import eu.kanade.tachiyomi.util.system.copyToClipboard | ||||
| import eu.kanade.tachiyomi.util.view.loadAnyAutoPause | ||||
| import eu.kanade.tachiyomi.util.view.setChips | ||||
| import kotlinx.coroutines.flow.launchIn | ||||
| import kotlinx.coroutines.flow.merge | ||||
| @@ -92,6 +93,12 @@ class MangaInfoHeaderAdapter( | ||||
|  | ||||
|     inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { | ||||
|         fun bind() { | ||||
|             val headerTransition = binding.root.getTransition(R.id.manga_info_header_transition) | ||||
|             headerTransition.applySystemAnimatorScale(view.context) | ||||
|  | ||||
|             val summaryTransition = binding.mangaSummarySection.getTransition(R.id.manga_summary_section_transition) | ||||
|             summaryTransition.applySystemAnimatorScale(view.context) | ||||
|  | ||||
|             // For rounded corners | ||||
|             binding.mangaCover.clipToOutline = true | ||||
|  | ||||
| @@ -278,8 +285,8 @@ class MangaInfoHeaderAdapter( | ||||
|             setFavoriteButtonState(manga.favorite) | ||||
|  | ||||
|             // Set cover if changed. | ||||
|             binding.backdrop.loadAny(manga) | ||||
|             binding.mangaCover.loadAny(manga) { | ||||
|             binding.backdrop.loadAnyAutoPause(manga) | ||||
|             binding.mangaCover.loadAnyAutoPause(manga) { | ||||
|                 listener( | ||||
|                     onSuccess = { request, _ -> | ||||
|                         (request.target as? ImageViewTarget)?.drawable?.let { drawable -> | ||||
|   | ||||
| @@ -66,6 +66,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator | ||||
| import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer | ||||
| import eu.kanade.tachiyomi.util.storage.getUriCompat | ||||
| import eu.kanade.tachiyomi.util.system.GLUtil | ||||
| import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale | ||||
| import eu.kanade.tachiyomi.util.system.createReaderThemeContext | ||||
| import eu.kanade.tachiyomi.util.system.getThemeColor | ||||
| import eu.kanade.tachiyomi.util.system.hasDisplayCutout | ||||
| @@ -528,6 +529,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() | ||||
|  | ||||
|             if (animate) { | ||||
|                 val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top) | ||||
|                 toolbarAnimation.applySystemAnimatorScale(this) | ||||
|                 toolbarAnimation.setAnimationListener( | ||||
|                     object : SimpleAnimationListener() { | ||||
|                         override fun onAnimationStart(animation: Animation) { | ||||
| @@ -539,6 +541,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() | ||||
|                 binding.toolbar.startAnimation(toolbarAnimation) | ||||
|  | ||||
|                 val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom) | ||||
|                 bottomAnimation.applySystemAnimatorScale(this) | ||||
|                 binding.readerMenuBottom.startAnimation(bottomAnimation) | ||||
|             } | ||||
|  | ||||
| @@ -553,6 +556,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() | ||||
|  | ||||
|             if (animate) { | ||||
|                 val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top) | ||||
|                 toolbarAnimation.applySystemAnimatorScale(this) | ||||
|                 toolbarAnimation.setAnimationListener( | ||||
|                     object : SimpleAnimationListener() { | ||||
|                         override fun onAnimationEnd(animation: Animation) { | ||||
| @@ -563,6 +567,7 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>() | ||||
|                 binding.toolbar.startAnimation(toolbarAnimation) | ||||
|  | ||||
|                 val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom) | ||||
|                 bottomAnimation.applySystemAnimatorScale(this) | ||||
|                 binding.readerMenuBottom.startAnimation(bottomAnimation) | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,16 @@ | ||||
| package eu.kanade.tachiyomi.util.system | ||||
|  | ||||
| import android.content.Context | ||||
| import android.view.animation.Animation | ||||
| import androidx.constraintlayout.motion.widget.MotionScene.Transition | ||||
|  | ||||
| /** Scale the duration of this [Animation] by [Context.animatorDurationScale] */ | ||||
| fun Animation.applySystemAnimatorScale(context: Context) { | ||||
|     this.duration = (this.duration * context.animatorDurationScale).toLong() | ||||
| } | ||||
|  | ||||
| /** Scale the duration of this [Transition] by [Context.animatorDurationScale] */ | ||||
| fun Transition.applySystemAnimatorScale(context: Context) { | ||||
|     // End layout of cover expanding animation tends to break when the transition is less than ~25ms | ||||
|     this.duration = (this.duration * context.animatorDurationScale).toInt().coerceAtLeast(25) | ||||
| } | ||||
| @@ -18,6 +18,7 @@ import android.net.ConnectivityManager | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import android.os.PowerManager | ||||
| import android.provider.Settings | ||||
| import android.util.TypedValue | ||||
| import android.view.Display | ||||
| import android.view.View | ||||
| @@ -203,6 +204,12 @@ val Context.displayCompat: Display? | ||||
|         getSystemService<WindowManager>()?.defaultDisplay | ||||
|     } | ||||
|  | ||||
| /** Gets the duration multiplier for general animations on the device | ||||
|  * @see Settings.Global.ANIMATOR_DURATION_SCALE | ||||
|  */ | ||||
| val Context.animatorDurationScale: Float | ||||
|     get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) | ||||
|  | ||||
| /** | ||||
|  * Convenience method to acquire a partial wake lock. | ||||
|  */ | ||||
|   | ||||
| @@ -1,9 +1,17 @@ | ||||
| package eu.kanade.tachiyomi.util.view | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.drawable.Animatable | ||||
| import android.widget.ImageView | ||||
| import androidx.annotation.AttrRes | ||||
| import androidx.annotation.DrawableRes | ||||
| import androidx.appcompat.content.res.AppCompatResources | ||||
| import coil.ImageLoader | ||||
| import coil.imageLoader | ||||
| import coil.loadAny | ||||
| import coil.request.ImageRequest | ||||
| import coil.target.ImageViewTarget | ||||
| import eu.kanade.tachiyomi.util.system.animatorDurationScale | ||||
| import eu.kanade.tachiyomi.util.system.getResourceColor | ||||
|  | ||||
| /** | ||||
| @@ -19,3 +27,30 @@ fun ImageView.setVectorCompat(@DrawableRes drawable: Int, @AttrRes tint: Int? = | ||||
|     } | ||||
|     setImageDrawable(vector) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Load the image referenced by [data] and set it on this [ImageView], | ||||
|  * and if the image is animated, this will also disable that animation | ||||
|  * if [Context.animatorDurationScale] is 0 | ||||
|  */ | ||||
| fun ImageView.loadAnyAutoPause( | ||||
|     data: Any?, | ||||
|     loader: ImageLoader = context.imageLoader, | ||||
|     builder: ImageRequest.Builder.() -> Unit = {} | ||||
| ) { | ||||
|     this.loadAny(data, loader) { | ||||
|         // Build the original request so we can add on our success listener | ||||
|         val originalBuild = apply(builder).build() | ||||
|         listener( | ||||
|             onSuccess = { request, metadata -> | ||||
|                 (request.target as? ImageViewTarget)?.drawable.let { | ||||
|                     if (it is Animatable && context.animatorDurationScale == 0f) it.stop() | ||||
|                 } | ||||
|                 originalBuild.listener?.onSuccess(request, metadata) | ||||
|             }, | ||||
|             onStart = { request -> originalBuild.listener?.onStart(request) }, | ||||
|             onCancel = { request -> originalBuild.listener?.onCancel(request) }, | ||||
|             onError = { request, throwable -> originalBuild.listener?.onError(request, throwable) } | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import androidx.appcompat.view.ActionMode | ||||
| import androidx.core.view.isVisible | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.databinding.ActionToolbarBinding | ||||
| import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale | ||||
| import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener | ||||
|  | ||||
| /** | ||||
| @@ -50,6 +51,7 @@ class ActionToolbar @JvmOverloads constructor(context: Context, attrs: Attribute | ||||
|  | ||||
|         binding.actionToolbar.isVisible = true | ||||
|         val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.enter_from_bottom) | ||||
|         bottomAnimation.applySystemAnimatorScale(context) | ||||
|         binding.actionToolbar.startAnimation(bottomAnimation) | ||||
|     } | ||||
|  | ||||
| @@ -58,6 +60,7 @@ class ActionToolbar @JvmOverloads constructor(context: Context, attrs: Attribute | ||||
|      */ | ||||
|     fun hide() { | ||||
|         val bottomAnimation = AnimationUtils.loadAnimation(context, R.anim.exit_to_bottom) | ||||
|         bottomAnimation.applySystemAnimatorScale(context) | ||||
|         bottomAnimation.setAnimationListener( | ||||
|             object : SimpleAnimationListener() { | ||||
|                 override fun onAnimationEnd(animation: Animation) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user