feat: implement Telegram-style swipe functionality and animation enhancements
This commit is contained in:
@@ -23,11 +23,11 @@ import kotlinx.coroutines.launch
|
||||
// Telegram's CubicBezierInterpolator(0.25, 0.1, 0.25, 1.0)
|
||||
private val TelegramEasing = CubicBezierEasing(0.25f, 0.1f, 0.25f, 1.0f)
|
||||
|
||||
// Swipe-back thresholds
|
||||
private const val COMPLETION_THRESHOLD = 0.3f // 30% of screen width (was 50%)
|
||||
private const val FLING_VELOCITY_THRESHOLD = 400f // px/s (was 600)
|
||||
// Swipe-back thresholds (Telegram-like)
|
||||
private const val COMPLETION_THRESHOLD = 0.2f // 20% of screen width — very easy to complete
|
||||
private const val FLING_VELOCITY_THRESHOLD = 150f // px/s — very sensitive to flings
|
||||
private const val ANIMATION_DURATION_ENTER = 300
|
||||
private const val ANIMATION_DURATION_EXIT = 250
|
||||
private const val ANIMATION_DURATION_EXIT = 200
|
||||
private const val EDGE_ZONE_DP = 200
|
||||
|
||||
/**
|
||||
@@ -148,6 +148,7 @@ fun SwipeBackContainer(
|
||||
if (swipeEnabled && !isAnimatingIn && !isAnimatingOut) {
|
||||
Modifier.pointerInput(Unit) {
|
||||
val velocityTracker = VelocityTracker()
|
||||
val touchSlop = viewConfiguration.touchSlop
|
||||
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown(requireUnconsumed = false)
|
||||
@@ -159,35 +160,54 @@ fun SwipeBackContainer(
|
||||
|
||||
velocityTracker.resetTracking()
|
||||
var startedSwipe = false
|
||||
var totalDragX = 0f
|
||||
var totalDragY = 0f
|
||||
var passedSlop = false
|
||||
|
||||
try {
|
||||
horizontalDrag(down.id) { change ->
|
||||
val dragAmount = change.positionChange().x
|
||||
// Use Initial pass to intercept BEFORE children
|
||||
while (true) {
|
||||
val event = awaitPointerEvent(PointerEventPass.Initial)
|
||||
val change = event.changes.firstOrNull { it.id == down.id }
|
||||
?: break
|
||||
|
||||
// Only start swipe if moving right
|
||||
if (!startedSwipe && dragAmount > 0) {
|
||||
if (change.changedToUpIgnoreConsumed()) {
|
||||
break
|
||||
}
|
||||
|
||||
val dragDelta = change.positionChange()
|
||||
totalDragX += dragDelta.x
|
||||
totalDragY += dragDelta.y
|
||||
|
||||
if (!passedSlop) {
|
||||
val totalDistance = kotlin.math.sqrt(totalDragX * totalDragX + totalDragY * totalDragY)
|
||||
if (totalDistance < touchSlop) continue
|
||||
|
||||
// Slop exceeded — only claim rightward + mostly horizontal
|
||||
if (totalDragX > 0 && kotlin.math.abs(totalDragX) > kotlin.math.abs(totalDragY) * 1.5f) {
|
||||
passedSlop = true
|
||||
startedSwipe = true
|
||||
isDragging = true
|
||||
dragOffset = offsetAnimatable.value
|
||||
// 🔥 Скрываем клавиатуру при начале свайпа (надёжный метод)
|
||||
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
|
||||
if (startedSwipe) {
|
||||
// Direct state update - NO coroutines!
|
||||
dragOffset = (dragOffset + dragAmount)
|
||||
.coerceIn(0f, screenWidthPx)
|
||||
velocityTracker.addPosition(
|
||||
change.uptimeMillis,
|
||||
change.position
|
||||
)
|
||||
change.consume()
|
||||
} else {
|
||||
// Vertical or leftward — let children handle
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// We own the gesture — update drag
|
||||
dragOffset = (dragOffset + dragDelta.x)
|
||||
.coerceIn(0f, screenWidthPx)
|
||||
velocityTracker.addPosition(
|
||||
change.uptimeMillis,
|
||||
change.position
|
||||
)
|
||||
change.consume()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Gesture was cancelled
|
||||
}
|
||||
|
||||
// Handle drag end
|
||||
@@ -196,13 +216,12 @@ fun SwipeBackContainer(
|
||||
val velocity = velocityTracker.calculateVelocity().x
|
||||
val currentProgress = dragOffset / screenWidthPx
|
||||
|
||||
// Telegram logic: fling OR 50% threshold
|
||||
val shouldComplete =
|
||||
velocity > FLING_VELOCITY_THRESHOLD ||
|
||||
currentProgress > 0.5f || // Past 50% — always complete
|
||||
velocity > FLING_VELOCITY_THRESHOLD || // Fast fling right
|
||||
(currentProgress > COMPLETION_THRESHOLD &&
|
||||
velocity > -FLING_VELOCITY_THRESHOLD)
|
||||
velocity > -FLING_VELOCITY_THRESHOLD) // 20%+ and not flinging back
|
||||
|
||||
// Sync animatable with current drag position and animate
|
||||
scope.launch {
|
||||
offsetAnimatable.snapTo(dragOffset)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user