SyncBanner.swift
108 lignes · 3881 octets
import SwiftUI // Port of ui/sync/SyncBanner.kt. /// Stacked hairline banners: A) summary of the last sync (`summary`, with the /// verbatim server message when ops were refused); B) changes on the server (or /// error); C) local calendar ahead (`localAhead` > 0, events/entities not pushed /// yet). Hidden when there is nothing to report. struct SyncBanner: View { let pendingChanges: Int? let localAhead: Int let syncing: Bool let error: String? let onSyncClick: () -> Void var summary: SyncSummary? = nil var body: some View { let summaryLabel: String? = summary.map { s in if s.failures > 0 { let message = s.firstFailureMessage ?? "refus du serveur sans détail" return "Synchro : \(s.pushed) envoyé(s) · \(s.received) reçu(s) · \(s.failures) échec(s) : \(message)" } return "Synchro : \(s.pushed) envoyé(s) · \(s.received) reçu(s)" } let remoteLabel: String? = { if let error { return error } if let pendingChanges, pendingChanges > 0 { return "\(pendingChanges) changement(s) sur le serveur" } return nil }() if summaryLabel != nil || remoteLabel != nil || localAhead > 0 { VStack(spacing: 0) { if let summaryLabel { SyncBannerRow(label: summaryLabel, syncing: syncing, onSyncClick: onSyncClick) } if let remoteLabel { SyncBannerRow(label: remoteLabel, syncing: syncing, onSyncClick: onSyncClick) } if localAhead > 0 { SyncBannerRow( label: "Calendrier local en avance sur le serveur", syncing: syncing, onSyncClick: onSyncClick ) } } } } } private struct SyncBannerRow: View { let label: String let syncing: Bool let onSyncClick: () -> Void var body: some View { VStack(spacing: 0) { HStack { Text(label) .font(C2VFont.bodyLarge) .foregroundColor(C2VColor.ink) .frame(maxWidth: .infinity, alignment: .leading) Button(syncing ? "Synchronisation…" : "Synchroniser", action: onSyncClick) .buttonStyle(C2VTextButtonStyle()) .disabled(syncing) } .padding(.vertical, 8) C2VDivider() } } } /// « Annuler ma réservation ? » dialog shown after a `SyncChromeViewModel.syncNow` /// hit a conflict (409). Kotlin `SyncConflictDialog`, applied as a modifier. private struct SyncConflictDialogModifier: ViewModifier { let conflict: AgendaConflict? let onConfirm: () -> Void let onDismiss: () -> Void func body(content: Content) -> some View { content.overlay { if conflict != nil { C2VDialog( title: "Annuler ma réservation ?", confirmLabel: "Oui, annuler", dismissLabel: "Non", onConfirm: onConfirm, onDismiss: onDismiss ) { Text("Cette réservation est en conflit avec une autre sur le serveur. Voulez-vous l'annuler ?") .font(C2VFont.bodyLarge) .foregroundColor(C2VColor.ink) } } } } } extension View { func syncConflictDialog( conflict: AgendaConflict?, onConfirm: @escaping () -> Void, onDismiss: @escaping () -> Void ) -> some View { modifier(SyncConflictDialogModifier(conflict: conflict, onConfirm: onConfirm, onDismiss: onDismiss)) } }
GitRust