master
Koitharu 2 years ago
parent 46ded4af0d
commit 1905482b06
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

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

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

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.util.ext package org.koitharu.kotatsu.core.util.ext
import android.util.Log
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope 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>) { 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.lifecycleScope.launch {
owner.repeatOnLifecycle(Lifecycle.State.STARTED) { owner.repeatOnLifecycle(minState) {
collect { collect {
it?.consume(collector) it?.consume(collector)
} }
} }
} }
} }

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

@ -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.image.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.BottomSheetClollapseCallback 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.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.FileSize 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.crossfade
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
@ -534,7 +532,9 @@ class DetailsActivity :
info.totalChapters == -1 -> getString(R.string.error_occurred) info.totalChapters == -1 -> getString(R.string.error_occurred)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters) 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 buttonDownload?.isEnabled = info.isValid && info.canDownload
buttonRead.isEnabled = info.isValid buttonRead.isEnabled = info.isValid
} }

@ -26,18 +26,9 @@ fun chapterListItemAD(
itemView.setOnClickListener(eventListener) itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener) itemView.setOnLongClickListener(eventListener)
bind { payloads -> bind {
binding.textViewTitle.text = item.chapter.name binding.textViewTitle.text = item.chapter.name
binding.textViewDescription.textAndVisible = item.description 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 { when {
item.isCurrent -> { item.isCurrent -> {
binding.textViewTitle.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_current_chapter) binding.textViewTitle.drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_current_chapter)

@ -10,8 +10,6 @@ import kotlin.experimental.and
data class ChapterListItem( data class ChapterListItem(
val chapter: MangaChapter, val chapter: MangaChapter,
val flags: Byte, val flags: Byte,
private val uploadDateMs: Long,
private val groupPosition: Byte,
) : ListModel { ) : ListModel {
var description: String? = null var description: String? = null
@ -26,9 +24,9 @@ data class ChapterListItem(
private set private set
get() { get() {
if (field != null) return field if (field != null) return field
if (uploadDateMs == 0L) return null if (chapter.uploadDate == 0L) return null
field = DateUtils.getRelativeTimeSpanString( field = DateUtils.getRelativeTimeSpanString(
uploadDateMs, chapter.uploadDate,
System.currentTimeMillis(), System.currentTimeMillis(),
DateUtils.DAY_IN_MILLIS, DateUtils.DAY_IN_MILLIS,
) )
@ -53,15 +51,6 @@ data class ChapterListItem(
val isGrid: Boolean val isGrid: Boolean
get() = hasFlag(FLAG_GRID) 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 { private fun buildDescription(): String {
val joiner = StringJoiner("") val joiner = StringJoiner("")
chapter.formatNumber()?.let { chapter.formatNumber()?.let {
@ -105,9 +94,5 @@ data class ChapterListItem(
const val FLAG_BOOKMARKED: Byte = 16 const val FLAG_BOOKMARKED: Byte = 16
const val FLAG_DOWNLOADED: Byte = 32 const val FLAG_DOWNLOADED: Byte = 32
const val FLAG_GRID: Byte = 64 const val FLAG_GRID: Byte = 64
const val GROUP_START: Byte = 2
const val GROUP_MIDDLE: Byte = 4
const val GROUP_END: Byte = 8
} }
} }

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

@ -87,8 +87,9 @@ class ChaptersPagesSheet : BaseAdaptiveSheet<SheetChaptersPagesBinding>(), Actio
if (newState == STATE_DRAGGING || newState == STATE_SETTLING) { if (newState == STATE_DRAGGING || newState == STATE_SETTLING) {
return return
} }
val binding = viewBinding ?: return
val isActionModeStarted = actionModeDelegate?.isActionModeStarted == true 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) { override fun onActionModeStarted(mode: ActionMode) {

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

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

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

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

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

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

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

Loading…
Cancel
Save