feat: Sync tab, hamburger menu, network status, browse UX
- Replace History tab with Sync tab (file list, NAS status, free-up-space retention) - Add AppMenuView hamburger sheet (History, Errors, Connection Status, Sync Activity) - BackupView: Kisani logo chip in toolbar, hamburger menu button, swipeable ring pages (progress/pending/failed/uploaded), always-visible compact stats strip - SettingsView: NETWORK section (SSID, NAS reachability, trusted-network warning, refresh button) - BrowseView: pull-to-refresh, long-press context menu on items - LANMonitor: nasReachable, nasCheckTime, checkNASReachability(host:port:) - ConnectionStore: localRetentionDays (0/60/90/365 days) - Regenerate xcodeproj with xcodegen to include new files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ final class LANMonitor: ObservableObject {
|
||||
@Published private(set) var currentSSID: String? = nil
|
||||
@Published private(set) var isOnCellular: Bool = false
|
||||
@Published private(set) var isTailscaleActive: Bool = false
|
||||
@Published private(set) var nasReachable: Bool? = nil
|
||||
@Published private(set) var nasCheckTime: Date? = nil
|
||||
|
||||
private let monitor = NWPathMonitor()
|
||||
private let queue = DispatchQueue(label: "com.albert.nasbackup.lan", qos: .utility)
|
||||
@@ -67,7 +69,7 @@ final class LANMonitor: ObservableObject {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
private func checkSSID() async {
|
||||
func checkSSID() async {
|
||||
currentSSID = await fetchCurrentSSID()
|
||||
}
|
||||
|
||||
@@ -79,6 +81,42 @@ final class LANMonitor: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func checkNASReachability(host: String, port: Int) async {
|
||||
let ep = NWEndpoint.hostPort(
|
||||
host: NWEndpoint.Host(host),
|
||||
port: NWEndpoint.Port(rawValue: UInt16(port)) ?? 445
|
||||
)
|
||||
let connection = NWConnection(to: ep, using: .tcp)
|
||||
var done = false
|
||||
|
||||
let reachable = await withCheckedContinuation { (cont: CheckedContinuation<Bool, Never>) in
|
||||
connection.stateUpdateHandler = { state in
|
||||
guard !done else { return }
|
||||
switch state {
|
||||
case .ready:
|
||||
done = true
|
||||
connection.cancel()
|
||||
cont.resume(returning: true)
|
||||
case .failed, .waiting:
|
||||
done = true
|
||||
connection.cancel()
|
||||
cont.resume(returning: false)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
connection.start(queue: queue)
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 4) {
|
||||
guard !done else { return }
|
||||
done = true
|
||||
connection.cancel()
|
||||
cont.resume(returning: false)
|
||||
}
|
||||
}
|
||||
|
||||
nasReachable = reachable
|
||||
nasCheckTime = Date()
|
||||
}
|
||||
|
||||
func checkAndTriggerIfTrusted(trustedSSIDs: [String]) async {
|
||||
let ssid = await fetchCurrentSSID()
|
||||
guard let ssid, trustedSSIDs.contains(ssid) else { return }
|
||||
|
||||
Reference in New Issue
Block a user