Update list badges

This commit is contained in:
Koitharu
2024-12-30 09:48:50 +02:00
parent c2079ebca5
commit 15f37644c0
11 changed files with 173 additions and 16 deletions

View File

@@ -0,0 +1,100 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import android.util.AttributeSet
import androidx.core.content.withStyledAttributes
import androidx.customview.view.AbsSavedState
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.textview.MaterialTextView
import org.koitharu.kotatsu.R
class BadgeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : MaterialTextView(context, attrs, R.attr.badgeViewStyle) {
private var maxCharacterCount = Int.MAX_VALUE
var number: Int = 0
set(value) {
field = value
updateText()
}
init {
context.withStyledAttributes(attrs, R.styleable.BadgeView) {
maxCharacterCount = getInt(R.styleable.BadgeView_maxCharacterCount, maxCharacterCount)
number = getInt(R.styleable.BadgeView_number, number)
val shape = ShapeAppearanceModel.builder(
context,
getResourceId(R.styleable.BadgeView_shapeAppearance, 0),
0,
).build()
background = MaterialShapeDrawable(shape).also { bg ->
bg.fillColor = getColorStateList(R.styleable.BadgeView_backgroundColor)
}
}
}
override fun onSaveInstanceState(): Parcelable? {
val superState = super.onSaveInstanceState() ?: return null
return SavedState(superState, number)
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state is SavedState) {
super.onRestoreInstanceState(state.superState)
number = state.number
} else {
super.onRestoreInstanceState(state)
}
}
private fun updateText() {
if (number <= 0) {
text = null
return
}
val numberString = number.toString()
text = if (numberString.length > maxCharacterCount) {
buildString(maxCharacterCount) {
repeat(maxCharacterCount - 1) { append('9') }
append('+')
}
} else {
numberString
}
}
private class SavedState : AbsSavedState {
val number: Int
constructor(superState: Parcelable, number: Int) : super(superState) {
this.number = number
}
constructor(source: Parcel, classLoader: ClassLoader?) : super(source, classLoader) {
number = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(number)
}
companion object {
@Suppress("unused")
@JvmField
val CREATOR: Creator<SavedState> = object : Creator<SavedState> {
override fun createFromParcel(`in`: Parcel) = SavedState(`in`, SavedState::class.java.classLoader)
override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size)
}
}
}
}

View File

