All checks were successful
Ascently - Docs Deploy / build-and-push (pull_request) Successful in 8m4s
96 lines
3.4 KiB
Swift
96 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
/// User-visible errors that can arise while handling session-related intents.
|
|
enum SessionIntentError: LocalizedError {
|
|
case noRecentGym
|
|
case noActiveSession
|
|
case failedToStartSession
|
|
case failedToEndSession
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .noRecentGym:
|
|
return "There's no recent gym to start a session with."
|
|
case .noActiveSession:
|
|
return "There isn't an active session to end right now."
|
|
case .failedToStartSession:
|
|
return "Ascently couldn't start a new session."
|
|
case .failedToEndSession:
|
|
return "Ascently couldn't finish the active session."
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SessionIntentSummary: Sendable {
|
|
let sessionId: UUID
|
|
let gymName: String
|
|
let status: SessionStatus
|
|
}
|
|
|
|
/// Central controller that exposes the minimal climbing session operations used by App Intents and shortcuts.
|
|
@MainActor
|
|
final class SessionIntentController {
|
|
|
|
private let dataManager: ClimbingDataManager
|
|
|
|
init(dataManager: ClimbingDataManager = .shared) {
|
|
self.dataManager = dataManager
|
|
}
|
|
|
|
/// Starts a new session using the most recently visited gym.
|
|
func startSessionWithLastUsedGym() async throws -> SessionIntentSummary {
|
|
// Give a moment for data to be ready if app just launched
|
|
if dataManager.gyms.isEmpty {
|
|
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
|
|
}
|
|
|
|
guard let lastGym = dataManager.getLastUsedGym() else {
|
|
logFailure(.noRecentGym, context: "No recorded sessions available")
|
|
throw SessionIntentError.noRecentGym
|
|
}
|
|
|
|
guard let startedSession = await dataManager.startSessionAsync(gymId: lastGym.id) else {
|
|
logFailure(.failedToStartSession, context: "Data manager failed to create new session")
|
|
throw SessionIntentError.failedToStartSession
|
|
}
|
|
|
|
return SessionIntentSummary(
|
|
sessionId: startedSession.id,
|
|
gymName: lastGym.name,
|
|
status: startedSession.status
|
|
)
|
|
}
|
|
|
|
/// Ends the currently active climbing session, if one exists.
|
|
func endActiveSession() async throws -> SessionIntentSummary {
|
|
guard let activeSession = dataManager.activeSession else {
|
|
logFailure(.noActiveSession, context: "No active session stored in data manager")
|
|
throw SessionIntentError.noActiveSession
|
|
}
|
|
|
|
guard let completedSession = await dataManager.endSessionAsync(activeSession.id) else {
|
|
logFailure(
|
|
.failedToEndSession, context: "Data manager failed to complete active session")
|
|
throw SessionIntentError.failedToEndSession
|
|
}
|
|
|
|
guard let gym = dataManager.gym(withId: completedSession.gymId) else {
|
|
logFailure(
|
|
.failedToEndSession,
|
|
context: "Gym missing for completed session \(completedSession.id)")
|
|
throw SessionIntentError.failedToEndSession
|
|
}
|
|
|
|
return SessionIntentSummary(
|
|
sessionId: completedSession.id,
|
|
gymName: gym.name,
|
|
status: completedSession.status
|
|
)
|
|
}
|
|
|
|
private func logFailure(_ error: SessionIntentError, context: String) {
|
|
// Logging from intent context - errors are visible to user via dialog
|
|
print("SessionIntentError: \(error). Context: \(context)")
|
|
}
|
|
}
|