feat(ui): click on top bar to scroll back to top

This commit is contained in:
junkfood 2024-11-12 20:27:07 +08:00
parent fe22c445f0
commit 05b249b708
No known key found for this signature in database
GPG Key ID: 2EA5B648DB112A34
3 changed files with 125 additions and 81 deletions

View File

@ -17,17 +17,19 @@ import me.ash.reader.ui.theme.palette.onDark
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RYScaffold(
modifier: Modifier = Modifier,
containerColor: Color = MaterialTheme.colorScheme.surface,
topBarTonalElevation: Dp = 0.dp,
containerTonalElevation: Dp = 0.dp,
navigationIcon: (@Composable () -> Unit)? = null,
actions: (@Composable RowScope.() -> Unit)? = null,
topBar: (@Composable () -> Unit)? = null,
bottomBar: (@Composable () -> Unit)? = null,
floatingActionButton: (@Composable () -> Unit)? = null,
content: @Composable () -> Unit = {},
) {
Scaffold(
modifier = Modifier
modifier = modifier
.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation,
@ -39,7 +41,7 @@ fun RYScaffold(
color = containerColor
) onDark MaterialTheme.colorScheme.surface,
topBar = {
if (navigationIcon != null || actions != null) {
if (topBar != null) topBar() else if (navigationIcon != null || actions != null) {
TopAppBar(
title = {},
navigationIcon = { navigationIcon?.invoke() },

View File

@ -2,6 +2,7 @@ package me.ash.reader.ui.page.home.feeds
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -31,6 +32,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
@ -76,6 +79,7 @@ import me.ash.reader.ui.ext.currentAccountId
import me.ash.reader.ui.ext.findActivity
import me.ash.reader.ui.ext.getCurrentVersion
import me.ash.reader.ui.ext.getDefaultGroupId
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.FilterState
import me.ash.reader.ui.page.home.HomeViewModel
@ -207,29 +211,50 @@ fun FeedsPage(
RYScaffold(
topBarTonalElevation = topBarTonalElevation.value.dp,
containerTonalElevation = groupListTonalElevation.value.dp,
navigationIcon = {
FeedbackIconButton(
modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(R.string.settings),
tint = MaterialTheme.colorScheme.onSurface,
showBadge = newVersion.whetherNeedUpdate(currentVersion, skipVersion),
) {
navController.navigate(RouteName.SETTINGS) {
launchSingleTop = true
}
}
},
actions = {
if (subscribeViewModel.rssService.get().addSubscription) {
FeedbackIconButton(
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(R.string.subscribe),
tint = MaterialTheme.colorScheme.onSurface,
) {
subscribeViewModel.showDrawer()
}
}
topBar = {
TopAppBar(
modifier = Modifier.clickable(
onClick = {
scope.launch {
if (listState.firstVisibleItemIndex != 0) {
listState.animateScrollToItem(0)
}
}
},
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
title = {},
navigationIcon = {
FeedbackIconButton(
modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(R.string.settings),
tint = MaterialTheme.colorScheme.onSurface,
showBadge = newVersion.whetherNeedUpdate(currentVersion, skipVersion),
) {
navController.navigate(RouteName.SETTINGS) {
launchSingleTop = true
}
}
},
actions = {
if (subscribeViewModel.rssService.get().addSubscription) {
FeedbackIconButton(
imageVector = Icons.Rounded.Add,
contentDescription = stringResource(R.string.subscribe),
tint = MaterialTheme.colorScheme.onSurface,
) {
subscribeViewModel.showDrawer()
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
)
)
},
content = {
PullToRefreshBox(

View File

@ -1,6 +1,8 @@
package me.ash.reader.ui.page.home.flow
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
@ -15,7 +17,10 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
@ -61,6 +66,7 @@ import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.component.base.RYScaffold
import me.ash.reader.ui.ext.collectAsStateValue
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.HomeViewModel
@ -228,64 +234,78 @@ fun FlowPage(
RYScaffold(
topBarTonalElevation = topBarTonalElevation.value.dp,
containerTonalElevation = articleListTonalElevation.value.dp,
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {
onSearch = false
if (navController.previousBackStackEntry == null) {
navController.navigate(RouteName.FEEDS) {
launchSingleTop = true
}
} else {
navController.popBackStack()
}
}
},
actions = {
RYExtensibleVisibility(visible = !filterUiState.filter.isStarred()) {
FeedbackIconButton(
imageVector = Icons.Rounded.DoneAll,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = if (markAsRead) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
},
) {
scope.launch {
// java.lang.NullPointerException: Attempt to invoke virtual method
// 'boolean androidx.compose.ui.node.LayoutNode.getNeedsOnPositionedDispatch$ui_release()'
// on a null object reference
if (flowUiState.listState.firstVisibleItemIndex != 0) {
flowUiState.listState.scrollToItem(0)
topBar = {
TopAppBar(
modifier = Modifier.clickable(
onClick = {
scope.launch {
if (listState.firstVisibleItemIndex != 0) {
listState.animateScrollToItem(0)
}
}
markAsRead = !markAsRead
},
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
title = {},
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {
onSearch = false
if (navController.previousBackStackEntry == null) {
navController.navigate(RouteName.FEEDS) {
launchSingleTop = true
}
} else {
navController.popBackStack()
}
}
}
}
FeedbackIconButton(
imageVector = Icons.Rounded.Search,
contentDescription = stringResource(R.string.search),
tint = if (onSearch) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
},
) {
scope.launch {
// java.lang.NullPointerException: Attempt to invoke virtual method
// 'boolean androidx.compose.ui.node.LayoutNode.getNeedsOnPositionedDispatch$ui_release()'
// on a null object reference
if (flowUiState.listState.firstVisibleItemIndex != 0) {
flowUiState.listState.scrollToItem(0)
actions = {
RYExtensibleVisibility(visible = !filterUiState.filter.isStarred()) {
FeedbackIconButton(
imageVector = Icons.Rounded.DoneAll,
contentDescription = stringResource(R.string.mark_all_as_read),
tint = if (markAsRead) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
},
) {
scope.launch {
if (flowUiState.listState.firstVisibleItemIndex != 0) {
flowUiState.listState.animateScrollToItem(0)
}
markAsRead = !markAsRead
onSearch = false
}
}
}
onSearch = !onSearch
}
}
FeedbackIconButton(
imageVector = Icons.Rounded.Search,
contentDescription = stringResource(R.string.search),
tint = if (onSearch) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.onSurface
},
) {
scope.launch {
if (flowUiState.listState.firstVisibleItemIndex != 0) {
flowUiState.listState.animateScrollToItem(0)
}
onSearch = !onSearch
}
}
}, colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
topBarTonalElevation.value.dp
),
)
)
},
content = {
PullToRefreshBox(
@ -394,9 +414,6 @@ fun FlowPage(
filterBarTonalElevation = filterBarTonalElevation.value.dp,
) {
scope.launch {
// java.lang.NullPointerException: Attempt to invoke virtual method
// 'boolean androidx.compose.ui.node.LayoutNode.getNeedsOnPositionedDispatch$ui_release()'
// on a null object reference
if (flowUiState.listState.firstVisibleItemIndex != 0) {
flowUiState.listState.animateScrollToItem(0)
}