Enhanced theme system with immediate UI updates, improved progression speed, and added visual/haptic feedback for theme selection

This commit is contained in:
cmclark00 2025-03-28 09:11:59 -04:00
parent ae5ad1ed03
commit 809ae33e5e
8 changed files with 316 additions and 46 deletions

View file

@ -5,39 +5,46 @@ import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.mintris.databinding.HighScoreEntryBinding
import com.mintris.model.HighScore
import com.mintris.model.HighScoreManager
import com.mintris.model.PlayerProgressionManager
import android.graphics.Color
class HighScoreEntryActivity : AppCompatActivity() {
private lateinit var binding: HighScoreEntryBinding
private lateinit var highScoreManager: HighScoreManager
private lateinit var nameInput: EditText
private lateinit var scoreText: TextView
private lateinit var saveButton: Button
private lateinit var progressionManager: PlayerProgressionManager
private var currentTheme = PlayerProgressionManager.THEME_CLASSIC
private var score: Int = 0
// Track if we already saved to prevent double-saving
private var hasSaved = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.high_score_entry)
binding = HighScoreEntryBinding.inflate(layoutInflater)
setContentView(binding.root)
highScoreManager = HighScoreManager(this)
nameInput = findViewById(R.id.nameInput)
scoreText = findViewById(R.id.scoreText)
saveButton = findViewById(R.id.saveButton)
progressionManager = PlayerProgressionManager(this)
val score = intent.getIntExtra("score", 0)
val level = intent.getIntExtra("level", 1)
scoreText.text = getString(R.string.score) + ": $score"
// Load and apply theme
currentTheme = loadThemePreference()
applyTheme(currentTheme)
saveButton.setOnClickListener {
score = intent.getIntExtra("score", 0)
binding.scoreText.text = "Score: $score"
binding.saveButton.setOnClickListener {
// Only allow saving once
if (!hasSaved) {
val name = nameInput.text.toString().trim()
val name = binding.nameInput.text.toString().trim()
if (name.isNotEmpty()) {
hasSaved = true
val highScore = HighScore(name, score, level)
val highScore = HighScore(name, score, 1)
highScoreManager.addHighScore(highScore)
// Set result and finish
@ -50,10 +57,49 @@ class HighScoreEntryActivity : AppCompatActivity() {
// Prevent accidental back button press from causing issues
override fun onBackPressed() {
super.onBackPressed()
// If they haven't saved yet, consider it a cancel
if (!hasSaved) {
setResult(Activity.RESULT_CANCELED)
}
finish()
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
}
private fun applyTheme(themeId: String) {
// Set background color
val backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
binding.root.setBackgroundColor(backgroundColor)
// Set text color
val textColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.WHITE
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#FF00FF")
PlayerProgressionManager.THEME_MONOCHROME -> Color.LTGRAY
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#FF5A5F")
PlayerProgressionManager.THEME_MINIMALIST -> Color.BLACK
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
// Apply text color to score and input
binding.scoreText.setTextColor(textColor)
binding.nameInput.setTextColor(textColor)
binding.nameInput.setHintTextColor(Color.argb(128, Color.red(textColor), Color.green(textColor), Color.blue(textColor)))
// Apply theme to submit button
binding.saveButton.setTextColor(textColor)
}
}

View file

@ -2,44 +2,116 @@ package com.mintris
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mintris.databinding.HighScoresBinding
import com.mintris.model.HighScoreAdapter
import com.mintris.model.HighScoreManager
import com.mintris.model.PlayerProgressionManager
import android.graphics.Color
import android.util.Log
class HighScoresActivity : AppCompatActivity() {
private lateinit var binding: HighScoresBinding
private lateinit var highScoreManager: HighScoreManager
private lateinit var highScoreAdapter: HighScoreAdapter
private lateinit var highScoresList: RecyclerView
private lateinit var backButton: Button
private lateinit var progressionManager: PlayerProgressionManager
private var currentTheme = PlayerProgressionManager.THEME_CLASSIC
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.high_scores)
try {
binding = HighScoresBinding.inflate(layoutInflater)
setContentView(binding.root)
highScoreManager = HighScoreManager(this)
highScoresList = findViewById(R.id.highScoresList)
backButton = findViewById(R.id.backButton)
progressionManager = PlayerProgressionManager(this)
// Load and apply theme
currentTheme = loadThemePreference()
// Initialize adapter before applying theme
highScoreAdapter = HighScoreAdapter()
highScoresList.layoutManager = LinearLayoutManager(this)
highScoresList.adapter = highScoreAdapter
// Set up RecyclerView
binding.highScoresList.layoutManager = LinearLayoutManager(this)
binding.highScoresList.adapter = highScoreAdapter
// Now apply theme to UI
applyTheme(currentTheme)
// Load high scores
updateHighScores()
backButton.setOnClickListener {
// Set up back button
binding.backButton.setOnClickListener {
finish()
}
} catch (e: Exception) {
Log.e("HighScoresActivity", "Error in onCreate", e)
// Show an error message if necessary, or finish gracefully
finish()
}
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
}
private fun applyTheme(themeId: String) {
try {
// Set background color
val backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
binding.root.setBackgroundColor(backgroundColor)
// Set text color
val textColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.WHITE
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#FF00FF")
PlayerProgressionManager.THEME_MONOCHROME -> Color.LTGRAY
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#FF5A5F")
PlayerProgressionManager.THEME_MINIMALIST -> Color.BLACK
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
// Apply theme to back button
binding.backButton.setTextColor(textColor)
// Update adapter theme
highScoreAdapter.applyTheme(themeId)
} catch (e: Exception) {
Log.e("HighScoresActivity", "Error applying theme: $themeId", e)
}
}
private fun updateHighScores() {
try {
val scores = highScoreManager.getHighScores()
highScoreAdapter.updateHighScores(scores)
} catch (e: Exception) {
Log.e("HighScoresActivity", "Error updating high scores", e)
}
}
override fun onResume() {
super.onResume()
try {
updateHighScores()
} catch (e: Exception) {
Log.e("HighScoresActivity", "Error in onResume", e)
}
}
}

View file

@ -102,7 +102,16 @@ class MainActivity : AppCompatActivity() {
// Set up theme selector
val themeSelector = binding.themeSelector
themeSelector.onThemeSelected = { themeId ->
// Apply the new theme
applyTheme(themeId)
// Provide haptic feedback as a cue that the theme changed
gameHaptics.vibrateForPieceLock()
// Refresh the pause menu to immediately show theme changes
if (binding.pauseContainer.visibility == View.VISIBLE) {
showPauseMenu()
}
}
// Set up title screen
@ -381,6 +390,38 @@ class MainActivity : AppCompatActivity() {
binding.pauseLevelBadge.setLevel(progressionManager.getPlayerLevel())
binding.pauseLevelBadge.setThemeColor(getThemeColor(currentTheme))
// Get theme color
val textColor = getThemeColor(currentTheme)
// Apply theme color to pause container background
val backgroundColor = when (currentTheme) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
binding.pauseContainer.setBackgroundColor(backgroundColor)
// Apply theme colors to buttons
binding.pauseStartButton.setTextColor(textColor)
binding.pauseRestartButton.setTextColor(textColor)
binding.resumeButton.setTextColor(textColor)
binding.highScoresButton.setTextColor(textColor)
binding.statsButton.setTextColor(textColor)
binding.pauseLevelText.setTextColor(textColor)
binding.pauseLevelUpButton.setTextColor(textColor)
binding.pauseLevelDownButton.setTextColor(textColor)
binding.settingsButton.setTextColor(textColor)
binding.musicToggle.setColorFilter(textColor)
// Apply theme colors to text elements
binding.settingsTitle.setTextColor(textColor)
binding.selectLevelText.setTextColor(textColor)
binding.musicText.setTextColor(textColor)
// Update theme selector
updateThemeSelector()
}
@ -567,6 +608,10 @@ class MainActivity : AppCompatActivity() {
progressionScreen.applyTheme(themeId)
}
// Apply theme color to the stats button
val textColor = getThemeColor(currentTheme)
binding.statsButton.setTextColor(textColor)
// Update the game view to apply theme
gameView.invalidate()
}

View file

@ -7,12 +7,16 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.mintris.databinding.ActivityStatsBinding
import com.mintris.model.StatsManager
import com.mintris.model.PlayerProgressionManager
import android.graphics.Color
import java.text.SimpleDateFormat
import java.util.*
class StatsActivity : AppCompatActivity() {
private lateinit var binding: ActivityStatsBinding
private lateinit var statsManager: StatsManager
private lateinit var progressionManager: PlayerProgressionManager
private var currentTheme = PlayerProgressionManager.THEME_CLASSIC
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -20,6 +24,11 @@ class StatsActivity : AppCompatActivity() {
setContentView(binding.root)
statsManager = StatsManager(this)
progressionManager = PlayerProgressionManager(this)
// Load and apply theme
currentTheme = loadThemePreference()
applyTheme(currentTheme)
// Set up back button
binding.backButton.setOnClickListener {
@ -34,6 +43,54 @@ class StatsActivity : AppCompatActivity() {
updateStats()
}
private fun loadThemePreference(): String {
val prefs = getSharedPreferences("mintris_settings", MODE_PRIVATE)
return prefs.getString("selected_theme", PlayerProgressionManager.THEME_CLASSIC) ?: PlayerProgressionManager.THEME_CLASSIC
}
private fun applyTheme(themeId: String) {
// Set background color
val backgroundColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.BLACK
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#0D0221")
PlayerProgressionManager.THEME_MONOCHROME -> Color.parseColor("#1A1A1A")
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#3F2832")
PlayerProgressionManager.THEME_MINIMALIST -> Color.WHITE
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#0B0C10")
else -> Color.BLACK
}
binding.root.setBackgroundColor(backgroundColor)
// Set text color
val textColor = when (themeId) {
PlayerProgressionManager.THEME_CLASSIC -> Color.WHITE
PlayerProgressionManager.THEME_NEON -> Color.parseColor("#FF00FF")
PlayerProgressionManager.THEME_MONOCHROME -> Color.LTGRAY
PlayerProgressionManager.THEME_RETRO -> Color.parseColor("#FF5A5F")
PlayerProgressionManager.THEME_MINIMALIST -> Color.BLACK
PlayerProgressionManager.THEME_GALAXY -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
// Apply text color to all TextViews
binding.totalGamesText.setTextColor(textColor)
binding.totalScoreText.setTextColor(textColor)
binding.totalLinesText.setTextColor(textColor)
binding.totalPiecesText.setTextColor(textColor)
binding.totalTimeText.setTextColor(textColor)
binding.totalSinglesText.setTextColor(textColor)
binding.totalDoublesText.setTextColor(textColor)
binding.totalTriplesText.setTextColor(textColor)
binding.totalTetrisesText.setTextColor(textColor)
binding.maxLevelText.setTextColor(textColor)
binding.maxScoreText.setTextColor(textColor)
binding.maxLinesText.setTextColor(textColor)
// Apply theme to buttons
binding.backButton.setTextColor(textColor)
binding.resetStatsButton.setTextColor(textColor)
}
private fun showResetConfirmationDialog() {
AlertDialog.Builder(this)
.setTitle("Reset Stats")

View file

@ -1,5 +1,6 @@
package com.mintris.model
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -9,6 +10,8 @@ import com.mintris.R
class HighScoreAdapter : RecyclerView.Adapter<HighScoreAdapter.HighScoreViewHolder>() {
private var highScores: List<HighScore> = emptyList()
private var currentTheme = "theme_classic" // Default theme
private var textColor = Color.WHITE // Default text color
fun updateHighScores(newHighScores: List<HighScore>) {
highScores = newHighScores
@ -24,14 +27,19 @@ class HighScoreAdapter : RecyclerView.Adapter<HighScoreAdapter.HighScoreViewHold
override fun onBindViewHolder(holder: HighScoreViewHolder, position: Int) {
val highScore = highScores[position]
holder.bind(highScore, position + 1)
// Apply current text color to elements
holder.rankText.setTextColor(textColor)
holder.nameText.setTextColor(textColor)
holder.scoreText.setTextColor(textColor)
}
override fun getItemCount(): Int = highScores.size
class HighScoreViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val rankText: TextView = itemView.findViewById(R.id.rankText)
private val nameText: TextView = itemView.findViewById(R.id.nameText)
private val scoreText: TextView = itemView.findViewById(R.id.scoreText)
val rankText: TextView = itemView.findViewById(R.id.rankText)
val nameText: TextView = itemView.findViewById(R.id.nameText)
val scoreText: TextView = itemView.findViewById(R.id.scoreText)
fun bind(highScore: HighScore, rank: Int) {
rankText.text = "#$rank"
@ -39,4 +47,21 @@ class HighScoreAdapter : RecyclerView.Adapter<HighScoreAdapter.HighScoreViewHold
scoreText.text = highScore.score.toString()
}
}
fun applyTheme(themeId: String) {
currentTheme = themeId
// Update text color based on theme
textColor = when (themeId) {
"theme_classic" -> Color.WHITE
"theme_neon" -> Color.parseColor("#FF00FF")
"theme_monochrome" -> Color.LTGRAY
"theme_retro" -> Color.parseColor("#FF5A5F")
"theme_minimalist" -> Color.BLACK
"theme_galaxy" -> Color.parseColor("#66FCF1")
else -> Color.WHITE
}
notifyDataSetChanged()
}
}

View file

@ -286,15 +286,15 @@ class PlayerProgressionManager(context: Context) {
private const val KEY_UNLOCKED_BADGES = "unlocked_badges"
// XP curve parameters
private const val BASE_XP = 5000.0 // Base XP for level 1 (increased from 2500)
private const val XP_CURVE_FACTOR = 2.2 // Exponential factor for XP curve (increased from 2.0)
private const val BASE_XP = 4000.0 // Base XP for level 1 (reduced from 5000)
private const val XP_CURVE_FACTOR = 1.9 // Exponential factor for XP curve (reduced from 2.2)
// XP calculation constants
private const val LEVEL_MULTIPLIER = 0.1 // 10% bonus per level
private const val XP_PER_LINE = 10L
private const val TETRIS_XP_BONUS = 50L
private const val PERFECT_CLEAR_XP_BONUS = 200L
private const val TIME_XP_PER_MINUTE = 5L
private const val LEVEL_MULTIPLIER = 0.15 // 15% bonus per level (increased from 10%)
private const val XP_PER_LINE = 15L // Increased from 10
private const val TETRIS_XP_BONUS = 75L // Increased from 50
private const val PERFECT_CLEAR_XP_BONUS = 250L // Increased from 200
private const val TIME_XP_PER_MINUTE = 8L // Increased from 5
// Theme IDs with required levels
const val THEME_CLASSIC = "theme_classic"

View file

@ -86,16 +86,18 @@ class ThemeSelector @JvmOverloads constructor(
// Set card background color based on theme
setCardBackgroundColor(themeInfo.primaryColor)
// Add stroke for selected theme
// Add more noticeable visual indicator for selected theme
if (isSelected) {
setContentPadding(4, 4, 4, 4)
// Create a gradient drawable for the border
val gradientDrawable = android.graphics.drawable.GradientDrawable().apply {
setColor(themeInfo.primaryColor)
setStroke(4, Color.WHITE)
setStroke(6, Color.WHITE) // Thicker border
cornerRadius = 12f
}
background = gradientDrawable
// Add glow effect via elevation
cardElevation = 12f
}
// Set card dimensions
@ -194,9 +196,29 @@ class ThemeSelector @JvmOverloads constructor(
card.setOnClickListener {
// Only trigger callback if this isn't already the selected theme
if (themeId != selectedTheme) {
// Update visual state
themeCards[selectedTheme]?.cardElevation = 2f
card.cardElevation = 8f
// Update previously selected card
themeCards[selectedTheme]?.let { prevCard ->
prevCard.cardElevation = 2f
// Reset any special styling
prevCard.background = null
prevCard.setCardBackgroundColor(getThemes()[selectedTheme]?.primaryColor ?: Color.BLACK)
}
// Update visual state of newly selected card
card.cardElevation = 12f
// Flash animation for selection feedback
val flashColor = Color.WHITE
val originalColor = themeInfo.primaryColor
// Create animator for flash effect
val flashAnimator = android.animation.ValueAnimator.ofArgb(flashColor, originalColor)
flashAnimator.duration = 300 // 300ms
flashAnimator.addUpdateListener { animator ->
val color = animator.animatedValue as Int
card.setCardBackgroundColor(color)
}
flashAnimator.start()
// Update selected theme
selectedTheme = themeId

View file

@ -263,6 +263,7 @@
android:layout_marginBottom="32dp">
<TextView
android:id="@+id/settingsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings"
@ -347,6 +348,7 @@
android:gravity="center">
<TextView
android:id="@+id/selectLevelText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_level"
@ -416,6 +418,7 @@
android:layout_marginTop="16dp">
<TextView
android:id="@+id/musicText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/music"