UI fixes
This commit is contained in:
@@ -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.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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,5 @@ fun MangaChapter.toListItem(
|
||||
return ChapterListItem(
|
||||
chapter = this,
|
||||
flags = flags,
|
||||
uploadDateMs = uploadDate,
|
||||
groupPosition = 0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user