Add cellular data toggle, Tailscale remote access, fix ToggleRow padding

- ToggleRow: fix cramped layout — proper horizontal + vertical card padding
- ConnectionStore: add allowCellularBackup, useTailscaleWhenRemote, tailscaleHost
- LANMonitor: add isOnCellular (NWPath cellular interface), isTailscaleActive
  (detects utun interface with 100.x.x.x CGNAT address), openTailscaleApp()
- BackupEngine: gate backup on cellular setting; resolveHost() switches to
  tailscaleHost when not on trusted LAN and Tailscale tunnel is active
- SettingsView: CONNECTIVITY section (cellular toggle + live status),
  REMOTE ACCESS section (Tailscale toggle, host field, status dot, Open button)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Robin Kutesa
2026-05-16 11:40:07 +03:00
parent 07b5e45c78
commit 03d63e60de
5 changed files with 211 additions and 26 deletions

View File

@@ -30,6 +30,7 @@ struct ToggleRow: View {
.labelsHidden()
.tint(AppTheme.ink)
}
.padding(.vertical, 2)
.padding(.horizontal, AppTheme.cardPad)
.padding(.vertical, 12)
}
}

View File

@@ -41,19 +41,34 @@ final class ConnectionStore: ObservableObject {
@Published var quietHoursEnd: Int {
didSet { UserDefaults.standard.set(quietHoursEnd, forKey: "quietHoursEnd") }
}
// Cellular & remote access
@Published var allowCellularBackup: Bool {
didSet { UserDefaults.standard.set(allowCellularBackup, forKey: "allowCellularBackup") }
}
@Published var useTailscaleWhenRemote: Bool {
didSet { UserDefaults.standard.set(useTailscaleWhenRemote, forKey: "useTailscaleWhenRemote") }
}
@Published var tailscaleHost: String {
didSet { UserDefaults.standard.set(tailscaleHost, forKey: "tailscaleHost") }
}
@Published private(set) var historyEntries: [BackupHistoryEntry] = []
private init() {
savedConnection = ud(NASConnection.self, key: "savedConnection")
backupFilter = ud(BackupFilter.self, key: "backupFilter") ?? BackupFilter()
trustedSSIDs = UserDefaults.standard.stringArray(forKey: "trustedSSIDs") ?? []
lanTriggerEnabled = UserDefaults.standard.object(forKey: "lanTriggerEnabled") as? Bool ?? true
startDelaySeconds = UserDefaults.standard.object(forKey: "startDelaySeconds") as? Int ?? 30
chargingOnlyMode = UserDefaults.standard.object(forKey: "chargingOnlyMode") as? Bool ?? true
quietHoursEnabled = UserDefaults.standard.object(forKey: "quietHoursEnabled") as? Bool ?? false
quietHoursStart = UserDefaults.standard.object(forKey: "quietHoursStart") as? Int ?? 23
quietHoursEnd = UserDefaults.standard.object(forKey: "quietHoursEnd") as? Int ?? 7
historyEntries = ud([BackupHistoryEntry].self, key: "historyEntries") ?? []
savedConnection = ud(NASConnection.self, key: "savedConnection")
backupFilter = ud(BackupFilter.self, key: "backupFilter") ?? BackupFilter()
trustedSSIDs = UserDefaults.standard.stringArray(forKey: "trustedSSIDs") ?? []
lanTriggerEnabled = UserDefaults.standard.object(forKey: "lanTriggerEnabled") as? Bool ?? true
startDelaySeconds = UserDefaults.standard.object(forKey: "startDelaySeconds") as? Int ?? 30
chargingOnlyMode = UserDefaults.standard.object(forKey: "chargingOnlyMode") as? Bool ?? true
quietHoursEnabled = UserDefaults.standard.object(forKey: "quietHoursEnabled") as? Bool ?? false
quietHoursStart = UserDefaults.standard.object(forKey: "quietHoursStart") as? Int ?? 23
quietHoursEnd = UserDefaults.standard.object(forKey: "quietHoursEnd") as? Int ?? 7
allowCellularBackup = UserDefaults.standard.object(forKey: "allowCellularBackup") as? Bool ?? false
useTailscaleWhenRemote = UserDefaults.standard.object(forKey: "useTailscaleWhenRemote") as? Bool ?? false
tailscaleHost = UserDefaults.standard.string(forKey: "tailscaleHost") ?? ""
historyEntries = ud([BackupHistoryEntry].self, key: "historyEntries") ?? []
}
func appendHistoryEntry(_ entry: BackupHistoryEntry) {