|
| 1 | +package to.bitkit.ui.shared.modifiers |
| 2 | + |
| 3 | +import androidx.compose.foundation.gestures.detectHorizontalDragGestures |
| 4 | +import androidx.compose.runtime.remember |
| 5 | +import androidx.compose.ui.Modifier |
| 6 | +import androidx.compose.ui.composed |
| 7 | +import androidx.compose.ui.input.pointer.pointerInput |
| 8 | +import androidx.compose.ui.input.pointer.util.VelocityTracker |
| 9 | + |
| 10 | +/** |
| 11 | + * Enables tab navigation via horizontal swipe gestures. |
| 12 | + * |
| 13 | + * Detects horizontal swipe velocity and navigates to adjacent tabs when the velocity |
| 14 | + * exceeds the specified threshold. Provides boundary protection to prevent navigation |
| 15 | + * beyond the first and last tabs. |
| 16 | + * |
| 17 | + * @param currentTabIndex The currently selected tab index (0-based) |
| 18 | + * @param tabCount Total number of tabs available for navigation |
| 19 | + * @param onTabChange Callback invoked when user swipes to change tabs, receives the new tab index |
| 20 | + * @param threshold Velocity threshold in pixels per second (default: 1500f) |
| 21 | + * Swipe velocity must exceed this value to trigger navigation |
| 22 | + */ |
| 23 | +fun Modifier.swipeToChangeTab( |
| 24 | + currentTabIndex: Int, |
| 25 | + tabCount: Int, |
| 26 | + onTabChange: (Int) -> Unit, |
| 27 | + threshold: Float = DEFAULT_SWIPE_THRESHOLD, |
| 28 | +): Modifier = composed { |
| 29 | + val velocityTracker = remember { VelocityTracker() } |
| 30 | + |
| 31 | + pointerInput(currentTabIndex) { |
| 32 | + detectHorizontalDragGestures( |
| 33 | + onHorizontalDrag = { change, _ -> |
| 34 | + velocityTracker.addPosition(change.uptimeMillis, change.position) |
| 35 | + }, |
| 36 | + onDragEnd = { |
| 37 | + val velocity = velocityTracker.calculateVelocity().x |
| 38 | + when { |
| 39 | + velocity >= threshold && currentTabIndex > 0 -> |
| 40 | + onTabChange(currentTabIndex - 1) |
| 41 | + |
| 42 | + velocity <= -threshold && currentTabIndex < tabCount - 1 -> |
| 43 | + onTabChange(currentTabIndex + 1) |
| 44 | + } |
| 45 | + velocityTracker.resetTracking() |
| 46 | + }, |
| 47 | + onDragCancel = { |
| 48 | + velocityTracker.resetTracking() |
| 49 | + }, |
| 50 | + ) |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +private const val DEFAULT_SWIPE_THRESHOLD = 1500f |
0 commit comments