Stats activity

This commit is contained in:
Koitharu
2024-02-29 14:01:31 +02:00
parent 11cd5609bb
commit 101d357eff
19 changed files with 213 additions and 99 deletions

View File

@@ -239,6 +239,9 @@
<data android:scheme="kotatsu+kitsu" /> <data android:scheme="kotatsu+kitsu" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="org.koitharu.kotatsu.stats.ui.StatsActivity"
android:label="@string/reading_stats" />
<service <service
android:name="androidx.work.impl.foreground.SystemForegroundService" android:name="androidx.work.impl.foreground.SystemForegroundService"

View File

@@ -10,6 +10,7 @@ data class ReadingTime(
) { ) {
fun format(resources: Resources): String = when { fun format(resources: Resources): String = when {
hours == 0 && minutes == 0 -> resources.getString(R.string.less_than_minute)
hours == 0 -> resources.getQuantityString(R.plurals.minutes, minutes, minutes) hours == 0 -> resources.getQuantityString(R.plurals.minutes, minutes, minutes)
minutes == 0 -> resources.getQuantityString(R.plurals.hours, hours, hours) minutes == 0 -> resources.getQuantityString(R.plurals.hours, hours, hours)
else -> resources.getString( else -> resources.getString(

View File

@@ -11,7 +11,9 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkManageIntent import org.koitharu.kotatsu.core.os.NetworkManageIntent
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver import org.koitharu.kotatsu.list.ui.size.DynamicItemSizeResolver
@@ -27,6 +29,7 @@ class HistoryListFragment : MangaListFragment() {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
RecyclerScrollKeeper(binding.recyclerView).attach() RecyclerScrollKeeper(binding.recyclerView).attach()
addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel)) addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel))
viewModel.isStatsEnabled.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
} }
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.history.ui package org.koitharu.kotatsu.history.ui
import android.content.Context import android.content.Context
import android.content.Intent
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
@@ -9,6 +10,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
import org.koitharu.kotatsu.stats.ui.StatsActivity
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.ZoneId import java.time.ZoneId
@@ -24,6 +26,11 @@ class HistoryListMenuProvider(
menuInflater.inflate(R.menu.opt_history, menu) menuInflater.inflate(R.menu.opt_history, menu)
} }
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
menu.findItem(R.id.action_stats)?.isVisible = viewModel.isStatsEnabled.value
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean { override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_clear_history -> { R.id.action_clear_history -> {
@@ -31,6 +38,11 @@ class HistoryListMenuProvider(
true true
} }
R.id.action_stats -> {
context.startActivity(Intent(context, StatsActivity::class.java))
true
}
else -> false else -> false
} }
} }

View File

@@ -71,6 +71,12 @@ class HistoryListViewModel @Inject constructor(
g && s.isGroupingSupported() g && s.isGroupingSupported()
} }
val isStatsEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_STATS_ENABLED,
valueProducer = { isStatsEnabled },
)
override val content = combine( override val content = combine(
sortOrder.flatMapLatest { repository.observeAllWithHistory(it) }, sortOrder.flatMapLatest { repository.observeAllWithHistory(it) },
isGroupingEnabled, isGroupingEnabled,

View File

@@ -1,14 +0,0 @@
package org.koitharu.kotatsu.settings
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
@AndroidEntryPoint
class StatsSettingsFragment : BasePreferenceFragment(R.string.reading_stats) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_stats)
}
}

View File

