Add progress chart to statistics page showing score progression over time
Some checks failed
Build and Release Android APK / build (push) Has been cancelled

This commit is contained in:
cmclark00 2025-03-24 17:28:48 -04:00
parent aeb463fa88
commit 71a2485aac
5 changed files with 124 additions and 0 deletions

View file

@ -83,6 +83,9 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
// MPAndroidChart for progress visualization
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
// Room
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")

View file

@ -12,6 +12,9 @@ interface ScoreDao {
@Query("SELECT * FROM scores WHERE gameVersion = :gameVersion")
fun getScoresForGame(gameVersion: String): LiveData<List<Score>>
@Query("SELECT * FROM scores WHERE gameVersion = :gameVersion ORDER BY dateRecorded ASC")
fun getScoresForGameByDate(gameVersion: String): LiveData<List<Score>>
@Query("SELECT DISTINCT gameVersion FROM scores")
fun getGamesWithScores(): LiveData<List<String>>

View file

@ -1,5 +1,6 @@
package com.accidentalproductions.tetristats.ui.stats
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -8,7 +9,16 @@ import android.widget.ArrayAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.accidentalproductions.tetristats.R
import com.accidentalproductions.tetristats.databinding.FragmentStatsBinding
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class StatsFragment : Fragment() {
private var _binding: FragmentStatsBinding? = null
@ -30,6 +40,7 @@ class StatsFragment : Fragment() {
setupRecyclerView()
setupGameFilter()
setupProgressChart()
observeStats()
}
@ -52,6 +63,64 @@ class StatsFragment : Fragment() {
viewModel.setSelectedGame(selectedGame)
}
}
private fun setupProgressChart() {
with(binding.chartProgress) {
description.isEnabled = false
legend.isEnabled = true
setTouchEnabled(true)
setDrawGridBackground(false)
isDragEnabled = true
setScaleEnabled(true)
setPinchZoom(true)
axisRight.isEnabled = false
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.granularity = 1f
xAxis.setDrawGridLines(false)
axisLeft.setDrawGridLines(true)
axisLeft.axisMinimum = 0f
}
}
private fun updateProgressChart(scores: List<Entry>, dates: List<Long>) {
if (scores.isEmpty()) {
binding.chartProgress.clear()
binding.chartProgress.invalidate()
return
}
val dataSet = LineDataSet(scores, "Score Progress").apply {
mode = LineDataSet.Mode.CUBIC_BEZIER
color = resources.getColor(R.color.tetris_navy, null)
lineWidth = 2f
setDrawCircles(true)
setCircleColor(resources.getColor(R.color.tetris_navy, null))
circleRadius = 4f
setDrawValues(false)
highLightColor = Color.rgb(244, 117, 117)
}
val lineData = LineData(dataSet)
binding.chartProgress.data = lineData
// Format X-axis labels (dates)
val dateFormat = SimpleDateFormat("MM/dd", Locale.getDefault())
binding.chartProgress.xAxis.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
val index = value.toInt()
return if (index >= 0 && index < dates.size) {
dateFormat.format(Date(dates[index]))
} else {
""
}
}
}
binding.chartProgress.invalidate()
}
private fun observeStats() {
viewModel.filteredScores.observe(viewLifecycleOwner) { scores ->
@ -65,6 +134,24 @@ class StatsFragment : Fragment() {
viewModel.highScore.observe(viewLifecycleOwner) { highScore ->
binding.textViewHighScore.text = "%,d".format(highScore)
}
viewModel.scoresByDate.observe(viewLifecycleOwner) { scores ->
// Convert scores to entries for the chart
if (scores.isNotEmpty()) {
val entries = mutableListOf<Entry>()
val dates = mutableListOf<Long>()
scores.forEachIndexed { index, score ->
entries.add(Entry(index.toFloat(), score.scoreValue.toFloat()))
dates.add(score.dateRecorded)
}
updateProgressChart(entries, dates)
} else {
binding.chartProgress.clear()
binding.chartProgress.invalidate()
}
}
}
override fun onDestroyView() {

View file

@ -18,6 +18,10 @@ class StatsViewModel(application: Application) : AndroidViewModel(application) {
scoreDao.getScoresForGame(game)
}
val scoresByDate: LiveData<List<Score>> = _selectedGame.switchMap { game ->
scoreDao.getScoresForGameByDate(game)
}
val averageScore: LiveData<Double> = _selectedGame.switchMap { game ->
scoreDao.getAverageScore(game)
}

View file

@ -31,6 +31,33 @@
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Progress Chart"
android:textAppearance="?attr/textAppearanceSubtitle1" />
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chartProgress"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewScores"
android:layout_width="match_parent"