diff --git a/app/src/main/java/com/paulcoding/hviewer/extensions/ListExtensions.kt b/app/src/main/java/com/paulcoding/hviewer/extensions/ListExtensions.kt index 57b217b..3919f93 100644 --- a/app/src/main/java/com/paulcoding/hviewer/extensions/ListExtensions.kt +++ b/app/src/main/java/com/paulcoding/hviewer/extensions/ListExtensions.kt @@ -1,7 +1,30 @@ package com.paulcoding.hviewer.extensions import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.produceState +import androidx.compose.runtime.snapshotFlow fun LazyListState.isScrolledToEnd(): Boolean { return layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1 } + +@Composable +fun LazyListState.isScrollingUp(): State { + return produceState(initialValue = true) { + var previousIndex = firstVisibleItemIndex + var previousOffset = firstVisibleItemScrollOffset + + snapshotFlow { firstVisibleItemIndex to firstVisibleItemScrollOffset } + .collect { (index, offset) -> + value = if (index != previousIndex) { + index < previousIndex + } else { + offset < previousOffset + } + previousIndex = index + previousOffset = offset + } + } +} diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/component/HGoTop.kt b/app/src/main/java/com/paulcoding/hviewer/ui/component/HGoTop.kt new file mode 100644 index 0000000..1d161db --- /dev/null +++ b/app/src/main/java/com/paulcoding/hviewer/ui/component/HGoTop.kt @@ -0,0 +1,43 @@ +package com.paulcoding.hviewer.ui.component + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.KeyboardArrowUp +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.paulcoding.hviewer.extensions.isScrollingUp +import com.paulcoding.hviewer.ui.page.fadeInWithBlur +import com.paulcoding.hviewer.ui.page.fadeOutWithBlur +import kotlinx.coroutines.launch + +@Composable +fun BoxScope.HGoTop(listState: LazyListState) { + val scope = rememberCoroutineScope() + + AnimatedVisibility( + listState.isScrollingUp().value, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(16.dp), + enter = fadeInWithBlur(), + exit = fadeOutWithBlur(), + ) { + FloatingActionButton( + onClick = { + scope.launch { + listState.animateScrollToItem(0, 0) + } + }, + ) { + Icon(Icons.Outlined.KeyboardArrowUp, "Go to Top") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt index 8a9e9d6..f8a9124 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/post/PostPage.kt @@ -1,21 +1,16 @@ package com.paulcoding.hviewer.ui.page.post import android.widget.Toast -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface -import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -23,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -34,11 +28,9 @@ import coil3.compose.AsyncImage import com.paulcoding.hviewer.MainApp.Companion.appContext import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.helper.makeToast -import com.paulcoding.hviewer.model.SiteConfig -import com.paulcoding.hviewer.ui.component.HBackIcon +import com.paulcoding.hviewer.ui.component.HGoTop import com.paulcoding.hviewer.ui.component.HImage import com.paulcoding.hviewer.ui.component.HLoading -import com.paulcoding.hviewer.ui.component.HPageProgress import com.paulcoding.hviewer.ui.component.HideSystemBars import com.paulcoding.hviewer.ui.page.AppViewModel import me.saket.telephoto.zoomable.DoubleClickToZoomListener @@ -46,7 +38,6 @@ import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.rememberZoomableState import me.saket.telephoto.zoomable.zoomable -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { val appState by appViewModel.stateFlow.collectAsState() @@ -75,47 +66,11 @@ fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { } } - var isScrollingUp by remember { mutableStateOf(false) } - - // Detect scroll direction - LaunchedEffect(listState) { - var previousIndex = listState.firstVisibleItemIndex - var previousOffset = listState.firstVisibleItemScrollOffset - - snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } - .collect { (index, offset) -> - isScrollingUp = if (index != previousIndex) { - index < previousIndex - } else { - offset < previousOffset - } - previousIndex = index - previousOffset = offset - } - } - HideSystemBars() - Scaffold(topBar = { - AnimatedVisibility( - visible = isScrollingUp - ) { - TopAppBar( - navigationIcon = { - HBackIcon { goBack() } - }, - title = {}, - actions = { - if (uiState.images.isNotEmpty()) - HPageProgress(uiState.postPage, uiState.postTotalPage) - } - ) - } - }) { - + Box(modifier = Modifier.fillMaxSize()) { LazyColumn( state = listState, - modifier = Modifier.padding(it), verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(uiState.images) { image -> @@ -128,8 +83,11 @@ fun PostPage(appViewModel: AppViewModel, goBack: () -> Unit) { item { HLoading() } + } + HGoTop(listState) + if (selectedImage != null) { ImageModal(url = selectedImage!!) { selectedImage = null diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt index 773763f..2f9317f 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/posts/PostsPage.kt @@ -2,6 +2,7 @@ package com.paulcoding.hviewer.ui.page.posts import android.widget.Toast import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -36,6 +37,7 @@ import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.model.SiteConfig import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HEmpty +import com.paulcoding.hviewer.ui.component.HGoTop import com.paulcoding.hviewer.ui.component.HIcon import com.paulcoding.hviewer.ui.component.HImage import com.paulcoding.hviewer.ui.component.HLoading @@ -144,27 +146,30 @@ fun PageContent( onPageChange(uiState.postsPage, uiState.postsTotalPage) } - LazyColumn( - state = listState - ) { - items(uiState.postItems) { post -> - PostItemView(post) { - onClick(post) - } - } - if (uiState.isLoading) - item { - HLoading() - } - else if (uiState.postItems.isEmpty()) - item { - HEmpty( - title = "No posts found", - message = "Refresh?" - ) { - viewModel.getPosts(1) + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState + ) { + items(uiState.postItems) { post -> + PostItemView(post) { + onClick(post) } } + if (uiState.isLoading) + item { + HLoading() + } + else if (uiState.postItems.isEmpty()) + item { + HEmpty( + title = "No posts found", + message = "Refresh?" + ) { + viewModel.getPosts(1) + } + } + } + HGoTop(listState) } } diff --git a/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt b/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt index 99a8b1a..d84c75d 100644 --- a/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt +++ b/app/src/main/java/com/paulcoding/hviewer/ui/page/search/SearchPage.kt @@ -2,8 +2,10 @@ package com.paulcoding.hviewer.ui.page.search import android.widget.Toast import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -37,6 +39,7 @@ import com.paulcoding.hviewer.extensions.isScrolledToEnd import com.paulcoding.hviewer.model.PostItem import com.paulcoding.hviewer.ui.component.HBackIcon import com.paulcoding.hviewer.ui.component.HEmpty +import com.paulcoding.hviewer.ui.component.HGoTop import com.paulcoding.hviewer.ui.component.HLoading import com.paulcoding.hviewer.ui.component.HPageProgress import com.paulcoding.hviewer.ui.icon.EditIcon @@ -130,23 +133,27 @@ fun PageContent( } } - LazyColumn( - state = listState - ) { - items(uiState.postItems) { post -> - PostItemView(post) { - onClick(post) + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState + ) { + items(uiState.postItems) { post -> + PostItemView(post) { + onClick(post) + } } + if (uiState.isLoading) + item { + HLoading() + } + else if (uiState.postItems.isEmpty() && uiState.query.isNotEmpty()) + item { + HEmpty( + title = "No posts found", + ) + } } - if (uiState.isLoading) - item { - HLoading() - } - else if (uiState.postItems.isEmpty() && uiState.query.isNotEmpty()) - item { - HEmpty( - title = "No posts found", - ) - } + HGoTop(listState) } } +