72 lines
2.5 KiB
Swift
72 lines
2.5 KiB
Swift
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) мин"
|
|
}
|
|
}
|