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:
@@ -23,6 +23,9 @@ struct BackupView: View {
|
|||||||
@State private var showCleanConfirm = false
|
@State private var showCleanConfirm = false
|
||||||
@State private var showCleanSheet = false
|
@State private var showCleanSheet = false
|
||||||
@State private var cleanScreenshotDays: Int? = nil
|
@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 pullProgress: CGFloat = 0
|
||||||
@State private var rippleScale: CGFloat = 0.88
|
@State private var rippleScale: CGFloat = 0.88
|
||||||
@State private var rippleOpacity: Double = 0
|
@State private var rippleOpacity: Double = 0
|
||||||
@@ -124,6 +127,7 @@ struct BackupView: View {
|
|||||||
.task {
|
.task {
|
||||||
coordinator.onActive()
|
coordinator.onActive()
|
||||||
loadDeviceStorage()
|
loadDeviceStorage()
|
||||||
|
loadCleanupData()
|
||||||
}
|
}
|
||||||
.onChange(of: scenePhase) { phase in
|
.onChange(of: scenePhase) { phase in
|
||||||
// Re-check every time the app returns to the foreground.
|
// 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 usedFraction: CGFloat { CGFloat(deviceUsedGB / max(1, deviceTotalGB)) }
|
||||||
private var safeFraction: CGFloat { CGFloat(min(junkGB, deviceTotalGB) / 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 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() {
|
private func loadDeviceStorage() {
|
||||||
let attrs = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory())
|
let attrs = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory())
|
||||||
@@ -905,11 +928,13 @@ struct BackupView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
cleanupRow(label: "Backed-up photos", sizeGB: backedUpPhotosGB, color: AppTheme.positive)
|
cleanupRow(label: "Backed-up photos", sizeGB: backedUpPhotosGB, color: AppTheme.positive)
|
||||||
Divider().opacity(0.25).padding(.leading, AppTheme.hPad)
|
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)
|
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))
|
color: Color(red: 1.0, green: 0.58, blue: 0.0))
|
||||||
Divider().opacity(0.25).padding(.leading, AppTheme.hPad)
|
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)
|
cleanupRow(label: "Large videos", sizeGB: 0, color: AppTheme.inkQuaternary)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, AppTheme.hPad)
|
.padding(.horizontal, AppTheme.hPad)
|
||||||
@@ -1033,6 +1058,45 @@ struct BackupView: View {
|
|||||||
.padding(.horizontal, AppTheme.hPad)
|
.padding(.horizontal, AppTheme.hPad)
|
||||||
.padding(.bottom, 12)
|
.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
|
// Screenshots section
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
|
|||||||
Reference in New Issue
Block a user