import Foundation enum VPNIndicatorStyle: String, CaseIterable, Identifiable { case shield, ring, clock, time var id: String { rawValue } var title: String { switch self { case .shield: return "Щит" case .ring: return "Кольцо" case .clock: return "Часы" case .time: return "Время" } } } struct SessionTiming { enum Urgency { case normal, warning, critical, unknown } private(set) var startedAt: Date? private(set) var expiresAt: Date? private var duration: TimeInterval? { guard let start = startedAt, let end = expiresAt, end > start else { return nil } return end.timeIntervalSince(start) } mutating func observe(startedAt: String?, expires: String?, now: Date = Date()) { let formatter = ISO8601DateFormatter() if let start = startedAt.flatMap({ formatter.date(from: $0) }) { // The CLI timestamp is authoritative, including after a legacy fallback. self.startedAt = start } else if self.startedAt == nil { self.startedAt = now } if let expires = expires { expiresAt = formatter.date(from: expires) } } func remaining(at now: Date) -> TimeInterval? { guard duration != nil, let end = expiresAt else { return nil } return max(0, end.timeIntervalSince(now)) } func progress(at now: Date) -> Double? { guard let duration = duration, let start = startedAt else { return nil } return min(1, max(0, now.timeIntervalSince(start) / duration)) } func urgency(at now: Date) -> Urgency { guard let remaining = remaining(at: now) else { return .unknown } if remaining <= 15 * 60 { return .critical } if remaining <= 60 * 60 { return .warning } return .normal } func compactRemaining(at now: Date) -> String? { guard let seconds = remaining(at: now) else { return nil } let minutes = Int(ceil(seconds / 60)) return String(format: "%d:%02d", minutes / 60, minutes % 60) } func remainingLabel(at now: Date) -> String { guard let seconds = remaining(at: now) else { return "Срок сессии неизвестен" } if seconds <= 0 { return "Время сессии истекло" } let minutes = Int(ceil(seconds / 60)) if minutes < 60 { return "Осталось \(minutes) мин" } return "Осталось \(minutes / 60) ч \(minutes % 60) мин" } }