From cf2e2f7c571fd089e7be7105ac5e6ed8a7205715 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Sat, 27 Sep 2025 18:47:09 -0600 Subject: [PATCH] New builds for iOS 1.0.3 and Android 1.5.1 --- android/app/build.gradle.kts | 2 +- .../openclimb/data/database/dao/ProblemDao.kt | 72 +- .../atridad/openclimb/data/model/Problem.kt | 102 +- .../data/repository/ClimbRepository.kt | 6 +- .../openclimb/ui/screens/AddEditScreens.kt | 986 +++++++++--------- .../openclimb/ui/screens/DetailScreens.kt | 9 - .../openclimb/ui/screens/ProblemsScreen.kt | 322 +++--- .../openclimb/utils/SessionShareUtils.kt | 529 ++++++---- ios/OpenClimb.xcodeproj/project.pbxproj | 8 +- .../UserInterfaceState.xcuserstate | Bin 114205 -> 115423 bytes .../xcshareddata/xcschemes/OpenClimb.xcscheme | 0 .../SessionStatusLiveExtension.xcscheme | 114 ++ .../xcschemes/xcschememanagement.plist | 15 +- ios/OpenClimb/Models/DataModels.swift | 18 +- .../ViewModels/ClimbingDataManager.swift | 12 +- .../Views/AddEdit/AddAttemptView.swift | 6 - .../Views/AddEdit/AddEditProblemView.swift | 54 +- .../Views/Detail/GymDetailView.swift | 5 +- .../Views/Detail/ProblemDetailView.swift | 11 +- .../Views/Detail/SessionDetailView.swift | 1 - ios/OpenClimb/Views/GymsView.swift | 6 +- ios/OpenClimb/Views/ProblemsView.swift | 29 +- 22 files changed, 1272 insertions(+), 1035 deletions(-) create mode 100644 ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/OpenClimb.xcscheme create mode 100644 ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/SessionStatusLiveExtension.xcscheme diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a777f93..1b83931 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -16,7 +16,7 @@ android { applicationId = "com.atridad.openclimb" minSdk = 31 targetSdk = 36 - versionCode = 25 + versionCode = 26 versionName = "1.5.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/android/app/src/main/java/com/atridad/openclimb/data/database/dao/ProblemDao.kt b/android/app/src/main/java/com/atridad/openclimb/data/database/dao/ProblemDao.kt index 19697e4..6546258 100644 --- a/android/app/src/main/java/com/atridad/openclimb/data/database/dao/ProblemDao.kt +++ b/android/app/src/main/java/com/atridad/openclimb/data/database/dao/ProblemDao.kt @@ -7,62 +7,58 @@ import kotlinx.coroutines.flow.Flow @Dao interface ProblemDao { - + @Query("SELECT * FROM problems ORDER BY updatedAt DESC") fun getAllProblems(): Flow> - - @Query("SELECT * FROM problems WHERE id = :id") - suspend fun getProblemById(id: String): Problem? - + + @Query("SELECT * FROM problems WHERE id = :id") suspend fun getProblemById(id: String): Problem? + @Query("SELECT * FROM problems WHERE gymId = :gymId ORDER BY updatedAt DESC") fun getProblemsByGym(gymId: String): Flow> - + @Query("SELECT * FROM problems WHERE climbType = :climbType ORDER BY updatedAt DESC") fun getProblemsByClimbType(climbType: ClimbType): Flow> - - @Query("SELECT * FROM problems WHERE gymId = :gymId AND climbType = :climbType ORDER BY updatedAt DESC") + + @Query( + "SELECT * FROM problems WHERE gymId = :gymId AND climbType = :climbType ORDER BY updatedAt DESC" + ) fun getProblemsByGymAndType(gymId: String, climbType: ClimbType): Flow> - + @Query("SELECT * FROM problems WHERE isActive = 1 ORDER BY updatedAt DESC") fun getActiveProblems(): Flow> - + @Query("SELECT * FROM problems WHERE gymId = :gymId AND isActive = 1 ORDER BY updatedAt DESC") fun getActiveProblemsByGym(gymId: String): Flow> - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertProblem(problem: Problem) - + + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertProblem(problem: Problem) + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertProblems(problems: List) - - @Update - suspend fun updateProblem(problem: Problem) - - @Delete - suspend fun deleteProblem(problem: Problem) - - @Query("DELETE FROM problems WHERE id = :id") - suspend fun deleteProblemById(id: String) - + + @Update suspend fun updateProblem(problem: Problem) + + @Delete suspend fun deleteProblem(problem: Problem) + + @Query("DELETE FROM problems WHERE id = :id") suspend fun deleteProblemById(id: String) + @Query("SELECT COUNT(*) FROM problems WHERE gymId = :gymId") suspend fun getProblemsCountByGym(gymId: String): Int - + @Query("SELECT COUNT(*) FROM problems WHERE isActive = 1") suspend fun getActiveProblemsCount(): Int - - @Query(""" - SELECT * FROM problems - WHERE (name LIKE '%' || :searchQuery || '%' + + @Query( + """ + SELECT * FROM problems + WHERE (name LIKE '%' || :searchQuery || '%' OR description LIKE '%' || :searchQuery || '%' - OR location LIKE '%' || :searchQuery || '%' - OR setter LIKE '%' || :searchQuery || '%') + OR location LIKE '%' || :searchQuery || '%') ORDER BY updatedAt DESC - """) + """ + ) fun searchProblems(searchQuery: String): Flow> - - @Query("SELECT COUNT(*) FROM problems") - suspend fun getProblemsCount(): Int - - @Query("DELETE FROM problems") - suspend fun deleteAllProblems() + + @Query("SELECT COUNT(*) FROM problems") suspend fun getProblemsCount(): Int + + @Query("DELETE FROM problems") suspend fun deleteAllProblems() } diff --git a/android/app/src/main/java/com/atridad/openclimb/data/model/Problem.kt b/android/app/src/main/java/com/atridad/openclimb/data/model/Problem.kt index 80463e9..1116f7f 100644 --- a/android/app/src/main/java/com/atridad/openclimb/data/model/Problem.kt +++ b/android/app/src/main/java/com/atridad/openclimb/data/model/Problem.kt @@ -4,71 +4,67 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey -import kotlinx.serialization.Serializable import java.time.LocalDateTime +import kotlinx.serialization.Serializable @Entity( - tableName = "problems", - foreignKeys = [ - ForeignKey( - entity = Gym::class, - parentColumns = ["id"], - childColumns = ["gymId"], - onDelete = ForeignKey.CASCADE - ) - ], - indices = [Index(value = ["gymId"])] + tableName = "problems", + foreignKeys = + [ + ForeignKey( + entity = Gym::class, + parentColumns = ["id"], + childColumns = ["gymId"], + onDelete = ForeignKey.CASCADE + )], + indices = [Index(value = ["gymId"])] ) @Serializable data class Problem( - @PrimaryKey - val id: String, - val gymId: String, - val name: String? = null, - val description: String? = null, - val climbType: ClimbType, - val difficulty: DifficultyGrade, - val setter: String? = null, - val tags: List = emptyList(), - val location: String? = null, - val imagePaths: List = emptyList(), - val isActive: Boolean = true, - val dateSet: String? = null, - val notes: String? = null, - val createdAt: String, - val updatedAt: String + @PrimaryKey val id: String, + val gymId: String, + val name: String? = null, + val description: String? = null, + val climbType: ClimbType, + val difficulty: DifficultyGrade, + val tags: List = emptyList(), + val location: String? = null, + val imagePaths: List = emptyList(), + val isActive: Boolean = true, + val dateSet: String? = null, + val notes: String? = null, + val createdAt: String, + val updatedAt: String ) { companion object { fun create( - gymId: String, - name: String? = null, - description: String? = null, - climbType: ClimbType, - difficulty: DifficultyGrade, - setter: String? = null, - tags: List = emptyList(), - location: String? = null, - imagePaths: List = emptyList(), - dateSet: String? = null, - notes: String? = null + gymId: String, + name: String? = null, + description: String? = null, + climbType: ClimbType, + difficulty: DifficultyGrade, + tags: List = emptyList(), + location: String? = null, + imagePaths: List = emptyList(), + dateSet: String? = null, + notes: String? = null ): Problem { val now = LocalDateTime.now().toString() return Problem( - id = java.util.UUID.randomUUID().toString(), - gymId = gymId, - name = name, - description = description, - climbType = climbType, - difficulty = difficulty, - setter = setter, - tags = tags, - location = location, - imagePaths = imagePaths, - isActive = true, - dateSet = dateSet, - notes = notes, - createdAt = now, - updatedAt = now + id = java.util.UUID.randomUUID().toString(), + gymId = gymId, + name = name, + description = description, + climbType = climbType, + difficulty = difficulty, + tags = tags, + location = location, + imagePaths = imagePaths, + isActive = true, + dateSet = dateSet, + notes = notes, + createdAt = now, + updatedAt = now ) } } diff --git a/android/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt b/android/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt index 0670e30..e4d3e0d 100644 --- a/android/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt +++ b/android/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt @@ -82,7 +82,7 @@ class ClimbRepository(database: OpenClimbDatabase, private val context: Context) val exportData = ClimbDataExport( exportedAt = LocalDateTime.now().toString(), - version = "1.0", + version = "2.0", gyms = allGyms, problems = allProblems, sessions = allSessions, @@ -141,7 +141,7 @@ class ClimbRepository(database: OpenClimbDatabase, private val context: Context) val exportData = ClimbDataExport( exportedAt = LocalDateTime.now().toString(), - version = "1.0", + version = "2.0", gyms = allGyms, problems = allProblems, sessions = allSessions, @@ -343,7 +343,7 @@ class ClimbRepository(database: OpenClimbDatabase, private val context: Context) @kotlinx.serialization.Serializable data class ClimbDataExport( val exportedAt: String, - val version: String = "1.0", + val version: String = "2.0", val gyms: List, val problems: List, val sessions: List, diff --git a/android/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt b/android/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt index abadcea..8fea3d0 100644 --- a/android/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt +++ b/android/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt @@ -12,46 +12,44 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment 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.platform.LocalContext import com.atridad.openclimb.data.model.* import com.atridad.openclimb.ui.components.ImagePicker import com.atridad.openclimb.ui.viewmodel.ClimbViewModel -import kotlinx.coroutines.flow.first import java.time.LocalDateTime +import kotlinx.coroutines.flow.first @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AddEditGymScreen( - gymId: String?, - viewModel: ClimbViewModel, - onNavigateBack: () -> Unit -) { +fun AddEditGymScreen(gymId: String?, viewModel: ClimbViewModel, onNavigateBack: () -> Unit) { var name by remember { mutableStateOf("") } var location by remember { mutableStateOf("") } var notes by remember { mutableStateOf("") } var selectedClimbTypes by remember { mutableStateOf(setOf()) } var selectedDifficultySystems by remember { mutableStateOf(setOf()) } - + val isEditing = gymId != null - + // Calculate available difficulty systems based on selected climb types - val availableDifficultySystems = if (selectedClimbTypes.isEmpty()) { - emptyList() - } else { - selectedClimbTypes.flatMap { climbType -> - DifficultySystem.getSystemsForClimbType(climbType) - }.distinct() - } - + val availableDifficultySystems = + if (selectedClimbTypes.isEmpty()) { + emptyList() + } else { + selectedClimbTypes + .flatMap { climbType -> DifficultySystem.getSystemsForClimbType(climbType) } + .distinct() + } + // Reset selected difficulty systems when available systems change LaunchedEffect(availableDifficultySystems) { - selectedDifficultySystems = selectedDifficultySystems.filter { it in availableDifficultySystems }.toSet() + selectedDifficultySystems = + selectedDifficultySystems.filter { it in availableDifficultySystems }.toSet() } - + // Load existing gym data for editing LaunchedEffect(gymId) { if (gymId != null) { @@ -65,96 +63,105 @@ fun AddEditGymScreen( } } } - + Scaffold( - topBar = { - TopAppBar( - title = { Text(if (isEditing) "Edit Gym" else "Add Gym") }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - }, - actions = { - TextButton( - onClick = { - val gym = Gym.create(name, location, selectedClimbTypes.toList(), selectedDifficultySystems.toList(), notes = notes) - - if (isEditing) { - viewModel.updateGym(gym.copy(id = gymId!!)) - } else { - viewModel.addGym(gym) + topBar = { + TopAppBar( + title = { Text(if (isEditing) "Edit Gym" else "Add Gym") }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" + ) } - onNavigateBack() }, - enabled = name.isNotBlank() && selectedClimbTypes.isNotEmpty() && selectedDifficultySystems.isNotEmpty() - ) { - Text("Save") - } - } - ) - } + actions = { + TextButton( + onClick = { + val gym = + Gym.create( + name, + location, + selectedClimbTypes.toList(), + selectedDifficultySystems.toList(), + notes = notes + ) + + if (isEditing) { + viewModel.updateGym(gym.copy(id = gymId!!)) + } else { + viewModel.addGym(gym) + } + onNavigateBack() + }, + enabled = + name.isNotBlank() && + selectedClimbTypes.isNotEmpty() && + selectedDifficultySystems.isNotEmpty() + ) { Text("Save") } + } + ) + } ) { paddingValues -> Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + modifier = Modifier.fillMaxSize().padding(paddingValues).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Name field OutlinedTextField( - value = name, - onValueChange = { name = it }, - label = { Text("Gym Name") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true + value = name, + onValueChange = { name = it }, + label = { Text("Gym Name") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) - + // Location field OutlinedTextField( - value = location, - onValueChange = { location = it }, - label = { Text("Location (Optional)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true + value = location, + onValueChange = { location = it }, + label = { Text("Location (Optional)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) - + // Climb Types - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Supported Climb Types", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Supported Climb Types", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - + ClimbType.entries.forEach { climbType -> Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = climbType in selectedClimbTypes, - onClick = { - selectedClimbTypes = if (climbType in selectedClimbTypes) { - selectedClimbTypes - climbType - } else { - selectedClimbTypes + climbType - } - }, - role = Role.Checkbox - ) + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth() + .selectable( + selected = climbType in selectedClimbTypes, + onClick = { + selectedClimbTypes = + if (climbType in + selectedClimbTypes + ) { + selectedClimbTypes - + climbType + } else { + selectedClimbTypes + + climbType + } + }, + role = Role.Checkbox + ) ) { Checkbox( - checked = climbType in selectedClimbTypes, - onCheckedChange = null + checked = climbType in selectedClimbTypes, + onCheckedChange = null ) Spacer(modifier = Modifier.width(8.dp)) Text(climbType.getDisplayName()) @@ -162,50 +169,54 @@ fun AddEditGymScreen( } } } - + // Difficulty Systems - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Difficulty Systems", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Difficulty Systems", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - + if (selectedClimbTypes.isEmpty()) { Text( - text = "Select climb types first to see available difficulty systems", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(vertical = 8.dp) + text = + "Select climb types first to see available difficulty systems", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(vertical = 8.dp) ) } else { availableDifficultySystems.forEach { system -> Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = system in selectedDifficultySystems, - onClick = { - selectedDifficultySystems = if (system in selectedDifficultySystems) { - selectedDifficultySystems - system - } else { - selectedDifficultySystems + system - } - }, - role = Role.Checkbox - ) + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth() + .selectable( + selected = + system in + selectedDifficultySystems, + onClick = { + selectedDifficultySystems = + if (system in + selectedDifficultySystems + ) { + selectedDifficultySystems - + system + } else { + selectedDifficultySystems + + system + } + }, + role = Role.Checkbox + ) ) { Checkbox( - checked = system in selectedDifficultySystems, - onCheckedChange = null + checked = system in selectedDifficultySystems, + onCheckedChange = null ) Spacer(modifier = Modifier.width(8.dp)) Text(system.getDisplayName()) @@ -214,14 +225,14 @@ fun AddEditGymScreen( } } } - + // Notes field OutlinedTextField( - value = notes, - onValueChange = { notes = it }, - label = { Text("Notes (Optional)") }, - modifier = Modifier.fillMaxWidth(), - minLines = 3 + value = notes, + onValueChange = { notes = it }, + label = { Text("Notes (Optional)") }, + modifier = Modifier.fillMaxWidth(), + minLines = 3 ) } } @@ -230,28 +241,30 @@ fun AddEditGymScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddEditProblemScreen( - problemId: String?, - gymId: String?, - viewModel: ClimbViewModel, - onNavigateBack: () -> Unit + problemId: String?, + gymId: String?, + viewModel: ClimbViewModel, + onNavigateBack: () -> Unit ) { val isEditing = problemId != null val gyms by viewModel.gyms.collectAsState() - + // Problem form state - var selectedGym by remember { mutableStateOf(gymId?.let { id -> gyms.find { it.id == id } }) } + var selectedGym by remember { + mutableStateOf(gymId?.let { id -> gyms.find { it.id == id } }) + } var problemName by remember { mutableStateOf("") } var description by remember { mutableStateOf("") } var selectedClimbType by remember { mutableStateOf(ClimbType.BOULDER) } var selectedDifficultySystem by remember { mutableStateOf(DifficultySystem.V_SCALE) } var difficultyGrade by remember { mutableStateOf("") } - var setter by remember { mutableStateOf("") } + var location by remember { mutableStateOf("") } var tags by remember { mutableStateOf("") } var notes by remember { mutableStateOf("") } var isActive by remember { mutableStateOf(true) } var imagePaths by remember { mutableStateOf>(emptyList()) } - + // Load existing problem data for editing LaunchedEffect(problemId) { if (problemId != null) { @@ -262,7 +275,7 @@ fun AddEditProblemScreen( selectedClimbType = p.climbType selectedDifficultySystem = p.difficulty.system difficultyGrade = p.difficulty.grade - setter = p.setter ?: "" + location = p.location ?: "" tags = p.tags.joinToString(", ") notes = p.notes ?: "" @@ -272,39 +285,42 @@ fun AddEditProblemScreen( } } } - + LaunchedEffect(gymId, gyms) { if (gymId != null && selectedGym == null) { selectedGym = gyms.find { it.id == gymId } } } - + val availableClimbTypes = selectedGym?.supportedClimbTypes ?: ClimbType.entries.toList() - val availableDifficultySystems = DifficultySystem.getSystemsForClimbType(selectedClimbType).filter { system -> - selectedGym?.difficultySystems?.contains(system) != false - } - + val availableDifficultySystems = + DifficultySystem.getSystemsForClimbType(selectedClimbType).filter { system -> + selectedGym?.difficultySystems?.contains(system) != false + } + // Auto-select climb type if there's only one available LaunchedEffect(availableClimbTypes) { if (availableClimbTypes.size == 1 && selectedClimbType != availableClimbTypes.first()) { selectedClimbType = availableClimbTypes.first() } } - + // Auto-select or reset difficulty system based on climb type LaunchedEffect(selectedClimbType, availableDifficultySystems) { when { // If current system is not compatible, select the first available one selectedDifficultySystem !in availableDifficultySystems -> { - selectedDifficultySystem = availableDifficultySystems.firstOrNull() ?: DifficultySystem.CUSTOM + selectedDifficultySystem = + availableDifficultySystems.firstOrNull() ?: DifficultySystem.CUSTOM } // If there's only one available system and nothing is selected, auto-select it - availableDifficultySystems.size == 1 && selectedDifficultySystem != availableDifficultySystems.first() -> { + availableDifficultySystems.size == 1 && + selectedDifficultySystem != availableDifficultySystems.first() -> { selectedDifficultySystem = availableDifficultySystems.first() } } } - + // Reset grade when difficulty system changes (unless it's a valid grade for the new system) LaunchedEffect(selectedDifficultySystem) { val availableGrades = selectedDifficultySystem.getAvailableGrades() @@ -312,96 +328,108 @@ fun AddEditProblemScreen( difficultyGrade = "" } } - + Scaffold( - topBar = { - TopAppBar( - title = { Text(if (isEditing) "Edit Problem" else "Add Problem") }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - }, - actions = { - TextButton( - onClick = { - selectedGym?.let { gym -> - val difficulty = DifficultyGrade( - system = selectedDifficultySystem, - grade = difficultyGrade, - numericValue = when (selectedDifficultySystem) { - DifficultySystem.V_SCALE -> difficultyGrade.removePrefix("V").toIntOrNull() ?: 0 - else -> difficultyGrade.hashCode() % 100 // Simple mapping for other systems - } + topBar = { + TopAppBar( + title = { Text(if (isEditing) "Edit Problem" else "Add Problem") }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" ) - - val problem = Problem.create( - gymId = gym.id, - name = problemName.ifBlank { null }, - description = description.ifBlank { null }, - climbType = selectedClimbType, - difficulty = difficulty, - setter = setter.ifBlank { null }, - tags = tags.split(",").map { it.trim() }.filter { it.isNotBlank() }, - location = location.ifBlank { null }, - imagePaths = imagePaths, - notes = notes.ifBlank { null } - ) - - if (isEditing) { - viewModel.updateProblem(problem.copy(id = problemId!!)) - } else { - viewModel.addProblem(problem) - } - onNavigateBack() } }, - enabled = selectedGym != null && difficultyGrade.isNotBlank() - ) { - Text("Save") - } - } - ) - } + actions = { + TextButton( + onClick = { + selectedGym?.let { gym -> + val difficulty = + DifficultyGrade( + system = selectedDifficultySystem, + grade = difficultyGrade, + numericValue = + when (selectedDifficultySystem + ) { + DifficultySystem.V_SCALE -> + difficultyGrade + .removePrefix( + "V" + ) + .toIntOrNull() + ?: 0 + else -> + difficultyGrade + .hashCode() % + 100 // Simple mapping for other systems + } + ) + + val problem = + Problem.create( + gymId = gym.id, + name = problemName.ifBlank { null }, + description = + description.ifBlank { null }, + climbType = selectedClimbType, + difficulty = difficulty, + tags = + tags.split(",") + .map { it.trim() } + .filter { + it.isNotBlank() + }, + location = location.ifBlank { null }, + imagePaths = imagePaths, + notes = notes.ifBlank { null } + ) + + if (isEditing) { + viewModel.updateProblem( + problem.copy(id = problemId!!) + ) + } else { + viewModel.addProblem(problem) + } + onNavigateBack() + } + }, + enabled = selectedGym != null && difficultyGrade.isNotBlank() + ) { Text("Save") } + } + ) + } ) { paddingValues -> LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + modifier = Modifier.fillMaxSize().padding(paddingValues).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Gym Selection item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Select Gym", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Select Gym", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - + if (gyms.isEmpty()) { Text( - text = "No gyms available. Add a gym first.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.error + text = "No gyms available. Add a gym first.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error ) } else { - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(gyms) { gym -> FilterChip( - onClick = { selectedGym = gym }, - label = { Text(gym.name) }, - selected = selectedGym?.id == gym.id + onClick = { selectedGym = gym }, + label = { Text(gym.name) }, + selected = selectedGym?.id == gym.id ) } } @@ -409,92 +437,74 @@ fun AddEditProblemScreen( } } } - + // Basic Problem Info item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Problem Details", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Problem Details", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(16.dp)) - + OutlinedTextField( - value = problemName, - onValueChange = { problemName = it }, - label = { Text("Problem Name (Optional)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - placeholder = { Text("e.g., 'The Overhang Monster', 'Yellow V4'") } + value = problemName, + onValueChange = { problemName = it }, + label = { Text("Problem Name (Optional)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + placeholder = { Text("e.g., 'The Overhang Monster', 'Yellow V4'") } ) - + Spacer(modifier = Modifier.height(8.dp)) - + OutlinedTextField( - value = description, - onValueChange = { description = it }, - label = { Text("Description (Optional)") }, - modifier = Modifier.fillMaxWidth(), - minLines = 2, - placeholder = { Text("Describe the problem, holds, style, etc.") } + value = description, + onValueChange = { description = it }, + label = { Text("Description (Optional)") }, + modifier = Modifier.fillMaxWidth(), + minLines = 2, + placeholder = { Text("Describe the problem, holds, style, etc.") } ) - + Spacer(modifier = Modifier.height(8.dp)) - - OutlinedTextField( - value = setter, - onValueChange = { setter = it }, - label = { Text("Route Setter (Optional)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true - ) - + Spacer(modifier = Modifier.height(8.dp)) - + OutlinedTextField( - value = location, - onValueChange = { location = it }, - label = { Text("Location (Optional)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - placeholder = { Text("e.g., 'Cave area', 'Wall 3', 'Right side'") } + value = location, + onValueChange = { location = it }, + label = { Text("Location (Optional)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + placeholder = { Text("e.g., 'Cave area', 'Wall 3', 'Right side'") } ) } } } - + // Climb Type if (selectedGym != null) { item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Climb Type", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Climb Type", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { availableClimbTypes.forEach { climbType -> FilterChip( - onClick = { selectedClimbType = climbType }, - label = { Text(climbType.getDisplayName()) }, - selected = selectedClimbType == climbType + onClick = { selectedClimbType = climbType }, + label = { Text(climbType.getDisplayName()) }, + selected = selectedClimbType == climbType ) } } @@ -502,91 +512,102 @@ fun AddEditProblemScreen( } } } - + // Difficulty if (selectedGym != null) { item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Difficulty", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Difficulty", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - + Text( - text = "Difficulty System", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Medium + text = "Difficulty System", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium ) - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(availableDifficultySystems) { system -> FilterChip( - onClick = { selectedDifficultySystem = system }, - label = { Text(system.getDisplayName()) }, - selected = selectedDifficultySystem == system + onClick = { selectedDifficultySystem = system }, + label = { Text(system.getDisplayName()) }, + selected = selectedDifficultySystem == system ) } } - + Spacer(modifier = Modifier.height(8.dp)) - + if (selectedDifficultySystem == DifficultySystem.CUSTOM) { OutlinedTextField( - value = difficultyGrade, - 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 numeric grade (e.g. 5, 10, 15)") }, - supportingText = { Text("Custom grades must be whole numbers") }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) + value = difficultyGrade, + 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 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) } val availableGrades = selectedDifficultySystem.getAvailableGrades() - + ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, - modifier = Modifier.fillMaxWidth() + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + modifier = Modifier.fillMaxWidth() ) { OutlinedTextField( - value = difficultyGrade, - onValueChange = { }, - readOnly = true, - label = { Text("Grade *") }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - modifier = Modifier - .menuAnchor(androidx.compose.material3.MenuAnchorType.PrimaryNotEditable, enabled = true) - .fillMaxWidth() + value = difficultyGrade, + onValueChange = {}, + readOnly = true, + label = { Text("Grade *") }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon( + expanded = expanded + ) + }, + colors = + ExposedDropdownMenuDefaults + .outlinedTextFieldColors(), + modifier = + Modifier.menuAnchor( + androidx.compose.material3 + .MenuAnchorType + .PrimaryNotEditable, + enabled = true + ) + .fillMaxWidth() ) ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } + expanded = expanded, + onDismissRequest = { expanded = false } ) { availableGrades.forEach { grade -> DropdownMenuItem( - text = { Text(grade) }, - onClick = { - difficultyGrade = grade - expanded = false - } + text = { Text(grade) }, + onClick = { + difficultyGrade = grade + expanded = false + } ) } } @@ -596,87 +617,76 @@ fun AddEditProblemScreen( } } } - + // Images Section item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Photos", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Photos", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(16.dp)) - + ImagePicker( - imageUris = imagePaths, - onImagesChanged = { imagePaths = it }, - maxImages = 5 + imageUris = imagePaths, + onImagesChanged = { imagePaths = it }, + maxImages = 5 ) } } } item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Additional Info", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Additional Info", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(16.dp)) - + OutlinedTextField( - value = tags, - onValueChange = { tags = it }, - label = { Text("Tags (Optional)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - placeholder = { Text("e.g., crimpy, dynamic (comma-separated)") } + value = tags, + onValueChange = { tags = it }, + label = { Text("Tags (Optional)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + placeholder = { Text("e.g., crimpy, dynamic (comma-separated)") } ) - + Spacer(modifier = Modifier.height(8.dp)) - + OutlinedTextField( - value = notes, - onValueChange = { notes = it }, - label = { Text("Notes (Optional)") }, - modifier = Modifier.fillMaxWidth(), - minLines = 3, - placeholder = { Text("Any additional notes about this problem") } + value = notes, + onValueChange = { notes = it }, + label = { Text("Notes (Optional)") }, + modifier = Modifier.fillMaxWidth(), + minLines = 3, + placeholder = { Text("Any additional notes about this problem") } ) - + Spacer(modifier = Modifier.height(16.dp)) - + Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = isActive, - onClick = { isActive = !isActive }, - role = Role.Checkbox - ) + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth() + .selectable( + selected = isActive, + onClick = { isActive = !isActive }, + role = Role.Checkbox + ) ) { - Checkbox( - checked = isActive, - onCheckedChange = null - ) + Checkbox(checked = isActive, onCheckedChange = null) Spacer(modifier = Modifier.width(8.dp)) Text( - text = "Problem is currently active", - style = MaterialTheme.typography.bodyMedium + text = "Problem is currently active", + style = MaterialTheme.typography.bodyMedium ) } } @@ -689,21 +699,23 @@ fun AddEditProblemScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable fun AddEditSessionScreen( - sessionId: String?, - gymId: String?, - viewModel: ClimbViewModel, - onNavigateBack: () -> Unit + sessionId: String?, + gymId: String?, + viewModel: ClimbViewModel, + onNavigateBack: () -> Unit ) { val isEditing = sessionId != null val gyms by viewModel.gyms.collectAsState() val context = LocalContext.current // Session form state - var selectedGym by remember { mutableStateOf(gymId?.let { id -> gyms.find { it.id == id } }) } + var selectedGym by remember { + mutableStateOf(gymId?.let { id -> gyms.find { it.id == id } }) + } var sessionDate by remember { mutableStateOf(LocalDateTime.now().toLocalDate().toString()) } var duration by remember { mutableStateOf("") } var sessionNotes by remember { mutableStateOf("") } - + // Load existing session data for editing LaunchedEffect(sessionId) { if (sessionId != null) { @@ -716,84 +728,86 @@ fun AddEditSessionScreen( } } } - + LaunchedEffect(gymId, gyms) { if (gymId != null && selectedGym == null) { selectedGym = gyms.find { it.id == gymId } } } - + Scaffold( - topBar = { - TopAppBar( - title = { Text(if (isEditing) "Edit Session" else "Add Session") }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - }, - actions = { - TextButton( - onClick = { - selectedGym?.let { gym -> - if (isEditing) { - val session = ClimbSession.create( - gymId = gym.id, - notes = sessionNotes.ifBlank { null } - ) - viewModel.updateSession(session.copy(id = sessionId!!)) - } else { - viewModel.startSession(context, gym.id, sessionNotes.ifBlank { null }) - } - onNavigateBack() + topBar = { + TopAppBar( + title = { Text(if (isEditing) "Edit Session" else "Add Session") }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" + ) } }, - enabled = selectedGym != null - ) { - Text("Save") - } - } - ) - } + actions = { + TextButton( + onClick = { + selectedGym?.let { gym -> + if (isEditing) { + val session = + ClimbSession.create( + gymId = gym.id, + notes = + sessionNotes.ifBlank { + null + } + ) + viewModel.updateSession( + session.copy(id = sessionId!!) + ) + } else { + viewModel.startSession( + context, + gym.id, + sessionNotes.ifBlank { null } + ) + } + onNavigateBack() + } + }, + enabled = selectedGym != null + ) { Text("Save") } + } + ) + } ) { paddingValues -> LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + modifier = Modifier.fillMaxSize().padding(paddingValues).padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Gym Selection item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Select Gym", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Select Gym", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(8.dp)) - + if (gyms.isEmpty()) { Text( - text = "No gyms available. Add a gym first.", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.error + text = "No gyms available. Add a gym first.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error ) } else { - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(gyms) { gym -> FilterChip( - onClick = { selectedGym = gym }, - label = { Text(gym.name) }, - selected = selectedGym?.id == gym.id + onClick = { selectedGym = gym }, + label = { Text(gym.name) }, + selected = selectedGym?.id == gym.id ) } } @@ -801,50 +815,47 @@ fun AddEditSessionScreen( } } } - + // Session Details item { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Session Details", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Session Details", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(16.dp)) - + OutlinedTextField( - value = sessionDate, - onValueChange = { sessionDate = it }, - label = { Text("Date (YYYY-MM-DD)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true + value = sessionDate, + onValueChange = { sessionDate = it }, + label = { Text("Date (YYYY-MM-DD)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true ) - + Spacer(modifier = Modifier.height(8.dp)) - + OutlinedTextField( - value = duration, - onValueChange = { duration = it }, - label = { Text("Duration (minutes)") }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) + value = duration, + onValueChange = { duration = it }, + label = { Text("Duration (minutes)") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + keyboardOptions = + KeyboardOptions(keyboardType = KeyboardType.Number) ) - + Spacer(modifier = Modifier.height(8.dp)) - + OutlinedTextField( - value = sessionNotes, - onValueChange = { sessionNotes = it }, - label = { Text("Session Notes (Optional)") }, - modifier = Modifier.fillMaxWidth(), - minLines = 3 + value = sessionNotes, + onValueChange = { sessionNotes = it }, + label = { Text("Session Notes (Optional)") }, + modifier = Modifier.fillMaxWidth(), + minLines = 3 ) } } @@ -852,6 +863,3 @@ fun AddEditSessionScreen( } } } - - - diff --git a/android/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt b/android/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt index 2e40d07..2a5ac9b 100644 --- a/android/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt +++ b/android/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt @@ -693,15 +693,6 @@ fun ProblemDetailScreen( } } - problem?.setter?.let { setter -> - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Set by: $setter", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - if (problem?.tags?.isNotEmpty() == true) { Spacer(modifier = Modifier.height(12.dp)) LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { diff --git a/android/app/src/main/java/com/atridad/openclimb/ui/screens/ProblemsScreen.kt b/android/app/src/main/java/com/atridad/openclimb/ui/screens/ProblemsScreen.kt index 80084e1..d7de626 100644 --- a/android/app/src/main/java/com/atridad/openclimb/ui/screens/ProblemsScreen.kt +++ b/android/app/src/main/java/com/atridad/openclimb/ui/screens/ProblemsScreen.kt @@ -21,181 +21,181 @@ import com.atridad.openclimb.ui.viewmodel.ClimbViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ProblemsScreen( - viewModel: ClimbViewModel, - onNavigateToProblemDetail: (String) -> Unit -) { +fun ProblemsScreen(viewModel: ClimbViewModel, onNavigateToProblemDetail: (String) -> Unit) { val problems by viewModel.problems.collectAsState() val gyms by viewModel.gyms.collectAsState() var showImageViewer by remember { mutableStateOf(false) } var selectedImagePaths by remember { mutableStateOf>(emptyList()) } var selectedImageIndex by remember { mutableIntStateOf(0) } - + // Filter state var selectedClimbType by remember { mutableStateOf(null) } var selectedGym by remember { mutableStateOf(null) } - + // Apply filters - val filteredProblems = problems.filter { problem -> - val climbTypeMatch = selectedClimbType?.let { it == problem.climbType } != false - val gymMatch = selectedGym?.let { it.id == problem.gymId } != false - climbTypeMatch && gymMatch - } - - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp) - ) { + val filteredProblems = + problems.filter { problem -> + val climbTypeMatch = selectedClimbType?.let { it == problem.climbType } != false + val gymMatch = selectedGym?.let { it.id == problem.gymId } != false + climbTypeMatch && gymMatch + } + + // Separate active and inactive problems + val activeProblems = filteredProblems.filter { it.isActive } + val inactiveProblems = filteredProblems.filter { !it.isActive } + val sortedProblems = activeProblems + inactiveProblems + + Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp) + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { Icon( - painter = painterResource(id = R.drawable.ic_mountains), - contentDescription = "OpenClimb Logo", - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.primary + painter = painterResource(id = R.drawable.ic_mountains), + contentDescription = "OpenClimb Logo", + modifier = Modifier.size(32.dp), + tint = MaterialTheme.colorScheme.primary ) Text( - text = "Problems & Routes", - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold + text = "Problems & Routes", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold ) } - + Spacer(modifier = Modifier.height(16.dp)) - + // Filters Section if (problems.isNotEmpty()) { - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( - text = "Filters", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = "Filters", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold ) - + Spacer(modifier = Modifier.height(12.dp)) - + // Climb Type Filter Text( - text = "Climb Type", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Medium + text = "Climb Type", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium ) - + Spacer(modifier = Modifier.height(8.dp)) - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { item { FilterChip( - onClick = { selectedClimbType = null }, - label = { Text("All Types") }, - selected = selectedClimbType == null + onClick = { selectedClimbType = null }, + label = { Text("All Types") }, + selected = selectedClimbType == null ) } items(ClimbType.entries) { climbType -> FilterChip( - onClick = { selectedClimbType = climbType }, - label = { Text(climbType.getDisplayName()) }, - selected = selectedClimbType == climbType + onClick = { selectedClimbType = climbType }, + label = { Text(climbType.getDisplayName()) }, + selected = selectedClimbType == climbType ) } } - + Spacer(modifier = Modifier.height(12.dp)) - + // Gym Filter Text( - text = "Gym", - style = MaterialTheme.typography.bodyMedium, - fontWeight = FontWeight.Medium + text = "Gym", + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium ) - + Spacer(modifier = Modifier.height(8.dp)) - - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { + + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { item { FilterChip( - onClick = { selectedGym = null }, - label = { Text("All Gyms") }, - selected = selectedGym == null + onClick = { selectedGym = null }, + label = { Text("All Gyms") }, + selected = selectedGym == null ) } items(gyms) { gym -> FilterChip( - onClick = { selectedGym = gym }, - label = { Text(gym.name) }, - selected = selectedGym?.id == gym.id + onClick = { selectedGym = gym }, + label = { Text(gym.name) }, + selected = selectedGym?.id == gym.id ) } } - + // Filter result count if (selectedClimbType != null || selectedGym != null) { Spacer(modifier = Modifier.height(12.dp)) Text( - text = "Showing ${filteredProblems.size} of ${problems.size} problems", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = + "Showing ${filteredProblems.size} of ${problems.size} problems (${activeProblems.size} active, ${inactiveProblems.size} reset)", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } - + Spacer(modifier = Modifier.height(16.dp)) } - + if (filteredProblems.isEmpty()) { EmptyStateMessage( - title = if (problems.isEmpty()) { - if (gyms.isEmpty()) "No Gyms Available" else "No Problems Yet" - } else { - "No Problems Match Filters" - }, - message = if (problems.isEmpty()) { - if (gyms.isEmpty()) "Add a gym first to start tracking problems and routes!" else "Start tracking your favorite problems and routes!" - } else { - "Try adjusting your filters to see more problems." - }, - onActionClick = { }, - actionText = "" + title = + if (problems.isEmpty()) { + if (gyms.isEmpty()) "No Gyms Available" else "No Problems Yet" + } else { + "No Problems Match Filters" + }, + message = + if (problems.isEmpty()) { + if (gyms.isEmpty()) + "Add a gym first to start tracking problems and routes!" + else "Start tracking your favorite problems and routes!" + } else { + "Try adjusting your filters to see more problems." + }, + onActionClick = {}, + actionText = "" ) } else { LazyColumn { - items(filteredProblems) { problem -> + items(sortedProblems) { problem -> ProblemCard( - problem = problem, - gymName = gyms.find { it.id == problem.gymId }?.name ?: "Unknown Gym", - onClick = { onNavigateToProblemDetail(problem.id) }, - onImageClick = { imagePaths, index -> - selectedImagePaths = imagePaths - selectedImageIndex = index - showImageViewer = true - } + problem = problem, + gymName = gyms.find { it.id == problem.gymId }?.name ?: "Unknown Gym", + onClick = { onNavigateToProblemDetail(problem.id) }, + onImageClick = { imagePaths, index -> + selectedImagePaths = imagePaths + selectedImageIndex = index + showImageViewer = true + }, + onToggleActive = { + val updatedProblem = problem.copy(isActive = !problem.isActive) + viewModel.updateProblem(updatedProblem) + } ) Spacer(modifier = Modifier.height(8.dp)) } } } } - + // Fullscreen Image Viewer if (showImageViewer && selectedImagePaths.isNotEmpty()) { FullscreenImageViewer( - imagePaths = selectedImagePaths, - initialIndex = selectedImageIndex, - onDismiss = { showImageViewer = false } + imagePaths = selectedImagePaths, + initialIndex = selectedImageIndex, + onDismiss = { showImageViewer = false } ) } } @@ -203,97 +203,117 @@ fun ProblemsScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProblemCard( - problem: Problem, - gymName: String, - onClick: () -> Unit, - onImageClick: ((List, Int) -> Unit)? = null + problem: Problem, + gymName: String, + onClick: () -> Unit, + onImageClick: ((List, Int) -> Unit)? = null, + onToggleActive: (() -> Unit)? = null ) { - Card( - onClick = onClick, - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) { + Card(onClick = onClick, modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Top + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top ) { Column(modifier = Modifier.weight(1f)) { Text( - text = problem.name ?: "Unnamed Problem", - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold + text = problem.name ?: "Unnamed Problem", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = + if (problem.isActive) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) - + Text( - text = gymName, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = gymName, + style = MaterialTheme.typography.bodyMedium, + color = + MaterialTheme.colorScheme.onSurfaceVariant.copy( + alpha = if (problem.isActive) 1f else 0.6f + ) ) } - + Column(horizontalAlignment = Alignment.End) { Text( - text = problem.difficulty.grade, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary + text = problem.difficulty.grade, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary ) - + Text( - text = problem.climbType.getDisplayName(), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = problem.climbType.getDisplayName(), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } - + problem.location?.let { location -> Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Location: $location", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + text = "Location: $location", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } - + if (problem.tags.isNotEmpty()) { Spacer(modifier = Modifier.height(8.dp)) Row { problem.tags.take(3).forEach { tag -> AssistChip( - onClick = { }, - label = { Text(tag) }, - modifier = Modifier.padding(end = 4.dp) + onClick = {}, + label = { Text(tag) }, + modifier = Modifier.padding(end = 4.dp) ) } } } - + // Display images if any if (problem.imagePaths.isNotEmpty()) { Spacer(modifier = Modifier.height(8.dp)) ImageDisplay( - imagePaths = problem.imagePaths.take(3), // Show max 3 images in list - imageSize = 60, - onImageClick = { index -> - onImageClick?.invoke(problem.imagePaths, index) - } + imagePaths = problem.imagePaths.take(3), // Show max 3 images in list + imageSize = 60, + onImageClick = { index -> onImageClick?.invoke(problem.imagePaths, index) } ) } - + if (!problem.isActive) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Inactive", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.error + text = "Reset / No Longer Set", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.tertiary, + fontWeight = FontWeight.Medium ) } + + // Toggle active button + if (onToggleActive != null) { + Spacer(modifier = Modifier.height(8.dp)) + OutlinedButton( + onClick = onToggleActive, + colors = + ButtonDefaults.outlinedButtonColors( + contentColor = + if (problem.isActive) + MaterialTheme.colorScheme.tertiary + else MaterialTheme.colorScheme.primary + ), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = if (problem.isActive) "Mark as Reset" else "Mark as Active", + style = MaterialTheme.typography.bodySmall + ) + } + } } } } diff --git a/android/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt b/android/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt index ad32149..fb6748e 100644 --- a/android/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt +++ b/android/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt @@ -5,139 +5,146 @@ import android.content.Intent import android.graphics.* import android.graphics.drawable.GradientDrawable import androidx.core.content.FileProvider +import androidx.core.graphics.createBitmap +import androidx.core.graphics.toColorInt import com.atridad.openclimb.data.model.* import java.io.File import java.io.FileOutputStream import java.time.LocalDateTime import java.time.format.DateTimeFormatter import kotlin.math.roundToInt -import androidx.core.graphics.createBitmap -import androidx.core.graphics.toColorInt object SessionShareUtils { - + data class SessionStats( - val totalAttempts: Int, - val successfulAttempts: Int, - val problems: List, - val uniqueProblemsAttempted: Int, - val uniqueProblemsCompleted: Int, - val averageGrade: String?, - val sessionDuration: String, - val topResult: AttemptResult?, - val topGrade: String? + val totalAttempts: Int, + val successfulAttempts: Int, + val problems: List, + val uniqueProblemsAttempted: Int, + val uniqueProblemsCompleted: Int, + val averageGrade: String?, + val sessionDuration: String, + val topResult: AttemptResult?, + val topGrade: String? ) - + fun calculateSessionStats( - session: ClimbSession, - attempts: List, - problems: List + session: ClimbSession, + attempts: List, + problems: List ): SessionStats { - val successfulResults = listOf( - AttemptResult.SUCCESS, - AttemptResult.FLASH - ) - + val successfulResults = listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) + val successfulAttempts = attempts.filter { it.result in successfulResults } val uniqueProblems = attempts.map { it.problemId }.distinct() val uniqueCompletedProblems = successfulAttempts.map { it.problemId }.distinct() - + val attemptedProblems = problems.filter { it.id in uniqueProblems } - + // Calculate separate averages for different climbing types and difficulty systems val boulderProblems = attemptedProblems.filter { it.climbType == ClimbType.BOULDER } val ropeProblems = attemptedProblems.filter { it.climbType == ClimbType.ROPE } - + val boulderAverage = calculateAverageGrade(boulderProblems, "Boulder") val ropeAverage = calculateAverageGrade(ropeProblems, "Rope") - + // Combine averages for display - val averageGrade = when { - boulderAverage != null && ropeAverage != null -> "$boulderAverage / $ropeAverage" - boulderAverage != null -> boulderAverage - ropeAverage != null -> ropeAverage - else -> null - } - + val averageGrade = + when { + boulderAverage != null && ropeAverage != null -> + "$boulderAverage / $ropeAverage" + boulderAverage != null -> boulderAverage + ropeAverage != null -> ropeAverage + else -> null + } + // Determine highest achieved grade (only from completed problems: SUCCESS or FLASH) val completedProblems = problems.filter { it.id in uniqueCompletedProblems } val completedBoulder = completedProblems.filter { it.climbType == ClimbType.BOULDER } val completedRope = completedProblems.filter { it.climbType == ClimbType.ROPE } val topBoulder = highestGradeForProblems(completedBoulder) val topRope = highestGradeForProblems(completedRope) - val topGrade = when { - topBoulder != null && topRope != null -> "$topBoulder / $topRope" - topBoulder != null -> topBoulder - topRope != null -> topRope - else -> null - } - + val topGrade = + when { + topBoulder != null && topRope != null -> "$topBoulder / $topRope" + topBoulder != null -> topBoulder + topRope != null -> topRope + else -> null + } + val duration = if (session.duration != null) "${session.duration}m" else "Unknown" - val topResult = attempts.maxByOrNull { - when (it.result) { - AttemptResult.FLASH -> 3 - AttemptResult.SUCCESS -> 2 - AttemptResult.FALL -> 1 - else -> 0 - } - }?.result - + val topResult = + attempts + .maxByOrNull { + when (it.result) { + AttemptResult.FLASH -> 3 + AttemptResult.SUCCESS -> 2 + AttemptResult.FALL -> 1 + else -> 0 + } + } + ?.result + return SessionStats( - totalAttempts = attempts.size, - successfulAttempts = successfulAttempts.size, - problems = attemptedProblems, - uniqueProblemsAttempted = uniqueProblems.size, - uniqueProblemsCompleted = uniqueCompletedProblems.size, - averageGrade = averageGrade, - sessionDuration = duration, - topResult = topResult, - topGrade = topGrade + totalAttempts = attempts.size, + successfulAttempts = successfulAttempts.size, + problems = attemptedProblems, + uniqueProblemsAttempted = uniqueProblems.size, + uniqueProblemsCompleted = uniqueCompletedProblems.size, + averageGrade = averageGrade, + sessionDuration = duration, + topResult = topResult, + topGrade = topGrade ) } - + /** * Calculate average grade for a specific set of problems, respecting their difficulty systems */ private fun calculateAverageGrade(problems: List, climbingType: String): 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() - } - } + 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() - } + 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 - } + 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)}") @@ -145,9 +152,13 @@ object SessionShareUtils { } DifficultySystem.CUSTOM -> { // For custom systems, try to extract numeric values - val gradeValues = systemProblems.mapNotNull { problem -> - problem.difficulty.grade.filter { it.isDigit() || it == '.' || it == '-' }.toDoubleOrNull() - } + 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)) @@ -155,7 +166,7 @@ object SessionShareUtils { } } } - + return if (averages.isNotEmpty()) { if (averages.size == 1) { averages.first() @@ -166,185 +177,262 @@ object SessionShareUtils { } fun generateShareCard( - context: Context, - session: ClimbSession, - gym: Gym, - stats: SessionStats + context: Context, + session: ClimbSession, + gym: Gym, + stats: SessionStats ): File? { return try { val width = 1242 // 3:4 aspect at higher resolution for better fit val height = 1656 - + val bitmap = createBitmap(width, height) val canvas = Canvas(bitmap) - - val gradientDrawable = GradientDrawable( - GradientDrawable.Orientation.TOP_BOTTOM, - intArrayOf( - "#667eea".toColorInt(), - "#764ba2".toColorInt() - ) - ) + + val gradientDrawable = + GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf("#667eea".toColorInt(), "#764ba2".toColorInt()) + ) gradientDrawable.setBounds(0, 0, width, height) gradientDrawable.draw(canvas) - + // Setup paint objects - val titlePaint = Paint().apply { - color = Color.WHITE - textSize = 72f - typeface = Typeface.DEFAULT_BOLD - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - val subtitlePaint = Paint().apply { - color = "#E8E8E8".toColorInt() - textSize = 48f - typeface = Typeface.DEFAULT - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - val statLabelPaint = Paint().apply { - color = "#B8B8B8".toColorInt() - textSize = 36f - typeface = Typeface.DEFAULT - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - val statValuePaint = Paint().apply { - color = Color.WHITE - textSize = 64f - typeface = Typeface.DEFAULT_BOLD - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - val cardPaint = Paint().apply { - color = "#40FFFFFF".toColorInt() - isAntiAlias = true - } - + val titlePaint = + Paint().apply { + color = Color.WHITE + textSize = 72f + typeface = Typeface.DEFAULT_BOLD + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + val subtitlePaint = + Paint().apply { + color = "#E8E8E8".toColorInt() + textSize = 48f + typeface = Typeface.DEFAULT + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + val statLabelPaint = + Paint().apply { + color = "#B8B8B8".toColorInt() + textSize = 36f + typeface = Typeface.DEFAULT + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + val statValuePaint = + Paint().apply { + color = Color.WHITE + textSize = 64f + typeface = Typeface.DEFAULT_BOLD + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + val cardPaint = + Paint().apply { + color = "#40FFFFFF".toColorInt() + isAntiAlias = true + } + // Draw main card background val cardRect = RectF(60f, 200f, width - 60f, height - 120f) canvas.drawRoundRect(cardRect, 40f, 40f, cardPaint) - + // Draw content var yPosition = 300f - + // Title canvas.drawText("Climbing Session", width / 2f, yPosition, titlePaint) yPosition += 80f - + // Gym and date canvas.drawText(gym.name, width / 2f, yPosition, subtitlePaint) yPosition += 60f - + val dateText = formatSessionDate(session.date) canvas.drawText(dateText, width / 2f, yPosition, subtitlePaint) yPosition += 120f - + // Stats grid val statsStartY = yPosition val columnWidth = width / 2f val columnMaxTextWidth = columnWidth - 120f - + // Left column stats var leftY = statsStartY - drawStatItemFitting(canvas, columnWidth / 2f, leftY, "Attempts", stats.totalAttempts.toString(), statLabelPaint, statValuePaint, columnMaxTextWidth) + drawStatItemFitting( + canvas, + columnWidth / 2f, + leftY, + "Attempts", + stats.totalAttempts.toString(), + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) leftY += 120f - drawStatItemFitting(canvas, columnWidth / 2f, leftY, "Problems", stats.uniqueProblemsAttempted.toString(), statLabelPaint, statValuePaint, columnMaxTextWidth) + drawStatItemFitting( + canvas, + columnWidth / 2f, + leftY, + "Problems", + stats.uniqueProblemsAttempted.toString(), + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) leftY += 120f - drawStatItemFitting(canvas, columnWidth / 2f, leftY, "Duration", stats.sessionDuration, statLabelPaint, statValuePaint, columnMaxTextWidth) - + drawStatItemFitting( + canvas, + columnWidth / 2f, + leftY, + "Duration", + stats.sessionDuration, + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) + // Right column stats var rightY = statsStartY - drawStatItemFitting(canvas, width - columnWidth / 2f, rightY, "Successful", stats.successfulAttempts.toString(), statLabelPaint, statValuePaint, columnMaxTextWidth) + drawStatItemFitting( + canvas, + width - columnWidth / 2f, + rightY, + "Completed", + stats.uniqueProblemsCompleted.toString(), + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) rightY += 120f - drawStatItemFitting(canvas, width - columnWidth / 2f, rightY, "Completed", stats.uniqueProblemsCompleted.toString(), statLabelPaint, statValuePaint, columnMaxTextWidth) - rightY += 120f - + var rightYAfter = rightY stats.topGrade?.let { grade -> - drawStatItemFitting(canvas, width - columnWidth / 2f, rightY, "Top Grade", grade, statLabelPaint, statValuePaint, columnMaxTextWidth) + drawStatItemFitting( + canvas, + width - columnWidth / 2f, + rightY, + "Top Grade", + grade, + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) rightYAfter += 120f } - + // Grade range(s) - val boulderRange = gradeRangeForProblems(stats.problems.filter { it.climbType == ClimbType.BOULDER }) - val ropeRange = gradeRangeForProblems(stats.problems.filter { it.climbType == ClimbType.ROPE }) + val boulderRange = + gradeRangeForProblems( + stats.problems.filter { it.climbType == ClimbType.BOULDER } + ) + val ropeRange = + gradeRangeForProblems(stats.problems.filter { it.climbType == ClimbType.ROPE }) val rangesY = kotlin.math.max(leftY, rightYAfter) + 120f if (boulderRange != null && ropeRange != null) { // Two evenly spaced items - drawStatItemFitting(canvas, columnWidth / 2f, rangesY, "Boulder Range", boulderRange, statLabelPaint, statValuePaint, columnMaxTextWidth) - drawStatItemFitting(canvas, width - columnWidth / 2f, rangesY, "Rope Range", ropeRange, statLabelPaint, statValuePaint, columnMaxTextWidth) + drawStatItemFitting( + canvas, + columnWidth / 2f, + rangesY, + "Boulder Range", + boulderRange, + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) + drawStatItemFitting( + canvas, + width - columnWidth / 2f, + rangesY, + "Rope Range", + ropeRange, + statLabelPaint, + statValuePaint, + columnMaxTextWidth + ) } else if (boulderRange != null || ropeRange != null) { // Single centered item val singleRange = boulderRange ?: ropeRange ?: "" - drawStatItemFitting(canvas, width / 2f, rangesY, "Grade Range", singleRange, statLabelPaint, statValuePaint, width - 200f) + drawStatItemFitting( + canvas, + width / 2f, + rangesY, + "Grade Range", + singleRange, + statLabelPaint, + statValuePaint, + width - 200f + ) } - - // App branding - val brandingPaint = Paint().apply { - color = "#80FFFFFF".toColorInt() - textSize = 32f - typeface = Typeface.DEFAULT - isAntiAlias = true - textAlign = Paint.Align.CENTER - } + val brandingPaint = + Paint().apply { + color = "#80FFFFFF".toColorInt() + textSize = 32f + typeface = Typeface.DEFAULT + isAntiAlias = true + textAlign = Paint.Align.CENTER + } canvas.drawText("OpenClimb", width / 2f, height - 40f, brandingPaint) - + // Save to file val shareDir = File(context.cacheDir, "shares") if (!shareDir.exists()) { shareDir.mkdirs() } - + val filename = "session_${session.id}_${System.currentTimeMillis()}.png" val file = File(shareDir, filename) - + val outputStream = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) outputStream.flush() outputStream.close() - + bitmap.recycle() - + file } catch (e: Exception) { e.printStackTrace() null } } - + private fun drawStatItem( - canvas: Canvas, - x: Float, - y: Float, - label: String, - value: String, - labelPaint: Paint, - valuePaint: Paint + canvas: Canvas, + x: Float, + y: Float, + label: String, + value: String, + labelPaint: Paint, + valuePaint: Paint ) { canvas.drawText(value, x, y, valuePaint) canvas.drawText(label, x, y + 50f, labelPaint) } - + /** - * Draws a stat item while fitting the value text to a max width by reducing text size if needed. + * Draws a stat item while fitting the value text to a max width by reducing text size if + * needed. */ private fun drawStatItemFitting( - canvas: Canvas, - x: Float, - y: Float, - label: String, - value: String, - labelPaint: Paint, - valuePaint: Paint, - maxTextWidth: Float + canvas: Canvas, + x: Float, + y: Float, + label: String, + value: String, + labelPaint: Paint, + valuePaint: Paint, + maxTextWidth: Float ) { val tempPaint = Paint(valuePaint) var textSize = tempPaint.textSize @@ -357,7 +445,7 @@ object SessionShareUtils { canvas.drawText(value, x, y, tempPaint) canvas.drawText(label, x, y + 50f, labelPaint) } - + /** * Returns a range string like "X - Y" for the given problems, based on their difficulty grades. */ @@ -367,9 +455,7 @@ object SessionShareUtils { val sorted = grades.sortedWith { a, b -> a.compareTo(b) } return "${sorted.first().grade} - ${sorted.last().grade}" } - - private fun formatSessionDate(dateString: String): String { return try { val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME @@ -380,23 +466,28 @@ object SessionShareUtils { dateString.take(10) } } - + fun shareSessionCard(context: Context, imageFile: File) { try { - val uri = FileProvider.getUriForFile( - context, - "${context.packageName}.fileprovider", - imageFile - ) - - val shareIntent = Intent().apply { - action = Intent.ACTION_SEND - type = "image/png" - putExtra(Intent.EXTRA_STREAM, uri) - putExtra(Intent.EXTRA_TEXT, "Check out my climbing session! 🧗‍♀️ #OpenClimb") - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - + val uri = + FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + imageFile + ) + + val shareIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "image/png" + putExtra(Intent.EXTRA_STREAM, uri) + putExtra( + Intent.EXTRA_TEXT, + "Check out my climbing session! 🧗‍♀️ #OpenClimb" + ) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + val chooser = Intent.createChooser(shareIntent, "Share Session") chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(chooser) @@ -406,16 +497,18 @@ object SessionShareUtils { } /** - * Returns the highest grade string among the given problems, respecting their difficulty system. + * Returns the highest grade string among the given problems, respecting their difficulty + * system. */ private fun highestGradeForProblems(problems: List): String? { if (problems.isEmpty()) return null - return problems.maxByOrNull { p -> gradeRank(p.difficulty.system, p.difficulty.grade) }?.difficulty?.grade + return problems + .maxByOrNull { p -> gradeRank(p.difficulty.system, p.difficulty.grade) } + ?.difficulty + ?.grade } - /** - * Produces a comparable numeric rank for grades across supported systems. - */ + /** Produces a comparable numeric rank for grades across supported systems. */ private fun gradeRank(system: DifficultySystem, grade: String): Double { return when (system) { DifficultySystem.V_SCALE -> { @@ -424,7 +517,8 @@ object SessionShareUtils { DifficultySystem.FONT -> { val list = DifficultySystem.FONT.getAvailableGrades() val idx = list.indexOf(grade.uppercase()) - if (idx >= 0) idx.toDouble() else grade.filter { it.isDigit() }.toDoubleOrNull() ?: -1.0 + if (idx >= 0) idx.toDouble() + else grade.filter { it.isDigit() }.toDoubleOrNull() ?: -1.0 } DifficultySystem.YDS -> { // Parse 5.X with optional letter a-d @@ -434,13 +528,14 @@ object SessionShareUtils { val numberPart = tail.takeWhile { it.isDigit() || it == '.' } val letterPart = tail.drop(numberPart.length).firstOrNull() val base = numberPart.toDoubleOrNull() ?: return -1.0 - val letterWeight = when (letterPart) { - 'a' -> 0.0 - 'b' -> 0.1 - 'c' -> 0.2 - 'd' -> 0.3 - else -> 0.0 - } + val letterWeight = + when (letterPart) { + 'a' -> 0.0 + 'b' -> 0.1 + 'c' -> 0.2 + 'd' -> 0.3 + else -> 0.0 + } base + letterWeight } DifficultySystem.CUSTOM -> { diff --git a/ios/OpenClimb.xcodeproj/project.pbxproj b/ios/OpenClimb.xcodeproj/project.pbxproj index d01e414..f8e7793 100644 --- a/ios/OpenClimb.xcodeproj/project.pbxproj +++ b/ios/OpenClimb.xcodeproj/project.pbxproj @@ -394,7 +394,7 @@ CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 4BC9Y2LL4B; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -437,7 +437,7 @@ CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 4BC9Y2LL4B; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -479,7 +479,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 4BC9Y2LL4B; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SessionStatusLive/Info.plist; @@ -509,7 +509,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_TEAM = 4BC9Y2LL4B; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = SessionStatusLive/Info.plist; diff --git a/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OpenClimb.xcodeproj/project.xcworkspace/xcuserdata/atridad.xcuserdatad/UserInterfaceState.xcuserstate index 10fa4a89987b53863ed7fb4282d4b59246ae2be6..6acfc545a745e71e6ae926132a20c2b0aaf0705a 100644 GIT binary patch literal 115423 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%0AgJ)JPyYM&F& zX7*x6)sAw4vz+G=m-&}_K}hvX*kSd`m`nBlVIS4UGL7l{!c3N+x9ZCvcA?_T3+ z5aR#58d5VgX~{rle3v!de9bV-w5BXI>*M{Jk@!Aqj%Efj)Qn*ZTiK3#s;NfJTR})I zZ`SfwEpOGzi~ZE9&bxey_iA;;tZQ{+0AJ%9sx_SNaKp9Ct=2piuoyF|WoEV9P_1>S zTk9|~*RrEpa@TUdwI1-8lpv&b2%)4SBU#8!QHoQNGL)whRjEO3eD}5WT)Q(v`H{Jp zcWv2g?_xK5IfnYRPx2%PsbdCp)T@&THS3r`okF<9I>mUNQq-X-&1g;sdeDbR#m3t2@Bv6xLAJE>zfb#|b?I{R=>b@W!}4Cgq{4es*@cTv|})HSEN z`mU?zy5>~Z9_yM(T|28gky$KaDJzIZ?Yj0+cO&LfcN>4QlPf_;y-?(>SCN`DLgsp} z(SrZchR^sMnd`ZSdhVg#_o!R%C+4DVJ$37;ThAWqtwr5>>ef@Yp1SpRv4?9xNPT(g zm!=BU=*a*Eq38O3r@mR$zk?ane-?x^D1$y5G~oqa;#J<@Z9b+IZLzNg?xTVEG|+p4 zDd?qPYUFJA4I>%PBxdj%i;$sVBJ24Bdun))Q(VIA8r};+8f73e*|3jBVdSL%g(*sL z^xVkZHmXMh8qkw$~0E9hrff6ChosUG0ISm>gcseL(IB~SvS#Zla_qMr?jCx=G!ES z?)0J$`fjp|he1fwRD_ZPvuSEJP0gmM*);W?H7!d8DpM8TT~jyM^wl7w`GzIS%vS8N`Je3MW)SjH9^Ao8pJCoF4PZFm;hmSv=fA%< b&wu|2_5UC9-#-cu`G5cV|Nk2DQtbZ$GqODt literal 114205 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

>=5b-uO1DqGLAo^QRty25&y^&0Cs>-E-K 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 R1wZ_M|NQ^|JEFm~{{wq0MD+jw diff --git a/ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/OpenClimb.xcscheme b/ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/OpenClimb.xcscheme new file mode 100644 index 0000000..e69de29 diff --git a/ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/SessionStatusLiveExtension.xcscheme b/ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/SessionStatusLiveExtension.xcscheme new file mode 100644 index 0000000..770352d --- /dev/null +++ b/ios/OpenClimb.xcodeproj/xcshareddata/xcschemes/SessionStatusLiveExtension.xcscheme @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist index 53d0216..7dfcbe0 100644 --- a/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ios/OpenClimb.xcodeproj/xcuserdata/atridad.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ OpenClimb.xcscheme_^#shared#^_ orderHint - 0 + 1 SessionStatusLiveExtension.xcscheme_^#shared#^_ @@ -15,5 +15,18 @@ 0 + SuppressBuildableAutocreation + + D24C19672E75002A0045894C + + primary + + + D2FE948A2E78FEE0008CDB25 + + primary + + + diff --git a/ios/OpenClimb/Models/DataModels.swift b/ios/OpenClimb/Models/DataModels.swift index c395279..a514538 100644 --- a/ios/OpenClimb/Models/DataModels.swift +++ b/ios/OpenClimb/Models/DataModels.swift @@ -260,7 +260,7 @@ struct Problem: Identifiable, Codable, Hashable { let description: String? let climbType: ClimbType let difficulty: DifficultyGrade - let setter: String? + let tags: [String] let location: String? let imagePaths: [String] @@ -272,7 +272,7 @@ struct Problem: Identifiable, Codable, Hashable { init( gymId: UUID, name: String? = nil, description: String? = nil, climbType: ClimbType, - difficulty: DifficultyGrade, setter: String? = nil, tags: [String] = [], + difficulty: DifficultyGrade, tags: [String] = [], location: String? = nil, imagePaths: [String] = [], dateSet: Date? = nil, notes: String? = nil ) { @@ -282,7 +282,7 @@ struct Problem: Identifiable, Codable, Hashable { self.description = description self.climbType = climbType self.difficulty = difficulty - self.setter = setter + self.tags = tags self.location = location self.imagePaths = imagePaths @@ -296,7 +296,7 @@ struct Problem: Identifiable, Codable, Hashable { func updated( name: String? = nil, description: String? = nil, climbType: ClimbType? = nil, - difficulty: DifficultyGrade? = nil, setter: String? = nil, tags: [String]? = nil, + difficulty: DifficultyGrade? = nil, tags: [String]? = nil, location: String? = nil, imagePaths: [String]? = nil, isActive: Bool? = nil, dateSet: Date? = nil, notes: String? = nil ) -> Problem { @@ -307,7 +307,7 @@ struct Problem: Identifiable, Codable, Hashable { description: description ?? self.description, climbType: climbType ?? self.climbType, difficulty: difficulty ?? self.difficulty, - setter: setter ?? self.setter, + tags: tags ?? self.tags, location: location ?? self.location, imagePaths: imagePaths ?? self.imagePaths, @@ -321,7 +321,7 @@ struct Problem: Identifiable, Codable, Hashable { private init( id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType, - difficulty: DifficultyGrade, setter: String?, tags: [String], location: String?, + difficulty: DifficultyGrade, tags: [String], location: String?, imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date, updatedAt: Date ) { @@ -331,7 +331,7 @@ struct Problem: Identifiable, Codable, Hashable { self.description = description self.climbType = climbType self.difficulty = difficulty - self.setter = setter + self.tags = tags self.location = location self.imagePaths = imagePaths @@ -344,7 +344,7 @@ struct Problem: Identifiable, Codable, Hashable { static func fromImport( id: UUID, gymId: UUID, name: String?, description: String?, climbType: ClimbType, - difficulty: DifficultyGrade, setter: String?, tags: [String], location: String?, + difficulty: DifficultyGrade, tags: [String], location: String?, imagePaths: [String], isActive: Bool, dateSet: Date?, notes: String?, createdAt: Date, updatedAt: Date ) -> Problem { @@ -355,7 +355,7 @@ struct Problem: Identifiable, Codable, Hashable { description: description, climbType: climbType, difficulty: difficulty, - setter: setter, + tags: tags, location: location, imagePaths: imagePaths, diff --git a/ios/OpenClimb/ViewModels/ClimbingDataManager.swift b/ios/OpenClimb/ViewModels/ClimbingDataManager.swift index cbd55fa..112f549 100644 --- a/ios/OpenClimb/ViewModels/ClimbingDataManager.swift +++ b/ios/OpenClimb/ViewModels/ClimbingDataManager.swift @@ -475,7 +475,7 @@ class ClimbingDataManager: ObservableObject { let exportData = ClimbDataExport( exportedAt: dateFormatter.string(from: Date()), - version: "1.0", + version: "2.0", gyms: gyms.map { AndroidGym(from: $0) }, problems: problems.map { AndroidProblem(from: $0) }, sessions: sessions.map { AndroidClimbSession(from: $0) }, @@ -593,7 +593,7 @@ struct ClimbDataExport: Codable { let attempts: [AndroidAttempt] init( - exportedAt: String, version: String = "1.0", gyms: [AndroidGym], problems: [AndroidProblem], + exportedAt: String, version: String = "2.0", gyms: [AndroidGym], problems: [AndroidProblem], sessions: [AndroidClimbSession], attempts: [AndroidAttempt] ) { self.exportedAt = exportedAt @@ -675,7 +675,6 @@ struct AndroidProblem: Codable { let description: String? let climbType: ClimbType let difficulty: DifficultyGrade - let setter: String? let tags: [String] let location: String? let imagePaths: [String]? @@ -692,7 +691,6 @@ struct AndroidProblem: Codable { self.description = problem.description self.climbType = problem.climbType self.difficulty = problem.difficulty - self.setter = problem.setter self.tags = problem.tags self.location = problem.location self.imagePaths = problem.imagePaths.isEmpty ? nil : problem.imagePaths @@ -707,7 +705,7 @@ struct AndroidProblem: Codable { init( id: String, gymId: String, name: String?, description: String?, climbType: ClimbType, - difficulty: DifficultyGrade, setter: String? = nil, tags: [String] = [], + difficulty: DifficultyGrade, tags: [String] = [], location: String? = nil, imagePaths: [String]? = nil, isActive: Bool = true, dateSet: String? = nil, notes: String? = nil, @@ -719,7 +717,6 @@ struct AndroidProblem: Codable { self.description = description self.climbType = climbType self.difficulty = difficulty - self.setter = setter self.tags = tags self.location = location self.imagePaths = imagePaths @@ -746,7 +743,6 @@ struct AndroidProblem: Codable { description: description, climbType: climbType, difficulty: difficulty, - setter: setter, tags: tags, location: location, imagePaths: imagePaths ?? [], @@ -766,7 +762,6 @@ struct AndroidProblem: Codable { description: self.description, climbType: self.climbType, difficulty: self.difficulty, - setter: self.setter, tags: self.tags, location: self.location, imagePaths: newImagePaths.isEmpty ? nil : newImagePaths, @@ -1331,7 +1326,6 @@ extension ClimbingDataManager { description: "Technical overhang with small holds", climbType: .boulder, difficulty: DifficultyGrade(system: .vScale, grade: "V4"), - setter: "John Doe", tags: ["technical", "overhang"], location: "Cave area" ) diff --git a/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift b/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift index 336b58f..3c3afae 100644 --- a/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift +++ b/ios/OpenClimb/Views/AddEdit/AddAttemptView.swift @@ -635,12 +635,6 @@ struct ProblemExpandedView: View { .foregroundColor(.secondary) } - if let setter = problem.setter, !setter.isEmpty { - Label(setter, systemImage: "person") - .font(.subheadline) - .foregroundColor(.secondary) - } - if let description = problem.description, !description.isEmpty { Text(description) .font(.body) diff --git a/ios/OpenClimb/Views/AddEdit/AddEditProblemView.swift b/ios/OpenClimb/Views/AddEdit/AddEditProblemView.swift index 811f64e..78e91ec 100644 --- a/ios/OpenClimb/Views/AddEdit/AddEditProblemView.swift +++ b/ios/OpenClimb/Views/AddEdit/AddEditProblemView.swift @@ -13,7 +13,6 @@ struct AddEditProblemView: View { @State private var selectedClimbType: ClimbType = .boulder @State private var selectedDifficultySystem: DifficultySystem = .vScale @State private var difficultyGrade = "" - @State private var setter = "" @State private var location = "" @State private var tags = "" @State private var notes = "" @@ -63,7 +62,7 @@ struct AddEditProblemView: View { PhotosSection() ClimbTypeSection() DifficultySection() - LocationAndSetterSection() + LocationSection() TagsSection() AdditionalInfoSection() } @@ -158,7 +157,6 @@ struct AddEditProblemView: View { ) } - TextField("Route Setter (Optional)", text: $setter) } } @@ -281,7 +279,7 @@ struct AddEditProblemView: View { } @ViewBuilder - private func LocationAndSetterSection() -> some View { + private func LocationSection() -> some View { Section("Location & Details") { TextField( "Location (Optional)", text: $location, prompt: Text("e.g., 'Cave area', 'Wall 3'")) @@ -334,25 +332,28 @@ struct AddEditProblemView: View { HStack(spacing: 12) { ForEach(imageData.indices, id: \.self) { index in if let uiImage = UIImage(data: imageData[index]) { - Image(uiImage: uiImage) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 80, height: 80) - .clipped() - .cornerRadius(8) - .overlay(alignment: .topTrailing) { - Button(action: { - imageData.remove(at: index) - if index < imagePaths.count { - imagePaths.remove(at: index) - } - }) { - Image(systemName: "xmark.circle.fill") - .foregroundColor(.red) - .background(Circle().fill(.white)) + ZStack(alignment: .topTrailing) { + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 80, height: 80) + .clipped() + .cornerRadius(8) + + Button(action: { + imageData.remove(at: index) + if index < imagePaths.count { + imagePaths.remove(at: index) } - .offset(x: 8, y: -8) + }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.red) + .background(Circle().fill(.white)) + .font(.system(size: 18)) } + .offset(x: 4, y: -4) + } + .frame(width: 88, height: 88) // Extra space for button } else { RoundedRectangle(cornerRadius: 8) .fill(.gray.opacity(0.3)) @@ -365,6 +366,7 @@ struct AddEditProblemView: View { } } .padding(.horizontal, 1) + .padding(.vertical, 8) } } } @@ -410,7 +412,7 @@ struct AddEditProblemView: View { selectedClimbType = problem.climbType selectedDifficultySystem = problem.difficulty.system difficultyGrade = problem.difficulty.grade - setter = problem.setter ?? "" + location = problem.location ?? "" tags = problem.tags.joined(separator: ", ") notes = problem.notes ?? "" @@ -420,7 +422,7 @@ struct AddEditProblemView: View { // Load image data for preview imageData = [] for imagePath in problem.imagePaths { - if let data = try? Data(contentsOf: URL(fileURLWithPath: imagePath)) { + if let data = ImageManager.shared.loadImageData(fromPath: imagePath) { imageData.append(data) } } @@ -479,7 +481,7 @@ struct AddEditProblemView: View { let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedDescription = description.trimmingCharacters(in: .whitespacesAndNewlines) - let trimmedSetter = setter.trimmingCharacters(in: .whitespacesAndNewlines) + let trimmedLocation = location.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedNotes = notes.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedTags = tags.split(separator: ",").map { @@ -494,7 +496,7 @@ struct AddEditProblemView: View { description: trimmedDescription.isEmpty ? nil : trimmedDescription, climbType: selectedClimbType, difficulty: difficulty, - setter: trimmedSetter.isEmpty ? nil : trimmedSetter, + tags: trimmedTags, location: trimmedLocation.isEmpty ? nil : trimmedLocation, imagePaths: imagePaths, @@ -510,7 +512,7 @@ struct AddEditProblemView: View { description: trimmedDescription.isEmpty ? nil : trimmedDescription, climbType: selectedClimbType, difficulty: difficulty, - setter: trimmedSetter.isEmpty ? nil : trimmedSetter, + tags: trimmedTags, location: trimmedLocation.isEmpty ? nil : trimmedLocation, imagePaths: imagePaths, diff --git a/ios/OpenClimb/Views/Detail/GymDetailView.swift b/ios/OpenClimb/Views/Detail/GymDetailView.swift index 3b6e57f..dd86fe7 100644 --- a/ios/OpenClimb/Views/Detail/GymDetailView.swift +++ b/ios/OpenClimb/Views/Detail/GymDetailView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct GymDetailView: View { @@ -60,8 +59,10 @@ struct GymDetailView: View { ToolbarItemGroup(placement: .navigationBarTrailing) { if gym != nil { Menu { - Button("Edit Gym") { + Button { // Navigate to edit view + } label: { + Label("Edit Gym", systemImage: "pencil") } Button(role: .destructive) { diff --git a/ios/OpenClimb/Views/Detail/ProblemDetailView.swift b/ios/OpenClimb/Views/Detail/ProblemDetailView.swift index 62dc277..fe38fa1 100644 --- a/ios/OpenClimb/Views/Detail/ProblemDetailView.swift +++ b/ios/OpenClimb/Views/Detail/ProblemDetailView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct ProblemDetailView: View { @@ -64,8 +63,10 @@ struct ProblemDetailView: View { ToolbarItemGroup(placement: .navigationBarTrailing) { if problem != nil { Menu { - Button("Edit Problem") { + Button { showingEditProblem = true + } label: { + Label("Edit Problem", systemImage: "pencil") } Button(role: .destructive) { @@ -167,12 +168,6 @@ struct ProblemHeaderCard: View { .font(.body) } - if let setter = problem.setter, !setter.isEmpty { - Text("Set by: \(setter)") - .font(.subheadline) - .foregroundColor(.secondary) - } - if !problem.tags.isEmpty { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { diff --git a/ios/OpenClimb/Views/Detail/SessionDetailView.swift b/ios/OpenClimb/Views/Detail/SessionDetailView.swift index b4ad000..84a8e4d 100644 --- a/ios/OpenClimb/Views/Detail/SessionDetailView.swift +++ b/ios/OpenClimb/Views/Detail/SessionDetailView.swift @@ -280,7 +280,6 @@ struct SessionStatsCard: View { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 16) { StatItem(label: "Total Attempts", value: "\(stats.totalAttempts)") StatItem(label: "Problems", value: "\(stats.uniqueProblemsAttempted)") - StatItem(label: "Successful", value: "\(stats.successfulAttempts)") StatItem(label: "Completed", value: "\(stats.uniqueProblemsCompleted)") } } diff --git a/ios/OpenClimb/Views/GymsView.swift b/ios/OpenClimb/Views/GymsView.swift index 078dd4f..31c895a 100644 --- a/ios/OpenClimb/Views/GymsView.swift +++ b/ios/OpenClimb/Views/GymsView.swift @@ -1,4 +1,3 @@ - import SwiftUI struct GymsView: View { @@ -49,7 +48,10 @@ struct GymsList: View { Button { gymToEdit = gym } label: { - Label("Edit", systemImage: "pencil") + HStack { + Image(systemName: "pencil") + Text("Edit") + } } .tint(.blue) } diff --git a/ios/OpenClimb/Views/ProblemsView.swift b/ios/OpenClimb/Views/ProblemsView.swift index 36b3fd2..da4bd6b 100644 --- a/ios/OpenClimb/Views/ProblemsView.swift +++ b/ios/OpenClimb/Views/ProblemsView.swift @@ -13,10 +13,9 @@ struct ProblemsView: View { // Apply search filter if !searchText.isEmpty { filtered = filtered.filter { problem in - (problem.name?.localizedCaseInsensitiveContains(searchText) ?? false) + return problem.name?.localizedCaseInsensitiveContains(searchText) ?? false || (problem.description?.localizedCaseInsensitiveContains(searchText) ?? false) || (problem.location?.localizedCaseInsensitiveContains(searchText) ?? false) - || (problem.setter?.localizedCaseInsensitiveContains(searchText) ?? false) || problem.tags.contains { $0.localizedCaseInsensitiveContains(searchText) } } } @@ -31,7 +30,11 @@ struct ProblemsView: View { filtered = filtered.filter { $0.gymId == gym.id } } - return filtered.sorted { $0.updatedAt > $1.updatedAt } + // Separate active and inactive problems + let active = filtered.filter { $0.isActive }.sorted { $0.updatedAt > $1.updatedAt } + let inactive = filtered.filter { !$0.isActive }.sorted { $0.updatedAt > $1.updatedAt } + + return active + inactive } var body: some View { @@ -195,10 +198,23 @@ struct ProblemsList: View { Label("Delete", systemImage: "trash") } + Button { + let updatedProblem = problem.updated(isActive: !problem.isActive) + dataManager.updateProblem(updatedProblem) + } label: { + Label( + problem.isActive ? "Mark as Reset" : "Mark as Active", + systemImage: problem.isActive ? "xmark.circle" : "checkmark.circle") + } + .tint(.orange) + Button { problemToEdit = problem } label: { - Label("Edit", systemImage: "pencil") + HStack { + Image(systemName: "pencil") + Text("Edit") + } } .tint(.blue) } @@ -239,6 +255,7 @@ struct ProblemRow: View { Text(problem.name ?? "Unnamed Problem") .font(.headline) .fontWeight(.semibold) + .foregroundColor(problem.isActive ? .primary : .secondary) Text(gym?.name ?? "Unknown Gym") .font(.subheadline) @@ -295,9 +312,9 @@ struct ProblemRow: View { } if !problem.isActive { - Text("Inactive") + Text("Reset / No Longer Set") .font(.caption) - .foregroundColor(.red) + .foregroundColor(.orange) .fontWeight(.medium) } }