iOS Build 23
This commit is contained in:
@@ -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 += "\n✅ No 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,
|
||||
|
||||
Reference in New Issue
Block a user