Improve stats ui
This commit is contained in:
@@ -4,34 +4,34 @@ import androidx.room.Dao
|
||||
import androidx.room.MapColumn
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
@Dao
|
||||
abstract class StatsDao {
|
||||
interface StatsDao {
|
||||
|
||||
@Query("SELECT * FROM stats ORDER BY started_at")
|
||||
abstract suspend fun findAll(): List<StatsEntity>
|
||||
suspend fun findAll(): List<StatsEntity>
|
||||
|
||||
@Query("SELECT * FROM stats WHERE manga_id = :mangaId ORDER BY started_at")
|
||||
abstract suspend fun findAll(mangaId: Long): List<StatsEntity>
|
||||
suspend fun findAll(mangaId: Long): List<StatsEntity>
|
||||
|
||||
@Query("SELECT IFNULL(SUM(pages),0) FROM stats WHERE manga_id = :mangaId")
|
||||
abstract suspend fun getReadPagesCount(mangaId: Long): Int
|
||||
suspend fun getReadPagesCount(mangaId: Long): Int
|
||||
|
||||
@Query("SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats WHERE manga_id = :mangaId")
|
||||
abstract suspend fun getAverageTimePerPage(mangaId: Long): Long
|
||||
suspend fun getAverageTimePerPage(mangaId: Long): Long
|
||||
|
||||
@Query("SELECT IFNULL(SUM(duration)/SUM(pages), 0) FROM stats")
|
||||
suspend fun getAverageTimePerPage(): Long
|
||||
|
||||
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats WHERE manga_id = :mangaId")
|
||||
abstract suspend fun getReadingTime(mangaId: Long): Long
|
||||
suspend fun getReadingTime(mangaId: Long): Long
|
||||
|
||||
@Query("SELECT IFNULL(SUM(duration), 0) FROM stats")
|
||||
abstract 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")
|
||||
abstract suspend fun getDurationStats(): Map<@MapColumn("manga_id") Long, @MapColumn("d") Long>
|
||||
suspend fun getDurationStats(): Map<@MapColumn("manga_id") Long, @MapColumn("d") Long>
|
||||
|
||||
@Upsert
|
||||
abstract suspend fun upsert(entity: StatsEntity)
|
||||
suspend fun upsert(entity: StatsEntity)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package org.koitharu.kotatsu.stats.data
|
||||
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.collection.MutableScatterMap
|
||||
import androidx.collection.ScatterMap
|
||||
import androidx.room.withTransaction
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
class StatsRepository @Inject constructor(
|
||||
|
||||
@@ -7,16 +7,19 @@ import com.google.android.material.R
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.details.data.ReadingTime
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.min
|
||||
|
||||
data class StatsRecord(
|
||||
val manga: Manga,
|
||||
val duration: Long,
|
||||
) {
|
||||
) : ListModel {
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
return other is StatsRecord && other.manga == manga
|
||||
}
|
||||
|
||||
val time: ReadingTime
|
||||
|
||||
|
||||
25
app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsAD.kt
Normal file
25
app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsAD.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package org.koitharu.kotatsu.stats.ui
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemStatsBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.stats.domain.StatsRecord
|
||||
|
||||
fun statsAD(
|
||||
listener: OnListItemClickListener<Manga>,
|
||||
) = adapterDelegateViewBinding<StatsRecord, StatsRecord, ItemStatsBinding>(
|
||||
{ layoutInflater, parent -> ItemStatsBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener { v ->
|
||||
listener.onItemClick(item.manga, v)
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.manga.title
|
||||
binding.textViewSummary.text = item.time.format(context.resources)
|
||||
binding.imageViewBadge.imageTintList = ColorStateList.valueOf(item.getColor(context))
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,25 @@
|
||||
package org.koitharu.kotatsu.stats.ui
|
||||
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.OvalShape
|
||||
import android.graphics.drawable.shapes.Shape
|
||||
import android.os.Bundle
|
||||
import android.text.style.DynamicDrawableSpan
|
||||
import android.text.style.ImageSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.databinding.FragmentStatsBinding
|
||||
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.StatsRecord
|
||||
import org.koitharu.kotatsu.stats.ui.views.PieChartView
|
||||
|
||||
@AndroidEntryPoint
|
||||
class StatsFragment : BaseFragment<FragmentStatsBinding>() {
|
||||
class StatsFragment : BaseFragment<FragmentStatsBinding>(), OnListItemClickListener<Manga> {
|
||||
|
||||
private val viewModel: StatsViewModel by viewModels()
|
||||
|
||||
@@ -33,6 +29,9 @@ class StatsFragment : BaseFragment<FragmentStatsBinding>() {
|
||||
|
||||
override fun onViewBindingCreated(binding: FragmentStatsBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val adapter = BaseListAdapter<StatsRecord>()
|
||||
.addDelegate(ListItemType.FEED, statsAD(this))
|
||||
binding.recyclerView.adapter = adapter
|
||||
viewModel.readingStats.observe(viewLifecycleOwner) {
|
||||
val sum = it.sumOf { it.duration }
|
||||
binding.chart.setData(
|
||||
@@ -45,27 +44,13 @@ class StatsFragment : BaseFragment<FragmentStatsBinding>() {
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.textViewLegend.text = buildLegend(it)
|
||||
adapter.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
|
||||
private fun buildLegend(stats: List<StatsRecord>) = buildSpannedString {
|
||||
val context = context ?: return@buildSpannedString
|
||||
for (item in stats) {
|
||||
ContextCompat.getDrawable(context, R.drawable.bg_rounded_square)?.let { icon ->
|
||||
icon.setBounds(0, 0, icon.intrinsicWidth, icon.intrinsicHeight)
|
||||
icon.setTint(item.getColor(context))
|
||||
inSpans(ImageSpan(icon, DynamicDrawableSpan.ALIGN_BASELINE)) {
|
||||
append(' ')
|
||||
}
|
||||
append(' ')
|
||||
}
|
||||
append(item.manga.title)
|
||||
append(" - ")
|
||||
append(item.time.format(context.resources))
|
||||
appendLine()
|
||||
}
|
||||
override fun onItemClick(item: Manga, view: View) {
|
||||
startActivity(DetailsActivity.newIntent(view.context, item))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
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:layout_height="match_parent"
|
||||
android:background="?android:colorBackground"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -21,16 +23,18 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_legend"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingHorizontal="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:text="@tools:sample/lorem/random" />
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/item_stats" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
51
app/src/main/res/layout/item_stats.xml
Normal file
51
app/src/main/res/layout/item_stats.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
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="wrap_content"
|
||||
android:background="@drawable/list_selector"
|
||||
android:clipChildren="false"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/bg_rounded_square" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
tools:text="@tools:sample/lorem[3]" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user