diff --git a/Features/Backup/BackupView.swift b/Features/Backup/BackupView.swift index 907eb5a..bbcdd6b 100644 --- a/Features/Backup/BackupView.swift +++ b/Features/Backup/BackupView.swift @@ -23,6 +23,9 @@ struct BackupView: View { @State private var showCleanConfirm = false @State private var showCleanSheet = false @State private var cleanScreenshotDays: Int? = nil + @State private var cleanDeletedPhotos: Bool = false + @State private var deletedPhotosGB: Double = 0 + @State private var screenshotsGB: Double = 0 @State private var pullProgress: CGFloat = 0 @State private var rippleScale: CGFloat = 0.88 @State private var rippleOpacity: Double = 0 @@ -124,6 +127,7 @@ struct BackupView: View { .task { coordinator.onActive() loadDeviceStorage() + loadCleanupData() } .onChange(of: scenePhase) { phase in // Re-check every time the app returns to the foreground. @@ -857,7 +861,26 @@ struct BackupView: View { private var usedFraction: CGFloat { CGFloat(deviceUsedGB / max(1, deviceTotalGB)) } private var safeFraction: CGFloat { CGFloat(min(junkGB, deviceTotalGB) / max(1, deviceTotalGB)) } private var backedUpPhotosGB: Double { Double(alreadySafeDisplayCount) * 3.5 / 1000 } - private var totalFreeableGB: Double { backedUpPhotosGB } + private var totalFreeableGB: Double { backedUpPhotosGB + deletedPhotosGB + screenshotsGB } + + private func loadCleanupData() { + // Recently Deleted — fetch by title since the subtype isn't public + let delOpts = PHFetchOptions() + delOpts.predicate = NSPredicate(format: "localizedTitle = 'Recently Deleted'") + let delCollections = PHAssetCollection.fetchAssetCollections( + with: .smartAlbum, subtype: .any, options: delOpts) + if let col = delCollections.firstObject { + let assets = PHAsset.fetchAssets(in: col, options: nil) + deletedPhotosGB = Double(assets.count) * 3.5 / 1000 + } + // Screenshots + let ssCollections = PHAssetCollection.fetchAssetCollections( + with: .smartAlbum, subtype: .smartAlbumScreenshots, options: nil) + if let col = ssCollections.firstObject { + let assets = PHAsset.fetchAssets(in: col, options: nil) + screenshotsGB = Double(assets.count) * 1.5 / 1000 + } + } private func loadDeviceStorage() { let attrs = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory()) @@ -905,11 +928,13 @@ struct BackupView: View { VStack(spacing: 0) { cleanupRow(label: "Backed-up photos", sizeGB: backedUpPhotosGB, color: AppTheme.positive) Divider().opacity(0.25).padding(.leading, AppTheme.hPad) - cleanupRow(label: "Duplicates", sizeGB: 0, color: AppTheme.interactive) + cleanupRow(label: "Recently Deleted", sizeGB: deletedPhotosGB, color: AppTheme.destructive) Divider().opacity(0.25).padding(.leading, AppTheme.hPad) - cleanupRow(label: "Screenshots", sizeGB: 0, + cleanupRow(label: "Screenshots", sizeGB: screenshotsGB, color: Color(red: 1.0, green: 0.58, blue: 0.0)) Divider().opacity(0.25).padding(.leading, AppTheme.hPad) + cleanupRow(label: "Duplicates", sizeGB: 0, color: AppTheme.interactive) + Divider().opacity(0.25).padding(.leading, AppTheme.hPad) cleanupRow(label: "Large videos", sizeGB: 0, color: AppTheme.inkQuaternary) } .padding(.horizontal, AppTheme.hPad) @@ -1033,6 +1058,45 @@ struct BackupView: View { .padding(.horizontal, AppTheme.hPad) .padding(.bottom, 12) + // Recently Deleted toggle + Button { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + cleanDeletedPhotos.toggle() + } + } label: { + HStack(spacing: 12) { + ZStack { + Circle() + .fill((cleanDeletedPhotos ? AppTheme.destructive : AppTheme.inkQuaternary).opacity(0.12)) + .frame(width: 36, height: 36) + Image(systemName: "trash") + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(cleanDeletedPhotos ? AppTheme.destructive : AppTheme.inkTertiary) + } + VStack(alignment: .leading, spacing: 2) { + Text("Recently Deleted") + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(AppTheme.ink) + Text(deletedPhotosGB > 0.01 + ? String(format: "~%.1f GB · iPhone trash", deletedPhotosGB) + : "Empty · iPhone trash") + .font(.system(size: 11)) + .foregroundStyle(AppTheme.inkTertiary) + } + Spacer() + Image(systemName: cleanDeletedPhotos ? "checkmark.circle.fill" : "circle") + .font(.system(size: 18)) + .foregroundStyle(cleanDeletedPhotos ? AppTheme.destructive : AppTheme.inkQuaternary) + } + .padding(.horizontal, AppTheme.hPad) + .padding(.vertical, 14) + .background(AppTheme.surfaceSunken) + .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + .buttonStyle(.plain) + .padding(.horizontal, AppTheme.hPad) + .padding(.bottom, 12) + // Screenshots section VStack(alignment: .leading, spacing: 10) { HStack(spacing: 12) {