learning_ai_clock/ios/ChronoMind/Shared/Sharing/ShareableTimerManager.swift
saravanakumardb1 424e804396 fix(ios): resolve Swift compile errors for iOS 26 / Xcode 26
- Fix CloudKitSyncManager deinit concurrency violation
- Replace deprecated List(selection:) with VStack+ForEach sidebar
- Replace removed Animation.none with Animation.linear(duration: 0)
- Fix CountdownRing initializer parameter mismatch
- Unwrap optional timer.duration in ShareableTimerManager and DataExportManager
- Add missing .event case to exhaustive switch
- Change CascadeWarning.scheduledTime from let to var
- Fix CalendarSyncManager CMTimer init (add elapsedBeforePause, remove non-existent params)
- Add missing UserNotifications import in SleepManager
- Remove parameterized App Intents phrases (iOS 26 restriction)
- Temporarily remove watchOS target dependency for iOS build
2026-03-19 14:22:50 -07:00

186 lines
5.7 KiB
Swift

// 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)")!
}
}