this repo has no description
1//
2// DownloadSection.swift
3// AtProtoBackup
4//
5// Created by Corey Alexander on 8/25/25.
6//
7
8import SwiftUI
9
10struct DownloadSection: View {
11 let account: Account
12 @ObservedObject var downloadManager: DownloadManager
13 @State private var lastBackupMetadata: BackupMetadata?
14
15 var body: some View {
16 VStack(spacing: 12) {
17 Button(action: {
18 downloadManager.startDownload(for: account)
19 }) {
20 HStack {
21 Image(systemName: "arrow.down.circle.fill")
22 Text("Download Backup")
23 }
24 .padding(.horizontal, 16)
25 .padding(.vertical, 8)
26 .background(Color.blue)
27 .foregroundColor(.white)
28 .cornerRadius(8)
29 }
30 .buttonStyle(PlainButtonStyle())
31
32 // Display last backup time
33 if let metadata = lastBackupMetadata {
34 LastBackupView(metadata: metadata)
35 }
36
37 if let download = downloadManager.getDownload(for: account) {
38 DownloadStatusView(download: download, account: account, downloadManager: downloadManager)
39 }
40 }
41 .onAppear {
42 loadLastBackupMetadata()
43 }
44 .onChange(of: downloadManager.getDownload(for: account)?.progress) { oldValue, newValue in
45 // Reload metadata when download completes
46 if newValue == 1.0 {
47 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
48 loadLastBackupMetadata()
49 }
50 }
51 }
52 }
53
54 private func loadLastBackupMetadata() {
55 guard let saveLocation = try? BackupDiscovery.accountBackupDirectory(for: account.did) else {
56 return
57 }
58
59 do {
60 lastBackupMetadata = try BackupMetadata.load(from: saveLocation)
61 } catch {
62 // No metadata file exists yet or failed to load
63 lastBackupMetadata = nil
64 }
65 }
66}
67
68struct LastBackupView: View {
69 let metadata: BackupMetadata
70
71 var body: some View {
72 HStack(spacing: 6) {
73 Image(systemName: "clock.fill")
74 .font(.caption2)
75 .foregroundColor(.secondary)
76 Text("Last backup: \(formatDate(metadata.completedAt))")
77 .font(.caption)
78 .foregroundColor(.secondary)
79 }
80 }
81
82 private func formatDate(_ date: Date) -> String {
83 let now = Date()
84 let calendar = Calendar.current
85 let components = calendar.dateComponents([.minute, .hour, .day], from: date, to: now)
86
87 if let days = components.day, days > 0 {
88 if days == 1 {
89 return "yesterday"
90 } else if days < 7 {
91 return "\(days) days ago"
92 } else {
93 let formatter = DateFormatter()
94 formatter.dateStyle = .medium
95 formatter.timeStyle = .none
96 return formatter.string(from: date)
97 }
98 } else if let hours = components.hour, hours > 0 {
99 return hours == 1 ? "1 hour ago" : "\(hours) hours ago"
100 } else if let minutes = components.minute, minutes > 0 {
101 return minutes == 1 ? "1 minute ago" : "\(minutes) minutes ago"
102 } else {
103 return "just now"
104 }
105 }
106}
107
108struct DownloadStatusView: View {
109 let download: DownloadInfo
110 let account: Account
111 @ObservedObject var downloadManager: DownloadManager
112
113 var body: some View {
114 VStack(spacing: 8) {
115 if download.isDownloading {
116 VStack(spacing: 4) {
117 if let totalBlobs = download.totalBlobs {
118 ProgressView(value: download.progress) {
119 Text("Downloading... \(Int(download.progress * 100))%")
120 .font(.caption)
121 }
122 .progressViewStyle(LinearProgressViewStyle())
123
124 Text("\(Int(download.progress * Double(totalBlobs))) of \(totalBlobs) blobs")
125 .font(.caption2)
126 .foregroundColor(.secondary)
127 } else {
128 HStack {
129 ProgressView()
130 .scaleEffect(0.8)
131 Text("Fetching repository data...")
132 .font(.caption)
133 .foregroundColor(.secondary)
134 }
135 }
136 }
137 } else if download.progress > 0 && download.progress < 1.0 {
138 VStack(spacing: 6) {
139 HStack {
140 Text("Download paused at \(Int(download.progress * 100))%")
141 .font(.caption)
142 .foregroundColor(.secondary)
143
144 Button("Resume") {
145 downloadManager.startDownload(for: account)
146 }
147 .buttonStyle(BorderedButtonStyle())
148 }
149
150 if let totalBlobs = download.totalBlobs {
151 Text("\(Int(download.progress * Double(totalBlobs))) of \(totalBlobs) blobs")
152 .font(.caption2)
153 .foregroundColor(.secondary)
154 }
155 }
156 } else if download.progress >= 1.0 {
157 HStack {
158 Image(systemName: "checkmark.circle.fill")
159 .foregroundColor(.green)
160 VStack(alignment: .leading, spacing: 2) {
161 Text("Download complete")
162 .font(.caption)
163 .foregroundColor(.secondary)
164 if let totalBlobs = download.totalBlobs {
165 Text("\(totalBlobs) blobs downloaded")
166 .font(.caption2)
167 .foregroundColor(.secondary)
168 }
169 }
170 }
171 }
172 }
173 }
174}