Improve VPN status reporting and recovery
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
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) мин"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user