From 24087803b1211b269253238dd0f6cb6af1a577b4 Mon Sep 17 00:00:00 2001 From: Will McCall Date: Mon, 14 Oct 2024 11:57:22 +0100 Subject: [PATCH] Replace deprecated SwipeRefresh with recomended PullRefreshIndicator and implement pull to refresh for Feeds Page. (#850) * Flow Page: replace SwipeRefresh with PullRefreshIndicator. * Feed Page: use PullRefreshIndicator for pull down refresh. * SwipeRefresh: Removed due to deprecation. * Switch from MD2 PullRefresh to MD3 PullToRefresh --------- Co-authored-by: Will McCall <532b81611e7e@mail.fordibben.zone> --- .../reader/ui/component/base/SwipeRefresh.kt | 31 -- .../reader/ui/page/home/feeds/FeedsPage.kt | 298 +++++++++--------- .../ash/reader/ui/page/home/flow/FlowPage.kt | 28 +- 3 files changed, 165 insertions(+), 192 deletions(-) delete mode 100644 app/src/main/java/me/ash/reader/ui/component/base/SwipeRefresh.kt diff --git a/app/src/main/java/me/ash/reader/ui/component/base/SwipeRefresh.kt b/app/src/main/java/me/ash/reader/ui/component/base/SwipeRefresh.kt deleted file mode 100644 index 5e2d311a..00000000 --- a/app/src/main/java/me/ash/reader/ui/component/base/SwipeRefresh.kt +++ /dev/null @@ -1,31 +0,0 @@ -package me.ash.reader.ui.component.base - -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import com.google.accompanist.swiperefresh.SwipeRefreshIndicator -import com.google.accompanist.swiperefresh.rememberSwipeRefreshState -import me.ash.reader.ui.theme.palette.onDark - -@Composable -fun SwipeRefresh( - isRefresh: Boolean = false, - onRefresh: () -> Unit = {}, - content: @Composable () -> Unit = {}, -) { - com.google.accompanist.swiperefresh.SwipeRefresh( - state = rememberSwipeRefreshState(isRefresh), - onRefresh = onRefresh, - indicator = { state, trigger -> - SwipeRefreshIndicator( - state = state, - refreshTriggerDistance = trigger, - fade = true, - scale = true, - contentColor = MaterialTheme.colorScheme.primary, - backgroundColor = MaterialTheme.colorScheme.surface onDark MaterialTheme.colorScheme.surfaceVariant, - ) - } - ) { - content() - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index a2a624a9..db6addbd 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -1,16 +1,12 @@ package me.ash.reader.ui.page.home.feeds import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars @@ -26,13 +22,15 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.rounded.Add -import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.UnfoldLess import androidx.compose.material.icons.rounded.UnfoldMore import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -47,7 +45,6 @@ import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -90,7 +87,7 @@ import kotlin.collections.set import kotlin.math.ln @OptIn( - androidx.compose.foundation.ExperimentalFoundationApi::class + ExperimentalMaterial3Api::class ) @Composable fun FeedsPage( @@ -130,7 +127,17 @@ fun FeedsPage( if (groupWithFeedList.isNotEmpty()) feedsUiState.listState else rememberLazyListState() val owner = LocalLifecycleOwner.current + var isSyncing by remember { mutableStateOf(false) } + val syncingState = rememberPullToRefreshState() + val syncingScope = rememberCoroutineScope() + val doSync:() -> Unit = { + isSyncing = true + syncingScope.launch { + + homeViewModel.sync() + } + } DisposableEffect(owner) { homeViewModel.syncWorkLiveData.observe(owner) { workInfoList -> @@ -141,15 +148,6 @@ fun FeedsPage( onDispose { homeViewModel.syncWorkLiveData.removeObservers(owner) } } - val infiniteTransition = rememberInfiniteTransition() - val angle by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 360f, - animationSpec = infiniteRepeatable( - animation = tween(1000, easing = LinearEasing) - ) - ) - val feedBadgeAlpha by remember { derivedStateOf { (ln(groupListTonalElevation.value + 1.4f) + 2f) / 100f } } val groupAlpha by remember { derivedStateOf { groupListTonalElevation.value.dp.alphaLN(weight = 1.2f) } } val groupIndicatorAlpha by remember { @@ -221,15 +219,6 @@ fun FeedsPage( } }, actions = { - FeedbackIconButton( - modifier = Modifier.rotate(if (isSyncing) angle else 0f), - imageVector = Icons.Rounded.Refresh, - contentDescription = stringResource(R.string.refresh), - tint = MaterialTheme.colorScheme.onSurface, - enabled = !isSyncing - ) { - if (!isSyncing) homeViewModel.sync() - } if (subscribeViewModel.rssService.get().addSubscription) { FeedbackIconButton( imageVector = Icons.Rounded.Add, @@ -241,153 +230,160 @@ fun FeedsPage( } }, content = { - LazyColumn( - state = listState + PullToRefreshBox( + state=syncingState, + isRefreshing = isSyncing, + onRefresh = doSync ) { - item { - DisplayText( - text = feedsUiState.account?.name ?: "", - desc = if (isSyncing) stringResource(R.string.syncing) else "", - ) { accountTabVisible = true } - } - item { - Banner( - title = filterUiState.filter.toName(), - desc = importantSum, - icon = filterUiState.filter.iconOutline, - action = { - Icon( - imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowRight, - contentDescription = stringResource(R.string.go_to), - ) - }, - ) { - filterChange( - navController = navController, - homeViewModel = homeViewModel, - filterState = filterUiState.copy( - group = null, - feed = null, - ) - ) + LazyColumn( + modifier=Modifier.fillMaxSize(), + state = listState + ) { + item { + DisplayText( + text = feedsUiState.account?.name ?: "", + desc = "", + ) { accountTabVisible = true } } - } - - item { - Spacer(modifier = Modifier.height(24.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 26.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringResource(R.string.feeds), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.labelLarge, - ) - Row( - modifier = Modifier - .padding(end = 12.dp) - .size(20.dp) - .clip(CircleShape) - .clickable { if (hasGroupVisible) collapseAllGroups() else expandAllGroups() }, - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, + item { + Banner( + title = filterUiState.filter.toName(), + desc = importantSum, + icon = filterUiState.filter.iconOutline, + action = { + Icon( + imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowRight, + contentDescription = stringResource(R.string.go_to), + ) + }, ) { - Icon( - imageVector = if (hasGroupVisible) Icons.Rounded.UnfoldLess else Icons.Rounded.UnfoldMore, - contentDescription = stringResource(R.string.unfold_less), - tint = MaterialTheme.colorScheme.primary, + filterChange( + navController = navController, + homeViewModel = homeViewModel, + filterState = filterUiState.copy( + group = null, + feed = null, + ) ) } } - Spacer(modifier = Modifier.height(8.dp)) - } - val defaultGroupId = context.currentAccountId.getDefaultGroupId() + item { + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 26.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.feeds), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.labelLarge, + ) + Row( + modifier = Modifier + .padding(end = 12.dp) + .size(20.dp) + .clip(CircleShape) + .clickable { if (hasGroupVisible) collapseAllGroups() else expandAllGroups() }, + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = if (hasGroupVisible) Icons.Rounded.UnfoldLess else Icons.Rounded.UnfoldMore, + contentDescription = stringResource(R.string.unfold_less), + tint = MaterialTheme.colorScheme.primary, + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + } - itemsIndexed(groupWithFeedList) { index, groupWithFeed -> - when (groupWithFeed) { - is GroupFeedsView.Group -> { - Spacer(modifier = Modifier.height(16.dp)) + val defaultGroupId = context.currentAccountId.getDefaultGroupId() - if (groupWithFeed.group.id != defaultGroupId || groupWithFeed.group.feeds > 0) { - GroupItem( - isExpanded = { - groupsVisible.getOrPut( - groupWithFeed.group.id, - groupListExpand::value - ) - }, - group = groupWithFeed.group, - alpha = groupAlpha, - indicatorAlpha = groupIndicatorAlpha, - roundedBottomCorner = { index == groupWithFeedList.lastIndex || groupWithFeed.group.feeds == 0 }, - onExpanded = { - groupsVisible[groupWithFeed.group.id] = + itemsIndexed(groupWithFeedList) { index, groupWithFeed -> + when (groupWithFeed) { + is GroupFeedsView.Group -> { + Spacer(modifier = Modifier.height(16.dp)) + + if (groupWithFeed.group.id != defaultGroupId || groupWithFeed.group.feeds > 0) { + GroupItem( + isExpanded = { groupsVisible.getOrPut( groupWithFeed.group.id, groupListExpand::value - ).not() - hasGroupVisible = - if (groupsVisible[groupWithFeed.group.id] == true) { - true - } else { - groupsVisible.any { it.value } + ) + }, + group = groupWithFeed.group, + alpha = groupAlpha, + indicatorAlpha = groupIndicatorAlpha, + roundedBottomCorner = { index == groupWithFeedList.lastIndex || groupWithFeed.group.feeds == 0 }, + onExpanded = { + groupsVisible[groupWithFeed.group.id] = + groupsVisible.getOrPut( + groupWithFeed.group.id, + groupListExpand::value + ).not() + hasGroupVisible = + if (groupsVisible[groupWithFeed.group.id] == true) { + true + } else { + groupsVisible.any { it.value } + } + }, + onLongClick = { + scope.launch { + groupDrawerState.show() } - }, - onLongClick = { - scope.launch { - groupDrawerState.show() } - } - ) { - filterChange( - navController = navController, - homeViewModel = homeViewModel, - filterState = filterUiState.copy( - group = groupWithFeed.group, - feed = null, + ) { + filterChange( + navController = navController, + homeViewModel = homeViewModel, + filterState = filterUiState.copy( + group = groupWithFeed.group, + feed = null, + ) ) - ) + } } } - } - is GroupFeedsView.Feed -> { - FeedItem( - feed = groupWithFeed.feed, - alpha = groupAlpha, - badgeAlpha = feedBadgeAlpha, - isEnded = { index == groupWithFeedList.lastIndex || groupWithFeedList[index + 1] is GroupFeedsView.Group }, - isExpanded = { - groupsVisible.getOrPut( - groupWithFeed.feed.groupId, - groupListExpand::value - ) - }, onClick = { - filterChange( - navController = navController, - homeViewModel = homeViewModel, - filterState = filterUiState.copy( - group = null, - feed = groupWithFeed.feed, + is GroupFeedsView.Feed -> { + FeedItem( + feed = groupWithFeed.feed, + alpha = groupAlpha, + badgeAlpha = feedBadgeAlpha, + isEnded = { index == groupWithFeedList.lastIndex || groupWithFeedList[index + 1] is GroupFeedsView.Group }, + isExpanded = { + groupsVisible.getOrPut( + groupWithFeed.feed.groupId, + groupListExpand::value ) - ) - }, onLongClick = { - scope.launch { - feedDrawerState.show() + }, onClick = { + filterChange( + navController = navController, + homeViewModel = homeViewModel, + filterState = filterUiState.copy( + group = null, + feed = groupWithFeed.feed, + ) + ) + }, onLongClick = { + scope.launch { + feedDrawerState.show() + } } - } - ) + ) + } } } - } - item { - Spacer(modifier = Modifier.height(128.dp)) - Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + item { + Spacer(modifier = Modifier.height(128.dp)) + Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) + } } } }, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index a376385c..fd93e09f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -14,7 +14,10 @@ import androidx.compose.material.icons.Icons 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.MaterialTheme +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -55,13 +58,12 @@ import me.ash.reader.ui.component.base.DisplayText 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.component.base.SwipeRefresh import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.home.HomeViewModel @OptIn( - androidx.compose.ui.ExperimentalComposeUiApi::class, + ExperimentalMaterial3Api::class, ) @Composable fun FlowPage( @@ -94,7 +96,16 @@ fun FlowPage( var onSearch by remember { mutableStateOf(false) } val owner = LocalLifecycleOwner.current + var isSyncing by remember { mutableStateOf(false) } + val syncingState = rememberPullToRefreshState() + val syncingScope = rememberCoroutineScope() + val doSync: () -> Unit = { + isSyncing = true + syncingScope.launch { + flowViewModel.sync() + } + } DisposableEffect(owner) { homeViewModel.syncWorkLiveData.observe(owner) { workInfoList -> @@ -242,14 +253,11 @@ fun FlowPage( } }, content = { - SwipeRefresh( - onRefresh = { - if (!isSyncing) { - flowViewModel.sync() - } - } + PullToRefreshBox( + state = syncingState, + isRefreshing = isSyncing, + onRefresh = doSync ) { - var showMenu by remember { mutableStateOf(false) } LazyColumn( modifier = Modifier.fillMaxSize(), state = listState, @@ -262,7 +270,7 @@ fun FlowPage( filterUiState.feed != null -> filterUiState.feed.name else -> filterUiState.filter.toName() }, - desc = if (isSyncing) stringResource(R.string.syncing) else "", + desc = "", ) RYExtensibleVisibility(visible = markAsRead) { Spacer(modifier = Modifier.height((56 + 24 + 10).dp))