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 <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
Robin Kutesa
2026-05-21 15:44:37 +03:00
parent 5a55931233
commit 8bd0149a60

View File

@@ -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) {