feat(backup): toggleable backed-up photos in clean sheet; anchor page 1 stats to bottom

Backed-up photos row in clean sheet now toggles on/off like other rows.
Page 1 storage stats (USED/FREE/JUNK) pushed to bottom of screen via Spacer,
matching the alignment of the stats strip on the main backup page.

Co-Authored-By: Kutesir <kutesir@provoc.ug>
Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
Robin Kutesa
2026-05-22 03:08:44 +03:00
parent 255addd9f8
commit 5049cfc7d6

View File

@@ -24,6 +24,7 @@ struct BackupView: View {
@State private var showCleanSheet = false
@State private var cleanScreenshotDays: Int? = nil
@State private var cleanDeletedPhotos: Bool = false
@State private var cleanBackedUpPhotos: Bool = true
@State private var deletedPhotosGB: Double = 0
@State private var screenshotsGB: Double = 0
@State private var pullProgress: CGFloat = 0
@@ -71,11 +72,25 @@ struct BackupView: View {
.padding(.bottom, 8)
// Scrollable stats / detail
ScrollView(.vertical, showsIndicators: false) {
belowContent
.padding(.bottom, 24)
if ringPage == 0 {
ScrollView(.vertical, showsIndicators: false) {
statsStrip
.padding(.horizontal, AppTheme.hPad)
.padding(.top, 4)
Spacer(minLength: 16).frame(maxHeight: 32)
nasStatusRow
.padding(.horizontal, AppTheme.hPad)
Spacer(minLength: 8).frame(maxHeight: 20)
}
.refreshable { await triggerRefreshAndBackup() }
} else if ringPage == 1 {
Spacer()
page1BelowContent
} else {
ScrollView(.vertical, showsIndicators: false) {
page2BelowContent
}
}
.refreshable { await triggerRefreshAndBackup() }
}
// Menu button floats above scroll content
@@ -564,24 +579,6 @@ struct BackupView: View {
circleSection
}
@ViewBuilder
private var belowContent: some View {
switch ringPage {
case 1:
page1BelowContent
case 2:
page2BelowContent
default:
statsStrip
.padding(.horizontal, AppTheme.hPad)
.padding(.top, 4)
Spacer(minLength: 16).frame(maxHeight: 32)
nasStatusRow
.padding(.horizontal, AppTheme.hPad)
Spacer(minLength: 8).frame(maxHeight: 20)
}
}
private func etaString(_ seconds: TimeInterval) -> String {
Int(seconds) / 60 > 0 ? "\(Int(seconds) / 60)m" : "\(Int(seconds))s"
}
@@ -925,7 +922,7 @@ struct BackupView: View {
color: AppTheme.positive)
}
.padding(.horizontal, AppTheme.hPad)
.padding(.bottom, 18)
.padding(.bottom, 14)
// Legend
HStack(spacing: 20) {
@@ -935,7 +932,7 @@ struct BackupView: View {
}
.frame(maxWidth: .infinity)
.padding(.horizontal, AppTheme.hPad)
.padding(.bottom, 36)
.padding(.bottom, 24)
}
@@ -1047,31 +1044,40 @@ struct BackupView: View {
.padding(.horizontal, AppTheme.hPad)
.padding(.bottom, 24)
// Row: backed-up photos (always included, non-toggleable)
HStack(spacing: 12) {
ZStack {
Circle().fill(AppTheme.positive.opacity(0.12)).frame(width: 36, height: 36)
Image(systemName: "photo.on.rectangle")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(AppTheme.positive)
// Row: backed-up photos (toggleable)
Button {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
cleanBackedUpPhotos.toggle()
}
VStack(alignment: .leading, spacing: 2) {
Text("Backed-up photos")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(AppTheme.ink)
Text(String(format: "~%.1f GB · already safe on NAS", junkGB))
.font(.system(size: 11))
.foregroundStyle(AppTheme.inkTertiary)
} label: {
HStack(spacing: 12) {
ZStack {
Circle()
.fill((cleanBackedUpPhotos ? AppTheme.positive : AppTheme.inkQuaternary).opacity(0.12))
.frame(width: 36, height: 36)
Image(systemName: "photo.on.rectangle")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(cleanBackedUpPhotos ? AppTheme.positive : AppTheme.inkTertiary)
}
VStack(alignment: .leading, spacing: 2) {
Text("Backed-up photos")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(AppTheme.ink)
Text(String(format: "~%.1f GB · already safe on NAS", junkGB))
.font(.system(size: 11))
.foregroundStyle(AppTheme.inkTertiary)
}
Spacer()
Image(systemName: cleanBackedUpPhotos ? "checkmark.circle.fill" : "circle")
.font(.system(size: 18))
.foregroundStyle(cleanBackedUpPhotos ? AppTheme.positive : AppTheme.inkQuaternary)
}
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 18))
.foregroundStyle(AppTheme.positive)
.padding(.horizontal, AppTheme.hPad)
.padding(.vertical, 14)
.background(AppTheme.surfaceSunken)
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
}
.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)