From 8bd0149a602c1d2a4246cdbc1401e4b7ba65b467 Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Thu, 21 May 2026 15:44:37 +0300 Subject: [PATCH] Add Recently Deleted + screenshot sizes to cleanup page - loadCleanupData() fetches Recently Deleted (by title) and Screenshots smart albums, estimates GB from asset counts - Page 2 breakdown adds Recently Deleted row (red) and uses real screenshotsGB value; reordered: backed-up, deleted, screenshots, duplicates, large videos - totalFreeableGB includes backed-up + deleted + screenshots - Clean sheet adds toggleable Recently Deleted row with trash icon; unchecked by default, turns red when selected Co-Authored-By: Kutesir Co-Authored-By: Sentry --- Features/Backup/BackupView.swift | 70 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) 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) {