@@ -12,16 +12,19 @@ import com.google.android.material.badge.ExperimentalBadgeUtils
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
@Deprecated("")
@CheckResult
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
return bindBadgeImpl(badge, null, counter)
}
@Deprecated("")
@CheckResult
fun View.bindBadge(badge: BadgeDrawable?, text: String?): BadgeDrawable? {
return bindBadgeImpl(badge, text, 0)
}
@Deprecated("")
fun View.clearBadge(badge: BadgeDrawable?) {
BadgeUtils.detachBadgeDrawable(badge, this)
}

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner
import coil3.ImageLoader
import coil3.request.allowRgb565
import coil3.request.transformations
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
@@ -30,7 +29,6 @@ fun mangaGridItemAD(
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null
AdapterDelegateClickListenerAdapter(this, clickListener, MangaGridModel::manga).attach(itemView)
sizeResolver.attachToView(lifecycleOwner, itemView, binding.textViewTitle, binding.progressView)
@@ -47,6 +45,7 @@ fun mangaGridItemAD(
mangaExtra(item.manga)
enqueueWith(coil)
}
badge = itemView.bindBadge(badge, item.counter)
binding.badge.number = item.counter
binding.badge.isVisible = item.counter > 0
}
}

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.list.ui.adapter
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import coil3.ImageLoader
import coil3.request.allowRgb565
import coil3.request.transformations
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
@@ -26,7 +26,6 @@ fun mangaListDetailedItemAD(
) = adapterDelegateViewBinding<MangaDetailedListModel, ListModel, ItemMangaListDetailsBinding>(
{ inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null
AdapterDelegateClickListenerAdapter(this, clickListener, MangaDetailedListModel::manga).attach(itemView)
@@ -46,6 +45,7 @@ fun mangaListDetailedItemAD(
enqueueWith(coil)
}
binding.textViewTags.text = item.tags.joinToString(separator = ", ") { it.title ?: "" }
badge = itemView.bindBadge(badge, item.counter)
binding.badge.number = item.counter
binding.badge.isVisible = item.counter > 0
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.list.ui.adapter
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import coil3.ImageLoader
import coil3.request.allowRgb565
@@ -26,7 +27,6 @@ fun mangaListItemAD(
) = adapterDelegateViewBinding<MangaCompactListModel, ListModel, ItemMangaListBinding>(
{ inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null
AdapterDelegateClickListenerAdapter(this, clickListener, MangaCompactListModel::manga).attach(itemView)
@@ -40,6 +40,7 @@ fun mangaListItemAD(
mangaExtra(item.manga)
enqueueWith(coil)
}
badge = itemView.bindBadge(badge, item.counter)
binding.badge.number = item.counter
binding.badge.isVisible = item.counter > 0
}
}

View File

@@ -45,6 +45,17 @@
app:srcCompat="@drawable/ic_heart"
app:tint="?colorSurfaceBright" />
<org.koitharu.kotatsu.core.ui.widgets.BadgeView
android:id="@+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="@dimen/margin_small"
android:visibility="gone"
app:maxCharacterCount="@integer/manga_badge_max_character_count"
tools:number="8"
tools:visibility="visible" />
</FrameLayout>
<TextView

View File

@@ -53,4 +53,16 @@
app:layout_constraintTop_toBottomOf="@+id/textView_title"
tools:text="@tools:sample/lorem/random" />
<org.koitharu.kotatsu.core.ui.widgets.BadgeView
android:id="@+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:maxCharacterCount="@integer/manga_badge_max_character_count"
tools:number="8"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -3,9 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/custom_selectable_item_background"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:background="@drawable/custom_selectable_item_background">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@@ -35,22 +35,22 @@
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp">
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
app:lineHeight="18dp"
android:maxLines="3"
android:textAppearance="?attr/textAppearanceBodyLarge"
app:lineHeight="18dp"
tools:text="@tools:sample/lorem/random" />
<TextView
@@ -73,17 +73,29 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
app:lineHeight="14dp"
android:gravity="center_vertical"
android:maxLines="2"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView_cover"
app:layout_constraintTop_toBottomOf="@+id/textView_author"
app:lineHeight="14dp"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
<org.koitharu.kotatsu.core.ui.widgets.BadgeView
android:id="@+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:maxCharacterCount="@integer/manga_badge_max_character_count"
tools:number="8"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@@ -10,6 +10,7 @@
<attr name="tipViewStyle" />
<attr name="progressButtonStyle" />
<attr name="dotIndicatorStyle" />
<attr name="badgeViewStyle" />
<!--CoverImageView attrs-->
<declare-styleable name="CoverImageView">
@@ -182,4 +183,11 @@
<attr name="showMoreButton" format="boolean" />
</declare-styleable>
<declare-styleable name="BadgeView">
<attr name="shapeAppearance" />
<attr name="backgroundColor" />
<attr name="maxCharacterCount" />
<attr name="number" />
</declare-styleable>
</resources>

View File

@@ -163,8 +163,18 @@
<item name="scrollerOffset">@dimen/grid_spacing_outer</item>
</style>
<style name="Widget.Kotatsu.DotIndicator" parent="">
<style name="Widget.Kotatsu.DotIndicator" parent="" />
<style name="Widget.Kotatsu.BadgeView" parent="">
<item name="android:textColor">@macro/m3_comp_badge_large_label_text_color</item>
<item name="android:minWidth">@dimen/m3_badge_with_text_size</item>
<item name="android:minHeight">@dimen/m3_badge_with_text_size</item>
<item name="android:textAppearance">@macro/m3_comp_badge_large_label_text_type</item>
<item name="android:gravity">center</item>
<item name="shapeAppearance">@style/ShapeAppearance.M3.Comp.Badge.Shape</item>
<item name="backgroundColor">?attr/colorError</item>
<item name="android:elegantTextHeight">false</item>
<item name="singleLine">true</item>
</style>
<style name="Widget.Kotatsu.ListItemTextView" parent="">

View File

@@ -84,6 +84,7 @@
<item name="bottomSheetDragHandleStyle">@style/Widget.Kotatsu.BottomSheet.DragHandle</item>
<item name="android:dropDownSpinnerStyle">@style/Widget.Kotatsu.Spinner.DropDown</item>
<item name="dotIndicatorStyle">@style/Widget.Kotatsu.DotIndicator</item>
<item name="badgeViewStyle">@style/Widget.Kotatsu.BadgeView</item>
<!-- Text appearance -->
<item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item>