diff --git a/.gradle/8.11.1/executionHistory/executionHistory.bin b/.gradle/8.11.1/executionHistory/executionHistory.bin index 695d30c..a5f8cd8 100644 Binary files a/.gradle/8.11.1/executionHistory/executionHistory.bin and b/.gradle/8.11.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.11.1/executionHistory/executionHistory.lock b/.gradle/8.11.1/executionHistory/executionHistory.lock index ba4a26b..ee54f3e 100644 Binary files a/.gradle/8.11.1/executionHistory/executionHistory.lock and b/.gradle/8.11.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.11.1/fileHashes/fileHashes.bin b/.gradle/8.11.1/fileHashes/fileHashes.bin index 95f0b10..98fea0b 100644 Binary files a/.gradle/8.11.1/fileHashes/fileHashes.bin and b/.gradle/8.11.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.11.1/fileHashes/fileHashes.lock b/.gradle/8.11.1/fileHashes/fileHashes.lock index 48926bf..80fec0a 100644 Binary files a/.gradle/8.11.1/fileHashes/fileHashes.lock and b/.gradle/8.11.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/.gradle/8.11.1/fileHashes/resourceHashesCache.bin index a7ac4a5..3451c87 100644 Binary files a/.gradle/8.11.1/fileHashes/resourceHashesCache.bin and b/.gradle/8.11.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 53dc418..5de3496 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 1de9762..5b18dea 100644 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index bd9f815..7576a83 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0682845..94b8699 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.atridad.openclimb" minSdk = 31 targetSdk = 35 - versionCode = 5 - versionName = "0.3.2" + versionCode = 6 + versionName = "0.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 29acc2a..4403f0d 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index 6a4dc07..53304f4 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index 4877449..3753055 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 7c66a20..812cfe1 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 4, - "versionName": "0.3.1", + "versionCode": 6, + "versionName": "0.3.3", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/java/com/atridad/openclimb/data/model/Attempt.kt b/app/src/main/java/com/atridad/openclimb/data/model/Attempt.kt index 9eeddd5..1f93a1c 100644 --- a/app/src/main/java/com/atridad/openclimb/data/model/Attempt.kt +++ b/app/src/main/java/com/atridad/openclimb/data/model/Attempt.kt @@ -9,12 +9,10 @@ import java.time.LocalDateTime @Serializable enum class AttemptResult { - SUCCESS, // Completed the problem/route - FALL, // Fell but made progress - NO_PROGRESS, // Couldn't make meaningful progress - FLASH, // Completed on first try - REDPOINT, // Completed after previous attempts - ONSIGHT // Completed on first try without prior knowledge + SUCCESS, + FALL, + NO_PROGRESS, + FLASH, } @Entity( diff --git a/app/src/main/java/com/atridad/openclimb/data/model/ClimbSession.kt b/app/src/main/java/com/atridad/openclimb/data/model/ClimbSession.kt index 87cbe73..f3b9c7d 100644 --- a/app/src/main/java/com/atridad/openclimb/data/model/ClimbSession.kt +++ b/app/src/main/java/com/atridad/openclimb/data/model/ClimbSession.kt @@ -65,7 +65,7 @@ data class ClimbSession( val start = LocalDateTime.parse(startTime) val end = LocalDateTime.parse(endTime) java.time.Duration.between(start, end).toMinutes() - } catch (e: Exception) { + } catch (_: Exception) { null } } else null diff --git a/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt b/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt index e24b73c..c748e37 100644 --- a/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt +++ b/app/src/main/java/com/atridad/openclimb/data/repository/ClimbRepository.kt @@ -123,7 +123,7 @@ class ClimbRepository( importData.gyms.forEach { gym -> try { gymDao.insertGym(gym) - } catch (e: Exception) { + } catch (_: Exception) { // If insertion fails, update instead gymDao.updateGym(gym) } @@ -133,7 +133,7 @@ class ClimbRepository( importData.problems.forEach { problem -> try { problemDao.insertProblem(problem) - } catch (e: Exception) { + } catch (_: Exception) { problemDao.updateProblem(problem) } } @@ -142,7 +142,7 @@ class ClimbRepository( importData.sessions.forEach { session -> try { sessionDao.insertSession(session) - } catch (e: Exception) { + } catch (_: Exception) { sessionDao.updateSession(session) } } @@ -151,7 +151,7 @@ class ClimbRepository( importData.attempts.forEach { attempt -> try { attemptDao.insertAttempt(attempt) - } catch (e: Exception) { + } catch (_: Exception) { attemptDao.updateAttempt(attempt) } } diff --git a/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt b/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt index 67dc080..6146481 100644 --- a/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt +++ b/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt @@ -38,9 +38,10 @@ class SessionTrackingService : Service() { } } - fun createStopIntent(context: Context): Intent { + fun createStopIntent(context: Context, sessionId: String): Intent { return Intent(context, SessionTrackingService::class.java).apply { action = ACTION_STOP_SESSION + putExtra(EXTRA_SESSION_ID, sessionId) } } } @@ -63,7 +64,21 @@ class SessionTrackingService : Service() { } } ACTION_STOP_SESSION -> { - stopSessionTracking() + val sessionId = intent.getStringExtra(EXTRA_SESSION_ID) + serviceScope.launch { + try { + val targetSession = when { + sessionId != null -> repository.getSessionById(sessionId) + else -> repository.getActiveSession() + } + if (targetSession != null && targetSession.status == com.atridad.openclimb.data.model.SessionStatus.ACTIVE) { + val completed = with(com.atridad.openclimb.data.model.ClimbSession) { targetSession.complete() } + repository.updateSession(completed) + } + } finally { + stopSessionTracking() + } + } } } return START_STICKY @@ -131,7 +146,7 @@ class SessionTrackingService : Service() { .addAction( android.R.drawable.ic_menu_close_clear_cancel, "End Session", - createStopIntent() + createStopPendingIntent(sessionId) ) .build() @@ -154,8 +169,8 @@ class SessionTrackingService : Service() { ) } - private fun createStopIntent(): PendingIntent { - val intent = createStopIntent(this) + private fun createStopPendingIntent(sessionId: String): PendingIntent { + val intent = createStopIntent(this, sessionId) return PendingIntent.getService( this, 1, diff --git a/app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt b/app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt index fccd9b2..5b576ed 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/OpenClimbApp.kt @@ -27,7 +27,6 @@ import com.atridad.openclimb.ui.viewmodel.ClimbViewModelFactory fun OpenClimbApp() { val navController = rememberNavController() val context = LocalContext.current - val currentBackStackEntry by navController.currentBackStackEntryAsState() val database = remember { OpenClimbDatabase.getDatabase(context) } val repository = remember { ClimbRepository(database, context) } @@ -148,6 +147,7 @@ fun OpenClimbApp() { // Detail screens composable { backStackEntry -> val args = backStackEntry.toRoute() + LaunchedEffect(Unit) { fabConfig = null } SessionDetailScreen( sessionId = args.sessionId, viewModel = viewModel, @@ -160,6 +160,7 @@ fun OpenClimbApp() { composable { backStackEntry -> val args = backStackEntry.toRoute() + LaunchedEffect(Unit) { fabConfig = null } ProblemDetailScreen( problemId = args.problemId, viewModel = viewModel, @@ -172,6 +173,7 @@ fun OpenClimbApp() { composable { backStackEntry -> val args = backStackEntry.toRoute() + LaunchedEffect(Unit) { fabConfig = null } GymDetailScreen( gymId = args.gymId, viewModel = viewModel, @@ -185,6 +187,7 @@ fun OpenClimbApp() { composable { backStackEntry -> val args = backStackEntry.toRoute() + LaunchedEffect(Unit) { fabConfig = null } AddEditGymScreen( gymId = args.gymId, viewModel = viewModel, @@ -194,6 +197,7 @@ fun OpenClimbApp() { composable { backStackEntry -> val args = backStackEntry.toRoute() + LaunchedEffect(Unit) { fabConfig = null } AddEditProblemScreen( problemId = args.problemId, gymId = args.gymId, diff --git a/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt b/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt index 37184f6..9739d25 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/screens/AddEditScreens.kt @@ -948,8 +948,7 @@ fun AddEditSessionScreen( text = "Result: ${attempt.result.name.lowercase().replaceFirstChar { it.uppercase() }}", style = MaterialTheme.typography.bodyMedium, color = when (attempt.result) { - AttemptResult.SUCCESS, AttemptResult.FLASH, - AttemptResult.REDPOINT, AttemptResult.ONSIGHT -> MaterialTheme.colorScheme.primary + AttemptResult.SUCCESS, AttemptResult.FLASH -> MaterialTheme.colorScheme.primary else -> MaterialTheme.colorScheme.onSurfaceVariant } ) diff --git a/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt b/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt index eaaf541..a2ef10c 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/screens/DetailScreens.kt @@ -59,7 +59,7 @@ fun SessionDetailScreen( // Calculate stats val successfulAttempts = attempts.filter { - it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT) + it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) } val uniqueProblems = attempts.map { it.problemId }.distinct() val attemptedProblems = problems.filter { it.id in uniqueProblems } @@ -71,9 +71,7 @@ fun SessionDetailScreen( }.sortedByDescending { attempt -> // Sort by result priority, then by timestamp when (attempt.first.result) { - AttemptResult.ONSIGHT -> 5 - AttemptResult.FLASH -> 4 - AttemptResult.REDPOINT -> 3 + AttemptResult.FLASH -> 3 AttemptResult.SUCCESS -> 2 AttemptResult.FALL -> 1 else -> 0 @@ -130,8 +128,7 @@ fun SessionDetailScreen( ) }, floatingActionButton = { - // Show FAB only for active sessions (those without duration) - if (session?.duration == null) { + if (session?.status == SessionStatus.ACTIVE) { FloatingActionButton( onClick = { showAddAttemptDialog = true } ) { @@ -418,7 +415,7 @@ fun ProblemDetailScreen( // Calculate stats val successfulAttempts = attempts.filter { - it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT) + it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) } val successRate = if (attempts.isNotEmpty()) { (successfulAttempts.size.toDouble() / attempts.size * 100).toInt() @@ -750,7 +747,7 @@ fun GymDetailScreen( } val successfulAttempts = gymAttempts.filter { - it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT) + it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) } val successRate = if (gymAttempts.isNotEmpty()) { @@ -984,7 +981,7 @@ fun GymDetailScreen( problems.sortedByDescending { it.createdAt }.take(5).forEach { problem -> val problemAttempts = gymAttempts.filter { it.problemId == problem.id } val problemSuccessful = problemAttempts.any { - it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT) + it.result in listOf(AttemptResult.SUCCESS, AttemptResult.FLASH) } Card( @@ -1264,13 +1261,13 @@ fun AttemptHistoryCard( @Composable fun AttemptResultBadge(result: AttemptResult) { val backgroundColor = when (result) { - AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT -> MaterialTheme.colorScheme.primaryContainer + AttemptResult.SUCCESS, AttemptResult.FLASH -> MaterialTheme.colorScheme.primaryContainer AttemptResult.FALL -> MaterialTheme.colorScheme.secondaryContainer else -> MaterialTheme.colorScheme.surfaceVariant } val textColor = when (result) { - AttemptResult.SUCCESS, AttemptResult.FLASH, AttemptResult.REDPOINT, AttemptResult.ONSIGHT -> MaterialTheme.colorScheme.onPrimaryContainer + AttemptResult.SUCCESS, AttemptResult.FLASH -> MaterialTheme.colorScheme.onPrimaryContainer AttemptResult.FALL -> MaterialTheme.colorScheme.onSecondaryContainer else -> MaterialTheme.colorScheme.onSurfaceVariant } diff --git a/app/src/main/java/com/atridad/openclimb/ui/viewmodel/ClimbViewModel.kt b/app/src/main/java/com/atridad/openclimb/ui/viewmodel/ClimbViewModel.kt index 25eaa82..0b8585e 100644 --- a/app/src/main/java/com/atridad/openclimb/ui/viewmodel/ClimbViewModel.kt +++ b/app/src/main/java/com/atridad/openclimb/ui/viewmodel/ClimbViewModel.kt @@ -175,8 +175,8 @@ class ClimbViewModel( val completedSession = with(ClimbSession) { session.complete() } repository.updateSession(completedSession) - // Stop the tracking service - val serviceIntent = SessionTrackingService.createStopIntent(context) + // Stop the tracking service, passing the session id so service can finalize if needed + val serviceIntent = SessionTrackingService.createStopIntent(context, sessionId) context.startService(serviceIntent) _uiState.value = _uiState.value.copy( @@ -186,32 +186,6 @@ class ClimbViewModel( } } - fun pauseSession(sessionId: String) { - viewModelScope.launch { - val session = repository.getSessionById(sessionId) - if (session != null && session.status == SessionStatus.ACTIVE) { - val pausedSession = session.copy( - status = SessionStatus.PAUSED, - updatedAt = java.time.LocalDateTime.now().toString() - ) - repository.updateSession(pausedSession) - } - } - } - - fun resumeSession(sessionId: String) { - viewModelScope.launch { - val session = repository.getSessionById(sessionId) - if (session != null && session.status == SessionStatus.PAUSED) { - val resumedSession = session.copy( - status = SessionStatus.ACTIVE, - updatedAt = java.time.LocalDateTime.now().toString() - ) - repository.updateSession(resumedSession) - } - } - } - // Attempt operations fun addAttempt(attempt: Attempt) { viewModelScope.launch { @@ -219,52 +193,12 @@ class ClimbViewModel( } } - fun updateAttempt(attempt: Attempt) { - viewModelScope.launch { - repository.updateAttempt(attempt) - } - } - - fun deleteAttempt(attempt: Attempt) { - viewModelScope.launch { - repository.deleteAttempt(attempt) - } - } - fun getAttemptsBySession(sessionId: String): Flow> = repository.getAttemptsBySession(sessionId) fun getAttemptsByProblem(problemId: String): Flow> = repository.getAttemptsByProblem(problemId) - - - // Analytics operations - // fun getProblemProgress(problemId: String): Flow = - // repository.getProblemProgress(problemId) - - // fun getSessionSummary(sessionId: String): Flow = - // repository.getSessionSummary(sessionId) - - // Export operations - fun exportData(context: Context, directory: File? = null) { - viewModelScope.launch { - try { - _uiState.value = _uiState.value.copy(isLoading = true) - val exportFile = repository.exportAllDataToJson(directory) - _uiState.value = _uiState.value.copy( - isLoading = false, - message = "Data exported to: ${exportFile.absolutePath}" - ) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = "Export failed: ${e.message}" - ) - } - } - } - fun exportDataToUri(context: Context, uri: android.net.Uri) { viewModelScope.launch { try { @@ -282,26 +216,7 @@ class ClimbViewModel( } } } - - // ZIP Export operations with images - fun exportDataToZip(context: Context, directory: File? = null) { - viewModelScope.launch { - try { - _uiState.value = _uiState.value.copy(isLoading = true) - val exportFile = repository.exportAllDataToZip(directory) - _uiState.value = _uiState.value.copy( - isLoading = false, - message = "Data with images exported to: ${exportFile.absolutePath}" - ) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = "Export failed: ${e.message}" - ) - } - } - } - + fun exportDataToZipUri(context: Context, uri: android.net.Uri) { viewModelScope.launch { try { @@ -358,10 +273,6 @@ class ClimbViewModel( _uiState.value = _uiState.value.copy(error = message) } - // Search operations - fun searchGyms(query: String): Flow> = repository.searchGyms(query) - fun searchProblems(query: String): Flow> = repository.searchProblems(query) - // Share operations suspend fun generateSessionShareCard( context: Context, diff --git a/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt b/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt index 6f2b3dc..b20ca13 100644 --- a/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt +++ b/app/src/main/java/com/atridad/openclimb/utils/SessionShareUtils.kt @@ -34,9 +34,7 @@ object SessionShareUtils { ): SessionStats { val successfulResults = listOf( AttemptResult.SUCCESS, - AttemptResult.FLASH, - AttemptResult.REDPOINT, - AttemptResult.ONSIGHT + AttemptResult.FLASH ) val successfulAttempts = attempts.filter { it.result in successfulResults } @@ -57,9 +55,7 @@ object SessionShareUtils { val duration = if (session.duration != null) "${session.duration}m" else "Unknown" val topResult = attempts.maxByOrNull { when (it.result) { - AttemptResult.ONSIGHT -> 5 - AttemptResult.FLASH -> 4 - AttemptResult.REDPOINT -> 3 + AttemptResult.FLASH -> 3 AttemptResult.SUCCESS -> 2 AttemptResult.FALL -> 1 else -> 0