2.2.0 - Final Builds
All checks were successful
Ascently - Sync Deploy / build-and-push (push) Successful in 2m32s
All checks were successful
Ascently - Sync Deploy / build-and-push (push) Successful in 2m32s
This commit is contained in:
@@ -16,7 +16,7 @@ android {
|
||||
applicationId = "com.atridad.ascently"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 44
|
||||
versionCode = 45
|
||||
versionName = "2.2.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -368,9 +368,22 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
repository.setAutoSyncCallback(null)
|
||||
|
||||
try {
|
||||
// Merge and apply deletions first to prevent resurrection
|
||||
val allDeletions = repository.getDeletedItems() + response.deletedItems
|
||||
val uniqueDeletions = allDeletions.distinctBy { "${it.type}:${it.id}" }
|
||||
|
||||
Log.d(TAG, "Applying ${uniqueDeletions.size} deletion records before merging data")
|
||||
applyDeletions(uniqueDeletions)
|
||||
|
||||
// Build deleted item lookup set
|
||||
val deletedItemSet = uniqueDeletions.map { "${it.type}:${it.id}" }.toSet()
|
||||
|
||||
// Download images for new/modified problems from server
|
||||
val imagePathMapping = mutableMapOf<String, String>()
|
||||
for (problem in response.problems) {
|
||||
if (deletedItemSet.contains("problem:${problem.id}")) {
|
||||
continue
|
||||
}
|
||||
problem.imagePaths?.forEach { imagePath ->
|
||||
val serverFilename = imagePath.substringAfterLast('/')
|
||||
try {
|
||||
@@ -384,9 +397,12 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
}
|
||||
}
|
||||
|
||||
// Merge gyms - check if exists and compare timestamps
|
||||
// Merge gyms
|
||||
val existingGyms = repository.getAllGyms().first()
|
||||
for (backupGym in response.gyms) {
|
||||
if (deletedItemSet.contains("gym:${backupGym.id}")) {
|
||||
continue
|
||||
}
|
||||
val existing = existingGyms.find { it.id == backupGym.id }
|
||||
if (existing == null || backupGym.updatedAt >= existing.updatedAt) {
|
||||
val gym = backupGym.toGym()
|
||||
@@ -401,6 +417,9 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
// Merge problems
|
||||
val existingProblems = repository.getAllProblems().first()
|
||||
for (backupProblem in response.problems) {
|
||||
if (deletedItemSet.contains("problem:${backupProblem.id}")) {
|
||||
continue
|
||||
}
|
||||
val updatedImagePaths =
|
||||
backupProblem.imagePaths?.map { oldPath ->
|
||||
imagePathMapping[oldPath] ?: oldPath
|
||||
@@ -421,6 +440,9 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
// Merge sessions
|
||||
val existingSessions = repository.getAllSessions().first()
|
||||
for (backupSession in response.sessions) {
|
||||
if (deletedItemSet.contains("session:${backupSession.id}")) {
|
||||
continue
|
||||
}
|
||||
val session = backupSession.toClimbSession()
|
||||
val existing = existingSessions.find { it.id == backupSession.id }
|
||||
if (existing == null || backupSession.updatedAt >= existing.updatedAt) {
|
||||
@@ -435,6 +457,9 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
// Merge attempts
|
||||
val existingAttempts = repository.getAllAttempts().first()
|
||||
for (backupAttempt in response.attempts) {
|
||||
if (deletedItemSet.contains("attempt:${backupAttempt.id}")) {
|
||||
continue
|
||||
}
|
||||
val attempt = backupAttempt.toAttempt()
|
||||
val existing = existingAttempts.find { it.id == backupAttempt.id }
|
||||
if (existing == null || backupAttempt.createdAt >= existing.createdAt) {
|
||||
@@ -446,15 +471,12 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
}
|
||||
}
|
||||
|
||||
// Apply deletions
|
||||
applyDeletions(response.deletedItems)
|
||||
// Apply deletions again for safety
|
||||
applyDeletions(uniqueDeletions)
|
||||
|
||||
// Update deletion records
|
||||
val allDeletions = repository.getDeletedItems() + response.deletedItems
|
||||
repository.clearDeletedItems()
|
||||
allDeletions.distinctBy { "${it.type}:${it.id}" }.forEach {
|
||||
repository.trackDeletion(it.id, it.type)
|
||||
}
|
||||
uniqueDeletions.forEach { repository.trackDeletion(it.id, it.type) }
|
||||
} finally {
|
||||
// Re-enable auto-sync
|
||||
repository.setAutoSyncCallback { serviceScope.launch { triggerAutoSync() } }
|
||||
@@ -542,7 +564,7 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
Request.Builder()
|
||||
.url("$serverUrl/sync")
|
||||
.header("Authorization", "Bearer $authToken")
|
||||
.post(requestBody)
|
||||
.put(requestBody)
|
||||
.build()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.window.Dialog
|
||||
import com.atridad.ascently.data.model.*
|
||||
import com.atridad.ascently.ui.components.FullscreenImageViewer
|
||||
import com.atridad.ascently.ui.components.ImageDisplaySection
|
||||
import com.atridad.ascently.ui.components.ImagePicker
|
||||
import com.atridad.ascently.ui.theme.CustomIcons
|
||||
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
|
||||
import com.atridad.ascently.utils.DateFormatUtils
|
||||
@@ -1489,6 +1490,7 @@ fun EnhancedAddAttemptDialog(
|
||||
// New problem creation state
|
||||
var newProblemName by remember { mutableStateOf("") }
|
||||
var newProblemGrade by remember { mutableStateOf("") }
|
||||
var newProblemImagePaths by remember { mutableStateOf<List<String>>(emptyList()) }
|
||||
var selectedClimbType by remember { mutableStateOf(ClimbType.BOULDER) }
|
||||
var selectedDifficultySystem by remember {
|
||||
mutableStateOf(gym.difficultySystems.firstOrNull() ?: DifficultySystem.V_SCALE)
|
||||
@@ -1690,7 +1692,14 @@ fun EnhancedAddAttemptDialog(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
IconButton(onClick = { showCreateProblem = false }) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
showCreateProblem = false
|
||||
newProblemName = ""
|
||||
newProblemGrade = ""
|
||||
newProblemImagePaths = emptyList()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
@@ -1905,6 +1914,21 @@ fun EnhancedAddAttemptDialog(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Photos Section
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = "Photos (Optional)",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
ImagePicker(
|
||||
imageUris = newProblemImagePaths,
|
||||
onImagesChanged = { newProblemImagePaths = it },
|
||||
maxImages = 5
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2069,7 +2093,9 @@ fun EnhancedAddAttemptDialog(
|
||||
null
|
||||
},
|
||||
climbType = selectedClimbType,
|
||||
difficulty = difficulty
|
||||
difficulty = difficulty,
|
||||
imagePaths =
|
||||
newProblemImagePaths
|
||||
)
|
||||
|
||||
onProblemCreated(newProblem)
|
||||
@@ -2087,6 +2113,12 @@ fun EnhancedAddAttemptDialog(
|
||||
notes = notes.ifBlank { null }
|
||||
)
|
||||
onAttemptAdded(attempt)
|
||||
|
||||
// Reset form
|
||||
newProblemName = ""
|
||||
newProblemGrade = ""
|
||||
newProblemImagePaths = emptyList()
|
||||
showCreateProblem = false
|
||||
}
|
||||
} else {
|
||||
// Create attempt for selected problem
|
||||
|
||||
@@ -338,18 +338,6 @@ fun CalendarView(
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
if (activeSession != null && activeSessionGym != null) {
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
ActiveSessionBanner(
|
||||
activeSession = activeSession,
|
||||
gym = activeSessionGym,
|
||||
onSessionClick = { onNavigateToSessionDetail(activeSession.id) },
|
||||
onEndSession = onEndSession
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors =
|
||||
|
||||
Reference in New Issue
Block a user