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 {
|
Button {
|
||||||
withAnimation(.spring(response: 0.4, dampingFraction: 0.85)) {
|
withAnimation(.spring(response: 0.4, dampingFraction: 0.85)) {
|
||||||
ringPage = (ringPage + 1) % ringPageCount
|
ringPage = (ringPage + 1) % ringPageCount
|
||||||
}
|
}
|
||||||
} label: {
|
} 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")
|
Image(systemName: "chevron.down")
|
||||||
.font(.system(size: 9, weight: .medium))
|
.font(.system(size: 9, weight: .medium))
|
||||||
.foregroundStyle(AppTheme.inkSecondary)
|
.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)
|
.frame(width: 30, height: 30)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 7, style: .continuous)
|
RoundedRectangle(cornerRadius: 7, style: .continuous)
|
||||||
@@ -961,6 +980,13 @@ struct BackupView: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.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
|
// MARK: — Storage helpers
|
||||||
|
|
||||||
private func storageStat(_ value: String, label: String, color: Color) -> some View {
|
private func storageStat(_ value: String, label: String, color: Color) -> some View {
|
||||||
|
|||||||
Reference in New Issue
Block a user