Improve drawable and views state management

This commit is contained in:
Koitharu
2025-01-04 16:22:13 +02:00
parent 14973298a0
commit dc2e603356
16 changed files with 254 additions and 153 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<EditText>? = 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

View File

@@ -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 <R> Bitmap.use(block: (Bitmap) -> R) = try {
} finally {
recycle()
}
fun ColorStateList.hasFocusStateSpecified(): Boolean {
return getColorForState(intArrayOf(android.R.attr.state_focused), defaultColor) != defaultColor
}

View File

@@ -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<SheetTagsBinding>(), OnListItemClickListener<TagCatalogItem>, TextWatcher,
AdaptiveSheetCallback, View.OnFocusChangeListener, TextView.OnEditorActionListener {
class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(),
OnListItemClickListener<TagCatalogItem>,
DefaultTextWatcher,
AdaptiveSheetCallback,
View.OnFocusChangeListener,
TextView.OnEditorActionListener {
private val viewModel by viewModels<TagsCatalogViewModel>(
extrasProducer = {
@@ -75,10 +79,6 @@ class TagsCatalogSheet : BaseAdaptiveSheet<SheetTagsBinding>(), 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

View File

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

View File

@@ -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<ActivityProtectBinding>(),
TextView.OnEditorActionListener,
TextWatcher,
DefaultTextWatcher,
View.OnClickListener {
private val viewModel by viewModels<ProtectViewModel>()
@@ -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()

View File

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

View File

@@ -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<ActivityKitsuAuthBinding>(), View.OnClickListener, TextWatcher {
class KitsuAuthActivity : BaseActivity<ActivityKitsuAuthBinding>(), 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<ActivityKitsuAuthBinding>(), 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()

View File

@@ -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<ActivitySetupProtectBinding>(),
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

View File

@@ -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<ActivitySyncAuthBinding>(), 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()