Refactor reading time estimator

This commit is contained in:
Koitharu
2024-01-18 13:38:17 +02:00
parent a1120ea709
commit c5d88f8700
7 changed files with 92 additions and 21 deletions

View File

@@ -0,0 +1,21 @@
package org.koitharu.kotatsu.details.data
import android.content.res.Resources
import org.koitharu.kotatsu.R
data class ReadingTime(
val minutes: Int,
val hours: Int,
val isContinue: Boolean,
) {
fun format(resources: Resources): String = when {
hours == 0 -> resources.getQuantityString(R.plurals.minutes, minutes, minutes)
minutes == 0 -> resources.getQuantityString(R.plurals.hours, hours, hours)
else -> resources.getString(
R.string.remaining_time_pattern,
resources.getQuantityString(R.plurals.hours, hours, hours),
resources.getQuantityString(R.plurals.minutes, minutes, minutes),
)
}
}

View File

@@ -0,0 +1,33 @@
package org.koitharu.kotatsu.details.domain
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.data.ReadingTime
import javax.inject.Inject
import kotlin.math.roundToInt
class ReadingTimeUseCase @Inject constructor() {
fun invoke(manga: MangaDetails?, branch: String?, history: MangaHistory?): ReadingTime? {
// FIXME MAXIMUM HARDCODE!!! To do calculation with user's page read speed and his favourites/history mangas average pages in chapter
val chapters = manga?.chapters?.get(branch)
if (chapters.isNullOrEmpty()) {
return null
}
val isOnHistoryBranch = history != null && chapters.findById(history.chapterId) != null
// Impossible task, I guess. Good luck on this.
var averageTimeSec: Int = 20 * 10 * chapters.size // 20 pages, 10 seconds per page
if (isOnHistoryBranch) {
averageTimeSec = (averageTimeSec * (1f - checkNotNull(history).percent)).roundToInt()
}
if (averageTimeSec < 60) {
return null
}
return ReadingTime(
minutes = averageTimeSec / 60,
hours = averageTimeSec / 3600,
isContinue = isOnHistoryBranch,
)
}
}

View File

@@ -48,6 +48,7 @@ import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.details.data.ReadingTime
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.details.ui.related.RelatedMangaActivity
@@ -70,6 +71,8 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity
import java.text.SimpleDateFormat
import java.util.Date
import javax.inject.Inject
@AndroidEntryPoint
@@ -118,6 +121,7 @@ class DetailsFragment :
viewModel.localSize.observe(viewLifecycleOwner, ::onLocalSizeChanged)
viewModel.relatedManga.observe(viewLifecycleOwner, ::onRelatedMangaChanged)
viewModel.chapters.observe(viewLifecycleOwner, ::onChaptersChanged)
viewModel.readingTime.observe(viewLifecycleOwner, ::onReadingTimeChanged)
}
override fun onItemClick(item: Bookmark, view: View) {
@@ -201,32 +205,29 @@ class DetailsFragment :
private fun onChaptersChanged(chapters: List<ChapterListItem>?) {
val infoLayout = requireViewBinding().infoLayout
val tv = requireViewBinding().approximateReadTime
if (chapters.isNullOrEmpty()) {
infoLayout.textViewChapters.isVisible = false
tv.text = "..."
} else {
val count = chapters.countChaptersByBranch()
// FIXME MAXIMUM HARDCODE!!! To do calculation with user's page read speed and his favourites/history mangas average pages in chapter
// Impossible task, I guess. Good luck on this.
val averageTime: Int = 20 * 10 * (count) // 20 pages, 10 seconds per page
val hours = averageTime / 3600
val minutes = averageTime % 3600 / 60
tv.text = buildString {
append(resources.getQuantityString(R.plurals.hour, hours, hours))
append(" ")
append(resources.getQuantityString(R.plurals.minute, minutes, minutes))
}
// Info
infoLayout.textViewChapters.isVisible = true
val chaptersText = resources.getQuantityString(R.plurals.chapters, count, count)
infoLayout.textViewChapters.text = chaptersText
}
}
private fun onReadingTimeChanged(time: ReadingTime?) {
val binding = viewBinding ?: return
if (time == null) {
binding.approximateReadTimeLayout.isVisible = false
return
}
binding.approximateReadTime.text = time.format(resources)
binding.approximateReadTimeTitle.setText(
if (time.isContinue) R.string.approximate_remaining_time else R.string.approximate_reading_time
)
binding.approximateReadTimeLayout.isVisible = true
}
private fun onDescriptionChanged(description: CharSequence?) {
val tv = requireViewBinding().textViewDescription
if (description.isNullOrBlank()) {

View File

@@ -42,6 +42,7 @@ import org.koitharu.kotatsu.details.domain.BranchComparator
import org.koitharu.kotatsu.details.domain.DetailsInteractor
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
import org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase
import org.koitharu.kotatsu.details.domain.ReadingTimeUseCase
import org.koitharu.kotatsu.details.domain.RelatedMangaUseCase
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
@@ -76,6 +77,7 @@ class DetailsViewModel @Inject constructor(
private val extraProvider: ListExtraProvider,
private val detailsLoadUseCase: DetailsLoadUseCase,
private val progressUpdateUseCase: ProgressUpdateUseCase,
private val readingTimeUseCase: ReadingTimeUseCase,
) : BaseViewModel() {
private val intent = MangaIntent(savedStateHandle)
@@ -200,6 +202,14 @@ class DetailsViewModel @Inject constructor(
(if (reversed) list.asReversed() else list).filterSearch(query)
}.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val readingTime = combine(
details,
selectedBranch,
history,
) { m, b, h ->
readingTimeUseCase.invoke(m, b, h)
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
val selectedBranchValue: String?
get() = selectedBranch.value

View File

@@ -147,9 +147,11 @@
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chips_tags">
app:layout_constraintTop_toBottomOf="@id/chips_tags"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
@@ -164,12 +166,13 @@
android:orientation="vertical">
<TextView
android:id="@+id/approximate_read_time_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingVertical="2dp"
android:text="@string/approximate_reading_time"
android:singleLine="true"
tools:text="@string/approximate_reading_time"
android:textAppearance="?attr/textAppearanceBodyLarge" />
<TextView
@@ -177,6 +180,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="2dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="12 minutes" />

View File

@@ -28,11 +28,11 @@
<item quantity="one">%1$d month ago</item>
<item quantity="other">%1$d months ago</item>
</plurals>
<plurals name="hour">
<plurals name="hours">
<item quantity="one">%1$d hour</item>
<item quantity="other">%1$d hours</item>
</plurals>
<plurals name="minute">
<plurals name="minutes">
<item quantity="one">%1$d minute</item>
<item quantity="other">%1$d minutes</item>
</plurals>

View File

@@ -560,4 +560,6 @@
<string name="mark_as_completed_prompt">Mark selected manga as completely read?\n\nWarning: current reading progress will be lost.</string>
<string name="category_hidden_done">This category was hidden from the main screen and is accessible through Menu → Manage categories</string>
<string name="approximate_reading_time">Approximate reading time</string>
<string name="approximate_remaining_time">Approximate remaining time</string>
<string name="remaining_time_pattern">%1$s %2$s</string>
</resources>