iOS Build 23

This commit is contained in:
2025-10-11 18:54:24 -06:00
parent e7c46634da
commit 53fa74cc83
23 changed files with 1351 additions and 285 deletions

View File

@@ -29,6 +29,7 @@ class ClimbingDataManager: ObservableObject {
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
nonisolated(unsafe) private var liveActivityObserver: NSObjectProtocol?
nonisolated(unsafe) private var migrationObserver: NSObjectProtocol?
let syncService = SyncService()
let healthKitService = HealthKitService.shared
@@ -68,8 +69,8 @@ class ClimbingDataManager: ObservableObject {
init() {
_ = ImageManager.shared
loadAllData()
migrateImagePaths()
setupLiveActivityNotifications()
setupMigrationNotifications()
// Keep our published isSyncing in sync with syncService.isSyncing
syncService.$isSyncing
@@ -88,6 +89,9 @@ class ClimbingDataManager: ObservableObject {
if let observer = liveActivityObserver {
NotificationCenter.default.removeObserver(observer)
}
if let observer = migrationObserver {
NotificationCenter.default.removeObserver(observer)
}
}
private func loadAllData() {
@@ -632,7 +636,7 @@ class ClimbingDataManager: ObservableObject {
saveAttempts()
let removedCount = initialAttemptCount - attempts.count
print(
"Cleanup complete. Removed \(removedCount) attempts. Remaining: \(attempts.count)"
"Cleanup complete. Removed \(removedCount) attempts. Remaining: \(attempts.count)"
)
}
@@ -707,9 +711,9 @@ class ClimbingDataManager: ObservableObject {
report += "Orphaned Sessions: \(orphanedSessions.count)\n"
if orphanedAttempts.isEmpty && orphanedProblems.isEmpty && orphanedSessions.isEmpty {
report += "\nNo integrity issues found"
report += "\nNo integrity issues found"
} else {
report += "\n⚠️ Issues found - run cleanup to fix"
report += "\nIssues found - run cleanup to fix"
}
return report
@@ -749,6 +753,7 @@ class ClimbingDataManager: ObservableObject {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"
// Create export data with normalized image paths
let exportData = ClimbDataBackup(
exportedAt: dateFormatter.string(from: Date()),
version: "2.0",
@@ -759,7 +764,7 @@ class ClimbingDataManager: ObservableObject {
attempts: attempts.map { BackupAttempt(from: $0) }
)
// Collect referenced image paths
// Collect actual image paths from disk for the ZIP
let referencedImagePaths = collectReferencedImagePaths()
print("Starting export with \(referencedImagePaths.count) images")
@@ -878,17 +883,19 @@ extension ClimbingDataManager {
"Problem '\(problem.name ?? "Unnamed")' has \(problem.imagePaths.count) images"
)
for imagePath in problem.imagePaths {
print(" - Relative path: \(imagePath)")
let fullPath = ImageManager.shared.getFullPath(from: imagePath)
print(" - Full path: \(fullPath)")
print(" - Stored path: \(imagePath)")
// Extract just the filename (migration should have normalized these)
let filename = URL(fileURLWithPath: imagePath).lastPathComponent
let fullPath = ImageManager.shared.getFullPath(from: filename)
print(" - Full disk path: \(fullPath)")
// Check if file exists
if FileManager.default.fileExists(atPath: fullPath) {
print(" File exists")
print(" File exists")
imagePaths.insert(fullPath)
} else {
print(" File does NOT exist")
// Still add it to let ZipUtils handle the error logging
print(" ✗ WARNING: File not found at \(fullPath)")
// Still add it to let ZipUtils handle the logging
imagePaths.insert(fullPath)
}
}
@@ -904,11 +911,53 @@ extension ClimbingDataManager {
imagePathMapping: [String: String]
) -> [BackupProblem] {
return problems.map { problem in
let updatedImagePaths = (problem.imagePaths ?? []).compactMap { oldPath in
let fileName = URL(fileURLWithPath: oldPath).lastPathComponent
return imagePathMapping[fileName]
guard let originalImagePaths = problem.imagePaths, !originalImagePaths.isEmpty else {
return problem
}
return problem.withUpdatedImagePaths(updatedImagePaths)
var deterministicImagePaths: [String] = []
for (index, oldPath) in originalImagePaths.enumerated() {
let originalFileName = URL(fileURLWithPath: oldPath).lastPathComponent
let deterministicName = ImageNamingUtils.generateImageFilename(
problemId: problem.id, imageIndex: index)
if let tempFileName = imagePathMapping[originalFileName] {
let imageManager = ImageManager.shared
let tempPath = imageManager.imagesDirectory.appendingPathComponent(tempFileName)
let deterministicPath = imageManager.imagesDirectory.appendingPathComponent(
deterministicName)
do {
if FileManager.default.fileExists(atPath: tempPath.path) {
try FileManager.default.moveItem(at: tempPath, to: deterministicPath)
let tempBackupPath = imageManager.backupDirectory
.appendingPathComponent(tempFileName)
let deterministicBackupPath = imageManager.backupDirectory
.appendingPathComponent(deterministicName)
if FileManager.default.fileExists(atPath: tempBackupPath.path) {
try? FileManager.default.moveItem(
at: tempBackupPath, to: deterministicBackupPath)
}
deterministicImagePaths.append(deterministicName)
print("Renamed imported image: \(tempFileName)\(deterministicName)")
}
} catch {
print(
"Failed to rename imported image \(tempFileName) to \(deterministicName): \(error)"
)
deterministicImagePaths.append(tempFileName)
}
} else {
deterministicImagePaths.append(deterministicName)
}
}
return problem.withUpdatedImagePaths(deterministicImagePaths)
}
}
@@ -1134,6 +1183,19 @@ extension ClimbingDataManager {
}
}
private func setupMigrationNotifications() {
migrationObserver = NotificationCenter.default.addObserver(
forName: NSNotification.Name("ImageMigrationCompleted"),
object: nil,
queue: .main
) { [weak self] notification in
if let updateCount = notification.userInfo?["updateCount"] as? Int {
print("🔔 Image migration completed with \(updateCount) updates - reloading data")
self?.loadProblems()
}
}
}
/// Handle Live Activity being dismissed by user
private func handleLiveActivityDismissed() async {
guard let activeSession = activeSession,