diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..23b2e1f
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index edd88b5..25a4269 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -7,14 +7,14 @@ plugins {
android {
namespace = "com.atridad.magiccounter"
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
applicationId = "com.atridad.magiccounter"
minSdk = 35
- targetSdk = 35
- versionCode = 1
- versionName = "1.0.0"
+ targetSdk = 36
+ versionCode = 2
+ versionName = "1.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/app/src/main/java/com/atridad/magiccounter/ui/MagicCounterApp.kt b/app/src/main/java/com/atridad/magiccounter/ui/MagicCounterApp.kt
index 56f4e3a..4fc59c8 100644
--- a/app/src/main/java/com/atridad/magiccounter/ui/MagicCounterApp.kt
+++ b/app/src/main/java/com/atridad/magiccounter/ui/MagicCounterApp.kt
@@ -6,15 +6,19 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -42,11 +46,11 @@ import com.atridad.magiccounter.ui.settings.AppSettingsViewModel
import com.atridad.magiccounter.ui.settings.ThemeMode
import com.atridad.magiccounter.ui.settings.MatchRecord
import com.atridad.magiccounter.ui.theme.MagicCounterTheme
-import androidx.compose.material3.FilterChip
import androidx.activity.compose.BackHandler
import androidx.compose.material3.HorizontalDivider
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.layout.Spacer
private sealed class Screen {
data object Home : Screen()
@@ -63,6 +67,8 @@ fun MagicCounterApp() {
val theme = settingsVm.themeMode.collectAsState()
val historyState = settingsVm.matchHistory.collectAsState()
+ // State for clear history confirmation
+ var showClearConfirm by remember { mutableStateOf(false) }
MagicCounterTheme(themeMode = theme.value) {
Scaffold(
@@ -77,11 +83,28 @@ fun MagicCounterApp() {
}
},
actions = {
+ // Show Clear History icon only on home screen
+ if (screenStack.last() is Screen.Home) {
+ IconButton(onClick = { showClearConfirm = true }) {
+ Icon(Icons.Default.Clear, contentDescription = "Clear history")
+ }
+ }
IconButton(onClick = { screenStack.add(Screen.Settings) }) {
Icon(Icons.Default.Settings, contentDescription = "App settings")
}
}
)
+ },
+ floatingActionButton = {
+ // Show FAB only on home screen when no active game is running
+ val currentScreen = screenStack.last()
+ if (currentScreen is Screen.Home && historyState.value.none { it.ongoing }) {
+ FloatingActionButton(
+ onClick = { screenStack.add(Screen.Setup) }
+ ) {
+ Icon(Icons.Default.Add, contentDescription = "New game")
+ }
+ }
}
) { paddingValues ->
val currentScreen = screenStack.last()
@@ -92,13 +115,17 @@ fun MagicCounterApp() {
.padding(paddingValues)
.fillMaxSize(),
history = historyState.value,
- onNewGame = { screenStack.add(Screen.Setup) },
onResume = { record -> screenStack.add(Screen.Game(record.id)) },
onDelete = { id ->
val newList = historyState.value.filterNot { it.id == id }
settingsVm.saveHistory(newList)
},
- onClear = { settingsVm.saveHistory(emptyList()) }
+ showClearConfirm = showClearConfirm,
+ onClearConfirm = { showClearConfirm = false },
+ onClear = {
+ settingsVm.saveHistory(emptyList())
+ showClearConfirm = false
+ }
)
is Screen.Setup -> SetupScreen(
modifier = Modifier
@@ -171,21 +198,58 @@ fun MagicCounterApp() {
onSelect = { settingsVm.setTheme(it) }
)
}
+
+ // Global clear history confirmation dialog
+ if (showClearConfirm) {
+ AlertDialog(
+ onDismissRequest = { showClearConfirm = false },
+ title = { Text("Clear history?") },
+ text = { Text("This will remove all matches. This action cannot be undone.") },
+ confirmButton = {
+ TextButton(onClick = {
+ settingsVm.saveHistory(emptyList())
+ showClearConfirm = false
+ }) { Text("Clear") }
+ },
+ dismissButton = {
+ TextButton(onClick = { showClearConfirm = false }) { Text("Cancel") }
+ }
+ )
+ }
}
}
}
@Composable
private fun SettingsContent(modifier: Modifier, current: ThemeMode, onSelect: (ThemeMode) -> Unit) {
- Column(modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
- Text("Theme")
- Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- ThemeMode.entries.forEach { mode ->
- FilterChip(
- selected = current == mode,
- onClick = { onSelect(mode) },
- label = { Text(mode.name) }
- )
+ Column(
+ modifier = modifier.padding(24.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ Text(
+ "Appearance",
+ style = MaterialTheme.typography.headlineSmall,
+ color = MaterialTheme.colorScheme.primary
+ )
+
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Text(
+ "Theme",
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ ThemeMode.entries.forEach { mode ->
+ androidx.compose.material3.FilterChip(
+ selected = current == mode,
+ onClick = { onSelect(mode) },
+ label = { Text(mode.name) },
+ modifier = Modifier.weight(1f)
+ )
+ }
}
}
}
@@ -195,17 +259,48 @@ private fun SettingsContent(modifier: Modifier, current: ThemeMode, onSelect: (T
private fun HomeScreen(
modifier: Modifier,
history: List,
- onNewGame: () -> Unit,
onResume: (MatchRecord) -> Unit,
onDelete: (String) -> Unit,
+ showClearConfirm: Boolean,
+ onClearConfirm: () -> Unit,
onClear: () -> Unit
) {
Column(modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
var pendingDeleteId by remember { mutableStateOf(null) }
- var showClearConfirm by remember { mutableStateOf(false) }
- Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
- Button(onClick = onNewGame) { Text("New game") }
- Button(onClick = { showClearConfirm = true }) { Text("Clear history") }
+
+ // Only show action buttons if there are ongoing games
+ if (history.any { it.ongoing }) {
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ "Quick Actions",
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.primary
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ androidx.compose.material3.OutlinedButton(
+ onClick = { onResume(history.first { it.ongoing }) },
+ modifier = Modifier.weight(1f)
+ ) {
+ Icon(Icons.Default.PlayArrow, contentDescription = null)
+ Spacer(modifier = Modifier.padding(4.dp))
+ Text("Resume Game")
+ }
+ androidx.compose.material3.OutlinedButton(
+ onClick = { pendingDeleteId = history.first { it.ongoing }.id },
+ modifier = Modifier.weight(1f)
+ ) {
+ Icon(Icons.Default.Delete, contentDescription = null)
+ Spacer(modifier = Modifier.padding(4.dp))
+ Text("Delete Game")
+ }
+ }
+ }
}
if (history.isEmpty()) {
@@ -213,57 +308,120 @@ private fun HomeScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
- Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp)) {
- Icon(Icons.Default.History, contentDescription = "Empty history")
- Text("Nothing to see here")
- Text("Start a new game to begin.")
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Icon(
+ Icons.Default.History,
+ contentDescription = "Empty history",
+ modifier = Modifier.size(64.dp),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Text(
+ "No games yet",
+ style = MaterialTheme.typography.headlineSmall,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Text(
+ "Start your first Magic: The Gathering game to begin tracking life totals and more.",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = androidx.compose.ui.text.style.TextAlign.Center
+ )
}
}
} else {
LazyColumn(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
item {
- Column(modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 4.dp)) {
+ Column(modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 12.dp)) {
Text(
- "Ongoing",
+ "Ongoing Games",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
- HorizontalDivider()
+ HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
items(history.filter { it.ongoing }, key = { it.id }) { rec ->
- Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
- Column {
- Text(rec.name)
- Text("Started ${java.text.DateFormat.getDateTimeInstance().format(java.util.Date(rec.startedAtEpochMs))}")
- }
- Row {
- IconButton(onClick = { onResume(rec) }) { Icon(Icons.Default.PlayArrow, contentDescription = "Resume game") }
- IconButton(onClick = { pendingDeleteId = rec.id }) { Icon(Icons.Default.Delete, contentDescription = "Delete") }
+ androidx.compose.material3.Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = androidx.compose.material3.CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ )
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ rec.name,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ Text(
+ "Started ${java.text.DateFormat.getDateTimeInstance().format(java.util.Date(rec.startedAtEpochMs))}",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
+ )
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ IconButton(onClick = { onResume(rec) }) {
+ Icon(Icons.Default.PlayArrow, contentDescription = "Resume game")
+ }
+ IconButton(onClick = { pendingDeleteId = rec.id }) {
+ Icon(Icons.Default.Delete, contentDescription = "Delete")
+ }
+ }
}
}
}
item {
- Column(modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 4.dp)) {
+ Column(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 12.dp)) {
Text(
- "Finished",
+ "Finished Games",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
- HorizontalDivider()
+ HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
items(history.filter { !it.ongoing }, key = { it.id }) { rec ->
- Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
- Column {
- Text(rec.name)
- val winner = rec.winnerPlayerId?.let { "Winner: Player ${it + 1}" } ?: ""
- Text("Finished • $winner")
- }
- Row {
- IconButton(onClick = { onResume(rec) }) { Icon(Icons.Default.Visibility, contentDescription = "View match") }
- IconButton(onClick = { pendingDeleteId = rec.id }) { Icon(Icons.Default.Delete, contentDescription = "Delete") }
+ androidx.compose.material3.Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = androidx.compose.material3.CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ )
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ rec.name,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ val winner = rec.winnerPlayerId?.let { "Winner: Player ${it + 1}" } ?: ""
+ Text(
+ "Finished • $winner",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
+ )
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ IconButton(onClick = { onResume(rec) }) {
+ Icon(Icons.Default.Visibility, contentDescription = "View match")
+ }
+ IconButton(onClick = { pendingDeleteId = rec.id }) {
+ Icon(Icons.Default.Delete, contentDescription = "Delete")
+ }
+ }
}
}
}
@@ -290,16 +448,13 @@ private fun HomeScreen(
// Confirm clear dialog
if (showClearConfirm) {
AlertDialog(
- onDismissRequest = { showClearConfirm = false },
+ onDismissRequest = { onClearConfirm() },
title = { Text("Clear history?") },
text = { Text("This will remove all matches. This action cannot be undone.") },
confirmButton = {
- TextButton(onClick = {
- onClear()
- showClearConfirm = false
- }) { Text("Clear") }
+ TextButton(onClick = { onClear() }) { Text("Clear") }
},
- dismissButton = { TextButton(onClick = { showClearConfirm = false }) { Text("Cancel") } }
+ dismissButton = { TextButton(onClick = { onClearConfirm() }) { Text("Cancel") } }
)
}
}
diff --git a/app/src/main/java/com/atridad/magiccounter/ui/screens/SetupScreen.kt b/app/src/main/java/com/atridad/magiccounter/ui/screens/SetupScreen.kt
index b34e8e0..0a422e7 100644
--- a/app/src/main/java/com/atridad/magiccounter/ui/screens/SetupScreen.kt
+++ b/app/src/main/java/com/atridad/magiccounter/ui/screens/SetupScreen.kt
@@ -9,8 +9,11 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
+import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
@@ -49,75 +52,138 @@ fun SetupScreen(
Column(
modifier = modifier
- .padding(16.dp)
+ .padding(24.dp)
.verticalScroll(rememberScrollState()),
- verticalArrangement = Arrangement.spacedBy(16.dp)
+ verticalArrangement = Arrangement.spacedBy(24.dp)
) {
- Text("Starting life: $startingLife")
- Slider(
- value = startingLife.toFloat(),
- onValueChange = {
- val snapped = ((it / 5f).roundToInt() * 5).coerceIn(10, 40)
- startingLife = snapped
- },
- valueRange = 10f..40f,
- steps = 5
- )
- Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
- Text("Players: $playerCount")
- }
- Slider(value = playerCount.toFloat(), onValueChange = { playerCount = it.toInt() }, valueRange = 2f..8f, steps = 6)
-
- Row(verticalAlignment = Alignment.CenterVertically) {
- Checkbox(checked = trackCommander, onCheckedChange = { trackCommander = it })
- Text("Track commander damage")
- }
- Row(verticalAlignment = Alignment.CenterVertically) {
- Checkbox(checked = trackPoison, onCheckedChange = { trackPoison = it })
- Text("Track poison")
- }
-
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(),
- value = matchName,
- onValueChange = { matchName = it },
- label = { Text("Match name") }
- )
-
- Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
- names.forEachIndexed { index, value ->
- OutlinedTextField(
- modifier = Modifier.fillMaxWidth(),
- value = value,
- onValueChange = { names[index] = it },
- label = { Text("Player ${index + 1} name") }
- )
- }
- }
-
- Spacer(modifier = Modifier.height(8.dp))
- val canStart = matchName.isNotBlank()
- Button(onClick = {
- val players = names.mapIndexed { index, name ->
- PlayerState(
- id = index,
- name = name.ifBlank { defaultPlayerName(index) },
- life = startingLife,
- poison = 0,
- commanderDamages = emptyMap()
- )
- }
- onStart(
- matchName,
- GameState(
- players = players,
- startingLife = startingLife,
- trackPoison = trackPoison,
- trackCommanderDamage = trackCommander
- )
+ // Game Settings Section
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Text(
+ "Game Settings",
+ style = androidx.compose.material3.MaterialTheme.typography.headlineSmall,
+ color = androidx.compose.material3.MaterialTheme.colorScheme.primary
)
- }, enabled = canStart) {
- Text("Start game")
+
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Text(
+ "Starting life: $startingLife",
+ style = androidx.compose.material3.MaterialTheme.typography.titleMedium
+ )
+ Slider(
+ value = startingLife.toFloat(),
+ onValueChange = {
+ val snapped = ((it / 5f).roundToInt() * 5).coerceIn(10, 40)
+ startingLife = snapped
+ },
+ valueRange = 10f..40f,
+ steps = 5
+ )
+ }
+
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Text(
+ "Players: $playerCount",
+ style = androidx.compose.material3.MaterialTheme.typography.titleMedium
+ )
+ Slider(
+ value = playerCount.toFloat(),
+ onValueChange = { playerCount = it.toInt() },
+ valueRange = 2f..8f,
+ steps = 6
+ )
+ }
+ }
+ }
+
+ // Game Options Section
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Text(
+ "Game Options",
+ style = androidx.compose.material3.MaterialTheme.typography.headlineSmall,
+ color = androidx.compose.material3.MaterialTheme.colorScheme.primary
+ )
+
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Checkbox(checked = trackCommander, onCheckedChange = { trackCommander = it })
+ Text(
+ "Track commander damage",
+ style = androidx.compose.material3.MaterialTheme.typography.bodyLarge
+ )
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Checkbox(checked = trackPoison, onCheckedChange = { trackPoison = it })
+ Text(
+ "Track poison",
+ style = androidx.compose.material3.MaterialTheme.typography.bodyLarge
+ )
+ }
+ }
+ }
+
+ // Match Details Section
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Text(
+ "Match Details",
+ style = androidx.compose.material3.MaterialTheme.typography.headlineSmall,
+ color = androidx.compose.material3.MaterialTheme.colorScheme.primary
+ )
+
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = matchName,
+ onValueChange = { matchName = it },
+ label = { Text("Match name") },
+ singleLine = true
+ )
+
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ names.forEachIndexed { index, value ->
+ OutlinedTextField(
+ modifier = Modifier.fillMaxWidth(),
+ value = value,
+ onValueChange = { names[index] = it },
+ label = { Text("Player ${index + 1} name") },
+ singleLine = true
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Note: The actual start button is now handled by a FAB in the parent Scaffold
+ // This button is kept for accessibility and as a fallback
+ val canStart = matchName.isNotBlank()
+ Button(
+ onClick = {
+ val players = names.mapIndexed { index, name ->
+ PlayerState(
+ id = index,
+ name = name.ifBlank { defaultPlayerName(index) },
+ life = startingLife,
+ poison = 0,
+ commanderDamages = emptyMap()
+ )
+ }
+ onStart(
+ matchName,
+ GameState(
+ players = players,
+ startingLife = startingLife,
+ trackPoison = trackPoison,
+ trackCommanderDamage = trackCommander
+ )
+ )
+ },
+ enabled = canStart,
+ modifier = Modifier.fillMaxWidth(),
+ contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp)
+ ) {
+ Icon(Icons.Default.PlayArrow, contentDescription = null)
+ Spacer(modifier = Modifier.padding(8.dp))
+ Text("Start Game", style = androidx.compose.material3.MaterialTheme.typography.titleMedium)
}
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f1d195c..3d74cbe 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.9.0"
+agp = "8.12.1"
kotlin = "2.0.21"
coreKtx = "1.10.1"
junit = "4.13.2"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7461975..7b9be93 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Sat Aug 09 23:53:05 MDT 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists