ReadKeep/readeck/Data/Repository/NetworkMonitorRepository.swift
Ilyas Hallak e4657aa281 Fix offline reading bugs and improve network monitoring (Phase 5)
Bugfixes:
- Add toggle for offline mode simulation (DEBUG only)
- Fix VPN false-positives with interface count check
- Add detailed error logging for download failures
- Fix last sync timestamp display
- Translate all strings to English

Network Monitoring:
- Add NetworkMonitorRepository with NWPathMonitor
- Check path.status AND availableInterfaces for reliability
- Add manual reportConnectionFailure/Success methods
- Auto-load cached bookmarks when offline
- Visual debug banner (green=online, red=offline)

Architecture:
- Clean architecture with Repository → UseCase → ViewModel
- Network status in AppSettings for global access
- Combine publishers for reactive updates
2025-11-21 21:37:24 +01:00

103 lines
2.9 KiB
Swift

//
// NetworkMonitorRepository.swift
// readeck
//
// Created by Claude on 18.11.25.
//
import Foundation
import Network
import Combine
// MARK: - Protocol
protocol PNetworkMonitorRepository {
var isConnected: AnyPublisher<Bool, Never> { get }
func startMonitoring()
func stopMonitoring()
func reportConnectionFailure()
func reportConnectionSuccess()
}
// MARK: - Implementation
final class NetworkMonitorRepository: PNetworkMonitorRepository {
// MARK: - Properties
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "com.readeck.networkmonitor")
private let _isConnectedSubject = CurrentValueSubject<Bool, Never>(true)
private var hasPathConnection = true
private var hasRealConnection = true
var isConnected: AnyPublisher<Bool, Never> {
_isConnectedSubject.eraseToAnyPublisher()
}
// MARK: - Initialization
init() {
// Repository just manages the monitor, doesn't start it automatically
}
deinit {
monitor.cancel()
}
// MARK: - Public Methods
func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
// More sophisticated check: path must be satisfied AND have actual interfaces
let hasInterfaces = path.availableInterfaces.count > 0
let isConnected = path.status == .satisfied && hasInterfaces
self.hasPathConnection = isConnected
self.updateConnectionState()
// Log network changes with details
if path.status == .satisfied {
if hasInterfaces {
Logger.network.info("📡 Network path available (interfaces: \(path.availableInterfaces.count))")
} else {
Logger.network.warning("⚠️ Network path satisfied but no interfaces (VPN?)")
}
} else {
Logger.network.warning("📡 Network path unavailable")
}
}
monitor.start(queue: queue)
Logger.network.debug("Network monitoring started")
}
func stopMonitoring() {
monitor.cancel()
Logger.network.debug("Network monitoring stopped")
}
func reportConnectionFailure() {
hasRealConnection = false
updateConnectionState()
Logger.network.warning("⚠️ Real connection failure reported (VPN/unreachable server)")
}
func reportConnectionSuccess() {
hasRealConnection = true
updateConnectionState()
Logger.network.info("✅ Real connection success reported")
}
private func updateConnectionState() {
// Only connected if BOTH path is available AND real connection works
let isConnected = hasPathConnection && hasRealConnection
DispatchQueue.main.async {
self._isConnectedSubject.send(isConnected)
}
}
}