Skip to content

Commit 785bf29

Browse files
committed
add ability to add new tab when overscroll
1 parent 00faedf commit 785bf29

File tree

1 file changed

+132
-2
lines changed
  • app/src/main/java/com/raival/compose/file/explorer/screen/main

1 file changed

+132
-2
lines changed

app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import android.os.Bundle
44
import androidx.activity.compose.BackHandler
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
7+
import androidx.compose.animation.core.FastOutSlowInEasing
8+
import androidx.compose.animation.core.animate
9+
import androidx.compose.animation.core.tween
710
import androidx.compose.foundation.layout.Arrangement
811
import androidx.compose.foundation.layout.Column
912
import androidx.compose.foundation.layout.ColumnScope
@@ -17,10 +20,20 @@ import androidx.compose.runtime.LaunchedEffect
1720
import androidx.compose.runtime.collectAsState
1821
import androidx.compose.runtime.getValue
1922
import androidx.compose.runtime.key
23+
import androidx.compose.runtime.mutableFloatStateOf
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.remember
2026
import androidx.compose.runtime.rememberCoroutineScope
27+
import androidx.compose.runtime.setValue
2128
import androidx.compose.runtime.snapshotFlow
2229
import androidx.compose.ui.Alignment
2330
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.geometry.Offset
32+
import androidx.compose.ui.graphics.graphicsLayer
33+
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
34+
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
35+
import androidx.compose.ui.input.nestedscroll.nestedScroll
36+
import androidx.compose.ui.unit.Velocity
2437
import androidx.lifecycle.Lifecycle
2538
import androidx.lifecycle.compose.LifecycleEventEffect
2639
import com.raival.compose.file.explorer.App.Companion.globalClass
@@ -44,6 +57,8 @@ import com.raival.compose.file.explorer.screen.main.ui.Toolbar
4457
import com.raival.compose.file.explorer.theme.FileExplorerTheme
4558
import kotlinx.coroutines.launch
4659
import java.io.File
60+
import kotlin.math.abs
61+
import kotlin.math.exp
4762

4863
class MainActivity : BaseActivity() {
4964
private val HOME_SCREEN_SHORTCUT_EXTRA_KEY = "filePath"
@@ -167,13 +182,128 @@ class MainActivity : BaseActivity() {
167182
}
168183
}
169184

185+
var overscrollAmount by remember { mutableFloatStateOf(0f) }
186+
val threshold = 100f
187+
val animationScope = rememberCoroutineScope()
188+
var isAnimatingBack by remember { mutableStateOf(false) }
189+
190+
fun applyExponentialTension(current: Float, addition: Float, threshold: Float): Float {
191+
return if (current < threshold) {
192+
current + addition
193+
} else {
194+
val excess = current - threshold
195+
val decayFactor =
196+
exp(-excess / threshold * 2f) // Adjust multiplier for steepness
197+
current + (addition * decayFactor)
198+
}
199+
}
200+
201+
val nestedScrollConnection = remember {
202+
object : NestedScrollConnection {
203+
override fun onPreScroll(
204+
available: Offset,
205+
source: NestedScrollSource
206+
): Offset {
207+
// Smoothly animate overscroll back to zero when scrolling in opposite direction
208+
if (available.x > 0 && overscrollAmount > 0 && !isAnimatingBack) {
209+
isAnimatingBack = true
210+
animationScope.launch {
211+
animate(
212+
initialValue = overscrollAmount,
213+
targetValue = 0f,
214+
animationSpec = tween(
215+
durationMillis = 150,
216+
easing = FastOutSlowInEasing
217+
)
218+
) { value, _ ->
219+
overscrollAmount = value
220+
}
221+
isAnimatingBack = false
222+
}
223+
}
224+
return Offset.Zero
225+
}
226+
227+
override fun onPostScroll(
228+
consumed: Offset,
229+
available: Offset,
230+
source: NestedScrollSource
231+
): Offset {
232+
// Check if we're on the last page and there's leftover scroll
233+
val isLastPage = pagerState.currentPage == pagerState.pageCount - 1
234+
if (isLastPage && available.x < 0 && source == NestedScrollSource.UserInput) {
235+
val availableAmount = abs(available.x)
236+
237+
overscrollAmount = applyExponentialTension(
238+
overscrollAmount,
239+
availableAmount,
240+
threshold
241+
)
242+
return Offset(available.x, 0f) // Consume the scroll
243+
}
244+
return Offset.Zero
245+
}
246+
247+
override suspend fun onPreFling(available: Velocity): Velocity {
248+
val isLastPage = pagerState.currentPage == pagerState.pageCount - 1
249+
250+
if (isLastPage && overscrollAmount > 0 && !isAnimatingBack) {
251+
// Trigger action when releasing overscroll
252+
if (overscrollAmount > threshold) {
253+
manager.addTabAndSelect(HomeTab())
254+
}
255+
256+
// Animate overscroll back to 0 to prevent page jumping
257+
// This helps avoid the pager shooting to previous page
258+
// Animate overscroll back to 0 to prevent page jumping
259+
isAnimatingBack = true
260+
animationScope.launch {
261+
animate(
262+
initialValue = overscrollAmount,
263+
targetValue = 0f,
264+
animationSpec = tween(
265+
durationMillis = 200,
266+
easing = FastOutSlowInEasing
267+
)
268+
) { value, _ ->
269+
overscrollAmount = value
270+
}
271+
isAnimatingBack = false
272+
}
273+
274+
// Don't consume velocity if we're not overscrolling significantly
275+
// This prevents interfering with normal pager fling behavior
276+
return if (overscrollAmount > 10f) {
277+
Velocity(
278+
available.x * 0.3f,
279+
available.y
280+
) // Reduce horizontal velocity
281+
} else {
282+
Velocity.Zero
283+
}
284+
}
285+
286+
overscrollAmount = 0f
287+
return Velocity.Zero
288+
}
289+
}
290+
}
291+
170292
HorizontalPager(
171293
state = pagerState,
172-
modifier = Modifier.weight(1f),
294+
modifier = Modifier
295+
.weight(1f)
296+
.nestedScroll(nestedScrollConnection),
173297
key = { state.tabs[it].id }
174298
) { index ->
175299
key(index) {
176-
Column(modifier = Modifier.weight(1f)) {
300+
Column(
301+
modifier = Modifier
302+
.weight(1f)
303+
.graphicsLayer {
304+
translationX = -overscrollAmount
305+
}
306+
) {
177307
if (state.tabs.isNotEmpty()) {
178308
val currentTab = state.tabs[index]
179309
when (currentTab) {

0 commit comments

Comments
 (0)