// // NetworkMonitor.swift // FluxRead // // Created by RSA on 29/03/2026. // import Foundation import Network import Combine // MARK: - Connection Type enum NetworkConnectionType { case wifi case cellular case ethernet case none case unknown var description: String { switch self { case .wifi: return "Wi-Fi" case .cellular: return "Cellular" case .ethernet: return "Ethernet" case .none: return "No Connection" case .unknown: return "Unknown" } } /// True for any connection that is generally considered unmetered / fast. var isWifi: Bool { self == .wifi || self == .ethernet } /// True only when the device is on a metered cellular connection. var isCellular: Bool { self == .cellular } /// True when any network path is available. var isConnected: Bool { self != .none } } // MARK: - NetworkMonitor /// Observes the device's active network path and exposes the current /// connection type as both a `@Published` property (for SwiftUI / Combine) /// and a synchronous getter. /// /// Usage: /// ```swift /// // Singleton /// let type = NetworkMonitor.shared.connectionType /// /// // Injected instance (via DI) /// monitor.$connectionType /// .sink { print($0.description) } /// .store(in: &cancellables) /// ``` final class NetworkMonitor: ObservableObject { // MARK: Singleton static let shared = NetworkMonitor() // MARK: Published State /// The current network connection type. Updated on the main queue. @Published private(set) var connectionType: NetworkConnectionType = .unknown /// Convenience: `true` when any usable network path exists. @Published private(set) var isConnected: Bool = false // MARK: Private private let monitor: NWPathMonitor private let monitorQueue = DispatchQueue(label: "com.fluxread.networkmonitor", qos: .utility) // MARK: Init init() { monitor = NWPathMonitor() start() } deinit { monitor.cancel() } // MARK: Public API /// Synchronously returns the current connection type based on the /// latest path snapshot. Always safe to call from any thread. var currentConnectionType: NetworkConnectionType { return connectionType } /// Returns `true` when the active interface matches a given type. func isUsing(_ interface: NWInterface.InterfaceType) -> Bool { monitor.currentPath.usesInterfaceType(interface) } // MARK: Private private func start() { monitor.pathUpdateHandler = { [weak self] path in guard let self else { return } let type = Self.resolve(path: path) DispatchQueue.main.async { self.connectionType = type self.isConnected = path.status == .satisfied } } monitor.start(queue: monitorQueue) } /// Maps an `NWPath` to a `NetworkConnectionType`. private static func resolve(path: NWPath) -> NetworkConnectionType { guard path.status == .satisfied else { return .none } if path.usesInterfaceType(.wifi) { return .wifi } if path.usesInterfaceType(.cellular) { return .cellular } if path.usesInterfaceType(.wiredEthernet) { return .ethernet } return .unknown } }