@@ -29,7 +29,7 @@ interface StatsDao {
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats") @Query("SELECT IFNULL(SUM(duration), 0) FROM stats")
suspend fun getTotalReadingTime(): Long suspend fun getTotalReadingTime(): Long
@Query("SELECT manga_id, SUM(duration) AS d FROM stats GROUP BY manga_id ORDER BY d") @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> suspend fun getDurationStats(): Map<@MapColumn("manga_id") Long, @MapColumn("d") Long>
@Upsert @Upsert

View File

@@ -4,6 +4,7 @@ import androidx.room.withTransaction
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.stats.domain.StatsRecord import org.koitharu.kotatsu.stats.domain.StatsRecord
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class StatsRepository @Inject constructor( class StatsRepository @Inject constructor(
@@ -12,14 +13,23 @@ class StatsRepository @Inject constructor(
suspend fun getReadingStats(): List<StatsRecord> = db.withTransaction { suspend fun getReadingStats(): List<StatsRecord> = db.withTransaction {
val stats = db.getStatsDao().getDurationStats() val stats = db.getStatsDao().getDurationStats()
val minute = TimeUnit.MINUTES.toMillis(1)
val mangaDao = db.getMangaDao() val mangaDao = db.getMangaDao()
val result = ArrayList<StatsRecord>(stats.size) val result = ArrayList<StatsRecord>(stats.size)
var other = StatsRecord(null, 0)
for ((mangaId, duration) in stats) { for ((mangaId, duration) in stats) {
val manga = mangaDao.find(mangaId)?.toManga() ?: continue val manga = mangaDao.find(mangaId)?.toManga()
result += StatsRecord( if (manga == null || duration < minute) {
manga = manga, other = other.copy(duration = other.duration + duration)
duration = duration, } else {
) result += StatsRecord(
manga = manga,
duration = duration,
)
}
}
if (other.duration != 0L) {
result += other
} }
result result
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.stats.domain
import android.content.Context import android.content.Context
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import com.google.android.material.R import com.google.android.material.R
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@@ -13,7 +14,7 @@ import java.util.concurrent.TimeUnit
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
data class StatsRecord( data class StatsRecord(
val manga: Manga, val manga: Manga?,
val duration: Long, val duration: Long,
) : ListModel { ) : ListModel {
@@ -34,8 +35,12 @@ data class StatsRecord(
@ColorInt @ColorInt
fun getColor(context: Context): Int { fun getColor(context: Context): Int {
val hue = (manga.id.absoluteValue % 360).toFloat() val color = if (manga != null) {
val color = ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f)) val hue = (manga.id.absoluteValue % 360).toFloat()
ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f))
} else {
context.getThemeColor(R.attr.colorSurface)
}
val backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh) val backgroundColor = context.getThemeColor(R.attr.colorSurfaceContainerHigh)
return MaterialColors.harmonize(color, backgroundColor) return MaterialColors.harmonize(color, backgroundColor)
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.stats.ui
import android.content.res.ColorStateList import android.content.res.ColorStateList
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemStatsBinding import org.koitharu.kotatsu.databinding.ItemStatsBinding
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@@ -14,12 +15,13 @@ fun statsAD(
) { ) {
binding.root.setOnClickListener { v -> binding.root.setOnClickListener { v ->
listener.onItemClick(item.manga, v) listener.onItemClick(item.manga ?: return@setOnClickListener, v)
} }
bind { bind {
binding.textViewTitle.text = item.manga.title binding.textViewTitle.text = item.manga?.title ?: getString(R.string.other_manga)
binding.textViewSummary.text = item.time.format(context.resources) binding.textViewSummary.text = item.time.format(context.resources)
binding.imageViewBadge.imageTintList = ColorStateList.valueOf(item.getColor(context)) binding.imageViewBadge.imageTintList = ColorStateList.valueOf(item.getColor(context))
binding.root.isClickable = item.manga != null
} }
} }

View File

@@ -4,14 +4,17 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.FragmentStatsBinding import org.koitharu.kotatsu.databinding.ActivityStatsBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@@ -19,28 +22,26 @@ import org.koitharu.kotatsu.stats.domain.StatsRecord
import org.koitharu.kotatsu.stats.ui.views.PieChartView import org.koitharu.kotatsu.stats.ui.views.PieChartView
@AndroidEntryPoint @AndroidEntryPoint
class StatsFragment : BaseFragment<FragmentStatsBinding>(), OnListItemClickListener<Manga> { class StatsActivity : BaseActivity<ActivityStatsBinding>(), OnListItemClickListener<Manga> {
private val viewModel: StatsViewModel by viewModels() private val viewModel: StatsViewModel by viewModels()
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentStatsBinding { override fun onCreate(savedInstanceState: Bundle?) {
return FragmentStatsBinding.inflate(inflater, container, false) super.onCreate(savedInstanceState)
} setContentView(ActivityStatsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
override fun onViewBindingCreated(binding: FragmentStatsBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
val adapter = BaseListAdapter<StatsRecord>() val adapter = BaseListAdapter<StatsRecord>()
.addDelegate(ListItemType.FEED, statsAD(this)) .addDelegate(ListItemType.FEED, statsAD(this))
binding.recyclerView.adapter = adapter viewBinding.recyclerView.adapter = adapter
viewModel.readingStats.observe(viewLifecycleOwner) { viewModel.readingStats.observe(this) {
val sum = it.sumOf { it.duration } val sum = it.sumOf { it.duration }
binding.chart.setData( viewBinding.chart.setData(
it.map { v -> it.map { v ->
PieChartView.Segment( PieChartView.Segment(
value = (v.duration / 1000).toInt(), value = (v.duration / 1000).toInt(),
label = v.manga.title, label = v.manga?.title ?: getString(R.string.other_manga),
percent = (v.duration.toDouble() / sum).toFloat(), percent = (v.duration.toDouble() / sum).toFloat(),
color = v.getColor(binding.chart.context), color = v.getColor(this),
) )
}, },
) )

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.stats.ui.views package org.koitharu.kotatsu.stats.ui.views
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
@@ -9,28 +10,37 @@ import android.graphics.PorterDuffXfermode
import android.graphics.RectF import android.graphics.RectF
import android.graphics.Xfermode import android.graphics.Xfermode
import android.util.AttributeSet import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View import android.view.View
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.collection.MutableIntList import androidx.collection.MutableIntList
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.graphics.minus import androidx.core.graphics.minus
import androidx.core.view.GestureDetectorCompat
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.parsers.util.replaceWith import org.koitharu.kotatsu.parsers.util.replaceWith
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.sqrt
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
class PieChartView @JvmOverloads constructor( class PieChartView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) { ) : View(context, attrs, defStyleAttr), GestureDetector.OnGestureListener {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val segments = ArrayList<Segment>() private val segments = ArrayList<Segment>()
private val chartBounds = RectF() private val chartBounds = RectF()
private val clearColor = context.getThemeColor(android.R.attr.colorBackground) private val clearColor = context.getThemeColor(android.R.attr.colorBackground)
private val touchDetector = GestureDetectorCompat(context, this)
private var hightlightedSegment = -1
var onSegmentClickListener: OnSegmentClickListener? = null
init { init {
touchDetector.setIsLongpressEnabled(false)
paint.strokeWidth = context.resources.resolveDp(2f) paint.strokeWidth = context.resources.resolveDp(2f)
} }
@@ -39,6 +49,9 @@ class PieChartView @JvmOverloads constructor(
var angle = 0f var angle = 0f
for ((i, segment) in segments.withIndex()) { for ((i, segment) in segments.withIndex()) {
paint.color = segment.color paint.color = segment.color
if (i == hightlightedSegment) {
paint.color = ColorUtils.setAlphaComponent(paint.color, 180)
}
paint.style = Paint.Style.FILL paint.style = Paint.Style.FILL
val sweepAngle = segment.percent * 360f val sweepAngle = segment.percent * 360f
canvas.drawArc( canvas.drawArc(
@@ -75,15 +88,80 @@ class PieChartView @JvmOverloads constructor(
) )
} }
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) {
hightlightedSegment = -1
invalidate()
}
return super.onTouchEvent(event) || touchDetector.onTouchEvent(event)
}
override fun onDown(e: MotionEvent): Boolean {
val segment = findSegmentIndex(e.x, e.y)
if (segment != hightlightedSegment) {
hightlightedSegment = segment
invalidate()
return true
} else {
return false
}
}
override fun onShowPress(e: MotionEvent) = Unit
override fun onSingleTapUp(e: MotionEvent): Boolean {
onSegmentClickListener?.run {
val segment = segments.getOrNull(findSegmentIndex(e.x, e.y))
if (segment != null) {
onSegmentClick(this@PieChartView, segment)
}
}
return true
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean = false
override fun onLongPress(e: MotionEvent) = Unit
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean = false
fun setData(value: List<Segment>) { fun setData(value: List<Segment>) {
segments.replaceWith(value) segments.replaceWith(value)
invalidate() invalidate()
} }
private fun findSegmentIndex(x: Float, y: Float): Int {
val dy = (y - chartBounds.centerY()).toDouble()
val dx = (x - chartBounds.centerX()).toDouble()
val distance = sqrt(dx * dx + dy * dy).toFloat()
if (distance < chartBounds.height() / 4f || distance > chartBounds.centerX()) {
return -1
}
var touchAngle = Math.toDegrees(Math.atan2(dy, dx)).toFloat()
if (touchAngle < 0) {
touchAngle += 360
}
var angle = 0f
for ((i, segment) in segments.withIndex()) {
val sweepAngle = segment.percent * 360f
if (touchAngle in angle..(angle + sweepAngle)) {
return i
}
angle += sweepAngle
}
return -1
}
class Segment( class Segment(
val value: Int, val value: Int,
val label: String, val label: String,
val percent: Float, val percent: Float,
val color: Int, val color: Int,
) )
interface OnSegmentClickListener {
fun onSegmentClick(view: PieChartView, segment: Segment)
}
} }

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="noScroll">
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<org.koitharu.kotatsu.stats.ui.views.PieChartView
android:id="@+id/chart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
android:overScrollMode="ifContentScrolls"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chart"
tools:itemCount="4"
tools:listitem="@layout/item_stats" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<org.koitharu.kotatsu.stats.ui.views.PieChartView
android:id="@+id/chart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chart"
tools:itemCount="4"
tools:listitem="@layout/item_stats" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -17,6 +17,7 @@
android:id="@+id/imageView_badge" android:id="@+id/imageView_badge"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@null"
app:srcCompat="@drawable/bg_rounded_square" /> app:srcCompat="@drawable/bg_rounded_square" />
<LinearLayout <LinearLayout

View File

@@ -9,4 +9,10 @@
android:title="@string/clear_history" android:title="@string/clear_history"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_stats"
android:orderInCategory="40"
android:title="@string/statistics"
app:showAsAction="never" />
</menu> </menu>

View File

@@ -605,4 +605,7 @@
<string name="multiple_cbz_files">Multiple CBZ files</string> <string name="multiple_cbz_files">Multiple CBZ files</string>
<string name="stats_enabled">Enable statistics</string> <string name="stats_enabled">Enable statistics</string>
<string name="reading_stats">Reading statistics</string> <string name="reading_stats">Reading statistics</string>
<string name="other_manga">Other manga</string>
<string name="less_than_minute">Less than a minute</string>
<string name="statistics">Statistics</string>
</resources> </resources>

View File

@@ -28,9 +28,9 @@
android:summary="@string/related_manga_summary" android:summary="@string/related_manga_summary"
android:title="@string/related_manga" /> android:title="@string/related_manga" />
<Preference <SwitchPreferenceCompat
android:fragment="org.koitharu.kotatsu.settings.StatsSettingsFragment" android:defaultValue="false"
android:key="stats" android:key="stats_on"
android:title="@string/reading_stats" android:title="@string/reading_stats"
app:allowDividerAbove="true" /> app:allowDividerAbove="true" />

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="stats_on"
android:layout="@layout/preference_toggle_header"
android:title="@string/stats_enabled" />
<Preference
android:dependency="stats_on"
android:fragment="org.koitharu.kotatsu.stats.ui.StatsFragment"
android:title="@string/reading_stats" />
</androidx.preference.PreferenceScreen>