0.3.3
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "com.atridad.openclimb"
|
applicationId = "com.atridad.openclimb"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 5
|
versionCode = 6
|
||||||
versionName = "0.3.2"
|
versionName = "0.3.3"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 4,
|
"versionCode": 6,
|
||||||
"versionName": "0.3.1",
|
"versionName": "0.3.3",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class AttemptResult {
|
enum class AttemptResult {
|
||||||
SUCCESS, // Completed the problem/route
|
SUCCESS,
|
||||||
FALL, // Fell but made progress
|
FALL,
|
||||||
NO_PROGRESS, // Couldn't make meaningful progress
|
NO_PROGRESS,
|
||||||
FLASH, // Completed on first try
|
FLASH,
|
||||||
REDPOINT, // Completed after previous attempts
|
|
||||||
ONSIGHT // Completed on first try without prior knowledge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ data class ClimbSession(
|
|||||||
val start = LocalDateTime.parse(startTime)
|
val start = LocalDateTime.parse(startTime)
|
||||||
val end = LocalDateTime.parse(endTime)
|
val end = LocalDateTime.parse(endTime)
|
||||||
java.time.Duration.between(start, end).toMinutes()
|
java.time.Duration.between(start, end).toMinutes()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class ClimbRepository(
|
|||||||
importData.gyms.forEach { gym ->
|
importData.gyms.forEach { gym ->
|
||||||
try {
|
try {
|
||||||
gymDao.insertGym(gym)
|
gymDao.insertGym(gym)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// If insertion fails, update instead
|
// If insertion fails, update instead
|
||||||
gymDao.updateGym(gym)
|
gymDao.updateGym(gym)
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ class ClimbRepository(
|
|||||||
importData.problems.forEach { problem ->
|
importData.problems.forEach { problem ->
|
||||||
try {
|
try {
|
||||||
problemDao.insertProblem(problem)
|
problemDao.insertProblem(problem)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
problemDao.updateProblem(problem)
|
problemDao.updateProblem(problem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ class ClimbRepository(
|
|||||||
importData.sessions.forEach { session ->
|
importData.sessions.forEach { session ->
|
||||||
try {
|
try {
|
||||||
sessionDao.insertSession(session)
|
sessionDao.insertSession(session)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
sessionDao.updateSession(session)
|
sessionDao.updateSession(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ class ClimbRepository(
|
|||||||
importData.attempts.forEach { attempt ->
|
importData.attempts.forEach { attempt ->
|
||||||
try {
|
try {
|
||||||
attemptDao.insertAttempt(attempt)
|
attemptDao.insertAttempt(attempt)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
attemptDao.updateAttempt(attempt)
|
attemptDao.updateAttempt(attempt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
return Intent(context, SessionTrackingService::class.java).apply {
|
||||||
action = ACTION_STOP_SESSION
|
action = ACTION_STOP_SESSION
|
||||||
|
putExtra(EXTRA_SESSION_ID, sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,21 @@ class SessionTrackingService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_STOP_SESSION -> {
|
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
|
return START_STICKY
|
||||||
@@ -131,7 +146,7 @@ class SessionTrackingService : Service() {
|
|||||||
.addAction(
|
.addAction(
|
||||||
android.R.drawable.ic_menu_close_clear_cancel,
|
android.R.drawable.ic_menu_close_clear_cancel,
|
||||||
"End Session",
|
"End Session",
|
||||||
createStopIntent()
|
createStopPendingIntent(sessionId)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -154,8 +169,8 @@ class SessionTrackingService : Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createStopIntent(): PendingIntent {
|
private fun createStopPendingIntent(sessionId: String): PendingIntent {
|
||||||
val intent = createStopIntent(this)
|
val intent = createStopIntent(this, sessionId)
|
||||||
return PendingIntent.getService(
|
return PendingIntent.getService(
|
||||||
this,
|
this,
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import com.atridad.openclimb.ui.viewmodel.ClimbViewModelFactory
|
|||||||
fun OpenClimbApp() {
|
fun OpenClimbApp() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val currentBackStackEntry by navController.currentBackStackEntryAsState()
|
|
||||||
|
|
||||||
val database = remember { OpenClimbDatabase.getDatabase(context) }
|
val database = remember { OpenClimbDatabase.getDatabase(context) }
|
||||||
val repository = remember { ClimbRepository(database, context) }
|
val repository = remember { ClimbRepository(database, context) }
|
||||||
@@ -148,6 +147,7 @@ fun OpenClimbApp() {
|
|||||||
// Detail screens
|
// Detail screens
|
||||||
composable<Screen.SessionDetail> { backStackEntry ->
|
composable<Screen.SessionDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.SessionDetail>()
|
val args = backStackEntry.toRoute<Screen.SessionDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
SessionDetailScreen(
|
SessionDetailScreen(
|
||||||
sessionId = args.sessionId,
|
sessionId = args.sessionId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -160,6 +160,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.ProblemDetail> { backStackEntry ->
|
composable<Screen.ProblemDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.ProblemDetail>()
|
val args = backStackEntry.toRoute<Screen.ProblemDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
ProblemDetailScreen(
|
ProblemDetailScreen(
|
||||||
problemId = args.problemId,
|
problemId = args.problemId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -172,6 +173,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.GymDetail> { backStackEntry ->
|
composable<Screen.GymDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.GymDetail>()
|
val args = backStackEntry.toRoute<Screen.GymDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
GymDetailScreen(
|
GymDetailScreen(
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -185,6 +187,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.AddEditGym> { backStackEntry ->
|
composable<Screen.AddEditGym> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.AddEditGym>()
|
val args = backStackEntry.toRoute<Screen.AddEditGym>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
AddEditGymScreen(
|
AddEditGymScreen(
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -194,6 +197,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.AddEditProblem> { backStackEntry ->
|
composable<Screen.AddEditProblem> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.AddEditProblem>()
|
val args = backStackEntry.toRoute<Screen.AddEditProblem>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
AddEditProblemScreen(
|
AddEditProblemScreen(
|
||||||
problemId = args.problemId,
|
problemId = args.problemId,
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
|
|||||||
@@ -948,8 +948,7 @@ fun AddEditSessionScreen(
|
|||||||
text = "Result: ${attempt.result.name.lowercase().replaceFirstChar { it.uppercase() }}",
|
text = "Result: ${attempt.result.name.lowercase().replaceFirstChar { it.uppercase() }}",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = when (attempt.result) {
|
color = when (attempt.result) {
|
||||||
AttemptResult.SUCCESS, AttemptResult.FLASH,
|
AttemptResult.SUCCESS, AttemptResult.FLASH -> MaterialTheme.colorScheme.primary
|
||||||
AttemptResult.REDPOINT, AttemptResult.ONSIGHT -> MaterialTheme.colorScheme.primary
|
|
||||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ fun SessionDetailScreen(
|
|||||||
|
|
||||||
// Calculate stats
|
// Calculate stats
|
||||||
val successfulAttempts = attempts.filter {
|
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 uniqueProblems = attempts.map { it.problemId }.distinct()
|
||||||
val attemptedProblems = problems.filter { it.id in uniqueProblems }
|
val attemptedProblems = problems.filter { it.id in uniqueProblems }
|
||||||
@@ -71,9 +71,7 @@ fun SessionDetailScreen(
|
|||||||
}.sortedByDescending { attempt ->
|
}.sortedByDescending { attempt ->
|
||||||
// Sort by result priority, then by timestamp
|
// Sort by result priority, then by timestamp
|
||||||
when (attempt.first.result) {
|
when (attempt.first.result) {
|
||||||
AttemptResult.ONSIGHT -> 5
|
AttemptResult.FLASH -> 3
|
||||||
AttemptResult.FLASH -> 4
|
|
||||||
AttemptResult.REDPOINT -> 3
|
|
||||||
AttemptResult.SUCCESS -> 2
|
AttemptResult.SUCCESS -> 2
|
||||||
AttemptResult.FALL -> 1
|
AttemptResult.FALL -> 1
|
||||||
else -> 0
|
else -> 0
|
||||||
@@ -130,8 +128,7 @@ fun SessionDetailScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
// Show FAB only for active sessions (those without duration)
|
if (session?.status == SessionStatus.ACTIVE) {
|
||||||
if (session?.duration == null) {
|
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = { showAddAttemptDialog = true }
|
onClick = { showAddAttemptDialog = true }
|
||||||
) {
|
) {
|
||||||
@@ -418,7 +415,7 @@ fun ProblemDetailScreen(
|
|||||||
|
|
||||||
// Calculate stats
|
// Calculate stats
|
||||||
val successfulAttempts = attempts.filter {
|
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()) {
|
val successRate = if (attempts.isNotEmpty()) {
|
||||||
(successfulAttempts.size.toDouble() / attempts.size * 100).toInt()
|
(successfulAttempts.size.toDouble() / attempts.size * 100).toInt()
|
||||||
@@ -750,7 +747,7 @@ fun GymDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val successfulAttempts = gymAttempts.filter {
|
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()) {
|
val successRate = if (gymAttempts.isNotEmpty()) {
|
||||||
@@ -984,7 +981,7 @@ fun GymDetailScreen(
|
|||||||
problems.sortedByDescending { it.createdAt }.take(5).forEach { problem ->
|
problems.sortedByDescending { it.createdAt }.take(5).forEach { problem ->
|
||||||
val problemAttempts = gymAttempts.filter { it.problemId == problem.id }
|
val problemAttempts = gymAttempts.filter { it.problemId == problem.id }
|
||||||
val problemSuccessful = problemAttempts.any {
|
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(
|
Card(
|
||||||
@@ -1264,13 +1261,13 @@ fun AttemptHistoryCard(
|
|||||||
@Composable
|
@Composable
|
||||||
fun AttemptResultBadge(result: AttemptResult) {
|
fun AttemptResultBadge(result: AttemptResult) {
|
||||||
val backgroundColor = when (result) {
|
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
|
AttemptResult.FALL -> MaterialTheme.colorScheme.secondaryContainer
|
||||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
else -> MaterialTheme.colorScheme.surfaceVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
val textColor = when (result) {
|
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
|
AttemptResult.FALL -> MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ class ClimbViewModel(
|
|||||||
val completedSession = with(ClimbSession) { session.complete() }
|
val completedSession = with(ClimbSession) { session.complete() }
|
||||||
repository.updateSession(completedSession)
|
repository.updateSession(completedSession)
|
||||||
|
|
||||||
// Stop the tracking service
|
// Stop the tracking service, passing the session id so service can finalize if needed
|
||||||
val serviceIntent = SessionTrackingService.createStopIntent(context)
|
val serviceIntent = SessionTrackingService.createStopIntent(context, sessionId)
|
||||||
context.startService(serviceIntent)
|
context.startService(serviceIntent)
|
||||||
|
|
||||||
_uiState.value = _uiState.value.copy(
|
_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
|
// Attempt operations
|
||||||
fun addAttempt(attempt: Attempt) {
|
fun addAttempt(attempt: Attempt) {
|
||||||
viewModelScope.launch {
|
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<List<Attempt>> =
|
fun getAttemptsBySession(sessionId: String): Flow<List<Attempt>> =
|
||||||
repository.getAttemptsBySession(sessionId)
|
repository.getAttemptsBySession(sessionId)
|
||||||
|
|
||||||
fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> =
|
fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> =
|
||||||
repository.getAttemptsByProblem(problemId)
|
repository.getAttemptsByProblem(problemId)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Analytics operations
|
|
||||||
// fun getProblemProgress(problemId: String): Flow<ProblemProgress?> =
|
|
||||||
// repository.getProblemProgress(problemId)
|
|
||||||
|
|
||||||
// fun getSessionSummary(sessionId: String): Flow<SessionSummary?> =
|
|
||||||
// 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) {
|
fun exportDataToUri(context: Context, uri: android.net.Uri) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
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) {
|
fun exportDataToZipUri(context: Context, uri: android.net.Uri) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
@@ -358,10 +273,6 @@ class ClimbViewModel(
|
|||||||
_uiState.value = _uiState.value.copy(error = message)
|
_uiState.value = _uiState.value.copy(error = message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search operations
|
|
||||||
fun searchGyms(query: String): Flow<List<Gym>> = repository.searchGyms(query)
|
|
||||||
fun searchProblems(query: String): Flow<List<Problem>> = repository.searchProblems(query)
|
|
||||||
|
|
||||||
// Share operations
|
// Share operations
|
||||||
suspend fun generateSessionShareCard(
|
suspend fun generateSessionShareCard(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ object SessionShareUtils {
|
|||||||
): SessionStats {
|
): SessionStats {
|
||||||
val successfulResults = listOf(
|
val successfulResults = listOf(
|
||||||
AttemptResult.SUCCESS,
|
AttemptResult.SUCCESS,
|
||||||
AttemptResult.FLASH,
|
AttemptResult.FLASH
|
||||||
AttemptResult.REDPOINT,
|
|
||||||
AttemptResult.ONSIGHT
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val successfulAttempts = attempts.filter { it.result in successfulResults }
|
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 duration = if (session.duration != null) "${session.duration}m" else "Unknown"
|
||||||
val topResult = attempts.maxByOrNull {
|
val topResult = attempts.maxByOrNull {
|
||||||
when (it.result) {
|
when (it.result) {
|
||||||
AttemptResult.ONSIGHT -> 5
|
AttemptResult.FLASH -> 3
|
||||||
AttemptResult.FLASH -> 4
|
|
||||||
AttemptResult.REDPOINT -> 3
|
|
||||||
AttemptResult.SUCCESS -> 2
|
AttemptResult.SUCCESS -> 2
|
||||||
AttemptResult.FALL -> 1
|
AttemptResult.FALL -> 1
|
||||||
else -> 0
|
else -> 0
|
||||||
|
|||||||
Reference in New Issue
Block a user