Skip to content
Open
Changes from 1 commit
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: 22 additions & 3 deletions bitchat/Views/Media/WaveformView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ struct WaveformView: View {
let isInteractive: Bool
@ThemedPalette private var palette

/// Converts a tap location into a bounded playback fraction.
///
/// Keep this separate from the gesture so the coordinate contract can be
/// tested without mounting SwiftUI. A zero-sized geometry can occur while
/// a row is being laid out and must not result in a bogus seek.
static func seekFraction(forX x: CGFloat, inWidth width: CGFloat) -> Double? {
guard width > 0 else { return nil }
return max(0, min(1, Double(x / width)))
}

private var clampedPlayback: Double {
max(0, min(1, playbackProgress))
}
Expand Down Expand Up @@ -52,11 +62,20 @@ struct WaveformView: View {
if isInteractive, let onSeek = onSeek {
Color.clear
.contentShape(Rectangle())
// The private conversation owns a high-priority
// swipe-to-leave drag on the message list. A drag here
// is therefore interpreted as navigation instead of
// seeking and can close the conversation. Seeking is
// intentionally a tap: it preserves the swipe gesture
// while keeping the waveform's direct-manipulation
// affordance deterministic.
.gesture(
DragGesture(minimumDistance: 0)
SpatialTapGesture(count: 1, coordinateSpace: .local)
.onEnded { value in
guard geometry.size.width > 0 else { return }
let fraction = max(0, min(1, value.location.x / geometry.size.width))
guard let fraction = Self.seekFraction(
forX: value.location.x,
inWidth: geometry.size.width
) else { return }
onSeek(fraction)
}
)
Expand Down