98 lines
3.5 KiB
Swift
98 lines
3.5 KiB
Swift
|
|
import SwiftUI
|
||
|
|
|
||
|
|
struct HistoryView: View {
|
||
|
|
@EnvironmentObject var store: ConnectionStore
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
ZStack {
|
||
|
|
Color.white.ignoresSafeArea()
|
||
|
|
|
||
|
|
if store.historyEntries.isEmpty {
|
||
|
|
VStack(spacing: 16) {
|
||
|
|
Image(systemName: "clock.arrow.circlepath")
|
||
|
|
.font(.system(size: 48))
|
||
|
|
.foregroundColor(AppTheme.textTertiary)
|
||
|
|
Text("No backup history yet")
|
||
|
|
.font(AppTheme.bodyFont)
|
||
|
|
.foregroundColor(AppTheme.textSecondary)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
List {
|
||
|
|
ForEach(store.historyEntries) { entry in
|
||
|
|
HistoryRowView(entry: entry)
|
||
|
|
.listRowSeparator(.visible)
|
||
|
|
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||
|
|
}
|
||
|
|
.onDelete { offsets in
|
||
|
|
store.removeHistoryEntries(at: offsets)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.listStyle(.plain)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.navigationTitle("History")
|
||
|
|
.toolbar {
|
||
|
|
if !store.historyEntries.isEmpty {
|
||
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||
|
|
Button("Clear") { store.clearHistory() }
|
||
|
|
.foregroundColor(AppTheme.red)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
struct HistoryRowView: View {
|
||
|
|
let entry: BackupHistoryEntry
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
HStack(spacing: 14) {
|
||
|
|
Circle()
|
||
|
|
.fill(entry.failedCount == 0 ? AppTheme.green : AppTheme.orange)
|
||
|
|
.frame(width: 10, height: 10)
|
||
|
|
|
||
|
|
VStack(alignment: .leading, spacing: 4) {
|
||
|
|
HStack {
|
||
|
|
Text(entry.date, style: .date)
|
||
|
|
.font(.system(size: 15, weight: .semibold))
|
||
|
|
.foregroundColor(AppTheme.textPrimary)
|
||
|
|
if entry.triggeredByLAN {
|
||
|
|
Text("AUTO")
|
||
|
|
.font(.system(size: 10, weight: .bold))
|
||
|
|
.foregroundColor(AppTheme.blue)
|
||
|
|
.padding(.horizontal, 5)
|
||
|
|
.padding(.vertical, 2)
|
||
|
|
.overlay(RoundedRectangle(cornerRadius: 4).stroke(AppTheme.blue, lineWidth: 1))
|
||
|
|
}
|
||
|
|
Spacer()
|
||
|
|
Text(entry.date, style: .time)
|
||
|
|
.font(AppTheme.captionFont)
|
||
|
|
.foregroundColor(AppTheme.textTertiary)
|
||
|
|
}
|
||
|
|
|
||
|
|
HStack(spacing: 12) {
|
||
|
|
statChip("\(entry.uploadedCount) uploaded", color: AppTheme.textSecondary)
|
||
|
|
if entry.skippedCount > 0 {
|
||
|
|
statChip("\(entry.skippedCount) skipped", color: AppTheme.textTertiary)
|
||
|
|
}
|
||
|
|
if entry.failedCount > 0 {
|
||
|
|
statChip("\(entry.failedCount) errors", color: AppTheme.red)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Text(entry.nasHost)
|
||
|
|
.font(AppTheme.captionFont)
|
||
|
|
.foregroundColor(AppTheme.textTertiary)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.padding(.horizontal, 16)
|
||
|
|
.padding(.vertical, 12)
|
||
|
|
}
|
||
|
|
|
||
|
|
private func statChip(_ text: String, color: Color) -> some View {
|
||
|
|
Text(text)
|
||
|
|
.font(AppTheme.captionFont)
|
||
|
|
.foregroundColor(color)
|
||
|
|
}
|
||
|
|
}
|