mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 13:12:53 +00:00
Home feeds and subscription providers join the subscriptions feed in dropping results that belong to a login the app has since moved off. Clearing a cache does not cancel what is already awaiting the server, and an Apple-account switch discarded providers without invalidating the fetches holding them. A sign-in whose credential the Keychain refused to store is now rolled back rather than deleted. Creating an account also queues the permanent marker that stops every other device synthesizing an account for that instance's pre-revamp login; leaving it behind would suppress a still valid legacy login everywhere on the strength of an account nobody has. An account the server already confirmed is still deleted properly, tombstone and all. Caches with no recorded owner are discarded even when the identity cannot be resolved at all — that is the same first launch after upgrading, only offline. An account whose credential is missing no longer hides a signed-in sibling on the same instance, which made the whole instance report itself as logged out. And connectivity returning recomputes statuses, so an account marked unreachable retries instead of waiting for a foreground transition it has no way to cause.
74 lines
2.2 KiB
Swift
74 lines
2.2 KiB
Swift
//
|
|
// ConnectivityMonitor.swift
|
|
// Yattee
|
|
//
|
|
// Network connectivity monitoring for offline mode support.
|
|
//
|
|
|
|
import Foundation
|
|
import Network
|
|
|
|
/// Monitors network connectivity status.
|
|
@Observable
|
|
final class ConnectivityMonitor: @unchecked Sendable {
|
|
/// Whether the device is currently online.
|
|
private(set) var isOnline: Bool = true
|
|
|
|
/// Whether we're on a cellular connection.
|
|
private(set) var isCellular: Bool = false
|
|
|
|
/// Whether the connection is considered expensive (cellular or hotspot).
|
|
private(set) var isExpensive: Bool = false
|
|
|
|
/// Whether the connection is constrained (low data mode).
|
|
private(set) var isConstrained: Bool = false
|
|
|
|
private let monitor = NWPathMonitor()
|
|
private let queue = DispatchQueue(label: "stream.yattee.connectivity")
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
init() {
|
|
startMonitoring()
|
|
}
|
|
|
|
deinit {
|
|
stopMonitoring()
|
|
}
|
|
|
|
// MARK: - Monitoring
|
|
|
|
/// Start monitoring network connectivity.
|
|
func startMonitoring() {
|
|
monitor.pathUpdateHandler = { [weak self] path in
|
|
Task { @MainActor [weak self] in
|
|
let wasOffline = self?.isOnline == false
|
|
self?.isOnline = path.status == .satisfied
|
|
|
|
// Anything that gave up while there was no network needs to
|
|
// hear that there is one again — an account marked unreachable
|
|
// stops reads from going out at all, so nothing else would ever
|
|
// discover it.
|
|
if wasOffline, path.status == .satisfied {
|
|
NotificationCenter.default.post(name: .connectivityDidReturn, object: nil)
|
|
}
|
|
|
|
self?.isCellular = path.usesInterfaceType(.cellular)
|
|
self?.isExpensive = path.isExpensive
|
|
self?.isConstrained = path.isConstrained
|
|
}
|
|
}
|
|
monitor.start(queue: queue)
|
|
}
|
|
|
|
/// Stop monitoring network connectivity.
|
|
func stopMonitoring() {
|
|
monitor.cancel()
|
|
}
|
|
}
|
|
|
|
extension Notification.Name {
|
|
/// Posted when the device goes from offline to online.
|
|
static let connectivityDidReturn = Notification.Name("stream.yattee.connectivityDidReturn")
|
|
}
|