Proper 1.0 release for iOS. Pending App Store submission.
This commit is contained in:
@@ -3,6 +3,10 @@ import Foundation
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
#if canImport(WidgetKit)
|
||||
import WidgetKit
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
class ClimbingDataManager: ObservableObject {
|
||||
|
||||
@@ -16,6 +20,7 @@ class ClimbingDataManager: ObservableObject {
|
||||
@Published var successMessage: String?
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let sharedUserDefaults = UserDefaults(suiteName: "group.com.atridad.OpenClimb")
|
||||
private let encoder = JSONEncoder()
|
||||
private let decoder = JSONDecoder()
|
||||
|
||||
@@ -35,6 +40,9 @@ class ClimbingDataManager: ObservableObject {
|
||||
Task {
|
||||
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
||||
await performImageMaintenance()
|
||||
|
||||
// Check if we need to restart Live Activity for active session
|
||||
await checkAndRestartLiveActivity()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,24 +97,34 @@ class ClimbingDataManager: ObservableObject {
|
||||
private func saveGyms() {
|
||||
if let data = try? encoder.encode(gyms) {
|
||||
userDefaults.set(data, forKey: Keys.gyms)
|
||||
// Share with widget
|
||||
sharedUserDefaults?.set(data, forKey: Keys.gyms)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveProblems() {
|
||||
if let data = try? encoder.encode(problems) {
|
||||
userDefaults.set(data, forKey: Keys.problems)
|
||||
// Share with widget
|
||||
sharedUserDefaults?.set(data, forKey: Keys.problems)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveSessions() {
|
||||
if let data = try? encoder.encode(sessions) {
|
||||
userDefaults.set(data, forKey: Keys.sessions)
|
||||
// Share with widget
|
||||
sharedUserDefaults?.set(data, forKey: Keys.sessions)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAttempts() {
|
||||
if let data = try? encoder.encode(attempts) {
|
||||
userDefaults.set(data, forKey: Keys.attempts)
|
||||
// Share with widget
|
||||
sharedUserDefaults?.set(data, forKey: Keys.attempts)
|
||||
// Update widget timeline
|
||||
updateWidgetTimeline()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +234,14 @@ class ClimbingDataManager: ObservableObject {
|
||||
|
||||
successMessage = "Session started successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// MARK: - Start Live Activity for new session
|
||||
if let gym = gym(withId: gymId) {
|
||||
Task {
|
||||
await LiveActivityManager.shared.startLiveActivity(
|
||||
for: newSession, gymName: gym.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func endSession(_ sessionId: UUID) {
|
||||
@@ -234,6 +260,11 @@ class ClimbingDataManager: ObservableObject {
|
||||
saveSessions()
|
||||
successMessage = "Session completed successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// MARK: - End Live Activity after session ends
|
||||
Task {
|
||||
await LiveActivityManager.shared.endLiveActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +280,9 @@ class ClimbingDataManager: ObservableObject {
|
||||
saveSessions()
|
||||
successMessage = "Session updated successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// Update Live Activity when session updates
|
||||
updateLiveActivityForActiveSession()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +324,9 @@ class ClimbingDataManager: ObservableObject {
|
||||
|
||||
successMessage = "Attempt logged successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// Update Live Activity when new attempt is added
|
||||
updateLiveActivityForActiveSession()
|
||||
}
|
||||
|
||||
func updateAttempt(_ attempt: Attempt) {
|
||||
@@ -298,6 +335,9 @@ class ClimbingDataManager: ObservableObject {
|
||||
saveAttempts()
|
||||
successMessage = "Attempt updated successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// Update Live Activity when attempt is updated
|
||||
updateLiveActivityForActiveSession()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +346,9 @@ class ClimbingDataManager: ObservableObject {
|
||||
saveAttempts()
|
||||
successMessage = "Attempt deleted successfully"
|
||||
clearMessageAfterDelay()
|
||||
|
||||
// Update Live Activity when attempt is deleted
|
||||
updateLiveActivityForActiveSession()
|
||||
}
|
||||
|
||||
func attempts(forSession sessionId: UUID) -> [Attempt] {
|
||||
@@ -924,6 +967,100 @@ extension ClimbingDataManager {
|
||||
"""
|
||||
}
|
||||
|
||||
func testLiveActivity() {
|
||||
print("🧪 Testing Live Activity functionality...")
|
||||
|
||||
// Check Live Activity availability
|
||||
let status = LiveActivityManager.shared.checkLiveActivityAvailability()
|
||||
print(status)
|
||||
|
||||
// Test with dummy data if we have a gym
|
||||
guard let testGym = gyms.first else {
|
||||
print("❌ No gyms available for testing")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a test session
|
||||
let testSession = ClimbSession(gymId: testGym.id, notes: "Test session for Live Activity")
|
||||
|
||||
Task {
|
||||
await LiveActivityManager.shared.startLiveActivity(
|
||||
for: testSession, gymName: testGym.name)
|
||||
|
||||
// Wait a bit then update
|
||||
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
||||
await LiveActivityManager.shared.updateLiveActivity(
|
||||
elapsed: 120, totalAttempts: 5, completedProblems: 1)
|
||||
|
||||
// Wait then end
|
||||
try? await Task.sleep(nanoseconds: 5_000_000_000)
|
||||
await LiveActivityManager.shared.endLiveActivity()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkAndRestartLiveActivity() async {
|
||||
guard let activeSession = activeSession else { return }
|
||||
|
||||
if let gym = gym(withId: activeSession.gymId) {
|
||||
await LiveActivityManager.shared.restartLiveActivityIfNeeded(
|
||||
activeSession: activeSession,
|
||||
gymName: gym.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this when app becomes active to check for Live Activity restart
|
||||
func onAppBecomeActive() {
|
||||
Task {
|
||||
await checkAndRestartLiveActivity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Update Live Activity with current session data
|
||||
private func updateLiveActivityForActiveSession() {
|
||||
guard let activeSession = activeSession,
|
||||
activeSession.status == .active,
|
||||
let gym = gym(withId: activeSession.gymId)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let attemptsForSession = attempts(forSession: activeSession.id)
|
||||
let totalAttempts = attemptsForSession.count
|
||||
|
||||
let completedProblemIds = Set(
|
||||
attemptsForSession.filter { $0.result.isSuccessful }.map { $0.problemId }
|
||||
)
|
||||
let completedProblems = completedProblemIds.count
|
||||
|
||||
let elapsedInterval: TimeInterval
|
||||
if let startTime = activeSession.startTime {
|
||||
elapsedInterval = Date().timeIntervalSince(startTime)
|
||||
} else {
|
||||
elapsedInterval = 0
|
||||
}
|
||||
|
||||
Task {
|
||||
await LiveActivityManager.shared.updateLiveActivity(
|
||||
elapsed: elapsedInterval,
|
||||
totalAttempts: totalAttempts,
|
||||
completedProblems: completedProblems
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually force Live Activity update (useful for debugging)
|
||||
func forceLiveActivityUpdate() {
|
||||
updateLiveActivityForActiveSession()
|
||||
}
|
||||
|
||||
/// Update widget timeline when data changes
|
||||
private func updateWidgetTimeline() {
|
||||
#if canImport(WidgetKit)
|
||||
WidgetCenter.shared.reloadTimelines(ofKind: "SessionStatusLive")
|
||||
#endif
|
||||
}
|
||||
|
||||
private func validateImportData(_ importData: ClimbDataExport) throws {
|
||||
if importData.gyms.isEmpty {
|
||||
throw NSError(
|
||||
|
||||
Reference in New Issue
Block a user