Animate page indicator button with cascading pull-down hint
TimelineView drives a 2.4s cycle: two sin-bell chevron pulses (primary + ghost trailing 7pt below) then a pause. Mirrors the cascadingArrows pattern — suggests pull-down on all three pages. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -941,15 +941,34 @@ struct BackupView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Square advance button with amber border
|
||||
// Square advance button — animated to hint pull-down
|
||||
Button {
|
||||
withAnimation(.spring(response: 0.4, dampingFraction: 0.85)) {
|
||||
ringPage = (ringPage + 1) % ringPageCount
|
||||
}
|
||||
} label: {
|
||||
TimelineView(.animation(minimumInterval: 1.0 / 30)) { tl in
|
||||
let t = tl.date.timeIntervalSince1970
|
||||
// 2.4s cycle: two cascading pulses then a long pause
|
||||
let cycle = t.truncatingRemainder(dividingBy: 2.4)
|
||||
let p1 = indicatorChevronPhase(cycle, start: 0.00, duration: 0.38)
|
||||
let p2 = indicatorChevronPhase(cycle, start: 0.42, duration: 0.38)
|
||||
|
||||
ZStack {
|
||||
// Primary chevron
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 9, weight: .medium))
|
||||
.foregroundStyle(AppTheme.inkSecondary)
|
||||
.offset(y: CGFloat(p1) * 4)
|
||||
.opacity(1.0 - p1 * 0.35)
|
||||
// Ghost — trails below with delay
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 8, weight: .medium))
|
||||
.foregroundStyle(AppTheme.inkSecondary.opacity(0.45))
|
||||
.offset(y: CGFloat(p2) * 4 + 7)
|
||||
.opacity(p2 * 0.85)
|
||||
}
|
||||
}
|
||||
.frame(width: 30, height: 30)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 7, style: .continuous)
|
||||
@@ -961,6 +980,13 @@ struct BackupView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
// sin-bell phase: rises from 0→1→0 over `duration` seconds starting at `start`
|
||||
private func indicatorChevronPhase(_ cycle: Double, start: Double, duration: Double) -> Double {
|
||||
let t = cycle - start
|
||||
guard t >= 0, t < duration else { return 0 }
|
||||
return sin(t / duration * .pi)
|
||||
}
|
||||
|
||||
// MARK: — Storage helpers
|
||||
|
||||
private func storageStat(_ value: String, label: String, color: Color) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user