diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt index 89f5e9068..d0a5dc319 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt @@ -72,7 +72,7 @@ abstract class BaseActivity : onBackPressedDispatcher.addCallback(actionModeDelegate) } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { putDataToExtras(intent) super.onNewIntent(intent) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt new file mode 100644 index 000000000..6b85d00d5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt @@ -0,0 +1,162 @@ +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.Outline +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.graphics.drawable.LayerDrawable +import android.os.Build +import androidx.annotation.ReturnThis +import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList +import org.koitharu.kotatsu.core.util.ext.resolveDp +import org.koitharu.kotatsu.parsers.util.toIntUp +import com.google.android.material.R as materialR + +class CardDrawable( + context: Context, + private var corners: Int, +) : Drawable() { + + private val cornerSize = context.resources.resolveDp(12f) + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val cornersF = FloatArray(8) + private val boundsF = RectF() + private val color: ColorStateList + private val path = Path() + private var alpha = 255 + private var state: IntArray? = null + private var horizontalInset: Int = 0 + + init { + paint.style = Paint.Style.FILL + color = context.getThemeColorStateList(materialR.attr.colorSurfaceContainerHighest) + ?: ColorStateList.valueOf(Color.TRANSPARENT) + setCorners(corners) + updateColor() + } + + override fun draw(canvas: Canvas) { + canvas.drawPath(path, paint) + } + + override fun setAlpha(alpha: Int) { + this.alpha = alpha + updateColor() + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + paint.colorFilter = colorFilter + } + + override fun getColorFilter(): ColorFilter? = paint.colorFilter + + override fun getOutline(outline: Outline) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + outline.setPath(path) + } else if (path.isConvex) { + outline.setConvexPath(path) + } + outline.alpha = 1f + } + + override fun getPadding(padding: Rect): Boolean { + padding.set( + horizontalInset, + 0, + horizontalInset, + 0, + ) + if (corners or TOP != 0) { + padding.top += cornerSize.toIntUp() + } + if (corners or BOTTOM != 0) { + padding.bottom += cornerSize.toIntUp() + } + return horizontalInset != 0 + } + + override fun onStateChange(state: IntArray): Boolean { + this.state = state + if (color.isStateful) { + updateColor() + return true + } else { + return false + } + } + + @Deprecated("Deprecated in Java") + override fun getOpacity(): Int = PixelFormat.TRANSPARENT + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + boundsF.set(bounds) + boundsF.inset(horizontalInset.toFloat(), 0f) + path.reset() + path.addRoundRect(boundsF, cornersF, Path.Direction.CW) + path.close() + } + + @ReturnThis + fun setCorners(corners: Int): CardDrawable { + this.corners = corners + val topLeft = if (corners and TOP_LEFT == TOP_LEFT) cornerSize else 0f + val topRight = if (corners and TOP_RIGHT == TOP_RIGHT) cornerSize else 0f + val bottomRight = if (corners and BOTTOM_RIGHT == BOTTOM_RIGHT) cornerSize else 0f + val bottomLeft = if (corners and BOTTOM_LEFT == BOTTOM_LEFT) cornerSize else 0f + cornersF[0] = topLeft + cornersF[1] = topLeft + cornersF[2] = topRight + cornersF[3] = topRight + cornersF[4] = bottomRight + cornersF[5] = bottomRight + cornersF[6] = bottomLeft + cornersF[7] = bottomLeft + invalidateSelf() + return this + } + + fun setHorizontalInset(inset: Int) { + horizontalInset = inset + invalidateSelf() + } + + private fun updateColor() { + paint.color = color.getColorForState(state, color.defaultColor) + paint.alpha = alpha + } + + companion object { + + const val TOP_LEFT = 1 + const val TOP_RIGHT = 2 + const val BOTTOM_LEFT = 4 + const val BOTTOM_RIGHT = 8 + + const val LEFT = TOP_LEFT or BOTTOM_LEFT + const val TOP = TOP_LEFT or TOP_RIGHT + const val RIGHT = TOP_RIGHT or BOTTOM_RIGHT + const val BOTTOM = BOTTOM_LEFT or BOTTOM_RIGHT + + const val NONE = 0 + const val ALL = TOP_LEFT or TOP_RIGHT or BOTTOM_RIGHT or BOTTOM_LEFT + + fun from(d: Drawable?): CardDrawable? = when (d) { + null -> null + is CardDrawable -> d + is LayerDrawable -> (0 until d.numberOfLayers).firstNotNullOfOrNull { i -> + from(d.getDrawable(i)) + } + + else -> null + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt index ca5d93ed0..28fad310e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/ChaptersMapper.kt @@ -72,7 +72,8 @@ fun MangaDetails.mapChapters( fun List.withVolumeHeaders(context: Context): List { var prevVolume = 0 val result = ArrayList((size * 1.4).toInt()) - for (item in this) { + var groupPos: Byte = 0 + for ((index, item) in this.withIndex()) { val chapter = item.chapter if (chapter.volume != prevVolume) { val text = if (chapter.volume == 0) { @@ -82,8 +83,19 @@ fun List.withVolumeHeaders(context: Context): List { } result.add(ListHeader(text)) prevVolume = chapter.volume + groupPos = ChapterListItem.GROUP_START + } else if (groupPos == ChapterListItem.GROUP_START) { + groupPos = ChapterListItem.GROUP_MIDDLE + } + if (groupPos != 0.toByte()) { + val next = this.getOrNull(index + 1) + if (next == null || next.chapter.volume != prevVolume) { + groupPos = ChapterListItem.GROUP_END + } + result.add(item.copy(groupPosition = groupPos)) + } else { + result.add(item) } - result.add(item) } return result } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt index 1d52845d1..f1f08e9e9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt @@ -13,13 +13,13 @@ import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemChapterBinding import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.list.ui.model.ListModel -import com.google.android.material.R as MR +import com.google.android.material.R as materialR fun chapterListItemAD( clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( viewBinding = { inflater, parent -> ItemChapterBinding.inflate(inflater, parent, false) }, - on = { item, _, _ -> item is ChapterListItem && !item.isGrid } + on = { item, _, _ -> item is ChapterListItem && !item.isGrid }, ) { val eventListener = AdapterDelegateClickListenerAdapter(this, clickListener) @@ -27,10 +27,17 @@ fun chapterListItemAD( itemView.setOnLongClickListener(eventListener) bind { payloads -> - if (payloads.isEmpty()) { - binding.textViewTitle.text = item.chapter.name - binding.textViewDescription.textAndVisible = item.description - } + binding.textViewTitle.text = item.chapter.name + binding.textViewDescription.textAndVisible = item.description + itemView.setBackgroundResource( + when { + item.isGroupStart && item.isGroupEnd -> R.drawable.bg_card_full + item.isGroupStart -> R.drawable.bg_card_top + item.isGroupMiddle -> R.drawable.bg_card_none + item.isGroupEnd -> R.drawable.bg_card_bottom + else -> R.drawable.list_selector + }, + ) when { item.isCurrent -> { binding.textViewTitle.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_current_chapter) @@ -47,7 +54,7 @@ fun chapterListItemAD( null } binding.textViewTitle.setTextColor(context.getThemeColorStateList(android.R.attr.textColorPrimary)) - binding.textViewDescription.setTextColor(context.getThemeColorStateList(MR.attr.colorOutline)) + binding.textViewDescription.setTextColor(context.getThemeColorStateList(materialR.attr.colorOutline)) binding.textViewTitle.typeface = Typeface.DEFAULT binding.textViewDescription.typeface = Typeface.DEFAULT } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt index f4cbadfb1..22d6b8dff 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt @@ -7,7 +7,6 @@ import android.graphics.Paint import android.graphics.RectF import android.view.View import androidx.cardview.widget.CardView -import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.recyclerview.widget.RecyclerView import org.koitharu.kotatsu.R @@ -20,10 +19,7 @@ import com.google.android.material.R as materialR class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() { private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - private val radius = context.resources.getDimension(materialR.dimen.abc_control_corner_material) - private val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle) - private val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_offset) - private val iconSize = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_size) + private val defaultRadius = context.resources.getDimension(materialR.dimen.abc_control_corner_material) private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED) private val fillColor = ColorUtils.setAlphaComponent( ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f), @@ -36,12 +32,11 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor 98, ) paint.style = Paint.Style.FILL - hasBackground = true + hasBackground = false hasForeground = true isIncludeDecorAndMargins = false paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width) - checkIcon?.setTint(strokeColor) } override fun getItemId(parent: RecyclerView, child: View): Long { @@ -50,19 +45,6 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor return item.chapter.id } - override fun onDrawBackground( - canvas: Canvas, - parent: RecyclerView, - child: View, - bounds: RectF, - state: RecyclerView.State, - ) { - if (child is CardView) { - return - } - canvas.drawRoundRect(bounds, radius, radius, paint) - } - override fun onDrawForeground( canvas: Canvas, parent: RecyclerView, @@ -70,24 +52,16 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor bounds: RectF, state: RecyclerView.State ) { - if (child !is CardView) { - return + val radius = if (child is CardView) { + child.radius + } else { + defaultRadius } - val radius = child.radius paint.color = fillColor paint.style = Paint.Style.FILL canvas.drawRoundRect(bounds, radius, radius, paint) paint.color = strokeColor paint.style = Paint.Style.STROKE canvas.drawRoundRect(bounds, radius, radius, paint) - checkIcon?.run { - setBounds( - (bounds.right - iconSize - iconOffset).toInt(), - (bounds.top + iconOffset).toInt(), - (bounds.right - iconOffset).toInt(), - (bounds.top + iconOffset + iconSize).toInt(), - ) - draw(canvas) - } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt index 19a14e133..434dbf199 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt @@ -5,11 +5,13 @@ import org.jsoup.internal.StringUtil.StringJoiner import org.koitharu.kotatsu.core.model.formatNumber import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.MangaChapter +import kotlin.experimental.and data class ChapterListItem( val chapter: MangaChapter, - val flags: Int, + val flags: Byte, private val uploadDateMs: Long, + private val groupPosition: Byte, ) : ListModel { var description: String? = null @@ -51,6 +53,15 @@ data class ChapterListItem( val isGrid: Boolean get() = hasFlag(FLAG_GRID) + val isGroupStart: Boolean + get() = (groupPosition and GROUP_START) == GROUP_START + + val isGroupMiddle: Boolean + get() = (groupPosition and GROUP_MIDDLE) == GROUP_MIDDLE + + val isGroupEnd: Boolean + get() = (groupPosition and GROUP_END) == GROUP_END + private fun buildDescription(): String { val joiner = StringJoiner(" • ") chapter.formatNumber()?.let { @@ -67,7 +78,7 @@ data class ChapterListItem( return joiner.complete() } - private fun hasFlag(flag: Int): Boolean { + private fun hasFlag(flag: Byte): Boolean { return (flags and flag) == flag } @@ -88,11 +99,15 @@ data class ChapterListItem( companion object { - const val FLAG_UNREAD = 2 - const val FLAG_CURRENT = 4 - const val FLAG_NEW = 8 - const val FLAG_BOOKMARKED = 16 - const val FLAG_DOWNLOADED = 32 - const val FLAG_GRID = 64 + const val FLAG_UNREAD: Byte = 2 + const val FLAG_CURRENT: Byte = 4 + const val FLAG_NEW: Byte = 8 + const val FLAG_BOOKMARKED: Byte = 16 + const val FLAG_DOWNLOADED: Byte = 32 + const val FLAG_GRID: Byte = 64 + + const val GROUP_START: Byte = 2 + const val GROUP_MIDDLE: Byte = 4 + const val GROUP_END: Byte = 8 } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt index f08ca79a6..5d1aff72d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_GRID import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_NEW import org.koitharu.kotatsu.details.ui.model.ChapterListItem.Companion.FLAG_UNREAD import org.koitharu.kotatsu.parsers.model.MangaChapter +import kotlin.experimental.or fun MangaChapter.toListItem( isCurrent: Boolean, @@ -16,7 +17,7 @@ fun MangaChapter.toListItem( isBookmarked: Boolean, isGrid: Boolean, ): ChapterListItem { - var flags = 0 + var flags: Byte = 0 if (isCurrent) flags = flags or FLAG_CURRENT if (isUnread) flags = flags or FLAG_UNREAD if (isNew) flags = flags or FLAG_NEW @@ -27,5 +28,6 @@ fun MangaChapter.toListItem( chapter = this, flags = flags, uploadDateMs = uploadDate, + groupPosition = 0, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaSelectionDecoration.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaSelectionDecoration.kt index 5518e15a3..a2824ed63 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaSelectionDecoration.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaSelectionDecoration.kt @@ -7,7 +7,6 @@ import android.graphics.Paint import android.graphics.RectF import android.view.View import androidx.cardview.widget.CardView -import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID @@ -21,9 +20,6 @@ import com.google.android.material.R as materialR open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() { protected val paint = Paint(Paint.ANTI_ALIAS_FLAG) - protected val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle) - protected val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_offset) - protected val iconSize = context.resources.getDimensionPixelOffset(R.dimen.card_indicator_size) protected val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED) protected val fillColor = ColorUtils.setAlphaComponent( ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f), @@ -37,7 +33,6 @@ open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDec isIncludeDecorAndMargins = false paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width) - checkIcon?.setTint(strokeColor) } override fun getItemId(parent: RecyclerView, child: View): Long { @@ -53,7 +48,6 @@ open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDec bounds: RectF, state: RecyclerView.State, ) { - val isCard = child is CardView val radius = (child as? CardView)?.radius ?: defaultRadius paint.color = fillColor paint.style = Paint.Style.FILL @@ -61,16 +55,5 @@ open class MangaSelectionDecoration(context: Context) : AbstractSelectionItemDec paint.color = strokeColor paint.style = Paint.Style.STROKE canvas.drawRoundRect(bounds, radius, radius, paint) - if (isCard) { - checkIcon?.run { - setBounds( - (bounds.left + iconOffset).toInt(), - (bounds.top + iconOffset).toInt(), - (bounds.left + iconOffset + iconSize).toInt(), - (bounds.top + iconOffset + iconSize).toInt(), - ) - draw(canvas) - } - } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt index a050061b5..736c5c5f4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt @@ -65,7 +65,7 @@ class ScrobblerConfigActivity : BaseActivity(), processIntent(intent) } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) if (intent != null) { setIntent(intent) diff --git a/app/src/main/res/drawable/bg_card_bottom.xml b/app/src/main/res/drawable/bg_card_bottom.xml new file mode 100644 index 000000000..f693f7c2a --- /dev/null +++ b/app/src/main/res/drawable/bg_card_bottom.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_card_full.xml b/app/src/main/res/drawable/bg_card_full.xml new file mode 100644 index 000000000..316531284 --- /dev/null +++ b/app/src/main/res/drawable/bg_card_full.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_card_none.xml b/app/src/main/res/drawable/bg_card_none.xml new file mode 100644 index 000000000..2e6a50b46 --- /dev/null +++ b/app/src/main/res/drawable/bg_card_none.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_card_top.xml b/app/src/main/res/drawable/bg_card_top.xml new file mode 100644 index 000000000..145b73611 --- /dev/null +++ b/app/src/main/res/drawable/bg_card_top.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index fb7f08e0f..081fb6f85 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -72,6 +72,7 @@ 6dp 400dp + 12dp 200dp