ζ-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>
This commit is contained in:
parent
61927d27a3
commit
53f8043a2d
3 changed files with 148 additions and 0 deletions
40
Sources/Core/Submit/ReachabilityWatcher.swift
Normal file
40
Sources/Core/Submit/ReachabilityWatcher.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue