diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a59006e..5644c10 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { applicationId = "com.atridad.openclimb" minSdk = 34 targetSdk = 36 - versionCode = 18 - versionName = "1.2.0" + versionCode = 19 + versionName = "1.3.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/atridad/openclimb/ui/components/LineChart.kt b/app/src/main/java/com/atridad/openclimb/ui/components/LineChart.kt index 3f5809c..ac62798 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/components/LineChart.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/components/LineChart.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -31,12 +32,12 @@ data class ChartDataPoint( * Configuration for chart styling */ data class ChartStyle( - val lineColor: Color = Color(0xFF6366F1), - val fillColor: Color = Color(0x336366F1), + val lineColor: Color, + val fillColor: Color, val lineWidth: Float = 3f, - val gridColor: Color = Color(0xFFE5E7EB), - val textColor: Color = Color(0xFF374151), - val backgroundColor: Color = Color.White + val gridColor: Color, + val textColor: Color, + val backgroundColor: Color ) /** @@ -46,7 +47,13 @@ data class ChartStyle( fun LineChart( data: List, modifier: Modifier = Modifier, - style: ChartStyle = ChartStyle(), + style: ChartStyle = ChartStyle( + lineColor = MaterialTheme.colorScheme.primary, + fillColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f), + gridColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f), + textColor = MaterialTheme.colorScheme.onSurfaceVariant, + backgroundColor = MaterialTheme.colorScheme.surface + ), showGrid: Boolean = true, xAxisFormatter: (Float) -> String = { it.toString() }, yAxisFormatter: (Float) -> String = { it.toString() } @@ -136,18 +143,27 @@ fun LineChart( ) } - // Draw data points + // Draw data points - more pronounced screenPoints.forEach { point -> + // Draw outer circle (larger) drawCircle( color = style.lineColor, - radius = style.lineWidth * 1.5f, + radius = 8f, center = point ) + // Draw inner circle (white center) drawCircle( color = style.backgroundColor, - radius = style.lineWidth * 0.8f, + radius = 5f, center = point ) + // Draw border for better visibility + drawCircle( + color = style.lineColor, + radius = 8f, + center = point, + style = Stroke(width = 2f) + ) } } } @@ -190,18 +206,7 @@ private fun DrawScope.drawGrid( strokeWidth = 1.dp.toPx() ) - // Draw label - val text = xAxisFormatter(sessionNum.toFloat()) - val textSize = textMeasurer.measure(text, textStyle) - drawText( - textMeasurer = textMeasurer, - text = text, - style = textStyle, - topLeft = Offset( - x - textSize.size.width / 2f, - padding + chartHeight + 8.dp.toPx() - ) - ) + // X-axis labels removed per user request } } diff --git a/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt b/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt index fb64ea4..abadcea 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt @@ -543,11 +543,18 @@ fun AddEditProblemScreen( if (selectedDifficultySystem == DifficultySystem.CUSTOM) { OutlinedTextField( value = difficultyGrade, - onValueChange = { difficultyGrade = it }, + onValueChange = { newValue -> + // Only allow integers for custom scales + if (newValue.isEmpty() || newValue.all { it.isDigit() }) { + difficultyGrade = newValue + } + }, label = { Text("Grade *") }, modifier = Modifier.fillMaxWidth(), singleLine = true, - placeholder = { Text("Enter custom grade") } + placeholder = { Text("Enter numeric grade (e.g. 5, 10, 15)") }, + supportingText = { Text("Custom grades must be whole numbers") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) } else { var expanded by remember { mutableStateOf(false) } diff --git a/app/src/main/java/com/atridad/openclimb/ui/screens/AnalyticsScreen.kt b/app/src/main/java/com/atridad/openclimb/ui/screens/AnalyticsScreen.kt index 4532466..56e007f 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/screens/AnalyticsScreen.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/screens/AnalyticsScreen.kt @@ -456,7 +456,10 @@ fun gradeToNumeric(system: DifficultySystem, grade: String): Int { else -> 0 } } - DifficultySystem.CUSTOM -> 0 + DifficultySystem.CUSTOM -> { + // Custom grades are numeric strings, so parse them directly + grade.toIntOrNull() ?: 0 + } } } diff --git a/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt b/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt index c7f72fb..f693631 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt @@ -3,6 +3,7 @@ package com.atridad.openclimb.ui.screens import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -25,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewModelScope @@ -35,7 +37,6 @@ import com.atridad.openclimb.ui.theme.CustomIcons import com.atridad.openclimb.ui.viewmodel.ClimbViewModel import java.time.LocalDateTime import java.time.format.DateTimeFormatter -import kotlin.math.roundToInt import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -291,8 +292,8 @@ fun SessionDetailScreen( // Show stop icon for active sessions, delete icon for completed sessions if (session?.status == SessionStatus.ACTIVE) { - IconButton(onClick = { - session?.let { s -> + IconButton(onClick = { + session.let { s -> viewModel.endSession(context, s.id) onNavigateBack() } @@ -916,11 +917,6 @@ fun GymDetailScreen( problems.any { problem -> problem.id == attempt.problemId } } - val successfulAttempts = - gymAttempts.filter { it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) } - - - val uniqueProblemsClimbed = gymAttempts.map { it.problemId }.toSet().size val totalSessions = sessions.size val activeSessions = sessions.count { it.status == SessionStatus.ACTIVE } @@ -1556,76 +1552,6 @@ private fun formatDate(dateString: String): String { } } -/** - * Calculate average grade for a specific set of problems, respecting their difficulty systems - */ -private fun calculateAverageGrade(problems: List): String? { - if (problems.isEmpty()) return null - - // Group problems by difficulty system - val problemsBySystem = problems.groupBy { it.difficulty.system } - - val averages = mutableListOf() - - problemsBySystem.forEach { (system, systemProblems) -> - when (system) { - DifficultySystem.V_SCALE -> { - val gradeValues = systemProblems.mapNotNull { problem -> - when { - problem.difficulty.grade == "VB" -> 0 - else -> problem.difficulty.grade.removePrefix("V").toIntOrNull() - } - } - if (gradeValues.isNotEmpty()) { - val avg = gradeValues.average().roundToInt() - averages.add(if (avg == 0) "VB" else "V$avg") - } - } - DifficultySystem.FONT -> { - val gradeValues = systemProblems.mapNotNull { problem -> - // Extract numeric part from Font grades (e.g., "6A" -> 6, "7C+" -> 7) - problem.difficulty.grade.filter { it.isDigit() }.toIntOrNull() - } - if (gradeValues.isNotEmpty()) { - val avg = gradeValues.average().roundToInt() - averages.add("$avg") - } - } - DifficultySystem.YDS -> { - val gradeValues = systemProblems.mapNotNull { problem -> - // Extract numeric part from YDS grades (e.g., "5.10a" -> 5.10) - val grade = problem.difficulty.grade - if (grade.startsWith("5.")) { - grade.substring(2).toDoubleOrNull() - } else null - } - if (gradeValues.isNotEmpty()) { - val avg = gradeValues.average() - averages.add("5.${String.format("%.1f", avg)}") - } - } - DifficultySystem.CUSTOM -> { - // For custom systems, try to extract numeric values - val gradeValues = systemProblems.mapNotNull { problem -> - problem.difficulty.grade.filter { it.isDigit() || it == '.' || it == '-' }.toDoubleOrNull() - } - if (gradeValues.isNotEmpty()) { - val avg = gradeValues.average() - averages.add(String.format("%.1f", avg)) - } - } - } - } - - return if (averages.isNotEmpty()) { - if (averages.size == 1) { - averages.first() - } else { - averages.joinToString(" / ") - } - } else null -} - @OptIn(ExperimentalMaterial3Api::class) @Composable fun EnhancedAddAttemptDialog( @@ -1835,8 +1761,12 @@ fun EnhancedAddAttemptDialog( color = MaterialTheme.colorScheme.onSurface ) - TextButton(onClick = { showCreateProblem = false }) { - Text("← Back", color = MaterialTheme.colorScheme.primary) + IconButton(onClick = { showCreateProblem = false }) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) } } @@ -1930,9 +1860,14 @@ fun EnhancedAddAttemptDialog( if (selectedDifficultySystem == DifficultySystem.CUSTOM) { OutlinedTextField( value = newProblemGrade, - onValueChange = { newProblemGrade = it }, + onValueChange = { newValue -> + // Only allow integers for custom scales + if (newValue.isEmpty() || newValue.all { it.isDigit() }) { + newProblemGrade = newValue + } + }, label = { Text("Grade *") }, - placeholder = { Text("Enter custom grade") }, + placeholder = { Text("Enter numeric grade (e.g. 5, 10, 15)") }, modifier = Modifier.fillMaxWidth(), singleLine = true, colors = @@ -1943,6 +1878,7 @@ fun EnhancedAddAttemptDialog( MaterialTheme.colorScheme.outline ), isError = newProblemGrade.isBlank(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), supportingText = if (newProblemGrade.isBlank()) { { @@ -1951,7 +1887,14 @@ fun EnhancedAddAttemptDialog( color = MaterialTheme.colorScheme.error ) } - } else null + } else { + { + Text( + "Custom grades must be whole numbers", + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } ) } else { var expanded by remember { mutableStateOf(false) }