android: add FAB to scroll to top

This commit is contained in:
Harsh Shandilya 2021-09-25 19:58:55 +05:30
parent dd424d6af9
commit c0f2089482
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
4 changed files with 108 additions and 2 deletions

View file

@ -0,0 +1,53 @@
package dev.msfjarvis.claw.android.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import dev.msfjarvis.claw.android.R
import kotlinx.coroutines.launch
private const val AnimationDuration = 100
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun ClawFab(
isFabVisible: Boolean,
listState: LazyListState,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
AnimatedVisibility(
visible = isFabVisible,
enter =
slideInVertically(
// Enters by sliding up from offset 0 to fullHeight.
initialOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(durationMillis = AnimationDuration, easing = LinearOutSlowInEasing),
),
exit =
slideOutVertically(
// Exits by sliding up from offset 0 to -fullHeight.
targetOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
),
modifier = Modifier.then(modifier),
) {
FloatingActionButton(onClick = { coroutineScope.launch { listState.animateScrollToItem(0) } }) {
Icon(
painter = painterResource(R.drawable.ic_arrow_upward_24),
contentDescription = null,
)
}
}
}

View file

@ -1,21 +1,33 @@
package dev.msfjarvis.claw.android.ui
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.primarySurface
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsPadding
import com.google.accompanist.insets.statusBarsPadding
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
@ -24,6 +36,9 @@ import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
private const val ScrollDelta = 50
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun LobstersApp(
viewModel: ClawViewModel = viewModel(),
@ -31,6 +46,24 @@ fun LobstersApp(
) {
val systemUiController = rememberSystemUiController()
val scaffoldState = rememberScaffoldState()
val listState = rememberLazyListState()
var isFabVisible by remember { mutableStateOf(true) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y
if (delta > ScrollDelta) {
isFabVisible = true
} else if (delta < -ScrollDelta) {
isFabVisible = false
}
// We didn't consume any offset here so return Offset.Zero
return Offset.Zero
}
}
}
LobstersTheme(darkTheme = isSystemInDarkTheme()) {
ProvideWindowInsets {
val useDarkIcons = MaterialTheme.colors.isLight
@ -44,7 +77,13 @@ fun LobstersApp(
Scaffold(
scaffoldState = scaffoldState,
topBar = { ClawAppBar(modifier = Modifier.statusBarsPadding()) },
modifier = Modifier,
floatingActionButton = {
ClawFab(
isFabVisible = isFabVisible,
listState = listState,
modifier = Modifier.navigationBarsPadding(),
)
},
) {
val isRefreshing = items.loadState.refresh == LoadState.Loading
SwipeRefresh(
@ -57,7 +96,8 @@ fun LobstersApp(
NetworkPosts(
items = items,
launchUrl = urlLauncher::launch,
modifier = Modifier.padding(top = 16.dp),
listState = listState,
modifier = Modifier.padding(top = 16.dp).nestedScroll(nestedScrollConnection),
)
}
}

View file

@ -2,6 +2,7 @@ package dev.msfjarvis.claw.android.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@ -14,10 +15,12 @@ import dev.msfjarvis.claw.common.posts.LobstersCard
@Composable
fun NetworkPosts(
items: LazyPagingItems<LobstersPost>,
listState: LazyListState,
launchUrl: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
state = listState,
modifier = Modifier.then(modifier),
) {
items(items) { item ->

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
</vector>