Statistics periods
This commit is contained in:
@@ -68,6 +68,13 @@ abstract class BaseViewModel : ViewModel() {
|
||||
errorEvent.call(error)
|
||||
}
|
||||
|
||||
protected inline suspend fun <T> withLoading(block: () -> T): T = try {
|
||||
loadingCounter.increment()
|
||||
block()
|
||||
} finally {
|
||||
loadingCounter.decrement()
|
||||
}
|
||||
|
||||
protected fun MutableStateFlow<Int>.increment() = update { it + 1 }
|
||||
|
||||
protected fun MutableStateFlow<Int>.decrement() = update { it - 1 }
|
||||
|
||||
@@ -29,8 +29,8 @@ interface StatsDao {
|
||||
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats")
|
||||
suspend fun getTotalReadingTime(): Long
|
||||
|
||||
@Query("SELECT manga_id, SUM(duration) AS d FROM stats GROUP BY manga_id ORDER BY d DESC")
|
||||
suspend fun getDurationStats(): Map<@MapColumn("manga_id") Long, @MapColumn("d") Long>
|
||||
@Query("SELECT manga_id, SUM(duration) AS d FROM stats WHERE started_at >= :fromDate GROUP BY manga_id ORDER BY d DESC")
|
||||
suspend fun getDurationStats(fromDate: Long): Map<@MapColumn("manga_id") Long, @MapColumn("d") Long>
|
||||
|
||||
@Query("DELETE FROM stats")
|
||||
suspend fun clear()
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.stats.data
|
||||
import androidx.room.withTransaction
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsPeriod
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
@@ -11,8 +12,13 @@ class StatsRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
suspend fun getReadingStats(): List<StatsRecord> = db.withTransaction {
|
||||
val stats = db.getStatsDao().getDurationStats()
|
||||
suspend fun getReadingStats(period: StatsPeriod): List<StatsRecord> = db.withTransaction {
|
||||
val fromDate = if (period == StatsPeriod.ALL) {
|
||||
0L
|
||||
} else {
|
||||
System.currentTimeMillis() - TimeUnit.DAYS.toMillis(period.days.toLong())
|
||||
}
|
||||
val stats = db.getStatsDao().getDurationStats(fromDate)
|
||||
val minute = TimeUnit.MINUTES.toMillis(1)
|
||||
val mangaDao = db.getMangaDao()
|
||||
val result = ArrayList<StatsRecord>(stats.size)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.stats.domain
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
enum class StatsPeriod(
|
||||
@StringRes val titleResId: Int,
|
||||
val days: Int,
|
||||
) {
|
||||
|
||||
DAY(R.string.day, 1),
|
||||
WEEK(R.string.week, 7),
|
||||
MONTH(R.string.month, 30),
|
||||
MONTHS_3(R.string.three_months, 90),
|
||||
ALL(R.string.all_time, Int.MAX_VALUE),
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package org.koitharu.kotatsu.stats.ui
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.style.DynamicDrawableSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.ImageSpan
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@@ -9,7 +14,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -20,6 +28,7 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.showOrHide
|
||||
@@ -27,6 +36,7 @@ import org.koitharu.kotatsu.databinding.ActivityStatsBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsPeriod
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import org.koitharu.kotatsu.stats.ui.views.PieChartView
|
||||
|
||||
@@ -47,6 +57,9 @@ class StatsActivity : BaseActivity<ActivityStatsBinding>(), OnListItemClickListe
|
||||
viewModel.isLoading.observe(this) {
|
||||
viewBinding.progressBar.showOrHide(it)
|
||||
}
|
||||
viewModel.period.observe(this) {
|
||||
supportActionBar?.setSubtitle(it.titleResId)
|
||||
}
|
||||
viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView))
|
||||
viewModel.readingStats.observe(this) {
|
||||
val sum = it.sumOf { it.duration }
|
||||
@@ -88,6 +101,11 @@ class StatsActivity : BaseActivity<ActivityStatsBinding>(), OnListItemClickListe
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_period -> {
|
||||
showPeriodSelector()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
@@ -102,4 +120,17 @@ class StatsActivity : BaseActivity<ActivityStatsBinding>(), OnListItemClickListe
|
||||
viewModel.clear()
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun showPeriodSelector() {
|
||||
val menu = PopupMenu(this, viewBinding.toolbar)
|
||||
for ((i, branch) in StatsPeriod.entries.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, Menu.NONE, i, branch.titleResId)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
StatsPeriod.entries.getOrNull(it.order)?.also {
|
||||
viewModel.period.value = it
|
||||
} != null
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,18 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.stats.data.StatsRepository
|
||||
import org.koitharu.kotatsu.stats.domain.StatsPeriod
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -22,12 +25,17 @@ class StatsViewModel @Inject constructor(
|
||||
private val repository: StatsRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val period = MutableStateFlow(StatsPeriod.WEEK)
|
||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||
val readingStats = MutableStateFlow<List<StatsRecord>>(emptyList())
|
||||
|
||||
init {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
readingStats.value = repository.getReadingStats()
|
||||
launchJob(Dispatchers.Default) {
|
||||
period.collectLatest { p ->
|
||||
readingStats.value = withLoading {
|
||||
repository.getReadingStats(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_period"
|
||||
android:icon="@drawable/ic_expand_more"
|
||||
android:orderInCategory="0"
|
||||
android:title="@string/chapters"
|
||||
app:showAsAction="always"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_clear"
|
||||
|
||||
@@ -611,4 +611,9 @@
|
||||
<string name="clear_stats">Clear statistics</string>
|
||||
<string name="stats_cleared">Statistics cleared</string>
|
||||
<string name="clear_stats_confirm">Do you really want to clear all reading statistics? This action cannot be undone.</string>
|
||||
<string name="week">Week</string>
|
||||
<string name="month">Month</string>
|
||||
<string name="all_time">All time</string>
|
||||
<string name="day">Day</string>
|
||||
<string name="three_months">Three months</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user