mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 10:25:19 +01:00
Fix: Correct namespace and applicationId in app build.gradle
This commit is contained in:
parent
5cf8aec02a
commit
5ace9d7fc5
25 changed files with 4 additions and 4 deletions
415
app/src/main/java/com/pixelmintdrop/ui/ThemeSelector.kt
Normal file
415
app/src/main/java/com/pixelmintdrop/ui/ThemeSelector.kt
Normal file
|
@ -0,0 +1,415 @@
|
|||
package com.mintris.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.GridLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.mintris.R
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* UI component for selecting game themes
|
||||
*/
|
||||
class ThemeSelector @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val themesGrid: GridLayout
|
||||
private val availableThemesLabel: TextView
|
||||
|
||||
// Callback when a theme is selected
|
||||
var onThemeSelected: ((String) -> Unit)? = null
|
||||
|
||||
// Currently selected theme (persisted)
|
||||
private var selectedTheme: String = PlayerProgressionManager.THEME_CLASSIC
|
||||
|
||||
// Theme cards map (themeId -> CardView)
|
||||
private val themeCards = mutableMapOf<String, CardView>()
|
||||
// Ordered list of theme IDs for navigation
|
||||
private val themeIdList = mutableListOf<String>()
|
||||
// Currently focused theme ID (for gamepad navigation within the selector)
|
||||
private var focusedThemeId: String? = null
|
||||
// Index of the currently focused theme in themeIdList
|
||||
private var focusedIndex: Int = -1
|
||||
// Flag indicating if the entire selector component has focus from the main menu
|
||||
private var hasComponentFocus: Boolean = false
|
||||
|
||||
init {
|
||||
// Inflate the layout
|
||||
LayoutInflater.from(context).inflate(R.layout.theme_selector, this, true)
|
||||
|
||||
// Get references to views
|
||||
themesGrid = findViewById(R.id.themes_grid)
|
||||
availableThemesLabel = findViewById(R.id.available_themes_label)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the theme selector with unlocked themes
|
||||
*/
|
||||
fun updateThemes(unlockedThemes: Set<String>, currentTheme: String) {
|
||||
// Clear existing theme cards and ID list
|
||||
themesGrid.removeAllViews()
|
||||
themeCards.clear()
|
||||
themeIdList.clear()
|
||||
|
||||
// Update selected theme
|
||||
selectedTheme = currentTheme
|
||||
focusedThemeId = currentTheme // Initially focus the selected theme
|
||||
focusedIndex = -1 // Reset index
|
||||
|
||||
// Get all possible themes and their details, sorted for consistent order
|
||||
val allThemes = getThemes().entries.sortedWith(compareBy({ it.value.unlockLevel }, { it.value.displayName })).associate { it.key to it.value }
|
||||
|
||||
// Add theme cards to the grid and build the ID list
|
||||
allThemes.forEach { (themeId, themeInfo) ->
|
||||
val isUnlocked = unlockedThemes.contains(themeId)
|
||||
val isSelected = themeId == selectedTheme
|
||||
|
||||
// Only add unlocked themes to the navigable list
|
||||
if (isUnlocked) {
|
||||
themeIdList.add(themeId)
|
||||
}
|
||||
|
||||
val themeCard = createThemeCard(themeId, themeInfo, isUnlocked, isSelected)
|
||||
themeCards[themeId] = themeCard
|
||||
themesGrid.addView(themeCard)
|
||||
|
||||
// Update focused index if this is the currently selected/focused theme
|
||||
if (isUnlocked && themeId == focusedThemeId) {
|
||||
focusedIndex = themeIdList.indexOf(themeId)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure focus index is valid if the previously focused theme is no longer available/unlocked
|
||||
if (focusedIndex == -1 && themeIdList.isNotEmpty()) {
|
||||
focusedIndex = 0
|
||||
focusedThemeId = themeIdList[0]
|
||||
}
|
||||
|
||||
// Apply initial focus highlight if the component has focus
|
||||
highlightFocusedCard()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a card for a theme
|
||||
*/
|
||||
private fun createThemeCard(
|
||||
themeId: String,
|
||||
themeInfo: ThemeInfo,
|
||||
isUnlocked: Boolean,
|
||||
isSelected: Boolean
|
||||
): CardView {
|
||||
// Create the card
|
||||
val card = CardView(context).apply {
|
||||
id = View.generateViewId()
|
||||
radius = 12f
|
||||
cardElevation = if (isSelected) 8f else 2f
|
||||
useCompatPadding = true
|
||||
|
||||
// Set card background color based on theme
|
||||
setCardBackgroundColor(themeInfo.primaryColor)
|
||||
|
||||
// Set card dimensions
|
||||
val cardSize = resources.getDimensionPixelSize(R.dimen.theme_card_size)
|
||||
layoutParams = GridLayout.LayoutParams().apply {
|
||||
width = cardSize
|
||||
height = cardSize
|
||||
columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
|
||||
rowSpec = GridLayout.spec(GridLayout.UNDEFINED, 1f)
|
||||
setMargins(8, 8, 8, 8)
|
||||
}
|
||||
|
||||
// Apply locked/selected state visuals
|
||||
alpha = if (isUnlocked) 1.0f else 0.5f
|
||||
}
|
||||
|
||||
// Create theme content container
|
||||
val container = FrameLayout(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
|
||||
// Create the theme preview
|
||||
val themePreview = View(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
// Set the background color
|
||||
setBackgroundColor(themeInfo.primaryColor)
|
||||
}
|
||||
|
||||
// Add a label with the theme name
|
||||
val themeLabel = TextView(context).apply {
|
||||
text = themeInfo.displayName
|
||||
setTextColor(themeInfo.textColor)
|
||||
textSize = 14f
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
|
||||
// Position at the bottom of the card
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
gravity = android.view.Gravity.BOTTOM or android.view.Gravity.CENTER_HORIZONTAL
|
||||
setMargins(4, 4, 4, 8)
|
||||
}
|
||||
}
|
||||
|
||||
// Add level requirement for locked themes
|
||||
val levelRequirement = TextView(context).apply {
|
||||
text = "Level ${themeInfo.unlockLevel}"
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 12f
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
visibility = if (isUnlocked) View.GONE else View.VISIBLE
|
||||
|
||||
// Position at the center of the card
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
gravity = android.view.Gravity.CENTER
|
||||
}
|
||||
// Make text bold and more visible for better readability
|
||||
typeface = android.graphics.Typeface.DEFAULT_BOLD
|
||||
setShadowLayer(3f, 1f, 1f, Color.BLACK)
|
||||
}
|
||||
|
||||
// Add a lock icon if the theme is locked
|
||||
val lockOverlay = View(context).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
// Add lock icon or visual indicator
|
||||
setBackgroundResource(R.drawable.lock_overlay)
|
||||
visibility = if (isUnlocked) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
// Add all elements to container
|
||||
container.addView(themePreview)
|
||||
container.addView(themeLabel)
|
||||
container.addView(lockOverlay)
|
||||
container.addView(levelRequirement)
|
||||
|
||||
// Add container to card
|
||||
card.addView(container)
|
||||
|
||||
// Set up click listener only for unlocked themes
|
||||
if (isUnlocked) {
|
||||
card.setOnClickListener {
|
||||
// Clicking directly selects the theme
|
||||
Log.d("ThemeSelector", "Theme card clicked: $themeId (isUnlocked=$isUnlocked)")
|
||||
focusedThemeId = themeId
|
||||
focusedIndex = themeIdList.indexOf(themeId)
|
||||
confirmSelection() // Directly confirm click selection
|
||||
}
|
||||
}
|
||||
|
||||
return card
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class for theme information
|
||||
*/
|
||||
data class ThemeInfo(
|
||||
val displayName: String,
|
||||
val primaryColor: Int,
|
||||
val secondaryColor: Int,
|
||||
val textColor: Int,
|
||||
val unlockLevel: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Get all available themes with their details
|
||||
*/
|
||||
private fun getThemes(): Map<String, ThemeInfo> {
|
||||
return mapOf(
|
||||
PlayerProgressionManager.THEME_CLASSIC to ThemeInfo(
|
||||
displayName = "Classic",
|
||||
primaryColor = Color.parseColor("#000000"),
|
||||
secondaryColor = Color.parseColor("#1F1F1F"),
|
||||
textColor = Color.WHITE,
|
||||
unlockLevel = 1
|
||||
),
|
||||
PlayerProgressionManager.THEME_NEON to ThemeInfo(
|
||||
displayName = "Neon",
|
||||
primaryColor = Color.parseColor("#0D0221"),
|
||||
secondaryColor = Color.parseColor("#650D89"),
|
||||
textColor = Color.parseColor("#FF00FF"),
|
||||
unlockLevel = 5
|
||||
),
|
||||
PlayerProgressionManager.THEME_MONOCHROME to ThemeInfo(
|
||||
displayName = "Monochrome",
|
||||
primaryColor = Color.parseColor("#1A1A1A"),
|
||||
secondaryColor = Color.parseColor("#333333"),
|
||||
textColor = Color.LTGRAY,
|
||||
unlockLevel = 10
|
||||
),
|
||||
PlayerProgressionManager.THEME_RETRO to ThemeInfo(
|
||||
displayName = "Retro",
|
||||
primaryColor = Color.parseColor("#3F2832"),
|
||||
secondaryColor = Color.parseColor("#087E8B"),
|
||||
textColor = Color.parseColor("#FF5A5F"),
|
||||
unlockLevel = 15
|
||||
),
|
||||
PlayerProgressionManager.THEME_MINIMALIST to ThemeInfo(
|
||||
displayName = "Minimalist",
|
||||
primaryColor = Color.parseColor("#FFFFFF"),
|
||||
secondaryColor = Color.parseColor("#F0F0F0"),
|
||||
textColor = Color.BLACK,
|
||||
unlockLevel = 20
|
||||
),
|
||||
PlayerProgressionManager.THEME_GALAXY to ThemeInfo(
|
||||
displayName = "Galaxy",
|
||||
primaryColor = Color.parseColor("#0B0C10"),
|
||||
secondaryColor = Color.parseColor("#1F2833"),
|
||||
textColor = Color.parseColor("#66FCF1"),
|
||||
unlockLevel = 25
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the entire component has focus (from the parent menu).
|
||||
* Controls the outer border visibility.
|
||||
*/
|
||||
fun setHasFocus(hasFocus: Boolean) {
|
||||
hasComponentFocus = hasFocus
|
||||
// Update visual state based on component focus
|
||||
highlightFocusedCard() // Re-apply highlights
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal focus to the next available theme.
|
||||
*/
|
||||
fun focusNextItem() {
|
||||
if (!hasComponentFocus || themeIdList.isEmpty()) return // Only navigate if component has focus
|
||||
|
||||
focusedIndex = (focusedIndex + 1) % themeIdList.size
|
||||
focusedThemeId = themeIdList[focusedIndex]
|
||||
highlightFocusedCard()
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal focus to the previous available theme.
|
||||
*/
|
||||
fun focusPreviousItem() {
|
||||
if (!hasComponentFocus || themeIdList.isEmpty()) return // Only navigate if component has focus
|
||||
|
||||
focusedIndex = (focusedIndex - 1 + themeIdList.size) % themeIdList.size
|
||||
focusedThemeId = themeIdList[focusedIndex]
|
||||
highlightFocusedCard()
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the currently focused theme as the selected theme.
|
||||
* Triggers the onThemeSelected callback.
|
||||
*/
|
||||
fun confirmSelection() {
|
||||
Log.d("ThemeSelector", "confirmSelection called. Focused theme: $focusedThemeId")
|
||||
if (focusedThemeId == null || focusedThemeId == selectedTheme) {
|
||||
// No change needed if nothing is focused,
|
||||
// or the focused item is already selected
|
||||
return
|
||||
}
|
||||
|
||||
// Update the selected theme
|
||||
selectedTheme = focusedThemeId!!
|
||||
|
||||
// Update visual states for all cards
|
||||
highlightFocusedCard() // This will now mark the new theme as selected
|
||||
|
||||
// Trigger the callback
|
||||
onThemeSelected?.invoke(selectedTheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visual highlight state of the theme cards based on
|
||||
* selection and internal focus.
|
||||
*/
|
||||
private fun highlightFocusedCard() {
|
||||
if (themeCards.isEmpty()) return
|
||||
|
||||
val focusColor = Color.YELLOW // Color for the focused-but-not-selected item
|
||||
val selectedColor = Color.WHITE // Color for the selected item (might be focused or not)
|
||||
|
||||
themeCards.forEach { (themeId, card) ->
|
||||
val themeInfo = getThemes()[themeId] ?: return@forEach
|
||||
val isUnlocked = themeIdList.contains(themeId) // Check if it's in the navigable list
|
||||
|
||||
if (!isUnlocked) {
|
||||
// Keep locked themes visually distinct
|
||||
card.alpha = 0.5f
|
||||
card.cardElevation = 2f
|
||||
card.background = null // Remove any border/background
|
||||
card.setCardBackgroundColor(themeInfo.primaryColor)
|
||||
return@forEach // Skip further styling for locked themes
|
||||
}
|
||||
|
||||
// Reset unlocked cards first
|
||||
card.alpha = 1.0f
|
||||
card.cardElevation = 4f // Default elevation for unlocked cards
|
||||
card.background = null
|
||||
card.setCardBackgroundColor(themeInfo.primaryColor)
|
||||
card.scaleX = 1.0f
|
||||
card.scaleY = 1.0f
|
||||
|
||||
val isSelected = (themeId == selectedTheme)
|
||||
val isFocused = (hasComponentFocus && themeId == focusedThemeId)
|
||||
|
||||
var borderColor = Color.TRANSPARENT
|
||||
var borderWidth = 0
|
||||
var elevation = 4f
|
||||
var scale = 1.0f
|
||||
|
||||
if (isSelected) {
|
||||
borderColor = selectedColor
|
||||
borderWidth = 6 // Thick border for selected
|
||||
elevation = 12f // Higher elevation for selected
|
||||
}
|
||||
|
||||
if (isFocused) {
|
||||
// Focused item gets a distinct border (unless it's also selected)
|
||||
if (!isSelected) {
|
||||
borderColor = focusColor
|
||||
borderWidth = 4 // Slightly thinner border for focused
|
||||
}
|
||||
elevation = 12f // Use high elevation for focus too
|
||||
scale = 1.1f // Scale up the focused item
|
||||
}
|
||||
|
||||
// Apply scale
|
||||
card.scaleX = scale
|
||||
card.scaleY = scale
|
||||
|
||||
// Apply border and elevation
|
||||
if (borderWidth > 0) {
|
||||
val gradientDrawable = GradientDrawable().apply {
|
||||
setColor(themeInfo.primaryColor)
|
||||
setStroke(borderWidth, borderColor)
|
||||
cornerRadius = 12f // Keep consistent corner radius
|
||||
}
|
||||
card.background = gradientDrawable
|
||||
} else {
|
||||
card.background = null // Ensure no border if not selected/focused
|
||||
}
|
||||
card.cardElevation = elevation
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue