diff --git a/.idea/misc.xml b/.idea/misc.xml index b8f888114..56dc302cc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,6 +4,8 @@ diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt new file mode 100644 index 000000000..52ca53616 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt @@ -0,0 +1,96 @@ +package org.koitharu.kotatsu.base.ui.widgets + +import android.content.Context +import android.util.AttributeSet +import android.view.View.OnClickListener +import androidx.annotation.DrawableRes +import androidx.core.view.children +import com.google.android.material.R +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipGroup +import org.koitharu.kotatsu.utils.ext.getThemeColor + +class ChipsView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.chipGroupStyle +) : ChipGroup(context, attrs, defStyleAttr) { + + private var isLayoutSuppressedCompat = false + private var isLayoutCalledOnSuppressed = false + private var chipOnClickListener = OnClickListener { + onChipClickListener?.onChipClick(it as Chip, it.tag) + } + var onChipClickListener: OnChipClickListener? = null + set(value) { + field = value + val isChipClickable = value != null + children.forEach { it.isClickable = isChipClickable } + } + + override fun requestLayout() { + if (isLayoutSuppressedCompat) { + isLayoutCalledOnSuppressed = true + } else { + super.requestLayout() + } + } + + fun setChips(items: List) { + suppressLayoutCompat(true) + try { + for ((i, model) in items.withIndex()) { + val chip = getChildAt(i) as Chip? ?: addChip() + bindChip(chip, model) + } + for (i in items.size until childCount) { + removeViewAt(i) + } + } finally { + suppressLayoutCompat(false) + } + } + + private fun bindChip(chip: Chip, model: ChipModel) { + chip.text = model.title + if (model.icon == 0) { + chip.isChipIconVisible = false + } else { + chip.isCheckedIconVisible = true + chip.setChipIconResource(model.icon) + } + chip.tag = model.data + } + + private fun addChip(): Chip { + val chip = Chip(context) + chip.setTextColor(context.getThemeColor(android.R.attr.textColorPrimary)) + chip.isCloseIconVisible = false + chip.setEnsureMinTouchTargetSize(false) + chip.setOnClickListener(chipOnClickListener) + chip.isClickable = onChipClickListener != null + addView(chip) + return chip + } + + private fun suppressLayoutCompat(suppress: Boolean) { + isLayoutSuppressedCompat = suppress + if (!suppress) { + if (isLayoutCalledOnSuppressed) { + requestLayout() + isLayoutCalledOnSuppressed = false + } + } + } + + data class ChipModel( + @DrawableRes val icon: Int, + val title: CharSequence, + val data: Any? = null + ) + + fun interface OnChipClickListener { + + fun onChipClick(chip: Chip, data: Any?) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt deleted file mode 100644 index d18524e4b..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/ChipsFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.koitharu.kotatsu.core.ui - -import android.content.Context -import android.view.View -import androidx.annotation.DrawableRes -import com.google.android.material.chip.Chip -import org.koitharu.kotatsu.utils.ext.getThemeColor - -class ChipsFactory(val context: Context) { - - fun create( - convertView: Chip? = null, - text: CharSequence, - @DrawableRes iconRes: Int = 0, - tag: Any? = null, - onClickListener: View.OnClickListener? = null - ): Chip { - val chip = convertView ?: Chip(context).apply { - setTextColor(context.getThemeColor(android.R.attr.textColorPrimary)) - isCloseIconVisible = false - } - chip.text = text - if (iconRes == 0) { - chip.isChipIconVisible = false - } else { - chip.isCheckedIconVisible = true - chip.setChipIconResource(iconRes) - } - chip.tag = tag - chip.setEnsureMinTouchTargetSize(false) - chip.setOnClickListener(onClickListener) - return chip - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index 5b712b1d6..00d3baa53 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -13,12 +13,14 @@ import androidx.core.view.updatePadding import coil.ImageLoader import coil.util.CoilUtils import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment +import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.databinding.FragmentDetailsBinding @@ -34,6 +36,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList private val viewModel by sharedViewModel() private val coil by inject() + private var tagsJob: Job? = null override fun onInflateView( inflater: LayoutInflater, @@ -67,47 +70,11 @@ class DetailsFragment : BaseFragment(), View.OnClickList ratingBar.progress = (ratingBar.max * manga.rating).roundToInt() ratingBar.isVisible = true } - chipsTags.removeAllViews() - manga.author?.let { a -> - chipsTags.addChips(listOf(a)) { - create( - text = it, - iconRes = R.drawable.ic_chip_user, - tag = it - ) - } - } - chipsTags.addChips(manga.tags) { - create( - text = it.title, - iconRes = R.drawable.ic_chip_tag, - tag = it - ) - } - manga.url.toUri().toFileOrNull()?.let { f -> - viewLifecycleScope.launch { - val size = withContext(Dispatchers.IO) { - f.length() - } - chipsTags.addChips(listOf(f)) { - create( - text = FileSizeUtils.formatBytes(context, size), - iconRes = R.drawable.ic_chip_storage, - tag = it - ) - } - } - } ?: chipsTags.addChips(listOf(manga.source)) { - create( - text = it.title, - iconRes = R.drawable.ic_chip_web, - tag = it - ) - } imageViewFavourite.setOnClickListener(this@DetailsFragment) buttonRead.setOnClickListener(this@DetailsFragment) buttonRead.setOnLongClickListener(this@DetailsFragment) buttonRead.isEnabled = !manga.chapters.isNullOrEmpty() + bindTags(manga) } } @@ -191,4 +158,39 @@ class DetailsFragment : BaseFragment(), View.OnClickList bottom = insets.bottom ) } + + private fun bindTags(manga: Manga) { + tagsJob?.cancel() + tagsJob = viewLifecycleScope.launch { + val tags = ArrayList(manga.tags.size + 2) + if (manga.author != null) { + tags += ChipsView.ChipModel( + title = manga.author, + icon = R.drawable.ic_chip_user + ) + } + for (tag in manga.tags) { + tags += ChipsView.ChipModel( + title = tag.title, + icon = R.drawable.ic_chip_tag + ) + } + val file = manga.url.toUri().toFileOrNull() + if (file != null) { + val size = withContext(Dispatchers.IO) { + file.length() + } + tags += ChipsView.ChipModel( + title = FileSizeUtils.formatBytes(requireContext(), size), + icon = R.drawable.ic_chip_storage + ) + } else { + tags += ChipsView.ChipModel( + title = manga.source.title, + icon = R.drawable.ic_chip_web + ) + } + binding.chipsTags.setChips(tags) + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt index 4a13722ec..43ea9b89d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt @@ -22,7 +22,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup -import org.koitharu.kotatsu.core.ui.ChipsFactory fun View.hideKeyboard() { val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager @@ -40,14 +39,6 @@ inline fun ViewGroup.inflate(@LayoutRes resId: Int) = val RecyclerView.hasItems: Boolean get() = (adapter?.itemCount ?: 0) > 0 -inline fun ChipGroup.addChips(data: Iterable, action: ChipsFactory.(T) -> Chip) { - val factory = ChipsFactory(context) - data.forEach { - val chip = factory.action(it) - addView(chip) - } -} - fun RecyclerView.clearItemDecorations() { while (itemDecorationCount > 0) { removeItemDecorationAt(0) diff --git a/app/src/main/res/layout-w600dp/fragment_details.xml b/app/src/main/res/layout-w600dp/fragment_details.xml index 2d2640a9c..354fda3a9 100644 --- a/app/src/main/res/layout-w600dp/fragment_details.xml +++ b/app/src/main/res/layout-w600dp/fragment_details.xml @@ -23,8 +23,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintWidth_percent="0.3" /> - - -