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 index 39d1405f4..23efccce9 100644 --- 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 @@ -14,7 +14,6 @@ import com.google.android.material.animation.ArgbEvaluatorCompat import com.google.android.material.color.MaterialColors import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.util.KotatsuColors import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.mangaSourceKey import kotlin.math.abs @@ -29,8 +28,8 @@ class AnimatedFaviconDrawable( 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) + private var colorHigh = MaterialColors.harmonize(colorForeground, currentBackgroundColor) + private var colorLow = ArgbEvaluatorCompat.getInstance().evaluate(0.3f, colorHigh, currentBackgroundColor) init { timeAnimator.setTimeListener(this) @@ -49,6 +48,8 @@ class AnimatedFaviconDrawable( override fun getAlpha(): Int = 255 + override fun isOpaque(): Boolean = false + override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) { callback?.also { updateColor() @@ -66,13 +67,21 @@ class AnimatedFaviconDrawable( override fun isRunning(): Boolean = timeAnimator.isStarted + override fun onStateChange(state: IntArray): Boolean { + val res = super.onStateChange(state) + colorHigh = MaterialColors.harmonize(currentForegroundColor, currentBackgroundColor) + colorLow = ArgbEvaluatorCompat.getInstance().evaluate(0.3f, colorHigh, currentBackgroundColor) + updateColor() + return res + } + private fun updateColor() { if (period <= 0f) { return } val ph = period / 2 val fraction = abs((System.currentTimeMillis() % period) - ph) / ph.toFloat() - colorForeground = ArgbEvaluatorCompat.getInstance() + currentForegroundColor = ArgbEvaluatorCompat.getInstance() .evaluate(interpolator.getInterpolation(fraction), colorLow, colorHigh) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt index 2fb7aa969..ee6542775 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt @@ -41,14 +41,14 @@ class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, Ti // this.alpha = alpha FIXME coil's crossfade } - override fun setColorFilter(colorFilter: ColorFilter?) = Unit - @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("Deprecated in Java") - override fun getOpacity(): Int = PixelFormat.OPAQUE + override fun getOpacity(): Int = PixelFormat.TRANSLUCENT override fun getAlpha(): Int = 255 + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) { callback?.also { updateColor() 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 108b7175d..468163bdd 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 @@ -1,15 +1,15 @@ package org.koitharu.kotatsu.core.ui.image import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color -import android.graphics.ColorFilter import android.graphics.Paint import android.graphics.Path -import android.graphics.PixelFormat import android.graphics.Rect import android.graphics.RectF -import android.graphics.drawable.Drawable +import android.os.Build +import androidx.annotation.RequiresApi import androidx.annotation.StyleRes import androidx.core.content.withStyledAttributes import androidx.core.graphics.withClip @@ -21,18 +21,24 @@ import com.google.android.material.color.MaterialColors import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.util.KotatsuColors +import org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified import org.koitharu.kotatsu.core.util.ext.mangaSourceKey open class FaviconDrawable( context: Context, @StyleRes styleResId: Int, name: String, -) : Drawable() { +) : PaintDrawable() { - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - protected var colorBackground = Color.WHITE + override val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) + protected var currentBackgroundColor = Color.WHITE + private set + private var colorBackground: ColorStateList = ColorStateList.valueOf(currentBackgroundColor) protected var colorForeground = Color.DKGRAY - private var colorStroke = Color.LTGRAY + protected var currentForegroundColor = Color.DKGRAY + protected var currentStrokeColor = Color.LTGRAY + private set + private var colorStroke: ColorStateList = ColorStateList.valueOf(currentStrokeColor) private val letter = name.take(1).uppercase() private var cornerSize = 0f private var intrinsicSize = -1 @@ -43,15 +49,16 @@ open class FaviconDrawable( init { context.withStyledAttributes(styleResId, R.styleable.FaviconFallbackDrawable) { - colorBackground = getColor(R.styleable.FaviconFallbackDrawable_backgroundColor, colorBackground) - colorStroke = getColor(R.styleable.FaviconFallbackDrawable_strokeColor, colorStroke) + colorBackground = getColorStateList(R.styleable.FaviconFallbackDrawable_backgroundColor) ?: colorBackground + colorStroke = getColorStateList(R.styleable.FaviconFallbackDrawable_strokeColor) ?: colorStroke cornerSize = getDimension(R.styleable.FaviconFallbackDrawable_cornerSize, cornerSize) paint.strokeWidth = getDimension(R.styleable.FaviconFallbackDrawable_strokeWidth, 0f) * 2f intrinsicSize = getDimensionPixelSize(R.styleable.FaviconFallbackDrawable_drawableSize, intrinsicSize) } paint.textAlign = Paint.Align.CENTER paint.isFakeBoldText = true - colorForeground = MaterialColors.harmonize(KotatsuColors.random(name), colorBackground) + colorForeground = KotatsuColors.random(name) + currentForegroundColor = MaterialColors.harmonize(colorForeground, colorBackground.defaultColor) } override fun draw(canvas: Canvas) { @@ -75,35 +82,42 @@ open class FaviconDrawable( clipPath.close() } - override fun setAlpha(alpha: Int) { - paint.alpha = alpha - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.colorFilter = colorFilter - } - override fun getIntrinsicWidth(): Int = intrinsicSize override fun getIntrinsicHeight(): Int = intrinsicSize - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Deprecated in Java") - override fun getOpacity() = PixelFormat.TRANSPARENT + override fun isOpaque(): Boolean = cornerSize == 0f && colorBackground.isOpaque + + override fun isStateful(): Boolean = colorStroke.isStateful || colorBackground.isStateful + + @RequiresApi(Build.VERSION_CODES.S) + override fun hasFocusStateSpecified(): Boolean = + colorBackground.hasFocusStateSpecified() || colorStroke.hasFocusStateSpecified() + + override fun onStateChange(state: IntArray): Boolean { + val prevStrokeColor = currentStrokeColor + val prevBackgroundColor = currentBackgroundColor + currentStrokeColor = colorStroke.getColorForState(state, colorStroke.defaultColor) + currentBackgroundColor = colorBackground.getColorForState(state, colorBackground.defaultColor) + if (currentBackgroundColor != prevBackgroundColor) { + currentForegroundColor = MaterialColors.harmonize(colorForeground, currentBackgroundColor) + } + return prevBackgroundColor != currentBackgroundColor || prevStrokeColor != currentStrokeColor + } private fun doDraw(canvas: Canvas) { // background - paint.color = colorBackground + paint.color = currentBackgroundColor paint.style = Paint.Style.FILL canvas.drawPaint(paint) // letter - paint.color = colorForeground + paint.color = currentForegroundColor val cx = (boundsF.left + boundsF.right) * 0.6f val ty = boundsF.bottom * 0.7f + textBounds.height() * 0.5f - textBounds.bottom canvas.drawText(letter, cx, ty, paint) if (paint.strokeWidth > 0f) { // stroke - paint.color = colorStroke + paint.color = currentStrokeColor paint.style = Paint.Style.STROKE canvas.drawPath(clipPath, paint) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/PaintDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/PaintDrawable.kt new file mode 100644 index 000000000..a5d6a7e91 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/PaintDrawable.kt @@ -0,0 +1,53 @@ +package org.koitharu.kotatsu.core.ui.image + +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable + +@Suppress("OVERRIDE_DEPRECATION") +abstract class PaintDrawable : Drawable() { + + protected abstract val paint: Paint + + override fun setAlpha(alpha: Int) { + paint.alpha = alpha + } + + override fun getAlpha(): Int { + return paint.alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + paint.colorFilter = colorFilter + } + + override fun getColorFilter(): ColorFilter? { + return paint.colorFilter + } + + override fun setDither(dither: Boolean) { + paint.isDither = dither + } + + override fun setFilterBitmap(filter: Boolean) { + paint.isFilterBitmap = filter + } + + override fun isFilterBitmap(): Boolean { + return paint.isFilterBitmap + } + + override fun getOpacity(): Int { + if (paint.colorFilter != null) { + return PixelFormat.TRANSLUCENT + } + return when (paint.alpha) { + 0 -> PixelFormat.TRANSPARENT + 255 -> if (isOpaque()) PixelFormat.OPAQUE else PixelFormat.TRANSLUCENT + else -> PixelFormat.TRANSLUCENT + } + } + + protected open fun isOpaque() = false +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextDrawable.kt index 14cd84006..f2d2cd78a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/TextDrawable.kt @@ -3,20 +3,20 @@ package org.koitharu.kotatsu.core.ui.image import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color -import android.graphics.ColorFilter import android.graphics.Paint -import android.graphics.PixelFormat import android.graphics.PointF import android.graphics.Rect -import android.graphics.drawable.Drawable +import android.os.Build import android.widget.TextView +import androidx.annotation.RequiresApi import androidx.core.graphics.PaintCompat +import org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified class TextDrawable( val text: String, -) : Drawable() { +) : PaintDrawable() { - private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) + override val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) private val textBounds = Rect() private val textPoint = PointF() @@ -41,20 +41,6 @@ class TextDrawable( canvas.drawText(text, textPoint.x, textPoint.y, paint) } - override fun setAlpha(alpha: Int) { - paint.alpha = alpha - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.setColorFilter(colorFilter) - } - - override fun getOpacity(): Int = when (paint.alpha) { - 0 -> PixelFormat.TRANSPARENT - 255 -> PixelFormat.OPAQUE - else -> PixelFormat.TRANSLUCENT - } - override fun onBoundsChange(bounds: Rect) { textPoint.set( bounds.exactCenterX() - textBounds.exactCenterX(), @@ -66,16 +52,10 @@ class TextDrawable( override fun getIntrinsicHeight(): Int = textBounds.height() - override fun setDither(dither: Boolean) { - paint.isDither = dither - } - override fun isStateful(): Boolean = textColor.isStateful - override fun hasFocusStateSpecified(): Boolean = textColor.getColorForState( - intArrayOf(android.R.attr.state_focused), - textColor.defaultColor, - ) != textColor.defaultColor + @RequiresApi(Build.VERSION_CODES.S) + override fun hasFocusStateSpecified(): Boolean = textColor.hasFocusStateSpecified() override fun onStateChange(state: IntArray): Boolean { val prevColor = paint.color diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/DotsIndicator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/DotsIndicator.kt index 17eaaca1d..d05145bb6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/DotsIndicator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/DotsIndicator.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.core.ui.widgets import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint @@ -11,7 +12,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import androidx.viewpager2.widget.ViewPager2 import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.util.ext.getThemeColor +import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList import org.koitharu.kotatsu.core.util.ext.measureDimension import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.parsers.util.toIntUp @@ -30,6 +31,7 @@ class DotsIndicator @JvmOverloads constructor( private var smallDotAlpha = 0.6f private var positionOffset: Float = 0f private var position: Int = 0 + private var dotsColor: ColorStateList = ColorStateList.valueOf(Color.DKGRAY) private val inset = context.resources.resolveDp(1f) var max: Int = 6 @@ -52,10 +54,10 @@ class DotsIndicator @JvmOverloads constructor( init { paint.style = Paint.Style.FILL context.withStyledAttributes(attrs, R.styleable.DotsIndicator, defStyleAttr) { - paint.color = getColor( - R.styleable.DotsIndicator_dotColor, - context.getThemeColor(materialR.attr.colorOnBackground, Color.DKGRAY), - ) + dotsColor = getColorStateList(R.styleable.DotsIndicator_dotColor) + ?: context.getThemeColorStateList(materialR.attr.colorOnBackground) + ?: dotsColor + paint.color = dotsColor.defaultColor indicatorSize = getDimension(R.styleable.DotsIndicator_dotSize, indicatorSize) dotSpacing = getDimension(R.styleable.DotsIndicator_dotSpacing, dotSpacing) smallDotScale = getFloat(R.styleable.DotsIndicator_dotScale, smallDotScale).coerceIn(0f, 1f) @@ -89,6 +91,13 @@ class DotsIndicator @JvmOverloads constructor( } } + override fun drawableStateChanged() { + if (dotsColor.isStateful) { + paint.color = dotsColor.getColorForState(drawableState, dotsColor.defaultColor) + } + super.drawableStateChanged() + } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val dotSize = getDotSize() val desiredHeight = (dotSize + paddingTop + paddingBottom).toIntUp() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ProgressButton.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ProgressButton.kt index 133b66071..33f955630 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ProgressButton.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ProgressButton.kt @@ -28,6 +28,7 @@ import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.core.util.ext.textAndVisible import com.google.android.material.R as materialR +@Deprecated("") class ProgressButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt index 3554fc194..e00d05efb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/EditTextValidator.kt @@ -2,13 +2,13 @@ package org.koitharu.kotatsu.core.util import android.content.Context import android.text.Editable -import android.text.TextWatcher import android.widget.EditText import androidx.annotation.CallSuper +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import java.lang.ref.WeakReference -abstract class EditTextValidator : TextWatcher { +abstract class EditTextValidator : DefaultTextWatcher { private var editTextRef: WeakReference? = null @@ -17,10 +17,6 @@ abstract class EditTextValidator : TextWatcher { "EditTextValidator is not attached to EditText" } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - @CallSuper override fun afterTextChanged(s: Editable?) { val editText = editTextRef?.get() ?: return diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt index 2a9f0b81c..9464303f0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Graphics.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.util.ext +import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.Rect import kotlin.math.roundToInt @@ -18,3 +19,7 @@ inline fun Bitmap.use(block: (Bitmap) -> R) = try { } finally { recycle() } + +fun ColorStateList.hasFocusStateSpecified(): Boolean { + return getColorForState(intArrayOf(android.R.attr.state_focused), defaultColor) != defaultColor +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt index e6c8ae481..f209ab7ea 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/tags/TagsCatalogSheet.kt @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.filter.ui.tags import android.os.Bundle import android.text.Editable -import android.text.TextWatcher import android.view.KeyEvent import android.view.LayoutInflater import android.view.View @@ -17,14 +16,19 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetBehavior import org.koitharu.kotatsu.core.ui.sheet.AdaptiveSheetCallback import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.databinding.SheetTagsBinding import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem @AndroidEntryPoint -class TagsCatalogSheet : BaseAdaptiveSheet(), OnListItemClickListener, TextWatcher, - AdaptiveSheetCallback, View.OnFocusChangeListener, TextView.OnEditorActionListener { +class TagsCatalogSheet : BaseAdaptiveSheet(), + OnListItemClickListener, + DefaultTextWatcher, + AdaptiveSheetCallback, + View.OnFocusChangeListener, + TextView.OnEditorActionListener { private val viewModel by viewModels( extrasProducer = { @@ -75,10 +79,6 @@ class TagsCatalogSheet : BaseAdaptiveSheet(), OnListItemClickL } } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) { val q = s?.toString().orEmpty() viewModel.searchQuery.value = q diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressDrawable.kt index 48a9f5e76..526a52c23 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/util/ReadingProgressDrawable.kt @@ -1,17 +1,18 @@ package org.koitharu.kotatsu.history.ui.util import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color -import android.graphics.ColorFilter import android.graphics.Paint -import android.graphics.PixelFormat import android.graphics.Rect -import android.graphics.drawable.Drawable +import android.os.Build +import androidx.annotation.RequiresApi import androidx.annotation.StyleRes import androidx.appcompat.content.res.AppCompatResources -import androidx.core.graphics.ColorUtils import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.ui.image.PaintDrawable +import org.koitharu.kotatsu.core.util.ext.hasFocusStateSpecified import org.koitharu.kotatsu.core.util.ext.scale import org.koitharu.kotatsu.list.domain.ReadingProgress import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE @@ -19,23 +20,29 @@ import org.koitharu.kotatsu.list.domain.ReadingProgress.Companion.PROGRESS_NONE class ReadingProgressDrawable( context: Context, @StyleRes styleResId: Int, -) : Drawable() { +) : PaintDrawable() { - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + override val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) private val checkDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_check) - private val lineColor: Int - private val outlineColor: Int - private val backgroundColor: Int - private val textColor: Int + private val lineColor: ColorStateList + private val outlineColor: ColorStateList + private val backgroundColor: ColorStateList + private val textColor: ColorStateList private val textBounds = Rect() private val tempRect = Rect() - private val hasBackground: Boolean - private val hasOutline: Boolean - private val hasText: Boolean private val desiredHeight: Int private val desiredWidth: Int private val autoFitTextSize: Boolean + private var currentLineColor: Int = Color.TRANSPARENT + private var currentOutlineColor: Int = Color.TRANSPARENT + private var currentBackgroundColor: Int = Color.TRANSPARENT + private var currentTextColor: Int = Color.TRANSPARENT + private var hasBackground: Boolean = false + private var hasOutline: Boolean = false + private var hasText: Boolean = false + + var percent: Float = PROGRESS_NONE set(value) { field = value @@ -54,22 +61,21 @@ class ReadingProgressDrawable( desiredHeight = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_height, -1) desiredWidth = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_width, -1) autoFitTextSize = ta.getBoolean(R.styleable.ProgressDrawable_autoFitTextSize, false) - lineColor = ta.getColor(R.styleable.ProgressDrawable_android_strokeColor, Color.BLACK) - outlineColor = ta.getColor(R.styleable.ProgressDrawable_outlineColor, Color.TRANSPARENT) - backgroundColor = ColorUtils.setAlphaComponent( - ta.getColor(R.styleable.ProgressDrawable_android_fillColor, Color.TRANSPARENT), - (255 * ta.getFloat(R.styleable.ProgressDrawable_android_fillAlpha, 0f)).toInt(), + lineColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_strokeColor) ?: ColorStateList.valueOf( + Color.BLACK, ) - textColor = ta.getColor(R.styleable.ProgressDrawable_android_textColor, lineColor) + outlineColor = + ta.getColorStateList(R.styleable.ProgressDrawable_outlineColor) ?: ColorStateList.valueOf(Color.TRANSPARENT) + backgroundColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_fillColor)?.withAlpha( + (255 * ta.getFloat(R.styleable.ProgressDrawable_android_fillAlpha, 0f)).toInt(), + ) ?: ColorStateList.valueOf(Color.TRANSPARENT) + textColor = ta.getColorStateList(R.styleable.ProgressDrawable_android_textColor) ?: lineColor paint.strokeCap = Paint.Cap.ROUND paint.textAlign = Paint.Align.CENTER paint.textSize = ta.getDimension(R.styleable.ProgressDrawable_android_textSize, paint.textSize) paint.strokeWidth = ta.getDimension(R.styleable.ProgressDrawable_strokeWidth, 1f) ta.recycle() - hasBackground = Color.alpha(backgroundColor) != 0 - hasOutline = Color.alpha(outlineColor) != 0 - hasText = Color.alpha(textColor) != 0 && paint.textSize > 0 - checkDrawable?.setTint(textColor) + checkDrawable?.setTintList(textColor) } override fun onBoundsChange(bounds: Rect) { @@ -91,16 +97,16 @@ class ReadingProgressDrawable( val radius = minOf(bounds.width(), bounds.height()) / 2f if (hasBackground) { paint.style = Paint.Style.FILL - paint.color = backgroundColor + paint.color = currentBackgroundColor canvas.drawCircle(cx, cy, radius, paint) } val innerRadius = radius - paint.strokeWidth / 2f paint.style = Paint.Style.STROKE if (hasOutline) { - paint.color = outlineColor + paint.color = currentOutlineColor canvas.drawCircle(cx, cy, innerRadius, paint) } - paint.color = lineColor + paint.color = currentLineColor canvas.drawArc( cx - innerRadius, cy - innerRadius, @@ -119,29 +125,49 @@ class ReadingProgressDrawable( checkDrawable.draw(canvas) } else { paint.style = Paint.Style.FILL - paint.color = textColor + paint.color = currentTextColor val ty = bounds.height() / 2f + textBounds.height() / 2f - textBounds.bottom canvas.drawText(text, cx, ty, paint) } } } - override fun setAlpha(alpha: Int) { - paint.alpha = alpha - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.colorFilter = colorFilter - } - - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Deprecated in Java") - override fun getOpacity() = PixelFormat.TRANSLUCENT - override fun getIntrinsicHeight() = desiredHeight override fun getIntrinsicWidth() = desiredWidth + override fun isStateful(): Boolean = lineColor.isStateful || + outlineColor.isStateful || + backgroundColor.isStateful || + textColor.isStateful || + checkDrawable?.isStateful == true + + @RequiresApi(Build.VERSION_CODES.S) + override fun hasFocusStateSpecified(): Boolean = lineColor.hasFocusStateSpecified() || + outlineColor.hasFocusStateSpecified() || + backgroundColor.hasFocusStateSpecified() || + textColor.hasFocusStateSpecified() || + checkDrawable?.hasFocusStateSpecified() == true + + override fun onStateChange(state: IntArray): Boolean { + val prevLineColor = currentLineColor + currentLineColor = lineColor.getColorForState(state, lineColor.defaultColor) + val prevOutlineColor = currentOutlineColor + currentOutlineColor = outlineColor.getColorForState(state, outlineColor.defaultColor) + val prevBackgroundColor = currentBackgroundColor + currentBackgroundColor = backgroundColor.getColorForState(state, backgroundColor.defaultColor) + val prevTextColor = currentTextColor + currentTextColor = textColor.getColorForState(state, textColor.defaultColor) + hasBackground = Color.alpha(currentBackgroundColor) != 0 + hasOutline = Color.alpha(currentOutlineColor) != 0 + hasText = Color.alpha(currentTextColor) != 0 && paint.textSize > 0 + return checkDrawable?.setState(state) == true || + prevLineColor != currentLineColor || + prevOutlineColor != currentOutlineColor || + prevBackgroundColor != currentBackgroundColor || + prevTextColor != currentTextColor + } + private fun getTextSizeForWidth(width: Float, text: String): Float { val testTextSize = 48f paint.textSize = testTextSize diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt index 9516a8447..49867b411 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.Editable -import android.text.TextWatcher import android.view.KeyEvent import android.view.View import android.view.WindowManager @@ -21,6 +20,7 @@ import com.google.android.material.textfield.TextInputLayout import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.observe @@ -32,7 +32,7 @@ import com.google.android.material.R as materialR class ProtectActivity : BaseActivity(), TextView.OnEditorActionListener, - TextWatcher, + DefaultTextWatcher, View.OnClickListener { private val viewModel by viewModels() @@ -98,10 +98,6 @@ class ProtectActivity : } } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) { viewBinding.layoutPassword.error = null viewBinding.buttonNext.isEnabled = !s.isNullOrEmpty() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt index 7a5ca06d2..f772700ae 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt @@ -5,6 +5,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint @@ -19,12 +20,11 @@ import android.view.WindowInsets import androidx.annotation.AttrRes import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes -import androidx.core.graphics.ColorUtils import androidx.core.graphics.withScale import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.util.ext.getThemeColor +import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList import org.koitharu.kotatsu.core.util.ext.measureDimension import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.parsers.util.format @@ -41,7 +41,7 @@ class ReaderInfoBarView @JvmOverloads constructor( @AttrRes defStyleAttr: Int = 0, ) : View(context, attrs, defStyleAttr) { - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) private val textBounds = Rect() private val timeFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) private val systemStateReceiver = SystemStateReceiver() @@ -52,16 +52,16 @@ class ReaderInfoBarView @JvmOverloads constructor( private val insetRightFallback: Int private val insetTopFallback: Int private val insetCornerFallback = getSystemUiDimensionOffset("rounded_corner_content_padding") - private val colorText = ColorUtils.setAlphaComponent( - context.getThemeColor(materialR.attr.colorOnSurface, Color.BLACK), - 200, - ) - private val colorOutline = ColorUtils.setAlphaComponent( - context.getThemeColor(materialR.attr.colorSurface, Color.WHITE), - 200, - ) + private val colorText = + (context.getThemeColorStateList(materialR.attr.colorOnSurface) + ?: ColorStateList.valueOf(Color.BLACK)).withAlpha(200) + private val colorOutline = + (context.getThemeColorStateList(materialR.attr.colorSurface) + ?: ColorStateList.valueOf(Color.WHITE)).withAlpha(200) private val batteryIcon = ContextCompat.getDrawable(context, R.drawable.ic_battery_outline) + private var currentTextColor: Int = Color.TRANSPARENT + private var currentOutlineColor: Int = Color.TRANSPARENT private var timeText = timeFormat.format(LocalTime.now()) private var batteryText = "" private var text: String = "" @@ -169,6 +169,29 @@ class ReaderInfoBarView @JvmOverloads constructor( context.unregisterReceiver(systemStateReceiver) } + override fun verifyDrawable(who: Drawable): Boolean { + return who == batteryIcon || super.verifyDrawable(who) + } + + override fun jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState() + batteryIcon?.jumpToCurrentState() + } + + override fun onCreateDrawableState(extraSpace: Int): IntArray? { + val iconState = batteryIcon?.state ?: return super.onCreateDrawableState(extraSpace) + return mergeDrawableStates(super.onCreateDrawableState(extraSpace + iconState.size), iconState) + } + + override fun drawableStateChanged() { + currentTextColor = colorText.getColorForState(drawableState, colorText.defaultColor) + currentOutlineColor = colorOutline.getColorForState(drawableState, colorOutline.defaultColor) + super.drawableStateChanged() + if (batteryIcon != null && batteryIcon.isStateful && batteryIcon.setState(drawableState)) { + invalidateDrawable(batteryIcon) + } + } + fun update(state: ReaderUiState?) { text = if (state != null) { context.getString( @@ -200,17 +223,17 @@ class ReaderInfoBarView @JvmOverloads constructor( } private fun Canvas.drawTextOutline(text: String, x: Float, y: Float) { - paint.color = colorOutline + paint.color = currentOutlineColor paint.style = Paint.Style.STROKE drawText(text, x, y, paint) - paint.color = colorText + paint.color = currentTextColor paint.style = Paint.Style.FILL drawText(text, x, y, paint) } private fun Drawable.drawWithOutline(canvas: Canvas) { var requiredScale = (bounds.width() + paint.strokeWidth * 2f) / bounds.width().toFloat() - setTint(colorOutline) + setTint(currentOutlineColor) canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) { draw(canvas) } @@ -218,7 +241,7 @@ class ReaderInfoBarView @JvmOverloads constructor( canvas.withScale(requiredScale, requiredScale, bounds.exactCenterX(), bounds.exactCenterY()) { draw(canvas) } - setTint(colorText) + setTint(currentTextColor) draw(canvas) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt index d75576b22..f06a0857a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/ui/KitsuAuthActivity.kt @@ -4,16 +4,16 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.text.Editable -import android.text.TextWatcher import android.view.View import androidx.core.graphics.Insets import androidx.core.net.toUri import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.databinding.ActivityKitsuAuthBinding import org.koitharu.kotatsu.parsers.util.urlEncoded -class KitsuAuthActivity : BaseActivity(), View.OnClickListener, TextWatcher { +class KitsuAuthActivity : BaseActivity(), View.OnClickListener, DefaultTextWatcher { private val regexEmail = Regex("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", RegexOption.IGNORE_CASE) @@ -43,10 +43,6 @@ class KitsuAuthActivity : BaseActivity(), View.OnClick } } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) { val email = viewBinding.editEmail.text?.toString()?.trim() val password = viewBinding.editPassword.text?.toString()?.trim() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt index cc086c8cd..a4d62e73e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt @@ -4,7 +4,6 @@ import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.text.Editable -import android.text.TextWatcher import android.view.KeyEvent import android.view.View import android.view.WindowManager @@ -18,6 +17,7 @@ import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding @@ -27,7 +27,7 @@ private const val MIN_PASSWORD_LENGTH = 4 @AndroidEntryPoint class ProtectSetupActivity : BaseActivity(), - TextWatcher, + DefaultTextWatcher, View.OnClickListener, TextView.OnEditorActionListener, CompoundButton.OnCheckedChangeListener { @@ -90,10 +90,6 @@ class ProtectSetupActivity : } } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) { viewBinding.editPassword.error = null val isEnoughLength = (s?.length ?: 0) >= MIN_PASSWORD_LENGTH diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt index c8e71f564..34d342882 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt @@ -20,6 +20,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.util.DefaultTextWatcher import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat import org.koitharu.kotatsu.core.util.ext.observe @@ -187,11 +188,7 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi private class PasswordTextWatcher( private val button: Button, - ) : TextWatcher { - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + ) : DefaultTextWatcher { override fun afterTextChanged(s: Editable?) { val text = s?.toString()