From eba16797616be8545b478817e63e1e9a0ab89108 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 15 Jul 2024 16:11:42 +0300 Subject: [PATCH] Animated source placeholder --- .../core/ui/image/AnimatedFaviconDrawable.kt | 72 +++++++++++++++++++ .../kotatsu/core/ui/image/FaviconDrawable.kt | 6 +- .../ui/adapter/ExploreAdapterDelegates.kt | 5 +- .../adapter/SearchSuggestionSourceAD.kt | 3 +- .../adapter/SearchSuggestionSourceTipAD.kt | 3 +- .../adapter/SourceConfigAdapterDelegates.kt | 3 +- .../sources/catalog/SourceCatalogItemAD.kt | 34 +-------- 7 files changed, 86 insertions(+), 40 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt new file mode 100644 index 000000000..24e15150c --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt @@ -0,0 +1,72 @@ +package org.koitharu.kotatsu.core.ui.image + +import android.animation.TimeAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Animatable +import androidx.annotation.StyleRes +import androidx.interpolator.view.animation.FastOutSlowInInterpolator +import com.google.android.material.animation.ArgbEvaluatorCompat +import com.google.android.material.color.MaterialColors +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.KotatsuColors +import org.koitharu.kotatsu.core.util.ext.getAnimationDuration +import kotlin.math.abs + +class AnimatedFaviconDrawable( + context: Context, + @StyleRes styleResId: Int, + name: String, +) : FaviconDrawable(context, styleResId, name), Animatable, TimeAnimator.TimeListener { + + private val interpolator = FastOutSlowInInterpolator() + private val period = context.getAnimationDuration(R.integer.config_longAnimTime) * 2 + private val timeAnimator = TimeAnimator() + + private val colorHigh = MaterialColors.harmonize(KotatsuColors.random(name), colorBackground) + private val colorLow = ArgbEvaluatorCompat.getInstance().evaluate(0.3f, colorHigh, colorBackground) + + init { + timeAnimator.setTimeListener(this) + updateColor() + } + + override fun draw(canvas: Canvas) { + if (!isRunning && period > 0) { + updateColor() + start() + } + super.draw(canvas) + } + + override fun setAlpha(alpha: Int) = Unit + + override fun getAlpha(): Int = 255 + + override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) { + callback?.also { + updateColor() + it.invalidateDrawable(this) + } ?: stop() + } + + override fun start() { + timeAnimator.start() + } + + override fun stop() { + timeAnimator.end() + } + + override fun isRunning(): Boolean = timeAnimator.isStarted + + private fun updateColor() { + if (period <= 0f) { + return + } + val ph = period / 2 + val fraction = abs((System.currentTimeMillis() % period) - ph) / ph.toFloat() + colorForeground = ArgbEvaluatorCompat.getInstance() + .evaluate(interpolator.getInterpolation(fraction), colorLow, colorHigh) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconDrawable.kt index b4931d916..5beacf886 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconDrawable.kt @@ -17,18 +17,18 @@ import com.google.android.material.color.MaterialColors import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.util.KotatsuColors -class FaviconDrawable( +open class FaviconDrawable( context: Context, @StyleRes styleResId: Int, name: String, ) : Drawable() { private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - private var colorBackground = Color.WHITE + protected var colorBackground = Color.WHITE + protected var colorForeground = Color.DKGRAY private var colorStroke = Color.LTGRAY private val letter = name.take(1).uppercase() private var cornerSize = 0f - private var colorForeground = Color.DKGRAY private val textBounds = Rect() private val tempRect = Rect() private val boundsF = RectF() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index ecbb50d91..8952ac354 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.ui.BaseListAdapter +import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter @@ -126,7 +127,7 @@ fun exploreSourceListItemAD( val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { fallback(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) error(fallbackIcon) source(item.source) enqueueWith(coil) @@ -160,7 +161,7 @@ fun exploreSourceGridItemAD( val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name) binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { fallback(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name)) error(fallbackIcon) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt index 9029d7ef4..47415984d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest @@ -37,7 +38,7 @@ fun searchSuggestionSourceAD( val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { fallback(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) error(fallbackIcon) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt index 55a71a06d..7dad1c854 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest @@ -34,7 +35,7 @@ fun searchSuggestionSourceTipAD( val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { fallback(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) error(fallbackIcon) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index 3470c28de..ca269f4a0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -14,6 +14,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener import org.koitharu.kotatsu.core.util.ext.crossfade @@ -62,7 +63,7 @@ fun sourceConfigItemDelegate2( binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { crossfade(context) error(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) fallback(fallbackIcon) source(item.source) enqueueWith(coil) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt index fa7d0441c..035437529 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt @@ -1,9 +1,7 @@ package org.koitharu.kotatsu.settings.sources.catalog import androidx.core.content.ContextCompat -import androidx.core.view.ViewCompat import androidx.core.view.isVisible -import androidx.core.view.updatePadding import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding @@ -12,16 +10,15 @@ import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier.Companion.ignoreC import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.drawableStart import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.source -import org.koitharu.kotatsu.databinding.ItemCatalogPageBinding import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding import org.koitharu.kotatsu.list.ui.model.ListModel @@ -55,7 +52,7 @@ fun sourceCatalogItemSourceAD( binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { crossfade(context) error(fallbackIcon) - placeholder(fallbackIcon) + placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) fallback(fallbackIcon) source(item.source) ignoreCaptchaErrors() @@ -79,30 +76,3 @@ fun sourceCatalogItemHintAD( binding.textSecondary.setTextAndVisible(item.text) } } - -fun sourceCatalogPageAD( - listener: OnListItemClickListener, - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( - { inflater, parent -> ItemCatalogPageBinding.inflate(inflater, parent, false) }, -) { - - val sourcesAdapter = SourcesCatalogAdapter(listener, coil, lifecycleOwner) - with(binding.recyclerView) { - setHasFixedSize(true) - adapter = sourcesAdapter - } - val insetsDelegate = WindowInsetsDelegate() - ViewCompat.setOnApplyWindowInsetsListener(itemView, insetsDelegate) - itemView.addOnLayoutChangeListener(insetsDelegate) - insetsDelegate.addInsetsListener { insets -> - binding.recyclerView.updatePadding( - bottom = insets.bottom + binding.recyclerView.paddingTop, - ) - } - - bind { - sourcesAdapter.items = item.items - } -}