zitare-native/Sources/Core/Submit/ReachabilityWatcher.swift
Till JS 53f8043a2d ζ-3.5b: Pending-Queue-UI + NWPathMonitor-Reconnect-Flush
Pending-Queue-UI:
- AccountView.pendingQueueCard listet alle wartenden Submissions
  mit Text-Preview (120c), Author, createdAt, retryCount, lastError
- "Jetzt versuchen"-Button triggert tryFlush(api:)
- Trash-Icon pro Row löscht einzeln aus der Queue
- Pull-to-Refresh aktualisiert beide Listen
- Card-View nur sichtbar wenn Queue-Tiefe > 0

ReachabilityWatcher:
- NWPathMonitor erkennt Reconnect-Flanke (offline → online)
- Bei Reconnect: SubmissionQueue.tryFlush auf @MainActor
- Filtert reine Wifi↔Mobil-Switches raus (nur "wieder erreichbar"
  zählt)
- Lebt im App-Root, startet nach Launch via .task

Files:
- Sources/Core/Submit/ReachabilityWatcher.swift (neu)
- Sources/App/ZitareNativeApp.swift: reachability.start in .task
- Sources/Features/Account/AccountView.swift: pendingQueueCard

iOS + macOS BUILD SUCCEEDED.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:26:55 +02:00

40 lines
1.4 KiB
Swift

import Foundation
import Network
import OSLog
/// Wrapper um `NWPathMonitor`. Feuert das übergebene Closure bei jedem
/// Wechsel von "kein Pfad" "Pfad da" (= Wifi/Mobil wieder verfügbar
/// nach Offline). Ein wechselnder Pfad (Wifi Mobil) feuert nicht
/// erneut wir wollen nur die Reconnect-Flanke.
///
/// Lebenszyklus liegt beim App-Root. Stop-Methode existiert, aber
/// reale App startet Watcher einmal beim Launch und lässt ihn bis
/// zum Prozess-Ende laufen.
final class ReachabilityWatcher: @unchecked Sendable {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "ev.mana.zitare.reachability")
private let log = Logger(subsystem: "ev.mana.zitare", category: "reachability")
private var wasReachable: Bool? = nil
private var onReconnect: @Sendable () -> Void = {}
func start(onReconnect: @escaping @Sendable () -> Void) {
self.onReconnect = onReconnect
monitor.pathUpdateHandler = { [weak self] path in
guard let self else { return }
let reachable = path.status == .satisfied
let prev = self.wasReachable
self.wasReachable = reachable
if prev == false, reachable {
self.log.info("Reachable again — triggering reconnect-hook")
self.onReconnect()
} else if prev == nil {
self.log.info("Initial reachability: \(reachable ? "yes" : "no", privacy: .public)")
}
}
monitor.start(queue: queue)
}
func stop() {
monitor.cancel()
}
}