mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 05:15:20 +01:00
Add player progression system with XP, themes, and fix high score entry loop issue
This commit is contained in:
parent
3b2f25c61f
commit
4166fe2b7a
13 changed files with 1254 additions and 112 deletions
313
app/src/main/java/com/mintris/model/PlayerProgressionManager.kt
Normal file
313
app/src/main/java/com/mintris/model/PlayerProgressionManager.kt
Normal file
|
@ -0,0 +1,313 @@
|
|||
package com.mintris.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.mintris.R
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Manages player progression, experience points, and unlockable rewards
|
||||
*/
|
||||
class PlayerProgressionManager(context: Context) {
|
||||
private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
// Player level and XP
|
||||
private var playerLevel: Int = 1
|
||||
private var playerXP: Long = 0
|
||||
private var totalXPEarned: Long = 0
|
||||
|
||||
// Track unlocked rewards
|
||||
private val unlockedThemes = mutableSetOf<String>()
|
||||
private val unlockedBlocks = mutableSetOf<String>()
|
||||
private val unlockedPowers = mutableSetOf<String>()
|
||||
private val unlockedBadges = mutableSetOf<String>()
|
||||
|
||||
// XP gained in the current session
|
||||
private var sessionXPGained: Long = 0
|
||||
|
||||
init {
|
||||
loadProgress()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load player progression data from shared preferences
|
||||
*/
|
||||
private fun loadProgress() {
|
||||
playerLevel = prefs.getInt(KEY_PLAYER_LEVEL, 1)
|
||||
playerXP = prefs.getLong(KEY_PLAYER_XP, 0)
|
||||
totalXPEarned = prefs.getLong(KEY_TOTAL_XP_EARNED, 0)
|
||||
|
||||
// Load unlocked rewards
|
||||
val themesSet = prefs.getStringSet(KEY_UNLOCKED_THEMES, setOf()) ?: setOf()
|
||||
val blocksSet = prefs.getStringSet(KEY_UNLOCKED_BLOCKS, setOf()) ?: setOf()
|
||||
val powersSet = prefs.getStringSet(KEY_UNLOCKED_POWERS, setOf()) ?: setOf()
|
||||
val badgesSet = prefs.getStringSet(KEY_UNLOCKED_BADGES, setOf()) ?: setOf()
|
||||
|
||||
unlockedThemes.addAll(themesSet)
|
||||
unlockedBlocks.addAll(blocksSet)
|
||||
unlockedPowers.addAll(powersSet)
|
||||
unlockedBadges.addAll(badgesSet)
|
||||
|
||||
// Add default theme if nothing is unlocked
|
||||
if (unlockedThemes.isEmpty()) {
|
||||
unlockedThemes.add(THEME_CLASSIC)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save player progression data to shared preferences
|
||||
*/
|
||||
private fun saveProgress() {
|
||||
prefs.edit()
|
||||
.putInt(KEY_PLAYER_LEVEL, playerLevel)
|
||||
.putLong(KEY_PLAYER_XP, playerXP)
|
||||
.putLong(KEY_TOTAL_XP_EARNED, totalXPEarned)
|
||||
.putStringSet(KEY_UNLOCKED_THEMES, unlockedThemes)
|
||||
.putStringSet(KEY_UNLOCKED_BLOCKS, unlockedBlocks)
|
||||
.putStringSet(KEY_UNLOCKED_POWERS, unlockedPowers)
|
||||
.putStringSet(KEY_UNLOCKED_BADGES, unlockedBadges)
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate XP required to reach a specific level
|
||||
*/
|
||||
fun calculateXPForLevel(level: Int): Long {
|
||||
return (BASE_XP * level.toDouble().pow(XP_CURVE_FACTOR)).roundToInt().toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total XP required to reach a certain level from level 1
|
||||
*/
|
||||
fun calculateTotalXPForLevel(level: Int): Long {
|
||||
var totalXP = 0L
|
||||
for (lvl in 1 until level) {
|
||||
totalXP += calculateXPForLevel(lvl)
|
||||
}
|
||||
return totalXP
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate XP from a game session based on score, lines, level, etc.
|
||||
*/
|
||||
fun calculateGameXP(score: Int, lines: Int, level: Int, gameTime: Long,
|
||||
tetrisCount: Int, perfectClearCount: Int): Long {
|
||||
// Base XP from score with level multiplier
|
||||
val scoreXP = (score * (1 + LEVEL_MULTIPLIER * level)).toLong()
|
||||
|
||||
// XP from lines cleared
|
||||
val linesXP = lines * XP_PER_LINE
|
||||
|
||||
// XP from special moves
|
||||
val tetrisBonus = tetrisCount * TETRIS_XP_BONUS
|
||||
val perfectClearBonus = perfectClearCount * PERFECT_CLEAR_XP_BONUS
|
||||
|
||||
// Time bonus (to reward longer gameplay)
|
||||
val timeBonus = (gameTime / 60000) * TIME_XP_PER_MINUTE // XP per minute played
|
||||
|
||||
// Calculate total XP
|
||||
return scoreXP + linesXP + tetrisBonus + perfectClearBonus + timeBonus
|
||||
}
|
||||
|
||||
/**
|
||||
* Add XP to the player and handle level-ups
|
||||
* Returns a list of newly unlocked rewards
|
||||
*/
|
||||
fun addXP(xpAmount: Long): List<String> {
|
||||
sessionXPGained = xpAmount
|
||||
playerXP += xpAmount
|
||||
totalXPEarned += xpAmount
|
||||
|
||||
val newRewards = mutableListOf<String>()
|
||||
val oldLevel = playerLevel
|
||||
|
||||
// Check for level ups
|
||||
var xpForNextLevel = calculateXPForLevel(playerLevel)
|
||||
while (playerXP >= xpForNextLevel) {
|
||||
playerXP -= xpForNextLevel
|
||||
playerLevel++
|
||||
|
||||
// Check for new rewards at this level
|
||||
val levelRewards = checkLevelRewards(playerLevel)
|
||||
newRewards.addAll(levelRewards)
|
||||
|
||||
// Calculate XP needed for the next level
|
||||
xpForNextLevel = calculateXPForLevel(playerLevel)
|
||||
}
|
||||
|
||||
// Save progress if there were any changes
|
||||
if (oldLevel != playerLevel || newRewards.isNotEmpty()) {
|
||||
saveProgress()
|
||||
}
|
||||
|
||||
return newRewards
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the player unlocked new rewards at the current level
|
||||
*/
|
||||
private fun checkLevelRewards(level: Int): List<String> {
|
||||
val newRewards = mutableListOf<String>()
|
||||
|
||||
// Check for theme unlocks
|
||||
when (level) {
|
||||
5 -> {
|
||||
if (unlockedThemes.add(THEME_NEON)) {
|
||||
newRewards.add("Unlocked Neon Theme!")
|
||||
}
|
||||
}
|
||||
10 -> {
|
||||
if (unlockedThemes.add(THEME_MONOCHROME)) {
|
||||
newRewards.add("Unlocked Monochrome Theme!")
|
||||
}
|
||||
}
|
||||
15 -> {
|
||||
if (unlockedThemes.add(THEME_RETRO)) {
|
||||
newRewards.add("Unlocked Retro Arcade Theme!")
|
||||
}
|
||||
}
|
||||
20 -> {
|
||||
if (unlockedThemes.add(THEME_MINIMALIST)) {
|
||||
newRewards.add("Unlocked Minimalist Theme!")
|
||||
}
|
||||
}
|
||||
25 -> {
|
||||
if (unlockedThemes.add(THEME_GALAXY)) {
|
||||
newRewards.add("Unlocked Galaxy Theme!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for power unlocks
|
||||
when (level) {
|
||||
8 -> {
|
||||
if (unlockedPowers.add(POWER_FREEZE_TIME)) {
|
||||
newRewards.add("Unlocked Freeze Time Power!")
|
||||
}
|
||||
}
|
||||
12 -> {
|
||||
if (unlockedPowers.add(POWER_BLOCK_SWAP)) {
|
||||
newRewards.add("Unlocked Block Swap Power!")
|
||||
}
|
||||
}
|
||||
18 -> {
|
||||
if (unlockedPowers.add(POWER_SAFE_LANDING)) {
|
||||
newRewards.add("Unlocked Safe Landing Power!")
|
||||
}
|
||||
}
|
||||
30 -> {
|
||||
if (unlockedPowers.add(POWER_PERFECT_CLEAR)) {
|
||||
newRewards.add("Unlocked Perfect Clear Power!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for block skin unlocks
|
||||
if (level % 7 == 0 && level <= 35) {
|
||||
val blockSkin = "block_skin_${level / 7}"
|
||||
if (unlockedBlocks.add(blockSkin)) {
|
||||
newRewards.add("Unlocked New Block Skin!")
|
||||
}
|
||||
}
|
||||
|
||||
return newRewards
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new progression session
|
||||
*/
|
||||
fun startNewSession() {
|
||||
sessionXPGained = 0
|
||||
}
|
||||
|
||||
// Getters
|
||||
fun getPlayerLevel(): Int = playerLevel
|
||||
fun getCurrentXP(): Long = playerXP
|
||||
fun getXPForNextLevel(): Long = calculateXPForLevel(playerLevel)
|
||||
fun getSessionXPGained(): Long = sessionXPGained
|
||||
fun getUnlockedThemes(): Set<String> = unlockedThemes.toSet()
|
||||
fun getUnlockedPowers(): Set<String> = unlockedPowers.toSet()
|
||||
fun getUnlockedBlocks(): Set<String> = unlockedBlocks.toSet()
|
||||
fun getUnlockedBadges(): Set<String> = unlockedBadges.toSet()
|
||||
|
||||
/**
|
||||
* Check if a specific theme is unlocked
|
||||
*/
|
||||
fun isThemeUnlocked(themeId: String): Boolean {
|
||||
return unlockedThemes.contains(themeId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific power is unlocked
|
||||
*/
|
||||
fun isPowerUnlocked(powerId: String): Boolean {
|
||||
return unlockedPowers.contains(powerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Award a badge to the player
|
||||
*/
|
||||
fun awardBadge(badgeId: String): Boolean {
|
||||
val newlyAwarded = unlockedBadges.add(badgeId)
|
||||
if (newlyAwarded) {
|
||||
saveProgress()
|
||||
}
|
||||
return newlyAwarded
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all player progression data
|
||||
*/
|
||||
fun resetProgress() {
|
||||
playerLevel = 1
|
||||
playerXP = 0
|
||||
totalXPEarned = 0
|
||||
|
||||
unlockedThemes.clear()
|
||||
unlockedBlocks.clear()
|
||||
unlockedPowers.clear()
|
||||
unlockedBadges.clear()
|
||||
|
||||
// Add default theme
|
||||
unlockedThemes.add(THEME_CLASSIC)
|
||||
|
||||
saveProgress()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "mintris_progression"
|
||||
private const val KEY_PLAYER_LEVEL = "player_level"
|
||||
private const val KEY_PLAYER_XP = "player_xp"
|
||||
private const val KEY_TOTAL_XP_EARNED = "total_xp_earned"
|
||||
private const val KEY_UNLOCKED_THEMES = "unlocked_themes"
|
||||
private const val KEY_UNLOCKED_BLOCKS = "unlocked_blocks"
|
||||
private const val KEY_UNLOCKED_POWERS = "unlocked_powers"
|
||||
private const val KEY_UNLOCKED_BADGES = "unlocked_badges"
|
||||
|
||||
// XP curve parameters
|
||||
private const val BASE_XP = 1000.0 // Base XP for level 1
|
||||
private const val XP_CURVE_FACTOR = 1.5 // Exponential factor for XP curve
|
||||
|
||||
// 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
|
||||
|
||||
// Theme IDs
|
||||
const val THEME_CLASSIC = "theme_classic"
|
||||
const val THEME_NEON = "theme_neon"
|
||||
const val THEME_MONOCHROME = "theme_monochrome"
|
||||
const val THEME_RETRO = "theme_retro"
|
||||
const val THEME_MINIMALIST = "theme_minimalist"
|
||||
const val THEME_GALAXY = "theme_galaxy"
|
||||
|
||||
// Power IDs
|
||||
const val POWER_FREEZE_TIME = "power_freeze_time"
|
||||
const val POWER_BLOCK_SWAP = "power_block_swap"
|
||||
const val POWER_SAFE_LANDING = "power_safe_landing"
|
||||
const val POWER_PERFECT_CLEAR = "power_perfect_clear"
|
||||
}
|
||||
}
|
158
app/src/main/java/com/mintris/ui/ProgressionScreen.kt
Normal file
158
app/src/main/java/com/mintris/ui/ProgressionScreen.kt
Normal file
|
@ -0,0 +1,158 @@
|
|||
package com.mintris.ui
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import com.mintris.R
|
||||
import com.mintris.model.PlayerProgressionManager
|
||||
|
||||
/**
|
||||
* Screen that displays player progression, XP gain, and unlocked rewards
|
||||
*/
|
||||
class ProgressionScreen @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
// UI components
|
||||
private val xpProgressBar: XPProgressBar
|
||||
private val xpGainText: TextView
|
||||
private val playerLevelText: TextView
|
||||
private val rewardsContainer: LinearLayout
|
||||
private val continueButton: Button
|
||||
|
||||
// Callback for when the player dismisses the screen
|
||||
var onContinue: (() -> Unit)? = null
|
||||
|
||||
init {
|
||||
orientation = VERTICAL
|
||||
|
||||
// Inflate the layout
|
||||
LayoutInflater.from(context).inflate(R.layout.progression_screen, this, true)
|
||||
|
||||
// Get references to views
|
||||
xpProgressBar = findViewById(R.id.xp_progress_bar)
|
||||
xpGainText = findViewById(R.id.xp_gain_text)
|
||||
playerLevelText = findViewById(R.id.player_level_text)
|
||||
rewardsContainer = findViewById(R.id.rewards_container)
|
||||
continueButton = findViewById(R.id.continue_button)
|
||||
|
||||
// Set up button click listener
|
||||
continueButton.setOnClickListener {
|
||||
onContinue?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display progression data and animate XP gain
|
||||
*/
|
||||
fun showProgress(
|
||||
progressionManager: PlayerProgressionManager,
|
||||
xpGained: Long,
|
||||
newRewards: List<String>
|
||||
) {
|
||||
// Hide rewards container initially if there are no new rewards
|
||||
rewardsContainer.visibility = if (newRewards.isEmpty()) View.GONE else View.INVISIBLE
|
||||
|
||||
// Set initial progress bar state
|
||||
val playerLevel = progressionManager.getPlayerLevel()
|
||||
val currentXP = progressionManager.getCurrentXP()
|
||||
val xpForNextLevel = progressionManager.getXPForNextLevel()
|
||||
|
||||
// Update texts
|
||||
playerLevelText.text = "Player Level: $playerLevel"
|
||||
xpGainText.text = "+$xpGained XP"
|
||||
|
||||
// Begin animation sequence
|
||||
xpProgressBar.setXPValues(playerLevel, currentXP, xpForNextLevel)
|
||||
|
||||
// Animate XP gain text entrance
|
||||
val xpTextAnimator = ObjectAnimator.ofFloat(xpGainText, "alpha", 0f, 1f).apply {
|
||||
duration = 500
|
||||
}
|
||||
|
||||
// Schedule animation for the XP bar after text appears
|
||||
postDelayed({
|
||||
xpProgressBar.animateXPGain(xpGained, playerLevel, currentXP, xpForNextLevel)
|
||||
}, 600)
|
||||
|
||||
// If there are new rewards, show them with animation
|
||||
if (newRewards.isNotEmpty()) {
|
||||
// Create reward cards
|
||||
rewardsContainer.removeAllViews()
|
||||
newRewards.forEach { reward ->
|
||||
val rewardCard = createRewardCard(reward)
|
||||
rewardsContainer.addView(rewardCard)
|
||||
}
|
||||
|
||||
// Show rewards with animation after XP bar animation
|
||||
postDelayed({
|
||||
rewardsContainer.visibility = View.VISIBLE
|
||||
|
||||
// Animate each reward card
|
||||
for (i in 0 until rewardsContainer.childCount) {
|
||||
val card = rewardsContainer.getChildAt(i)
|
||||
card.alpha = 0f
|
||||
card.translationY = 100f
|
||||
|
||||
// Stagger animation for each card
|
||||
card.animate()
|
||||
.alpha(1f)
|
||||
.translationY(0f)
|
||||
.setDuration(400)
|
||||
.setStartDelay((i * 150).toLong())
|
||||
.setInterpolator(OvershootInterpolator())
|
||||
.start()
|
||||
}
|
||||
}, 2000) // Wait for XP bar animation to finish
|
||||
}
|
||||
|
||||
// Start with initial animations
|
||||
AnimatorSet().apply {
|
||||
play(xpTextAnimator)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a card view to display a reward
|
||||
*/
|
||||
private fun createRewardCard(rewardText: String): CardView {
|
||||
val card = CardView(context).apply {
|
||||
radius = 16f
|
||||
cardElevation = 8f
|
||||
useCompatPadding = true
|
||||
setCardBackgroundColor(Color.parseColor("#FFD700")) // Gold background
|
||||
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(16, 16, 16, 16)
|
||||
}
|
||||
}
|
||||
|
||||
// Add reward text
|
||||
val textView = TextView(context).apply {
|
||||
text = rewardText
|
||||
setTextColor(Color.BLACK)
|
||||
textSize = 18f
|
||||
setPadding(32, 24, 32, 24)
|
||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
}
|
||||
|
||||
card.addView(textView)
|
||||
return card
|
||||
}
|
||||
}
|
234
app/src/main/java/com/mintris/ui/ThemeSelector.kt
Normal file
234
app/src/main/java/com/mintris/ui/ThemeSelector.kt
Normal file
|
@ -0,0 +1,234 @@
|
|||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
private var selectedTheme: String = PlayerProgressionManager.THEME_CLASSIC
|
||||
|
||||
// Theme cards
|
||||
private val themeCards = mutableMapOf<String, CardView>()
|
||||
|
||||
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
|
||||
themesGrid.removeAllViews()
|
||||
themeCards.clear()
|
||||
|
||||
// Update selected theme
|
||||
selectedTheme = currentTheme
|
||||
|
||||
// Get all possible themes and their details
|
||||
val allThemes = getThemes()
|
||||
|
||||
// Add theme cards to the grid
|
||||
allThemes.forEach { (themeId, themeInfo) ->
|
||||
val isUnlocked = unlockedThemes.contains(themeId)
|
||||
val isSelected = themeId == selectedTheme
|
||||
|
||||
val themeCard = createThemeCard(themeId, themeInfo, isUnlocked, isSelected)
|
||||
themeCards[themeId] = themeCard
|
||||
themesGrid.addView(themeCard)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
// Add stroke for selected theme
|
||||
if (isSelected) {
|
||||
setContentPadding(4, 4, 4, 4)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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 the card
|
||||
card.addView(themePreview)
|
||||
card.addView(themeLabel)
|
||||
card.addView(lockOverlay)
|
||||
|
||||
// Set up click listener only for unlocked themes
|
||||
if (isUnlocked) {
|
||||
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 selected theme
|
||||
selectedTheme = themeId
|
||||
|
||||
// Notify listener
|
||||
onThemeSelected?.invoke(themeId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
239
app/src/main/java/com/mintris/ui/XPProgressBar.kt
Normal file
239
app/src/main/java/com/mintris/ui/XPProgressBar.kt
Normal file
|
@ -0,0 +1,239 @@
|
|||
package com.mintris.ui
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.mintris.R
|
||||
|
||||
/**
|
||||
* Custom progress bar for displaying player XP with animation capabilities
|
||||
*/
|
||||
class XPProgressBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
// Paints for drawing
|
||||
private val backgroundPaint = Paint().apply {
|
||||
color = Color.parseColor("#383838")
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val progressPaint = Paint().apply {
|
||||
color = Color.parseColor("#50C878") // Emerald green
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val progressGlowPaint = Paint().apply {
|
||||
color = Color.parseColor("#70F098") // Lighter emerald for glow
|
||||
isAntiAlias = true
|
||||
setShadowLayer(10f, 0f, 0f, Color.parseColor("#50C878"))
|
||||
}
|
||||
|
||||
private val textPaint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = 40f
|
||||
}
|
||||
|
||||
private val levelBadgePaint = Paint().apply {
|
||||
color = Color.parseColor("#FFD700") // Gold color for level badge
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
private val levelBadgeTextPaint = Paint().apply {
|
||||
color = Color.BLACK
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = 36f
|
||||
isFakeBoldText = true
|
||||
}
|
||||
|
||||
// Progress bar dimensions
|
||||
private val progressRect = RectF()
|
||||
private val backgroundRect = RectF()
|
||||
private val cornerRadius = 25f
|
||||
|
||||
// Progress animation
|
||||
private var progressAnimator: ValueAnimator? = null
|
||||
private var currentProgress = 0f
|
||||
private var targetProgress = 0f
|
||||
|
||||
// XP values
|
||||
private var currentXP = 0L
|
||||
private var xpForNextLevel = 100L
|
||||
private var playerLevel = 1
|
||||
|
||||
// Level up animation
|
||||
private var isLevelingUp = false
|
||||
private var levelUpAnimator: ValueAnimator? = null
|
||||
private var levelBadgeScale = 1f
|
||||
|
||||
/**
|
||||
* Set the player's current level and XP values
|
||||
*/
|
||||
fun setXPValues(level: Int, currentXP: Long, xpForNextLevel: Long) {
|
||||
this.playerLevel = level
|
||||
this.currentXP = currentXP
|
||||
this.xpForNextLevel = xpForNextLevel
|
||||
|
||||
// Update progress value
|
||||
targetProgress = calculateProgressPercentage()
|
||||
|
||||
// If not animating, set current progress immediately
|
||||
if (progressAnimator == null || !progressAnimator!!.isRunning) {
|
||||
currentProgress = targetProgress
|
||||
}
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate adding XP to the bar
|
||||
*/
|
||||
fun animateXPGain(xpGained: Long, newLevel: Int, newCurrentXP: Long, newXPForNextLevel: Long) {
|
||||
// Store original values before animation
|
||||
val startXP = currentXP
|
||||
val startLevel = playerLevel
|
||||
|
||||
// Calculate percentage before XP gain
|
||||
val startProgress = calculateProgressPercentage()
|
||||
|
||||
// Update to new values
|
||||
playerLevel = newLevel
|
||||
currentXP = newCurrentXP
|
||||
xpForNextLevel = newXPForNextLevel
|
||||
|
||||
// Calculate new target progress
|
||||
targetProgress = calculateProgressPercentage()
|
||||
|
||||
// Determine if level up occurred
|
||||
isLevelingUp = startLevel < newLevel
|
||||
|
||||
// Animate progress bar
|
||||
progressAnimator?.cancel()
|
||||
progressAnimator = ValueAnimator.ofFloat(startProgress, targetProgress).apply {
|
||||
duration = 1500 // 1.5 seconds animation
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
addUpdateListener { animation ->
|
||||
currentProgress = animation.animatedValue as Float
|
||||
invalidate()
|
||||
}
|
||||
|
||||
// When animation completes, trigger level up animation if needed
|
||||
if (isLevelingUp) {
|
||||
levelUpAnimation()
|
||||
}
|
||||
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a level up animation effect
|
||||
*/
|
||||
private fun levelUpAnimation() {
|
||||
levelUpAnimator?.cancel()
|
||||
levelUpAnimator = ValueAnimator.ofFloat(1f, 1.5f, 1f).apply {
|
||||
duration = 1000 // 1 second pulse animation
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
addUpdateListener { animation ->
|
||||
levelBadgeScale = animation.animatedValue as Float
|
||||
invalidate()
|
||||
}
|
||||
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the current progress percentage
|
||||
*/
|
||||
private fun calculateProgressPercentage(): Float {
|
||||
return if (xpForNextLevel > 0) {
|
||||
(currentXP.toFloat() / xpForNextLevel.toFloat()) * 100f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
|
||||
// Update progress bar dimensions based on view size
|
||||
val verticalPadding = h * 0.2f
|
||||
backgroundRect.set(
|
||||
h * 0.5f, // Left margin = height/2 (for level badge)
|
||||
verticalPadding,
|
||||
w - paddingRight.toFloat(),
|
||||
h - verticalPadding
|
||||
)
|
||||
|
||||
// Adjust text size based on height
|
||||
textPaint.textSize = h * 0.35f
|
||||
levelBadgeTextPaint.textSize = h * 0.3f
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
// Draw level badge
|
||||
val badgeRadius = height * 0.3f * levelBadgeScale
|
||||
val badgeCenterX = height * 0.25f
|
||||
val badgeCenterY = height * 0.5f
|
||||
|
||||
canvas.drawCircle(badgeCenterX, badgeCenterY, badgeRadius, levelBadgePaint)
|
||||
canvas.drawText(
|
||||
playerLevel.toString(),
|
||||
badgeCenterX,
|
||||
badgeCenterY + (levelBadgeTextPaint.textSize / 3),
|
||||
levelBadgeTextPaint
|
||||
)
|
||||
|
||||
// Draw background bar
|
||||
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
|
||||
|
||||
// Draw progress bar
|
||||
progressRect.set(
|
||||
backgroundRect.left,
|
||||
backgroundRect.top,
|
||||
backgroundRect.left + (backgroundRect.width() * currentProgress / 100f),
|
||||
backgroundRect.bottom
|
||||
)
|
||||
|
||||
// Only draw if there is progress to show
|
||||
if (progressRect.width() > 0) {
|
||||
// Draw glow effect first
|
||||
canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressGlowPaint)
|
||||
|
||||
// Draw actual progress bar
|
||||
canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressPaint)
|
||||
}
|
||||
|
||||
// Draw progress text
|
||||
val progressText = "${currentXP}/${xpForNextLevel} XP"
|
||||
canvas.drawText(
|
||||
progressText,
|
||||
backgroundRect.centerX(),
|
||||
backgroundRect.centerY() + (textPaint.textSize / 3),
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
progressAnimator?.cancel()
|
||||
levelUpAnimator?.cancel()
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/ic_lock.xml
Normal file
10
app/src/main/res/drawable/ic_lock.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/lock_overlay.xml
Normal file
11
app/src/main/res/drawable/lock_overlay.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#99000000" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:drawable="@drawable/ic_lock"
|
||||
android:gravity="center" />
|
||||
</layer-list>
|
15
app/src/main/res/drawable/rounded_button.xml
Normal file
15
app/src/main/res/drawable/rounded_button.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="#3366FF" />
|
||||
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:right="16dp"
|
||||
android:top="8dp"
|
||||
android:bottom="8dp" />
|
||||
|
||||
</shape>
|
|
@ -234,6 +234,17 @@
|
|||
android:textColor="@color/white" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Player Progression Screen -->
|
||||
<com.mintris.ui.ProgressionScreen
|
||||
android:id="@+id/progressionScreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Settings Menu overlay -->
|
||||
<LinearLayout
|
||||
android:id="@+id/pauseContainer"
|
||||
|
@ -253,129 +264,162 @@
|
|||
android:textStyle="bold"
|
||||
android:layout_marginBottom="32dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseStartButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/start"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/resumeButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/resume"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseRestartButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/restart"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/highScoresButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/high_scores"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/statsButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/stats"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/levelSelectorContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_level"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseLevelDownButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:id="@+id/pauseStartButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:text="−"
|
||||
android:text="@string/start"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pauseLevelText"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseLevelUpButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:id="@+id/resumeButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="+"
|
||||
android:text="@string/resume"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp" />
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseRestartButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/restart"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/highScoresButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/high_scores"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/statsButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/stats"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/levelSelectorContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_level"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseLevelDownButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="−"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pauseLevelText"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pauseLevelUpButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="+"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="24sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Theme Selector -->
|
||||
<com.mintris.ui.ThemeSelector
|
||||
android:id="@+id/themeSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/sound_on"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/music"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/musicToggle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/toggle_music"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_volume_up" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:background="@color/transparent"
|
||||
android:text="@string/sound_on"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/musicToggle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/toggle_music"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_volume_up"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/pauseStartButton"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
86
app/src/main/res/layout/progression_screen.xml
Normal file
86
app/src/main/res/layout/progression_screen.xml
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="#1F1F1F">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progression_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="LEVEL PROGRESS"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FFFFFF"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_level_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Player Level: 1"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<com.mintris.ui.XPProgressBar
|
||||
android:id="@+id/xp_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/xp_gain_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="+0 XP"
|
||||
android:textSize="22sp"
|
||||
android:textColor="#50C878"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rewards_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="REWARDS UNLOCKED"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FFD700"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rewards_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continue_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CONTINUE"
|
||||
android:textSize="18sp"
|
||||
android:padding="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
</LinearLayout>
|
27
app/src/main/res/layout/theme_selector.xml
Normal file
27
app/src/main/res/layout/theme_selector.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/available_themes_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="AVAILABLE THEMES"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<GridLayout
|
||||
android:id="@+id/themes_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:alignmentMode="alignMargins"
|
||||
android:useDefaultMargins="true" />
|
||||
|
||||
</LinearLayout>
|
Binary file not shown.
4
app/src/main/res/values/dimens.xml
Normal file
4
app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="theme_card_size">80dp</dimen>
|
||||
</resources>
|
|
@ -45,4 +45,5 @@
|
|||
<string name="triples">Triples: %d</string>
|
||||
<string name="tetrises">Tetrises: %d</string>
|
||||
<string name="reset_stats">Reset Stats</string>
|
||||
<string name="music">Music</string>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue