From 76c1189412fb8d4ce93db1d39224ad93b19aeab5 Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Thu, 21 May 2026 14:12:54 +0300 Subject: [PATCH] =?UTF-8?q?Add=20pull-to-clean=20on=20page=202=20=E2=80=94?= =?UTF-8?q?=20mirrors=20pull-to-backup=20on=20page=200?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull zone now shown on pages 0 and 2. Labels route to PULL/RELEASE TO CLEAN on page 2. Releasing triggers triggerCleanViaPull() which fires the clean confirmation dialog with the same ripple effect as the backup pull. Co-Authored-By: Kutesir Co-Authored-By: Sentry --- Features/Backup/BackupView.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Features/Backup/BackupView.swift b/Features/Backup/BackupView.swift index 2af1467..993ca58 100644 --- a/Features/Backup/BackupView.swift +++ b/Features/Backup/BackupView.swift @@ -693,9 +693,9 @@ struct BackupView: View { Color.clear.frame(height: 60) } } - // Pull zone floats above the ring on page 0 only + // Pull zone floats above the ring on pages 0 and 2 .overlay(alignment: .top) { - if ringPage == 0 { + if ringPage == 0 || ringPage == 2 { pullZone .offset(y: -max(0, pullProgress * 0.52)) } @@ -705,13 +705,15 @@ struct BackupView: View { private var pullZone: some View { let pct = min(pullProgress / 58, 1.0) let ready = pullProgress >= 58 + let pullLabel = ringPage == 2 ? "PULL TO CLEAN" : "PULL TO BACK UP" + let releaseLabel = ringPage == 2 ? "RELEASE TO CLEAN" : "RELEASE TO BACK UP" return VStack(spacing: 3) { Text("↓") .font(.system(size: 13)) .foregroundStyle(ready ? AppTheme.positive : AppTheme.inkTertiary) .rotationEffect(.degrees(ready ? 180 : pct * 160)) .opacity(min(pct * 2.5, 1)) - Text(ready ? "RELEASE TO BACK UP" : "PULL TO BACK UP") + Text(ready ? releaseLabel : pullLabel) .font(.system(size: 8, weight: .regular, design: .monospaced)) .foregroundStyle(ready ? AppTheme.positive : AppTheme.inkTertiary) .tracking(2) @@ -820,7 +822,7 @@ struct BackupView: View { withAnimation(.spring(response: 0.38, dampingFraction: 0.85)) { pullProgress = 0 } if fired { UIImpactFeedbackGenerator(style: .medium).impactOccurred() - triggerBackupViaPull() + if ringPage == 2 { triggerCleanViaPull() } else { triggerBackupViaPull() } } } } @@ -831,6 +833,12 @@ struct BackupView: View { if engine.job.status == .paused { engine.resume() } else { startBackup() } } + private func triggerCleanViaPull() { + rippleScale = 0.88; rippleOpacity = 0.6 + withAnimation(.easeOut(duration: 0.65)) { rippleScale = 1.35; rippleOpacity = 0 } + showCleanConfirm = true + } + private func arrowFlowOpacity(_ phase: Double) -> Double { if phase < 0.4 { return phase / 0.4 * 0.7 } if phase < 0.8 { return (0.8 - phase) / 0.4 * 0.7 }