170 lines
4.6 KiB
Swift
170 lines
4.6 KiB
Swift
//
|
|
// SyncStatusIndicator.swift
|
|
// oAI
|
|
//
|
|
// Git sync status indicator (bottom-right corner)
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
enum SyncState {
|
|
case disabled // Gray - sync not configured or disabled
|
|
case synced // Green - successfully synced
|
|
case syncing // Yellow - sync in progress
|
|
case error(String) // Red - sync failed with error message
|
|
|
|
var color: Color {
|
|
switch self {
|
|
case .disabled: return .secondary
|
|
case .synced: return .green
|
|
case .syncing: return .orange
|
|
case .error: return .red
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .disabled: return "arrow.triangle.2.circlepath"
|
|
case .synced: return "checkmark.circle.fill"
|
|
case .syncing: return "arrow.triangle.2.circlepath"
|
|
case .error: return "exclamationmark.triangle.fill"
|
|
}
|
|
}
|
|
|
|
var tooltipText: String {
|
|
switch self {
|
|
case .disabled:
|
|
return "Auto-sync disabled"
|
|
case .synced:
|
|
if let lastSync = GitSyncService.shared.syncStatus.lastSyncTime {
|
|
return "Last synced: \(timeAgo(lastSync))"
|
|
} else {
|
|
return "Synced"
|
|
}
|
|
case .syncing:
|
|
return "Syncing..."
|
|
case .error(let message):
|
|
return "Sync failed: \(message)"
|
|
}
|
|
}
|
|
|
|
private func timeAgo(_ date: Date) -> String {
|
|
let seconds = Int(Date().timeIntervalSince(date))
|
|
if seconds < 60 {
|
|
return "just now"
|
|
} else if seconds < 3600 {
|
|
let minutes = seconds / 60
|
|
return "\(minutes)m ago"
|
|
} else if seconds < 86400 {
|
|
let hours = seconds / 3600
|
|
return "\(hours)h ago"
|
|
} else {
|
|
let days = seconds / 86400
|
|
return "\(days)d ago"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SyncStatusIndicator: View {
|
|
@State private var isHovering = false
|
|
@State private var syncState: SyncState = .disabled
|
|
@State private var showSettings = false
|
|
|
|
private let settings = SettingsService.shared
|
|
private let gitSync = GitSyncService.shared
|
|
|
|
var body: some View {
|
|
VStack {
|
|
Spacer()
|
|
HStack {
|
|
Spacer()
|
|
|
|
// Floating indicator
|
|
ZStack {
|
|
// Background circle
|
|
Circle()
|
|
.fill(Color(nsColor: .windowBackgroundColor))
|
|
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
|
|
.frame(width: 40, height: 40)
|
|
|
|
// Icon
|
|
statusIcon
|
|
}
|
|
.scaleEffect(isHovering ? 1.1 : 1.0)
|
|
.animation(.spring(response: 0.3), value: isHovering)
|
|
.onHover { hovering in
|
|
isHovering = hovering
|
|
}
|
|
.help(syncState.tooltipText)
|
|
.onTapGesture {
|
|
if case .error = syncState {
|
|
// Open settings on error
|
|
showSettings = true
|
|
}
|
|
}
|
|
.sheet(isPresented: $showSettings) {
|
|
SettingsView()
|
|
}
|
|
.padding(.trailing, 16)
|
|
.padding(.bottom, 16)
|
|
}
|
|
}
|
|
.onAppear {
|
|
updateState()
|
|
}
|
|
.onChange(of: gitSync.syncStatus) {
|
|
updateState()
|
|
}
|
|
.onChange(of: gitSync.isSyncing) {
|
|
updateState()
|
|
}
|
|
.onChange(of: gitSync.lastSyncError) {
|
|
updateState()
|
|
}
|
|
.onChange(of: settings.syncEnabled) {
|
|
updateState()
|
|
}
|
|
.onChange(of: settings.syncAutoSave) {
|
|
updateState()
|
|
}
|
|
}
|
|
|
|
private var statusIcon: some View {
|
|
Image(systemName: syncState.icon)
|
|
.font(.system(size: 18))
|
|
.foregroundStyle(syncState.color)
|
|
}
|
|
|
|
private func updateState() {
|
|
// Determine current sync state
|
|
guard settings.syncEnabled && settings.syncConfigured else {
|
|
syncState = .disabled
|
|
return
|
|
}
|
|
|
|
guard gitSync.syncStatus.isCloned else {
|
|
syncState = .disabled
|
|
return
|
|
}
|
|
|
|
// Check for error
|
|
if let error = gitSync.lastSyncError {
|
|
syncState = .error(error)
|
|
return
|
|
}
|
|
|
|
// Check if currently syncing
|
|
if gitSync.isSyncing {
|
|
syncState = .syncing
|
|
return
|
|
}
|
|
|
|
// All good
|
|
syncState = .synced
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
SyncStatusIndicator()
|
|
}
|