Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions LoopFollow/Charts/BGChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -900,12 +900,16 @@ private struct MainBGChart: View {
}
}

// MARK: - Small chart (overview + tap-to-navigate)
// MARK: - Small chart (overview + tap/drag-to-navigate)

private struct SmallBGChart: View {
@ObservedObject var model: BGChartModel
@ObservedObject var interaction: BGChartInteraction

/// True once the touch has travelled far enough to count as a drag; from
/// then on the main chart tracks the finger live instead of waiting for release.
@State private var isScrubbing = false

var body: some View {
GeometryReader { geo in
let width = max(geo.size.width, 1)
Expand All @@ -931,8 +935,16 @@ private struct SmallBGChart: View {
.contentShape(Rectangle())
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
let distance = hypot(value.translation.width, value.translation.height)
if isScrubbing || distance >= BGChartConfig.inspectMovementTolerance {
isScrubbing = true
navigate(toLocalX: value.location.x, width: width, domainSeconds: domainSeconds, animated: false)
}
}
.onEnded { value in
navigate(toLocalX: value.location.x, width: width, domainSeconds: domainSeconds)
navigate(toLocalX: value.location.x, width: width, domainSeconds: domainSeconds, animated: !isScrubbing)
isScrubbing = false
}
)
}
Expand All @@ -951,10 +963,10 @@ private struct SmallBGChart: View {
.offset(x: boxLeft)
}

/// Maps a tap x to a time and recenters the main chart on it (clamped to
/// Maps a touch x to a time and recenters the main chart on it (clamped to
/// the data window). Following re-arms automatically via the main chart's
/// scroll observer if the user lands back at the latest data.
private func navigate(toLocalX x: CGFloat, width: CGFloat, domainSeconds: TimeInterval) {
private func navigate(toLocalX x: CGFloat, width: CGFloat, domainSeconds: TimeInterval, animated: Bool) {
let fraction = min(max(x / width, 0), 1)
let date = model.domainStart.addingTimeInterval(domainSeconds * TimeInterval(fraction))
let length = interaction.visibleSeconds
Expand All @@ -965,8 +977,9 @@ private struct SmallBGChart: View {
target = min(max(target, minStart), maxStart)
}
// Same rule as the main chart's scrollToNow: only nearby targets (still
// inside the render window) animate cleanly; long jumps snap.
if abs(target.timeIntervalSince(interaction.scrollPosition)) <= interaction.visibleSeconds {
// inside the render window) animate cleanly; long jumps snap. Live
// drag-tracking never animates — each move must land within the frame.
if animated, abs(target.timeIntervalSince(interaction.scrollPosition)) <= interaction.visibleSeconds {
withAnimation(.easeInOut(duration: 0.3)) {
interaction.scrollPosition = target
}
Expand Down
Loading