// ── Shareable Timer Manager ─────────────────────────────────── // Create shareable timer links: chronom.ind/t/abc123 // iOS Universal Links → open app directly // Web fallback → PWA timer page import Foundation import Combine @MainActor final class ShareableTimerManager: ObservableObject { static let shared = ShareableTimerManager() @Published var sharedTimers: [SharedTimer] = [] private let storageKey = "chronomind-shared-timers" private let baseURL = "https://chronomind.app/t/" private init() { loadSharedTimers() } // MARK: - Create Shareable Link func createShareableLink(for timer: CMTimer) -> SharedTimer { let shareCode = generateShareCode() let shared = SharedTimer( code: shareCode, label: timer.label, type: timer.type, durationSeconds: timer.duration ?? 0, urgency: timer.urgency, cascadePreset: timer.cascade.preset, category: timer.category, createdBy: nil, // anonymized createdAt: Date(), expiresAt: Calendar.current.date(byAdding: .day, value: 30, to: Date())! ) sharedTimers.append(shared) saveSharedTimers() return shared } /// Full shareable URL func shareURL(for shared: SharedTimer) -> URL { URL(string: "\(baseURL)\(shared.code)")! } /// Share text for messaging func shareText(for shared: SharedTimer) -> String { let url = shareURL(for: shared) return "Check out my \(shared.label) timer on ChronoMind: \(url.absoluteString)" } // MARK: - Import from Link /// Parse a share link and create a timer from it func importFromURL(_ url: URL) -> CMTimer? { guard let code = extractShareCode(from: url) else { return nil } return importFromCode(code) } func importFromCode(_ code: String) -> CMTimer? { // Look up in local shared timers first guard let shared = sharedTimers.first(where: { $0.code == code }) else { // In production, fetch from server return nil } guard !shared.isExpired else { return nil } // Create timer from shared data switch shared.type { case .countdown: return createCountdown(CreateCountdownParams( label: shared.label, durationSeconds: shared.durationSeconds, urgency: shared.urgency, cascade: CascadeConfig(preset: shared.cascadePreset, intervals: []), category: shared.category )) case .alarm: // Alarm needs a target time — set to duration from now return createAlarm(CreateAlarmParams( label: shared.label, targetTime: Date().addingTimeInterval(shared.durationSeconds), urgency: shared.urgency, cascade: CascadeConfig(preset: shared.cascadePreset, intervals: []) )) case .pomodoro: let workMinutes = Int(shared.durationSeconds / 60) return createPomodoro(CreatePomodoroParams( label: shared.label, config: PomodoroConfig( workMinutes: workMinutes, breakMinutes: 5, longBreakMinutes: 15, rounds: 4 ) )) case .event: // Events need a target date — set to duration from now return createAlarm(CreateAlarmParams( label: shared.label, targetTime: Date().addingTimeInterval(shared.durationSeconds), urgency: shared.urgency, cascade: CascadeConfig(preset: shared.cascadePreset, intervals: []) )) } } // MARK: - URL Handling func extractShareCode(from url: URL) -> String? { let path = url.path // Handle: chronom.ind/t/abc123 or chronomind.app/t/abc123 if path.hasPrefix("/t/") { return String(path.dropFirst(3)) } return nil } func canHandleURL(_ url: URL) -> Bool { let host = url.host ?? "" return (host == "chronomind.app" || host == "chronom.ind") && url.path.hasPrefix("/t/") } // MARK: - Manage Shared Timers func revokeShare(_ code: String) { sharedTimers.removeAll { $0.code == code } saveSharedTimers() } func cleanExpired() { sharedTimers.removeAll { $0.isExpired } saveSharedTimers() } // MARK: - Private private func generateShareCode() -> String { // 8-character alphanumeric code let chars = "abcdefghijklmnopqrstuvwxyz0123456789" return String((0..<8).map { _ in chars.randomElement()! }) } private func loadSharedTimers() { guard let data = UserDefaults.standard.data(forKey: storageKey), let decoded = try? JSONDecoder().decode([SharedTimer].self, from: data) else { return } sharedTimers = decoded } private func saveSharedTimers() { if let data = try? JSONEncoder().encode(sharedTimers) { UserDefaults.standard.set(data, forKey: storageKey) } } } // MARK: - Shared Timer Model struct SharedTimer: Codable, Identifiable { let code: String let label: String let type: CMTimerType let durationSeconds: TimeInterval let urgency: UrgencyLevel let cascadePreset: CascadePreset let category: String? let createdBy: String? let createdAt: Date let expiresAt: Date var id: String { code } var isExpired: Bool { Date() > expiresAt } var shareURL: URL { URL(string: "https://chronomind.app/t/\(code)")! } }