Files
crossdesk/apps/ios/CrossDeskMobile/RemotePrivacyScreen.swift
T

100 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
struct RemotePrivacyStatus: Decodable {
let state: Int
let supported: Bool
let inputBlockSupported: Bool
let overlayActive: Bool
let inputBlocked: Bool
let remotePaused: Bool
let revision: UInt32
let reason: String
}
struct RemotePrivacyScreen {
private(set) var snapshot: RemotePrivacyStatus?
private(set) var pending = false
private(set) var requestID: UInt32 = 0
private var requestRevision: UInt32 = 0
private var receivedAt: TimeInterval = 0
private var requestedAt: TimeInterval = 0
private var failure = ""
var active: Bool {
guard let s = snapshot else { return false }
return s.overlayActive || s.remotePaused || [2, 3, 4].contains(s.state)
}
func fresh(at now: TimeInterval) -> Bool { snapshot != nil && now - receivedAt <= 5 }
func enabled(at now: TimeInterval) -> Bool {
guard let s = snapshot else { return false }
return fresh(at: now) && s.state == 3 && s.overlayActive && !s.remotePaused
}
func buttonTitle(at now: TimeInterval) -> String {
if enabled(at: now) { return "隐私屏已开启" }
if fresh(at: now), let s = snapshot, s.supported && s.state == 1 && !active {
return "隐私屏已关闭"
}
return "隐私屏"
}
func feedback(at now: TimeInterval) -> String {
if failure.isEmpty && (pending || fresh(at: now) && [2, 4].contains(snapshot?.state ?? -1)) {
return ""
}
if failure.isEmpty && !pending && fresh(at: now), let s = snapshot, s.supported,
enabled(at: now) || (s.state == 1 && !active) { return "" }
let reason = detail(at: now)
return status(at: now) + (reason.isEmpty ? "" : ":" + reason)
}
func canToggle(at now: TimeInterval) -> Bool {
guard let s = snapshot else { return false }
return !pending && fresh(at: now) && s.state != 4 && (active || s.supported && s.state == 1)
}
mutating func receive(_ data: Data, at now: TimeInterval) {
struct Envelope: Decodable { let type: Int; let privacyStatus: RemotePrivacyStatus }
let decoder = JSONDecoder(); decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let message = try? decoder.decode(Envelope.self, from: data), message.type == 10 else { return }
let s = message.privacyStatus
guard (0...5).contains(s.state), s.reason.utf8.count < 256,
!s.reason.contains("\0"),
s.state != 3 || (s.supported && s.overlayActive && !s.remotePaused) else { return }
if let previous = snapshot, Int32(bitPattern: s.revision &- previous.revision) < 0 { return }
snapshot = s; receivedAt = now; failure = ""
if pending && Int32(bitPattern: s.revision &- requestRevision) > 0 && ![2, 4].contains(s.state) {
pending = false
}
}
mutating func begin(at now: TimeInterval) -> UInt32? {
tick(at: now)
guard canToggle(at: now), let s = snapshot else { return nil }
pending = true; requestedAt = now; requestRevision = s.revision; failure = ""
requestID &+= 1
return requestID
}
mutating func failed(request: UInt32) {
if pending && requestID == request { pending = false; failure = "指令发送失败,请重试" }
}
mutating func tick(at now: TimeInterval) {
if pending && now - requestedAt >= 15 {
pending = false; failure = "请求超时,尚未确认电脑的隐私屏状态"
}
}
func status(at now: TimeInterval) -> String {
if !failure.isEmpty { return failure }
guard let s = snapshot else { return "等待电脑返回隐私屏状态;旧版电脑端可能不支持" }
if !fresh(at: now) { return "隐私屏状态已过期,等待电脑确认" }
if s.state == 2 { return "正在开启隐私屏…" }
if s.state == 4 { return "正在关闭隐私屏…" }
if pending { return "指令已发送,等待电脑确认…" }
if s.state == 5 { return "隐私屏异常" }
if !s.supported || s.state == 0 { return "隐私屏暂不可用" }
if s.state == 3 && s.overlayActive && !s.remotePaused {
return "隐私屏已开启"
}
return s.state == 1 && !active ? "隐私屏已关闭" : "隐私屏状态待确认"
}
func detail(at now: TimeInterval) -> String {
guard let s = snapshot, fresh(at: now), s.state == 0 || s.state == 5 || !s.supported else { return "" }
return s.reason
}
}