diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bfe3c94..026b6df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -83,9 +83,6 @@ 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") diff --git a/app/src/main/java/com/accidentalproductions/tetristats/data/ScalingFactors.kt b/app/src/main/java/com/accidentalproductions/tetristats/data/ScalingFactors.kt index e272e19..93030f2 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/data/ScalingFactors.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/data/ScalingFactors.kt @@ -16,9 +16,7 @@ object ScalingFactors { "Tetris DS" to RangeScalingFactor(3.0, 3.3, 4.5), "Tetris Effect" to RangeScalingFactor(2.5, 3.8, 4.5), "Rosy Retrospection DX" to RangeScalingFactor(4.0, 1.5, 1.8), - "Apotris" to RangeScalingFactor(1.8, 3.8, 4.4), - "Modretro Tetris" to RangeScalingFactor(2.0, 2.5, 3.0), - "Tetris Mobile" to RangeScalingFactor(2.2, 2.8, 3.5) + "Apotris" to RangeScalingFactor(1.8, 3.8, 4.4) ), "Game Boy Tetris" to mapOf( "NES Tetris" to 1.33, @@ -26,9 +24,7 @@ object ScalingFactors { "Tetris DS" to RangeScalingFactor(4.0, 2.0, 2.0), "Tetris Effect" to RangeScalingFactor(4.0, 2.3, 2.3), "Rosy Retrospection DX" to 1.1, - "Apotris" to RangeScalingFactor(1.33, 1.33, 2.33), - "Modretro Tetris" to RangeScalingFactor(1.5, 1.8, 2.0), - "Tetris Mobile" to RangeScalingFactor(1.6, 1.9, 2.1) + "Apotris" to RangeScalingFactor(1.33, 1.33, 2.33) ), "Tetris DX" to mapOf( "NES Tetris" to 1.33, @@ -36,9 +32,7 @@ object ScalingFactors { "Tetris DS" to RangeScalingFactor(4.0, 2.0, 2.0), "Tetris Effect" to RangeScalingFactor(4.0, 2.3, 2.3), "Rosy Retrospection DX" to 1.1, - "Apotris" to RangeScalingFactor(1.33, 1.33, 2.33), - "Modretro Tetris" to RangeScalingFactor(1.5, 1.8, 2.0), - "Tetris Mobile" to RangeScalingFactor(1.6, 1.9, 2.1) + "Apotris" to RangeScalingFactor(1.33, 1.33, 2.33) ), "Tetris DS" to mapOf( "NES Tetris" to RangeScalingFactor(0.33, 0.3, 0.22), @@ -46,9 +40,7 @@ object ScalingFactors { "Tetris DX" to RangeScalingFactor(0.25, 0.5, 0.5), "Tetris Effect" to RangeScalingFactor(0.83, 0.91, 1.0), "Rosy Retrospection DX" to RangeScalingFactor(0.25, 0.91, 0.67), - "Apotris" to RangeScalingFactor(0.33, 0.67, 0.9), - "Modretro Tetris" to RangeScalingFactor(0.4, 0.5, 0.6), - "Tetris Mobile" to RangeScalingFactor(0.45, 0.55, 0.65) + "Apotris" to RangeScalingFactor(0.33, 0.67, 0.9) ), "Tetris Effect" to mapOf( "NES Tetris" to RangeScalingFactor(0.4, 0.26, 0.22), @@ -56,9 +48,7 @@ object ScalingFactors { "Tetris DX" to RangeScalingFactor(0.25, 0.43, 0.43), "Tetris DS" to RangeScalingFactor(1.2, 1.1, 1.0), "Rosy Retrospection DX" to RangeScalingFactor(0.25, 0.43, 0.57), - "Apotris" to RangeScalingFactor(0.33, 0.67, 0.85), - "Modretro Tetris" to RangeScalingFactor(0.45, 0.55, 0.65), - "Tetris Mobile" to RangeScalingFactor(0.5, 0.6, 0.7) + "Apotris" to RangeScalingFactor(0.33, 0.67, 0.85) ), "Rosy Retrospection DX" to mapOf( "NES Tetris" to RangeScalingFactor(0.25, 0.67, 0.57), @@ -66,9 +56,7 @@ object ScalingFactors { "Tetris DX" to 0.91, "Tetris DS" to RangeScalingFactor(4.0, 1.5, 1.8), "Tetris Effect" to RangeScalingFactor(4.0, 2.3, 1.8), - "Apotris" to RangeScalingFactor(1.1, 0.67, 0.5), - "Modretro Tetris" to RangeScalingFactor(1.3, 1.5, 1.7), - "Tetris Mobile" to RangeScalingFactor(1.4, 1.6, 1.8) + "Apotris" to RangeScalingFactor(1.1, 0.67, 0.5) ), "Apotris" to mapOf( "NES Tetris" to RangeScalingFactor(0.56, 0.26, 0.23), @@ -76,29 +64,7 @@ object ScalingFactors { "Tetris DX" to RangeScalingFactor(0.75, 0.75, 0.5), "Tetris DS" to RangeScalingFactor(3.0, 1.5, 1.0), "Tetris Effect" to RangeScalingFactor(3.0, 1.7, 1.2), - "Rosy Retrospection DX" to RangeScalingFactor(1.1, 0.67, 0.5), - "Modretro Tetris" to RangeScalingFactor(1.2, 0.9, 0.7), - "Tetris Mobile" to RangeScalingFactor(1.3, 1.0, 0.8) - ), - "Modretro Tetris" to mapOf( - "NES Tetris" to RangeScalingFactor(0.5, 0.4, 0.33), - "Game Boy Tetris" to RangeScalingFactor(0.67, 0.56, 0.5), - "Tetris DX" to RangeScalingFactor(0.67, 0.56, 0.5), - "Tetris DS" to RangeScalingFactor(2.5, 2.0, 1.67), - "Tetris Effect" to RangeScalingFactor(2.22, 1.82, 1.54), - "Rosy Retrospection DX" to RangeScalingFactor(0.77, 0.67, 0.59), - "Apotris" to RangeScalingFactor(0.83, 1.11, 1.43), - "Tetris Mobile" to RangeScalingFactor(1.1, 1.1, 1.1) - ), - "Tetris Mobile" to mapOf( - "NES Tetris" to RangeScalingFactor(0.45, 0.36, 0.29), - "Game Boy Tetris" to RangeScalingFactor(0.63, 0.53, 0.48), - "Tetris DX" to RangeScalingFactor(0.63, 0.53, 0.48), - "Tetris DS" to RangeScalingFactor(2.22, 1.82, 1.54), - "Tetris Effect" to RangeScalingFactor(2.0, 1.67, 1.43), - "Rosy Retrospection DX" to RangeScalingFactor(0.71, 0.63, 0.56), - "Apotris" to RangeScalingFactor(0.77, 1.0, 1.25), - "Modretro Tetris" to RangeScalingFactor(0.91, 0.91, 0.91) + "Rosy Retrospection DX" to RangeScalingFactor(1.1, 0.67, 0.5) ) ) diff --git a/app/src/main/java/com/accidentalproductions/tetristats/data/ScoreDao.kt b/app/src/main/java/com/accidentalproductions/tetristats/data/ScoreDao.kt index ecc25c0..416a3d6 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/data/ScoreDao.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/data/ScoreDao.kt @@ -12,9 +12,6 @@ interface ScoreDao { @Query("SELECT * FROM scores WHERE gameVersion = :gameVersion") fun getScoresForGame(gameVersion: String): LiveData> - @Query("SELECT * FROM scores WHERE gameVersion = :gameVersion ORDER BY dateRecorded ASC") - fun getScoresForGameByDate(gameVersion: String): LiveData> - @Query("SELECT DISTINCT gameVersion FROM scores") fun getGamesWithScores(): LiveData> diff --git a/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryFragment.kt b/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryFragment.kt index deb4109..8c73b79 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryFragment.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryFragment.kt @@ -56,50 +56,6 @@ class EntryFragment : Fragment() { if (game != null && score != null) { viewModel.refreshEquivalentScores(game, score) - - // Make sure UI updates immediately by forcing an adapter refresh - viewModel.equivalentScores.value?.let { scores -> - equivalentScoreAdapter.submitList(null) // Clear first - equivalentScoreAdapter.submitList(scores) // Then add new list - } - - // Ensure card is visible - updateAnalysisCard() - } - } - } - - /** - * Update the analysis card visibility and contents based on current state - */ - private fun updateAnalysisCard() { - if (viewModel.showConversion.value != true) { - binding.cardAnalysisResults.visibility = View.GONE - return - } - - val game = viewModel.lastSubmittedGame.value - val score = viewModel.lastSubmittedScore.value - - if (game != null && score != null) { - // Get the list of games with scores - val playedGames = viewModel.gamesWithScores.value ?: listOf() - - // Make sure we don't show the source game in the equivalent dropdown - val filteredGames = playedGames.filter { it != game } - if (filteredGames.isNotEmpty()) { - binding.textViewOriginalScore.text = "Your $game score of ${"%,d".format(score)} is equivalent to:" - binding.cardAnalysisResults.visibility = View.VISIBLE - - val filteredAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, filteredGames) - binding.autoCompleteEquivalentGame.setAdapter(filteredAdapter) - - // Select first game by default - binding.autoCompleteEquivalentGame.setText(filteredGames[0], false) - viewModel.setSelectedEquivalentGame(filteredGames[0]) - } else { - // If no other games to convert to, hide the card - binding.cardAnalysisResults.visibility = View.GONE } } } @@ -112,9 +68,7 @@ class EntryFragment : Fragment() { "Tetris DS", "Tetris Effect", "Rosy Retrospection DX", - "Apotris", - "Modretro Tetris", - "Tetris Mobile" + "Apotris" ) val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, games) binding.autoCompleteGameVersion.setAdapter(adapter) @@ -136,10 +90,8 @@ class EntryFragment : Fragment() { viewModel.showConversion.observe(viewLifecycleOwner) { shouldShow -> // No need to show toast here - we'll do it only after score submission if (shouldShow) { - // Update card when showConversion changes - updateAnalysisCard() - } else { - binding.cardAnalysisResults.visibility = View.GONE + // Refresh conversions whenever showConversion becomes true + refreshConversions() } } @@ -147,8 +99,11 @@ class EntryFragment : Fragment() { viewModel.gamesWithScores.observe(viewLifecycleOwner) { games -> // Setup the game dropdown for adding equivalents - only with played games if (games.isNotEmpty()) { - // Update card when games list changes - updateAnalysisCard() + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, games) + binding.autoCompleteEquivalentGame.setAdapter(adapter) + + // Also refresh conversions when game list changes + refreshConversions() } } @@ -171,22 +126,39 @@ class EntryFragment : Fragment() { } // Observe last submitted score details - viewModel.lastSubmittedGame.observe(viewLifecycleOwner) { _ -> - // Update the analysis card when last submitted game changes - updateAnalysisCard() - } - - // Observe last submitted score value - viewModel.lastSubmittedScore.observe(viewLifecycleOwner) { _ -> - // Update the analysis card when score changes - updateAnalysisCard() + viewModel.lastSubmittedGame.observe(viewLifecycleOwner) { game -> + // Only continue if showConversion is true + if (viewModel.showConversion.value != true) { + binding.cardAnalysisResults.visibility = View.GONE + return@observe + } + + viewModel.lastSubmittedScore.value?.let { score -> + binding.textViewOriginalScore.text = "Your $game score of ${"%,d".format(score)} is equivalent to:" + + // Get the list of games with scores + val playedGames = viewModel.gamesWithScores.value ?: listOf() + + // Make sure we don't show the source game in the equivalent dropdown + val filteredGames = playedGames.filter { it != game } + if (filteredGames.isNotEmpty()) { + binding.cardAnalysisResults.visibility = View.VISIBLE + val filteredAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, filteredGames) + binding.autoCompleteEquivalentGame.setAdapter(filteredAdapter) + + // Select first game by default + binding.autoCompleteEquivalentGame.setText(filteredGames[0], false) + viewModel.setSelectedEquivalentGame(filteredGames[0]) + } else { + // If no other games to convert to, hide the card + binding.cardAnalysisResults.visibility = View.GONE + } + } } // Observe equivalent scores viewModel.equivalentScores.observe(viewLifecycleOwner) { scores -> if (scores.isNotEmpty()) { - // Force a clean update by clearing first - equivalentScoreAdapter.submitList(null) equivalentScoreAdapter.submitList(scores) } else if (viewModel.showConversion.value == true) { // If we should be showing conversions but have no scores, probably no other games @@ -213,7 +185,7 @@ class EntryFragment : Fragment() { ) clearInputs() - // Force immediate refresh of conversions + // Check after submission if we should show requirements toast if (viewModel.showConversion.value == false) { Toast.makeText( context, @@ -221,9 +193,7 @@ class EntryFragment : Fragment() { Toast.LENGTH_LONG ).show() } else { - refreshConversions() - - // Scroll down to show the analysis results + // Only scroll down if we're going to show conversions binding.root.post { binding.root.fullScroll(View.FOCUS_DOWN) } diff --git a/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryViewModel.kt b/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryViewModel.kt index 62af537..b4178de 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryViewModel.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/ui/entry/EntryViewModel.kt @@ -23,9 +23,7 @@ class EntryViewModel(application: Application) : AndroidViewModel(application) { "Tetris DS", "Tetris Effect", "Rosy Retrospection DX", - "Apotris", - "Modretro Tetris", - "Tetris Mobile" + "Apotris" ) // Track user played games and score counts @@ -110,14 +108,13 @@ class EntryViewModel(application: Application) : AndroidViewModel(application) { scoreDao.insert(newScore) // After inserting, update the last submitted values - _lastSubmittedGame.value = gameVersion // Use immediate value change instead of postValue - _lastSubmittedScore.value = score // Use immediate value change instead of postValue + _lastSubmittedGame.postValue(gameVersion) + _lastSubmittedScore.postValue(score) - // Immediately check conversion criteria with current values - checkConversionCriteria() + // The criteria check will happen automatically through the observers in init - // Immediate refresh regardless if we just reached the criteria threshold - if (totalScoreCount.value ?: 0 >= 3 && (gamesWithScores.value?.size ?: 0) >= 2) { + // Only generate equivalent scores if we meet the criteria + if (_showConversion.value == true) { generateEquivalentScores(gameVersion, score) } } @@ -149,8 +146,7 @@ class EntryViewModel(application: Application) : AndroidViewModel(application) { } } - // Use setValue for immediate update on main thread rather than postValue - _equivalentScores.value = equivalents + _equivalentScores.postValue(equivalents) } /** diff --git a/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsFragment.kt b/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsFragment.kt index 94a7097..520981c 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsFragment.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsFragment.kt @@ -1,6 +1,5 @@ package com.accidentalproductions.tetristats.ui.stats -import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,16 +8,7 @@ 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 @@ -40,7 +30,6 @@ class StatsFragment : Fragment() { setupRecyclerView() setupGameFilter() - setupProgressChart() observeStats() } @@ -63,64 +52,6 @@ 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, dates: List) { - 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 -> @@ -134,24 +65,6 @@ 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() - val dates = mutableListOf() - - 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() { diff --git a/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsViewModel.kt b/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsViewModel.kt index c656adb..dbd3bd7 100644 --- a/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsViewModel.kt +++ b/app/src/main/java/com/accidentalproductions/tetristats/ui/stats/StatsViewModel.kt @@ -18,10 +18,6 @@ class StatsViewModel(application: Application) : AndroidViewModel(application) { scoreDao.getScoresForGame(game) } - val scoresByDate: LiveData> = _selectedGame.switchMap { game -> - scoreDao.getScoresForGameByDate(game) - } - val averageScore: LiveData = _selectedGame.switchMap { game -> scoreDao.getAverageScore(game) } diff --git a/app/src/main/res/layout/fragment_stats.xml b/app/src/main/res/layout/fragment_stats.xml index 2cf4a0b..426a01d 100644 --- a/app/src/main/res/layout/fragment_stats.xml +++ b/app/src/main/res/layout/fragment_stats.xml @@ -31,33 +31,6 @@ android:inputType="none"/> - - - - - - - - - -