- Raise the swipe dominance threshold to 2.0 and add a 24pt activation distance so diagonal timeline scrolls never move bubbles or fire haptics, and snap the indicator back when a gesture turns vertical mid-drag. - Cover the stricter thresholds in MessageReplySwipeTests.
39 lines
1.5 KiB
Swift
39 lines
1.5 KiB
Swift
import CoreGraphics
|
|
|
|
struct MessageReplySwipePolicy {
|
|
static let triggerDistance: CGFloat = 64
|
|
/// Horizontal travel required before the reply indicator appears. Keeps
|
|
/// the per-cell state untouched during ordinary vertical scrolling.
|
|
static let activationDistance: CGFloat = 24
|
|
static let maximumOffset: CGFloat = 76
|
|
/// The drag must be clearly horizontal: a mostly vertical timeline scroll
|
|
/// (even a diagonal one) must never arm the reply swipe or move bubbles.
|
|
static let horizontalDominance: CGFloat = 2.0
|
|
|
|
static func offset(for translation: CGSize) -> CGFloat {
|
|
guard isHorizontal(translation) else { return 0 }
|
|
let width = abs(translation.width)
|
|
guard width >= activationDistance else { return 0 }
|
|
let distance = min((width - activationDistance) * 0.72 + 12, maximumOffset)
|
|
return translation.width < 0 ? -distance : distance
|
|
}
|
|
|
|
static func shouldReply(
|
|
translation: CGSize,
|
|
predictedEndTranslation: CGSize
|
|
) -> Bool {
|
|
let candidate = abs(predictedEndTranslation.width) > abs(translation.width)
|
|
? predictedEndTranslation
|
|
: translation
|
|
return isHorizontal(candidate) && abs(candidate.width) >= triggerDistance
|
|
}
|
|
|
|
static func progress(for offset: CGFloat) -> CGFloat {
|
|
min(1, abs(offset) / triggerDistance)
|
|
}
|
|
|
|
private static func isHorizontal(_ translation: CGSize) -> Bool {
|
|
abs(translation.width) > abs(translation.height) * horizontalDominance
|
|
}
|
|
}
|