The auto-flush that runs when reconnecting (or turning Offline Mode back off) only ever tried once — any operation that still failed on that attempt sat stuck until the user manually hit Retry or connectivity changed again. Now retries every 5 minutes for as long as anything's still pending and the signal stays on, stopping on its own once the queue is empty. Full Local Sync's existing 20-minute loop is unaffected/separate.
104 lines
4.5 KiB
Swift
104 lines
4.5 KiB
Swift
import SwiftUI
|
|
import OutlineKit
|
|
|
|
struct RootView: View {
|
|
@Environment(SessionStore.self) private var session
|
|
@State private var welcomeName: String?
|
|
@State private var starStore = StarStore()
|
|
@AppStorage("outpost.fullLocalSyncEnabled") private var isFullLocalSyncEnabled = false
|
|
@AppStorage(CachingOutlineAPIClient.offlineModeDefaultsKey) private var isOfflineModeEnabled = false
|
|
|
|
/// Real connectivity is only half of "can talk to the server" — the
|
|
/// manual Offline Mode toggle is the other half. Syncing needs to
|
|
/// resume on either one clearing, not just a real reconnect: turning
|
|
/// the toggle off while the network had been up the whole time never
|
|
/// changes `networkMonitor.isOnline`, so keying the flush task off that
|
|
/// alone left pending operations stuck until the next real network
|
|
/// blip.
|
|
private var isEffectivelyOnline: Bool {
|
|
session.networkMonitor.isOnline && !isOfflineModeEnabled
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
if session.isSignedIn {
|
|
ContentView()
|
|
} else {
|
|
AuthView(onSigningIn: startWelcomeTransition)
|
|
}
|
|
|
|
if let welcomeName {
|
|
WelcomeSplashView(name: welcomeName)
|
|
.transition(.opacity)
|
|
.zIndex(1)
|
|
}
|
|
}
|
|
.environment(starStore)
|
|
.animation(.easeInOut(duration: 0.45), value: welcomeName != nil)
|
|
.task {
|
|
await session.refreshTeamInfoIfNeeded()
|
|
}
|
|
.task(id: session.isSignedIn) {
|
|
if session.isSignedIn, let apiClient = session.apiClient {
|
|
await starStore.load(apiClient: apiClient)
|
|
} else {
|
|
starStore.reset()
|
|
}
|
|
}
|
|
// Replay whatever queued up while offline the moment either signal
|
|
// clears — a real reconnect, or the user turning Offline Mode back
|
|
// off — no need to wait for the user to open Settings and hit Retry.
|
|
// Also catches Full Local Sync back up immediately, rather than
|
|
// leaving it to wait out the rest of the periodic loop below.
|
|
//
|
|
// The flush itself gets a retry loop with a cooloff, not just one
|
|
// attempt: a single transient failure (one bad operation, a blip
|
|
// mid-flush) used to leave everything else stuck until the user
|
|
// manually hit Retry or connectivity changed again. Keeps retrying
|
|
// every 5 minutes for as long as *anything* is still pending and
|
|
// this signal stays on — stops on its own once the queue is empty,
|
|
// and `.task(id:)` cancels/restarts it automatically if
|
|
// isEffectivelyOnline flips again in the meantime.
|
|
.task(id: isEffectivelyOnline) {
|
|
guard isEffectivelyOnline, let cachingClient = session.cachingClient else { return }
|
|
if isFullLocalSyncEnabled {
|
|
_ = await cachingClient.performFullSync()
|
|
}
|
|
while !Task.isCancelled {
|
|
_ = await cachingClient.flushPendingOperations()
|
|
let remaining = await cachingClient.pendingOperations()
|
|
guard !remaining.isEmpty else { break }
|
|
try? await Task.sleep(for: .seconds(300))
|
|
}
|
|
}
|
|
// Fully automatic — this is the only place Full Local Sync actually
|
|
// runs from (Settings' "Sync Now" is just an on-demand nudge at the
|
|
// same call). Syncs immediately whenever this task (re)starts, which
|
|
// covers both "just switched on" and "was already on at launch" —
|
|
// `.task(id:)` restarts on either, an `@AppStorage`-backed toggle
|
|
// changing anywhere updates every view reading that key — then every
|
|
// 20 minutes after, for as long as it stays enabled.
|
|
.task(id: isFullLocalSyncEnabled) {
|
|
guard isFullLocalSyncEnabled else { return }
|
|
while !Task.isCancelled {
|
|
if isEffectivelyOnline, let cachingClient = session.cachingClient {
|
|
_ = await cachingClient.performFullSync()
|
|
}
|
|
try? await Task.sleep(for: .seconds(1200))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startWelcomeTransition(_ result: AuthViewModel.AuthResult) {
|
|
welcomeName = result.user.name
|
|
session.signIn(serverURL: result.serverURL, user: result.user, team: result.team)
|
|
|
|
Task {
|
|
try? await Task.sleep(for: .seconds(1.1))
|
|
withAnimation(.easeInOut(duration: 0.45)) {
|
|
welcomeName = nil
|
|
}
|
|
}
|
|
}
|
|
}
|