This commit is contained in:
Koitharu
2024-05-06 17:10:08 +03:00
parent 46ded4af0d
commit 1905482b06
17 changed files with 81 additions and 69 deletions

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.AttrRes
import com.google.android.material.textview.MaterialTextView
class MultilineEllipsizeTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = android.R.attr.textViewStyle,
) : MaterialTextView(context, attrs, defStyleAttr) {
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val lh = lineHeight
maxLines = if (lh > 0) h / lh else 1
}
}

View File

@@ -16,6 +16,7 @@ import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.ColorUtils
import androidx.core.view.children
import androidx.core.widget.TextViewCompat
import org.koitharu.kotatsu.R
@@ -37,10 +38,14 @@ class ProgressButton @JvmOverloads constructor(
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var progress = 0f
private var targetProgress = 0f
private var colorBase: ColorStateList = ColorStateList.valueOf(Color.TRANSPARENT)
private var colorProgress: ColorStateList = ColorStateList.valueOf(Color.TRANSPARENT)
private var progressAnimator: ValueAnimator? = null
private var colorBaseCurrent = colorProgress.defaultColor
private var colorProgressCurrent = colorProgress.defaultColor
var title: CharSequence?
get() = textViewTitle.textAndVisible
set(value) {
@@ -97,10 +102,19 @@ class ProgressButton @JvmOverloads constructor(
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(colorBase.getColorForState(drawableState, colorBase.defaultColor))
paint.color = colorProgress.getColorForState(drawableState, colorProgress.defaultColor)
paint.alpha = 84 // 255 * 0.33F
canvas.drawRect(0f, 0f, width * progress, height.toFloat(), paint)
canvas.drawColor(colorBaseCurrent)
if (progress > 0f) {
canvas.drawRect(0f, 0f, width * progress, height.toFloat(), paint)
}
}
override fun drawableStateChanged() {
super.drawableStateChanged()
val state = drawableState
colorBaseCurrent = colorBase.getColorForState(state, colorBase.defaultColor)
colorProgressCurrent = colorProgress.getColorForState(state, colorProgress.defaultColor)
colorProgressCurrent = ColorUtils.setAlphaComponent(colorProgressCurrent, 84 /* 255 * 0.33F */)
paint.color = colorProgressCurrent
}
override fun setGravity(gravity: Int) {
@@ -116,8 +130,10 @@ class ProgressButton @JvmOverloads constructor(
}
override fun onAnimationUpdate(animation: ValueAnimator) {
progress = animation.animatedValue as Float
invalidate()
if (animation === progressAnimator) {
progress = animation.animatedValue as Float
invalidate()
}
}
fun setTitle(@StringRes titleResId: Int) {
@@ -129,19 +145,25 @@ class ProgressButton @JvmOverloads constructor(
}
fun setProgress(value: Float, animate: Boolean) {
progressAnimator?.cancel()
val prevAnimator = progressAnimator
if (animate) {
if (value == targetProgress) {
return
}
targetProgress = value
progressAnimator = ValueAnimator.ofFloat(progress, value).apply {
duration = context.getAnimationDuration(android.R.integer.config_shortAnimTime)
duration = context.getAnimationDuration(android.R.integer.config_mediumAnimTime)
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener(this@ProgressButton)
start()
}
progressAnimator?.start()
} else {
progressAnimator = null
progress = value
targetProgress = value
invalidate()
}
prevAnimator?.cancel()
}
private fun applyGravity() {

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.util.ext
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@@ -28,11 +27,16 @@ fun <T> Flow<T>.observe(owner: LifecycleOwner, minState: Lifecycle.State, collec
}
fun <T> Flow<Event<T>?>.observeEvent(owner: LifecycleOwner, collector: FlowCollector<T>) {
observeEvent(owner, Lifecycle.State.STARTED, collector)
}
fun <T> Flow<Event<T>?>.observeEvent(owner: LifecycleOwner, minState: Lifecycle.State, collector: FlowCollector<T>) {
owner.lifecycleScope.launch {
owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
owner.repeatOnLifecycle(minState) {
collect {
it?.consume(collector)
}
}
}
}

View File

@@ -72,8 +72,7 @@ fun MangaDetails.mapChapters(
fun List<ChapterListItem>.withVolumeHeaders(context: Context): List<ListModel> {
var prevVolume = 0
val result = ArrayList<ListModel>((size * 1.4).toInt())
var groupPos: Byte = 0
for ((index, item) in this.withIndex()) {
for (item in this) {
val chapter = item.chapter
if (chapter.volume != prevVolume) {
val text = if (chapter.volume == 0) {
@@ -83,19 +82,8 @@ fun List<ChapterListItem>.withVolumeHeaders(context: Context): List<ListModel> {
}
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
}

View File

@@ -54,12 +54,10 @@ import org.koitharu.kotatsu.core.ui.image.ChipIconTarget
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.BottomSheetClollapseCallback
import org.koitharu.kotatsu.core.ui.util.BottomSheetNoHalfExpandedCallback
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ViewBadge
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith
@@ -534,7 +532,9 @@ class DetailsActivity :
info.totalChapters == -1 -> getString(R.string.error_occurred)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
}
buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, true)
val isFirstCall = buttonRead.tag == null
buttonRead.tag = Unit
buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, !isFirstCall)
buttonDownload?.isEnabled = info.isValid && info.canDownload
buttonRead.isEnabled = info.isValid
}

View File

@@ -26,18 +26,9 @@ fun chapterListItemAD(
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
bind { payloads ->
bind {
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)

View File

@@ -10,8 +10,6 @@ import kotlin.experimental.and
data class ChapterListItem(
val chapter: MangaChapter,
val flags: Byte,
private val uploadDateMs: Long,
private val groupPosition: Byte,
) : ListModel {
var description: String? = null
@@ -26,9 +24,9 @@ data class ChapterListItem(
private set
get() {
if (field != null) return field
if (uploadDateMs == 0L) return null
if (chapter.uploadDate == 0L) return null
field = DateUtils.getRelativeTimeSpanString(
uploadDateMs,
chapter.uploadDate,
System.currentTimeMillis(),
DateUtils.DAY_IN_MILLIS,
)
@@ -53,15 +51,6 @@ 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 {
@@ -105,9 +94,5 @@ data class ChapterListItem(
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
}
}

View File

@@ -27,7 +27,5 @@ fun MangaChapter.toListItem(
return ChapterListItem(
chapter = this,
flags = flags,
uploadDateMs = uploadDate,
groupPosition = 0,
)
}

View File

@@ -87,8 +87,9 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
if (newState == STATE_DRAGGING || newState == STATE_SETTLING) {
return
}
val binding = viewBinding ?: return
val isActionModeStarted = actionModeDelegate?.isActionModeStarted == true
viewBinding?.toolbar?.menuView?.isVisible = newState != STATE_COLLAPSED && !isActionModeStarted
binding.toolbar.menuView?.isVisible = newState != STATE_COLLAPSED && !isActionModeStarted
}
override fun onActionModeStarted(mode: ActionMode) {

View File

@@ -8,10 +8,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
import androidx.core.view.ancestors
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
@@ -260,6 +262,9 @@ class ChaptersFragment :
}
private suspend fun onSelectChapter(chapterId: Long) {
if (!isResumed) {
view?.ancestors?.firstNotNullOfOrNull { it as? ViewPager2 }?.setCurrentItem(0, true)
}
val position = withContext(Dispatchers.Default) {
val predicate: (ListModel) -> Boolean = { x -> x is ChapterListItem && x.chapter.id == chapterId }
val items = chaptersAdapter?.observeItems()?.firstOrNull { it.any(predicate) }

View File

@@ -85,7 +85,6 @@ fun recommendationMangaItemAD(
binding.root.setOnClickListener { v ->
itemClickListener.onItemClick(item.manga, v)
}
bind {
binding.textViewTitle.text = item.manga.title
binding.textViewSubtitle.textAndVisible = item.subtitle

View File

@@ -4,14 +4,14 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding
import org.koitharu.kotatsu.databinding.ItemHeaderBinding
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
fun listHeaderAD(
listener: ListHeaderClickListener?,
) = adapterDelegateViewBinding<ListHeader, ListModel, ItemHeaderButtonBinding>(
{ inflater, parent -> ItemHeaderButtonBinding.inflate(inflater, parent, false) },
) = adapterDelegateViewBinding<ListHeader, ListModel, ItemHeaderBinding>(
{ inflater, parent -> ItemHeaderBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null

View File

@@ -3,12 +3,12 @@ package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter
import androidx.core.view.isInvisible
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding
import org.koitharu.kotatsu.databinding.ItemHeaderBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
fun scrobblingHeaderAD() = adapterDelegateViewBinding<ScrobblingStatus, ListModel, ItemHeaderButtonBinding>(
{ inflater, parent -> ItemHeaderButtonBinding.inflate(inflater, parent, false) },
fun scrobblingHeaderAD() = adapterDelegateViewBinding<ScrobblingStatus, ListModel, ItemHeaderBinding>(
{ inflater, parent -> ItemHeaderBinding.inflate(inflater, parent, false) },
) {
binding.buttonMore.isInvisible = true

View File

@@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeight"
android:background="?selectableItemBackground"
android:background="@drawable/list_selector"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="@dimen/chapter_list_item_height"

View File

@@ -34,7 +34,7 @@
app:layout_constraintTop_toTopOf="@+id/imageView_cover"
tools:text="@tools:sample/lorem" />
<TextView
<org.koitharu.kotatsu.core.ui.widgets.MultilineEllipsizeTextView
android:id="@+id/textView_subtitle"
android:layout_width="0dp"
android:layout_height="0dp"

View File

@@ -238,8 +238,8 @@
<style name="TextAppearance.Kotatsu.Menu" parent="TextAppearance.Material3.BodyLarge" />
<style name="TextAppearance.Kotatsu.SectionHeader" parent="TextAppearance.Material3.BodyLarge">
<item name="android:textStyle"></item>
<style name="TextAppearance.Kotatsu.SectionHeader" parent="TextAppearance.Material3.LabelLarge">
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.Kotatsu.GridTitle" parent="TextAppearance.Material3.TitleSmall" />