Merge branch 'main' into adaptive

This commit is contained in:
junkfood 2024-10-14 19:16:42 +08:00
commit d3f6fa63aa
No known key found for this signature in database
GPG Key ID: 2EA5B648DB112A34
7 changed files with 245 additions and 222 deletions

View File

@ -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()
}
}

View File

@ -1,16 +1,12 @@
package me.ash.reader.ui.page.home.feeds package me.ash.reader.ui.page.home.feeds
import androidx.activity.compose.BackHandler 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.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
@ -26,13 +22,16 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.rounded.Add 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.UnfoldLess
import androidx.compose.material.icons.rounded.UnfoldMore import androidx.compose.material.icons.rounded.UnfoldMore
import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text 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.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -47,7 +46,6 @@ import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -90,7 +88,7 @@ import kotlin.collections.set
import kotlin.math.ln import kotlin.math.ln
@OptIn( @OptIn(
androidx.compose.foundation.ExperimentalFoundationApi::class ExperimentalMaterial3Api::class
) )
@Composable @Composable
fun FeedsPage( fun FeedsPage(
@ -130,7 +128,17 @@ fun FeedsPage(
if (groupWithFeedList.isNotEmpty()) feedsUiState.listState else rememberLazyListState() if (groupWithFeedList.isNotEmpty()) feedsUiState.listState else rememberLazyListState()
val owner = LocalLifecycleOwner.current val owner = LocalLifecycleOwner.current
var isSyncing by remember { mutableStateOf(false) } var isSyncing by remember { mutableStateOf(false) }
val syncingState = rememberPullToRefreshState()
val syncingScope = rememberCoroutineScope()
val doSync: () -> Unit = {
isSyncing = true
syncingScope.launch {
homeViewModel.sync()
}
}
DisposableEffect(owner) { DisposableEffect(owner) {
homeViewModel.syncWorkLiveData.observe(owner) { workInfoList -> homeViewModel.syncWorkLiveData.observe(owner) { workInfoList ->
@ -141,15 +149,6 @@ fun FeedsPage(
onDispose { homeViewModel.syncWorkLiveData.removeObservers(owner) } 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 feedBadgeAlpha by remember { derivedStateOf { (ln(groupListTonalElevation.value + 1.4f) + 2f) / 100f } }
val groupAlpha by remember { derivedStateOf { groupListTonalElevation.value.dp.alphaLN(weight = 1.2f) } } val groupAlpha by remember { derivedStateOf { groupListTonalElevation.value.dp.alphaLN(weight = 1.2f) } }
val groupIndicatorAlpha by remember { val groupIndicatorAlpha by remember {
@ -221,15 +220,6 @@ fun FeedsPage(
} }
}, },
actions = { 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) { if (subscribeViewModel.rssService.get().addSubscription) {
FeedbackIconButton( FeedbackIconButton(
imageVector = Icons.Rounded.Add, imageVector = Icons.Rounded.Add,
@ -241,13 +231,20 @@ fun FeedsPage(
} }
}, },
content = { content = {
PullToRefreshBox(
state = syncingState,
isRefreshing = isSyncing,
onRefresh = doSync
) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState state = listState
) { ) {
item { item {
DisplayText( DisplayText(
text = feedsUiState.account?.name ?: "", text = feedsUiState.account?.name ?: "",
desc = if (isSyncing) stringResource(R.string.syncing) else "", desc = "",
) { accountTabVisible = true } ) { accountTabVisible = true }
} }
item { item {
@ -356,6 +353,7 @@ fun FeedsPage(
} }
} }
is GroupFeedsView.Feed -> { is GroupFeedsView.Feed -> {
FeedItem( FeedItem(
feed = groupWithFeed.feed, feed = groupWithFeed.feed,
@ -390,6 +388,7 @@ fun FeedsPage(
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
} }
} }
}
}, },
bottomBar = { bottomBar = {
FilterBar( FilterBar(

View File

@ -1,6 +1,14 @@
package me.ash.reader.ui.page.home.flow package me.ash.reader.ui.page.home.flow
import android.util.Log
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable

View File

@ -14,7 +14,10 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.DoneAll import androidx.compose.material.icons.rounded.DoneAll
import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme 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.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -56,13 +59,12 @@ import me.ash.reader.ui.component.base.DisplayText
import me.ash.reader.ui.component.base.FeedbackIconButton import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.component.base.RYScaffold 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.ext.collectAsStateValue
import me.ash.reader.ui.page.common.RouteName import me.ash.reader.ui.page.common.RouteName
import me.ash.reader.ui.page.home.HomeViewModel import me.ash.reader.ui.page.home.HomeViewModel
@OptIn( @OptIn(
androidx.compose.ui.ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class,
) )
@Composable @Composable
fun FlowPage( fun FlowPage(
@ -97,7 +99,16 @@ fun FlowPage(
var onSearch by remember { mutableStateOf(false) } var onSearch by remember { mutableStateOf(false) }
val owner = LocalLifecycleOwner.current val owner = LocalLifecycleOwner.current
var isSyncing by remember { mutableStateOf(false) } var isSyncing by remember { mutableStateOf(false) }
val syncingState = rememberPullToRefreshState()
val syncingScope = rememberCoroutineScope()
val doSync: () -> Unit = {
isSyncing = true
syncingScope.launch {
flowViewModel.sync()
}
}
DisposableEffect(owner) { DisposableEffect(owner) {
homeViewModel.syncWorkLiveData.observe(owner) { workInfoList -> homeViewModel.syncWorkLiveData.observe(owner) { workInfoList ->
@ -253,14 +264,11 @@ fun FlowPage(
} }
}, },
content = { content = {
SwipeRefresh( PullToRefreshBox(
onRefresh = { state = syncingState,
if (!isSyncing) { isRefreshing = isSyncing,
flowViewModel.sync() onRefresh = doSync
}
}
) { ) {
var showMenu by remember { mutableStateOf(false) }
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = listState, state = listState,
@ -273,7 +281,7 @@ fun FlowPage(
filterUiState.feed != null -> filterUiState.feed.name filterUiState.feed != null -> filterUiState.feed.name
else -> filterUiState.filter.toName() else -> filterUiState.filter.toName()
}, },
desc = if (isSyncing) stringResource(R.string.syncing) else "", desc = "",
) )
RYExtensibleVisibility(visible = markAsRead) { RYExtensibleVisibility(visible = markAsRead) {
Spacer(modifier = Modifier.height((56 + 24 + 10).dp)) Spacer(modifier = Modifier.height((56 + 24 + 10).dp))

View File

@ -1,9 +1,13 @@
package me.ash.reader.ui.page.home.reading package me.ash.reader.ui.page.home.reading
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -11,13 +15,16 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.foundation.text.selection.DisableSelection
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.ash.reader.infrastructure.preference.LocalOpenLink import me.ash.reader.infrastructure.preference.LocalOpenLink
import me.ash.reader.infrastructure.preference.LocalOpenLinkSpecificBrowser import me.ash.reader.infrastructure.preference.LocalOpenLinkSpecificBrowser
@ -42,6 +49,7 @@ fun Content(
publishedDate: Date, publishedDate: Date,
listState: LazyListState, listState: LazyListState,
isLoading: Boolean, isLoading: Boolean,
contentPadding: PaddingValues = PaddingValues(),
onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null, onImageClick: ((imgUrl: String, altText: String) -> Unit)? = null,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -60,18 +68,18 @@ fun Content(
} }
} else { } else {
SelectionContainer { when (renderer) {
LazyColumn( ReadingRendererPreference.WebView -> {
Column(
modifier = modifier modifier = modifier
.padding(top = contentPadding.calculateTopPadding())
.fillMaxSize() .fillMaxSize()
.drawVerticalScrollbar(listState), .verticalScroll(rememberScrollState())
state = listState,
) { ) {
item {
// Top bar height // Top bar height
Spacer(modifier = Modifier.height(64.dp)) Spacer(modifier = Modifier.height(64.dp))
// padding // padding
Spacer(modifier = Modifier.height(22.dp))
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
@ -86,21 +94,50 @@ fun Content(
) )
} }
} }
Spacer(modifier = Modifier.height(22.dp))
}
when (renderer) {
ReadingRendererPreference.WebView -> {
item {
RYWebView( RYWebView(
modifier = Modifier.fillMaxSize(),
content = content, content = content,
refererDomain = link.extractDomain(), refererDomain = link.extractDomain(),
onImageClick = onImageClick, onImageClick = onImageClick,
) )
Spacer(modifier = Modifier.height(128.dp))
Spacer(modifier = Modifier.height(contentPadding.calculateBottomPadding()))
} }
} }
ReadingRendererPreference.NativeComponent -> { ReadingRendererPreference.NativeComponent -> {
SelectionContainer {
LazyColumn(
modifier = modifier
.fillMaxSize()
.drawVerticalScrollbar(listState),
state = listState,
) {
item {
// Top bar height
Spacer(modifier = Modifier.height(64.dp))
// padding
Spacer(modifier = Modifier.height(contentPadding.calculateTopPadding()))
Column(
modifier = Modifier
.padding(horizontal = 12.dp)
) {
DisableSelection {
Metadata(
feedName = feedName,
title = title,
author = author,
link = link,
publishedDate = publishedDate,
)
}
}
}
Reader( Reader(
context = context, context = context,
subheadUpperCase = subheadUpperCase.value, subheadUpperCase = subheadUpperCase.value,
@ -111,14 +148,16 @@ fun Content(
context.openURL(it, openLink, openLinkSpecificBrowser) context.openURL(it, openLink, openLinkSpecificBrowser)
} }
) )
}
}
item { item {
Spacer(modifier = Modifier.height(128.dp)) Spacer(modifier = Modifier.height(128.dp))
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) Spacer(modifier = Modifier.height(contentPadding.calculateBottomPadding()))
} }
} }
} }
}
}
} }
} }

View File

@ -65,7 +65,7 @@ private class ReaderNestedScrollConnection(
!enabled || available.y == 0f -> Offset.Zero !enabled || available.y == 0f -> Offset.Zero
// Scroll down to reduce the progress when the offset is currently pulled up, same for the opposite // Scroll down to reduce the progress when the offset is currently pulled up, same for the opposite
source == Drag -> { source == NestedScrollSource.UserInput -> {
Offset(0f, onPreScroll(available.y)) Offset(0f, onPreScroll(available.y))
} }
@ -78,7 +78,7 @@ private class ReaderNestedScrollConnection(
consumed: Offset, available: Offset, source: NestedScrollSource consumed: Offset, available: Offset, source: NestedScrollSource
): Offset = when { ): Offset = when {
!enabled -> Offset.Zero !enabled -> Offset.Zero
source == Drag -> Offset(0f, onPostScroll(available.y)) // Pull to load source == NestedScrollSource.UserInput -> Offset(0f, onPostScroll(available.y)) // Pull to load
else -> Offset.Zero else -> Offset.Zero
} }

View File

@ -181,7 +181,6 @@ fun ReadingPage(
) { ) {
Content( Content(
modifier = Modifier modifier = Modifier
.padding(paddings)
.pullToLoad( .pullToLoad(
state = state, state = state,
onScroll = { f -> onScroll = { f ->
@ -190,6 +189,7 @@ fun ReadingPage(
}, },
enabled = isPullToSwitchArticleEnabled enabled = isPullToSwitchArticleEnabled
), ),
contentPadding = paddings,
content = content.text ?: "", content = content.text ?: "",
feedName = feedName, feedName = feedName,
title = title.toString(), title = title.toString(),