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