Show reading progress indicators in lists
This commit is contained in:
@@ -11,10 +11,10 @@ import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
|
||||
val favouritesModule
|
||||
get() = module {
|
||||
|
||||
factory { FavouritesRepository(get(), get()) }
|
||||
single { FavouritesRepository(get(), get()) }
|
||||
|
||||
viewModel { categoryId ->
|
||||
FavouritesListViewModel(categoryId.get(), get(), get(), get())
|
||||
FavouritesListViewModel(categoryId.get(), get(), get(), get(), get())
|
||||
}
|
||||
viewModel { FavouritesCategoriesViewModel(get(), get()) }
|
||||
viewModel { manga ->
|
||||
|
||||
@@ -11,7 +11,8 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
import org.koitharu.kotatsu.list.domain.CountersProvider
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
@@ -25,8 +26,9 @@ class FavouritesListViewModel(
|
||||
private val categoryId: Long,
|
||||
private val repository: FavouritesRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
settings: AppSettings,
|
||||
) : MangaListViewModel(settings), CountersProvider {
|
||||
) : MangaListViewModel(settings), ListExtraProvider {
|
||||
|
||||
var sortOrder: LiveData<SortOrder?> = if (categoryId == NO_ID) {
|
||||
MutableLiveData(null)
|
||||
@@ -92,4 +94,8 @@ class FavouritesListViewModel(
|
||||
override suspend fun getCounter(mangaId: Long): Int {
|
||||
return trackingRepository.getNewChaptersCount(mangaId)
|
||||
}
|
||||
|
||||
override suspend fun getProgress(mangaId: Long): Float {
|
||||
return historyRepository.getProgress(mangaId)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import org.koitharu.kotatsu.history.ui.HistoryListViewModel
|
||||
val historyModule
|
||||
get() = module {
|
||||
|
||||
factory { HistoryRepository(get(), get(), get()) }
|
||||
single { HistoryRepository(get(), get(), get()) }
|
||||
viewModel { HistoryListViewModel(get(), get(), get(), get()) }
|
||||
}
|
||||
@@ -45,26 +45,36 @@ abstract class HistoryDao {
|
||||
@Query("SELECT COUNT(*) FROM history")
|
||||
abstract fun observeCount(): Flow<Int>
|
||||
|
||||
@Query("SELECT percent FROM history WHERE manga_id = :id")
|
||||
abstract fun findProgress(id: Long): Float?
|
||||
|
||||
@Query("DELETE FROM history")
|
||||
abstract suspend fun clear()
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract suspend fun insert(entity: HistoryEntity): Long
|
||||
|
||||
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, updated_at = :updatedAt WHERE manga_id = :mangaId")
|
||||
@Query("UPDATE history SET page = :page, chapter_id = :chapterId, scroll = :scroll, percent = :percent, updated_at = :updatedAt WHERE manga_id = :mangaId")
|
||||
abstract suspend fun update(
|
||||
mangaId: Long,
|
||||
page: Int,
|
||||
chapterId: Long,
|
||||
scroll: Float,
|
||||
updatedAt: Long
|
||||
percent: Float,
|
||||
updatedAt: Long,
|
||||
): Int
|
||||
|
||||
@Query("DELETE FROM history WHERE manga_id = :mangaId")
|
||||
abstract suspend fun delete(mangaId: Long)
|
||||
|
||||
suspend fun update(entity: HistoryEntity) =
|
||||
update(entity.mangaId, entity.page, entity.chapterId, entity.scroll, entity.updatedAt)
|
||||
suspend fun update(entity: HistoryEntity) = update(
|
||||
mangaId = entity.mangaId,
|
||||
page = entity.page,
|
||||
chapterId = entity.chapterId,
|
||||
scroll = entity.scroll,
|
||||
percent = entity.percent,
|
||||
updatedAt = entity.updatedAt
|
||||
)
|
||||
|
||||
@Transaction
|
||||
open suspend fun upsert(entity: HistoryEntity): Boolean {
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
|
||||
const val PROGRESS_NONE = -1f
|
||||
|
||||
class HistoryRepository(
|
||||
private val db: MangaDatabase,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
@@ -86,6 +88,10 @@ class HistoryRepository(
|
||||
return db.historyDao.find(manga.id)?.toMangaHistory()
|
||||
}
|
||||
|
||||
suspend fun getProgress(mangaId: Long): Float {
|
||||
return db.historyDao.findProgress(mangaId) ?: PROGRESS_NONE
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
db.historyDao.clear()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -26,6 +24,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class HistoryListViewModel(
|
||||
private val repository: HistoryRepository,
|
||||
@@ -112,9 +112,9 @@ class HistoryListViewModel(
|
||||
}
|
||||
val counter = trackingRepository.getNewChaptersCount(manga.id)
|
||||
result += when (mode) {
|
||||
ListMode.LIST -> manga.toListModel(counter)
|
||||
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter)
|
||||
ListMode.GRID -> manga.toGridModel(counter)
|
||||
ListMode.LIST -> manga.toListModel(counter, history.percent)
|
||||
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, history.percent)
|
||||
ListMode.GRID -> manga.toGridModel(counter, history.percent)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.koitharu.kotatsu.history.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ReadingProgressDrawable(
|
||||
context: Context,
|
||||
@StyleRes styleResId: Int,
|
||||
) : Drawable() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val lineColor: Int
|
||||
private val outlineColor: Int
|
||||
private val backgroundColor: Int
|
||||
private val textColor: Int
|
||||
private val textPattern = context.getString(R.string.percent_string_pattern)
|
||||
private val textBounds = Rect()
|
||||
private val hasBackground: Boolean
|
||||
private val hasOutline: Boolean
|
||||
private val hasText: Boolean
|
||||
private val desiredHeight: Int
|
||||
private val desiredWidth: Int
|
||||
private val autoFitTextSize: Boolean
|
||||
|
||||
var progress: Float = PROGRESS_NONE
|
||||
set(value) {
|
||||
field = value
|
||||
text = textPattern.format((value * 100f).roundToInt().toString())
|
||||
paint.getTextBounds(text, 0, text.length, textBounds)
|
||||
invalidateSelf()
|
||||
}
|
||||
private var text = ""
|
||||
|
||||
init {
|
||||
val ta = context.obtainStyledAttributes(styleResId, R.styleable.ProgressDrawable)
|
||||
desiredHeight = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_height, -1)
|
||||
desiredWidth = ta.getDimensionPixelSize(R.styleable.ProgressDrawable_android_width, -1)
|
||||
autoFitTextSize = ta.getBoolean(R.styleable.ProgressDrawable_autoFitTextSize, false)
|
||||
lineColor = ta.getColor(R.styleable.ProgressDrawable_android_strokeColor, Color.BLACK)
|
||||
outlineColor = ta.getColor(R.styleable.ProgressDrawable_outlineColor, Color.TRANSPARENT)
|
||||
backgroundColor = ColorUtils.setAlphaComponent(
|
||||
ta.getColor(R.styleable.ProgressDrawable_android_fillColor, Color.TRANSPARENT),
|
||||
(255 * ta.getFloat(R.styleable.ProgressDrawable_android_fillAlpha, 0f)).toInt(),
|
||||
)
|
||||
textColor = ta.getColor(R.styleable.ProgressDrawable_android_textColor, lineColor)
|
||||
paint.strokeCap = Paint.Cap.ROUND
|
||||
paint.textAlign = Paint.Align.CENTER
|
||||
paint.textSize = ta.getDimension(R.styleable.ProgressDrawable_android_textSize, paint.textSize)
|
||||
paint.strokeWidth = ta.getDimension(R.styleable.ProgressDrawable_strokeWidth, 1f)
|
||||
hasBackground = Color.alpha(backgroundColor) != 0
|
||||
hasOutline = Color.alpha(outlineColor) != 0
|
||||
hasText = Color.alpha(textColor) != 0 && paint.textSize > 0
|
||||
ta.recycle()
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
super.onBoundsChange(bounds)
|
||||
if (autoFitTextSize) {
|
||||
val innerWidth = bounds.width() - (paint.strokeWidth * 2f)
|
||||
paint.textSize = getTextSizeForWidth(innerWidth, "100%")
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
if (progress < 0f) {
|
||||
return
|
||||
}
|
||||
val cx = bounds.exactCenterX()
|
||||
val cy = bounds.exactCenterY()
|
||||
val radius = minOf(bounds.width(), bounds.height()) / 2f
|
||||
if (hasBackground) {
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = backgroundColor
|
||||
canvas.drawCircle(cx, cy, radius, paint)
|
||||
}
|
||||
val innerRadius = radius - paint.strokeWidth / 2f
|
||||
paint.style = Paint.Style.STROKE
|
||||
if (hasOutline) {
|
||||
paint.color = outlineColor
|
||||
canvas.drawCircle(cx, cy, innerRadius, paint)
|
||||
}
|
||||
paint.color = lineColor
|
||||
canvas.drawArc(
|
||||
cx - innerRadius,
|
||||
cy - innerRadius,
|
||||
cx + innerRadius,
|
||||
cy + innerRadius,
|
||||
-90f,
|
||||
360f * progress,
|
||||
false,
|
||||
paint,
|
||||
)
|
||||
if (hasText) {
|
||||
paint.style = Paint.Style.FILL
|
||||
paint.color = textColor
|
||||
val ty = bounds.height() / 2f + textBounds.height() / 2f - textBounds.bottom
|
||||
canvas.drawText(text, cx, ty, paint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
paint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
paint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun getOpacity() = PixelFormat.TRANSLUCENT
|
||||
|
||||
override fun getIntrinsicHeight() = desiredHeight
|
||||
|
||||
override fun getIntrinsicWidth() = desiredWidth
|
||||
|
||||
private fun getTextSizeForWidth(width: Float, text: String): Float {
|
||||
val testTextSize = 48f
|
||||
paint.textSize = testTextSize
|
||||
paint.getTextBounds(text, 0, text.length, textBounds)
|
||||
return testTextSize * width / textBounds.width()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.koitharu.kotatsu.history.ui.util
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Outline
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.StyleRes
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
|
||||
class ReadingProgressView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = 0,
|
||||
) : View(context, attrs, defStyleAttr), ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
|
||||
|
||||
private var percentAnimator: ValueAnimator? = null
|
||||
private val animationDuration = context.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
|
||||
|
||||
@StyleRes
|
||||
private val drawableStyle: Int
|
||||
|
||||
var percent: Float
|
||||
get() = peekProgressDrawable()?.progress ?: PROGRESS_NONE
|
||||
set(value) {
|
||||
cancelAnimation()
|
||||
getProgressDrawable().progress = value
|
||||
}
|
||||
|
||||
init {
|
||||
val ta = context.obtainStyledAttributes(attrs, R.styleable.ReadingProgressView, defStyleAttr, 0)
|
||||
drawableStyle = ta.getResourceId(R.styleable.ReadingProgressView_progressStyle, R.style.ProgressDrawable)
|
||||
ta.recycle()
|
||||
outlineProvider = OutlineProvider()
|
||||
if (isInEditMode) {
|
||||
percent = 0.27f
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
percentAnimator?.run {
|
||||
if (isRunning) end()
|
||||
}
|
||||
percentAnimator = null
|
||||
}
|
||||
|
||||
override fun onAnimationUpdate(animation: ValueAnimator) {
|
||||
val p = animation.animatedValue as Float
|
||||
getProgressDrawable().progress = p
|
||||
}
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) = Unit
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
if (percentAnimator === animation) {
|
||||
percentAnimator = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) = Unit
|
||||
|
||||
override fun onAnimationRepeat(animation: Animator?) = Unit
|
||||
|
||||
fun setPercent(value: Float, animate: Boolean) {
|
||||
val currentDrawable = peekProgressDrawable()
|
||||
if (!animate || currentDrawable == null || value == PROGRESS_NONE) {
|
||||
percent = value
|
||||
return
|
||||
}
|
||||
percentAnimator?.cancel()
|
||||
percentAnimator = ValueAnimator.ofFloat(
|
||||
currentDrawable.progress.coerceAtLeast(0f),
|
||||
value
|
||||
).apply {
|
||||
duration = animationDuration
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
addUpdateListener(this@ReadingProgressView)
|
||||
addListener(this@ReadingProgressView)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelAnimation() {
|
||||
percentAnimator?.cancel()
|
||||
percentAnimator = null
|
||||
}
|
||||
|
||||
private fun peekProgressDrawable(): ReadingProgressDrawable? {
|
||||
return background as? ReadingProgressDrawable
|
||||
}
|
||||
|
||||
private fun getProgressDrawable(): ReadingProgressDrawable {
|
||||
var d = peekProgressDrawable()
|
||||
if (d != null) {
|
||||
return d
|
||||
}
|
||||
d = ReadingProgressDrawable(context, drawableStyle)
|
||||
background = d
|
||||
return d
|
||||
}
|
||||
|
||||
private class OutlineProvider : ViewOutlineProvider() {
|
||||
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
outline.setOval(0, 0, view.width, view.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.list.domain
|
||||
|
||||
fun interface CountersProvider {
|
||||
interface ListExtraProvider {
|
||||
|
||||
suspend fun getCounter(mangaId: Long): Int
|
||||
|
||||
suspend fun getProgress(mangaId: Long): Float
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -43,8 +44,9 @@ fun mangaGridItemAD(
|
||||
}
|
||||
}
|
||||
|
||||
bind {
|
||||
bind { payloads ->
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
||||
imageRequest?.dispose()
|
||||
imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
|
||||
.referer(item.manga.publicUrl)
|
||||
@@ -60,6 +62,7 @@ fun mangaGridItemAD(
|
||||
|
||||
onViewRecycled {
|
||||
itemView.clearBadge(badge)
|
||||
binding.progressView.percent = PROGRESS_NONE
|
||||
badge = null
|
||||
imageRequest?.dispose()
|
||||
imageRequest = null
|
||||
|
||||
@@ -54,9 +54,14 @@ class MangaListAdapter(
|
||||
|
||||
override fun getChangePayload(oldItem: ListModel, newItem: ListModel): Any? {
|
||||
return when (newItem) {
|
||||
is MangaListModel,
|
||||
is MangaGridModel,
|
||||
is MangaListDetailedModel,
|
||||
is MangaItemModel -> {
|
||||
oldItem as MangaItemModel
|
||||
if (oldItem.progress != newItem.progress) {
|
||||
PAYLOAD_PROGRESS
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
}
|
||||
is CurrentFilterModel -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
@@ -77,5 +82,7 @@ class MangaListAdapter(
|
||||
const val ITEM_TYPE_HEADER = 9
|
||||
const val ITEM_TYPE_FILTER = 10
|
||||
const val ITEM_TYPE_HEADER_FILTER = 11
|
||||
|
||||
val PAYLOAD_PROGRESS = Any()
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -36,10 +37,11 @@ fun mangaListDetailedItemAD(
|
||||
clickListener.onItemLongClick(item.manga, it)
|
||||
}
|
||||
|
||||
bind {
|
||||
bind { payloads ->
|
||||
imageRequest?.dispose()
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewSubtitle.textAndVisible = item.subtitle
|
||||
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
|
||||
imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl)
|
||||
.referer(item.manga.publicUrl)
|
||||
.placeholder(R.drawable.ic_placeholder)
|
||||
@@ -56,6 +58,7 @@ fun mangaListDetailedItemAD(
|
||||
|
||||
onViewRecycled {
|
||||
itemView.clearBadge(badge)
|
||||
binding.progressView.percent = PROGRESS_NONE
|
||||
badge = null
|
||||
imageRequest?.dispose()
|
||||
imageRequest = null
|
||||
|
||||
@@ -4,21 +4,23 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.domain.CountersProvider
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.ifZero
|
||||
|
||||
fun Manga.toListModel(counter: Int) = MangaListModel(
|
||||
fun Manga.toListModel(counter: Int, progress: Float) = MangaListModel(
|
||||
id = id,
|
||||
title = title,
|
||||
subtitle = tags.joinToString(", ") { it.title },
|
||||
coverUrl = coverUrl,
|
||||
manga = this,
|
||||
counter = counter,
|
||||
progress = progress,
|
||||
)
|
||||
|
||||
fun Manga.toListDetailedModel(counter: Int) = MangaListDetailedModel(
|
||||
fun Manga.toListDetailedModel(counter: Int, progress: Float) = MangaListDetailedModel(
|
||||
id = id,
|
||||
title = title,
|
||||
subtitle = altTitle,
|
||||
@@ -27,50 +29,48 @@ fun Manga.toListDetailedModel(counter: Int) = MangaListDetailedModel(
|
||||
coverUrl = coverUrl,
|
||||
manga = this,
|
||||
counter = counter,
|
||||
progress = progress,
|
||||
)
|
||||
|
||||
fun Manga.toGridModel(counter: Int) = MangaGridModel(
|
||||
fun Manga.toGridModel(counter: Int, progress: Float) = MangaGridModel(
|
||||
id = id,
|
||||
title = title,
|
||||
coverUrl = coverUrl,
|
||||
manga = this,
|
||||
counter = counter,
|
||||
progress = progress,
|
||||
)
|
||||
|
||||
suspend fun List<Manga>.toUi(
|
||||
mode: ListMode,
|
||||
countersProvider: CountersProvider,
|
||||
extraProvider: ListExtraProvider,
|
||||
): List<MangaItemModel> = when (mode) {
|
||||
ListMode.LIST -> map { it.toListModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.GRID -> map { it.toGridModel(countersProvider.getCounter(it.id)) }
|
||||
}
|
||||
|
||||
suspend fun <C : MutableCollection<ListModel>> List<Manga>.toUi(
|
||||
destination: C,
|
||||
mode: ListMode,
|
||||
countersProvider: CountersProvider,
|
||||
): C = when (mode) {
|
||||
ListMode.LIST -> mapTo(destination) { it.toListModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.GRID -> mapTo(destination) { it.toGridModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.LIST -> map {
|
||||
it.toListModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
|
||||
}
|
||||
ListMode.DETAILED_LIST -> map {
|
||||
it.toListDetailedModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
|
||||
}
|
||||
ListMode.GRID -> map {
|
||||
it.toGridModel(extraProvider.getCounter(it.id), extraProvider.getProgress(it.id))
|
||||
}
|
||||
}
|
||||
|
||||
fun List<Manga>.toUi(
|
||||
mode: ListMode,
|
||||
): List<MangaItemModel> = when (mode) {
|
||||
ListMode.LIST -> map { it.toListModel(0) }
|
||||
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(0) }
|
||||
ListMode.GRID -> map { it.toGridModel(0) }
|
||||
ListMode.LIST -> map { it.toListModel(0, PROGRESS_NONE) }
|
||||
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(0, PROGRESS_NONE) }
|
||||
ListMode.GRID -> map { it.toGridModel(0, PROGRESS_NONE) }
|
||||
}
|
||||
|
||||
fun <C : MutableCollection<ListModel>> List<Manga>.toUi(
|
||||
destination: C,
|
||||
mode: ListMode,
|
||||
): C = when (mode) {
|
||||
ListMode.LIST -> mapTo(destination) { it.toListModel(0) }
|
||||
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0) }
|
||||
ListMode.GRID -> mapTo(destination) { it.toGridModel(0) }
|
||||
ListMode.LIST -> mapTo(destination) { it.toListModel(0, PROGRESS_NONE) }
|
||||
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0, PROGRESS_NONE) }
|
||||
ListMode.GRID -> mapTo(destination) { it.toGridModel(0, PROGRESS_NONE) }
|
||||
}
|
||||
|
||||
fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState(
|
||||
|
||||
@@ -4,8 +4,9 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
data class MangaGridModel(
|
||||
override val id: Long,
|
||||
val title: String,
|
||||
val coverUrl: String,
|
||||
override val title: String,
|
||||
override val coverUrl: String,
|
||||
override val manga: Manga,
|
||||
val counter: Int,
|
||||
override val counter: Int,
|
||||
override val progress: Float,
|
||||
) : MangaItemModel
|
||||
@@ -6,4 +6,8 @@ sealed interface MangaItemModel : ListModel {
|
||||
|
||||
val id: Long
|
||||
val manga: Manga
|
||||
val title: String
|
||||
val coverUrl: String
|
||||
val counter: Int
|
||||
val progress: Float
|
||||
}
|
||||
@@ -4,11 +4,12 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
data class MangaListDetailedModel(
|
||||
override val id: Long,
|
||||
val title: String,
|
||||
override val title: String,
|
||||
val subtitle: String?,
|
||||
val tags: String,
|
||||
val coverUrl: String,
|
||||
override val coverUrl: String,
|
||||
val rating: String?,
|
||||
override val manga: Manga,
|
||||
val counter: Int,
|
||||
override val counter: Int,
|
||||
override val progress: Float,
|
||||
) : MangaItemModel
|
||||
@@ -4,9 +4,10 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
data class MangaListModel(
|
||||
override val id: Long,
|
||||
val title: String,
|
||||
override val title: String,
|
||||
val subtitle: String,
|
||||
val coverUrl: String,
|
||||
override val coverUrl: String,
|
||||
override val manga: Manga,
|
||||
val counter: Int,
|
||||
override val counter: Int,
|
||||
override val progress: Float,
|
||||
) : MangaItemModel
|
||||
@@ -376,6 +376,9 @@ class ReaderViewModel(
|
||||
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
|
||||
val pages = content.value?.pages ?: return -1f
|
||||
val pagesCount = pages.count { x -> x.chapterId == chapterId }
|
||||
if (chaptersCount == 0 || pagesCount == 0) {
|
||||
return -1f
|
||||
}
|
||||
val chapterPercent = (chapterIndex + 1) / chaptersCount.toFloat()
|
||||
val pagePercent = (pageIndex + 1) / pagesCount.toFloat()
|
||||
return pagePercent * chapterPercent // FIXME
|
||||
|
||||
@@ -13,15 +13,28 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/backgrounds/scenic[3]" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/backgrounds/scenic[3]" />
|
||||
|
||||
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
|
||||
android:id="@+id/progressView"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="4dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
|
||||
@@ -12,14 +12,27 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
|
||||
android:id="@+id/progressView"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="4dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -35,4 +35,21 @@
|
||||
<attr name="android:insetRight" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ProgressDrawable">
|
||||
<attr name="strokeWidth" />
|
||||
<attr name="android:strokeColor" />
|
||||
<attr name="android:textSize" />
|
||||
<attr name="android:textColor" />
|
||||
<attr name="android:fillColor" />
|
||||
<attr name="android:fillAlpha" />
|
||||
<attr name="android:width" />
|
||||
<attr name="android:height" />
|
||||
<attr name="outlineColor" format="color" />
|
||||
<attr name="autoFitTextSize" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="ReadingProgressView">
|
||||
<attr name="progressStyle" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@@ -26,6 +26,8 @@
|
||||
<dimen name="toolbar_button_margin">10dp</dimen>
|
||||
<dimen name="widget_cover_height">116dp</dimen>
|
||||
<dimen name="widget_cover_width">84dp</dimen>
|
||||
<dimen name="reading_progress_stroke">4dp</dimen>
|
||||
<dimen name="reading_progress_text_size">10dp</dimen>
|
||||
|
||||
<dimen name="search_suggestions_manga_height">124dp</dimen>
|
||||
<dimen name="search_suggestions_manga_spacing">4dp</dimen>
|
||||
|
||||
@@ -173,4 +173,15 @@
|
||||
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
|
||||
</style>
|
||||
|
||||
<!-- Progress drawable -->
|
||||
|
||||
<style name="ProgressDrawable">
|
||||
<item name="android:fillAlpha">0.8</item>
|
||||
<item name="android:fillColor">?android:colorBackground</item>
|
||||
<item name="android:strokeColor">?colorPrimaryDark</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="strokeWidth">3dp</item>
|
||||
<item name="android:textSize">9sp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user