控件优化和文件夹崩溃修复

This commit is contained in:
HapeLee
2026-05-15 13:32:19 +08:00
parent af84848a58
commit 584ada1819
9 changed files with 157 additions and 100 deletions
@@ -4,8 +4,8 @@ import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.animateContentSize
@@ -42,7 +42,6 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Slider
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -61,29 +60,30 @@ import androidx.compose.ui.unit.dp
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.domain.model.BookShelfState
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.theme.responsiveHazeSource
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.book.SearchBookGridItem
import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.button.AnimatedTextButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.explore.calculateExploreKindRows
import io.legado.app.ui.widget.components.explore.ExploreKindMultiTypeItem
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
import io.legado.app.ui.widget.components.explore.calculateExploreKindRows
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.book.SearchBookGridItem
import io.legado.app.ui.widget.components.book.SearchBookListItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
@@ -410,6 +410,7 @@ fun ExploreShowScreen(
modifier = Modifier.fillMaxSize(),
isRefreshing = isRefreshing,
onRefresh = { viewModel.loadMore(isRefresh = true) },
topPadding = paddingValues.calculateTopPadding()
) {
Crossfade(
targetState = isGridMode,
@@ -12,10 +12,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
@@ -32,21 +32,19 @@ import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.outlined.Book
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material3.ExperimentalMaterial3Api
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -54,24 +52,26 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.constant.AppConst
import io.legado.app.ui.config.importBookConfig.ImportBookConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
import io.legado.app.ui.config.importBookConfig.ImportBookConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.startActivityForBook
import io.legado.app.utils.toastOnUi
@@ -197,10 +197,13 @@ private fun ImportBookContent(
.padding(paddingValues),
isRefreshing = state.isLoading,
onRefresh = onScanFolder,
topPadding = paddingValues.calculateTopPadding()
) {
when {
state.items.isEmpty() && state.isLoading -> {
AppCircularProgressIndicator(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center))
AppCircularProgressIndicator(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center))
}
state.items.isEmpty() -> {
@@ -12,12 +12,12 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
@@ -41,11 +41,9 @@ import androidx.compose.material.icons.outlined.Book
import androidx.compose.material.icons.outlined.CloudSync
import androidx.compose.material.icons.outlined.Description
import androidx.compose.material3.ExperimentalMaterial3Api
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -71,6 +69,7 @@ import io.legado.app.lib.dialogs.selector
import io.legado.app.model.remote.RemoteBook
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppRadioButton
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.EmptyMessage
@@ -80,15 +79,15 @@ import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.button.SmallIconButton
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.startActivityForBook
import io.legado.app.utils.toastOnUi
@@ -323,10 +322,13 @@ fun RemoteBookScreen(
.padding(paddingValues),
isRefreshing = uiState.isLoading,
onRefresh = { viewModel.dispatch(RemoteBookIntent.Refresh) },
topPadding = paddingValues.calculateTopPadding()
) {
if (uiState.items.isEmpty()) {
if (uiState.isLoading) {
AppCircularProgressIndicator(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.Center))
AppCircularProgressIndicator(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center))
} else {
EmptyMessage(
modifier = Modifier
@@ -23,8 +23,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
import androidx.compose.material.icons.filled.Book
@@ -37,7 +37,6 @@ import androidx.compose.material.icons.filled.Timeline
import androidx.compose.material.icons.outlined.Book
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ExtendedFloatingActionButton
@@ -45,7 +44,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MediumFlexibleTopAppBar
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -82,29 +80,31 @@ import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LocalHazeState
import io.legado.app.ui.theme.ProvideThemeOverride
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.ThemeOverrideState
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.rememberImageSeedColor
import io.legado.app.ui.theme.rememberThemeOverride
import io.legado.app.ui.theme.responsiveHazeEffectFixedStyle
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.text.AnimatedTextLine
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarScrollBehavior
import io.legado.app.ui.widget.components.topbar.M3GlassScrollBehavior
import io.legado.app.ui.widget.components.topbar.MiuixGlassScrollBehavior
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import kotlinx.coroutines.delay
import org.koin.compose.koinInject
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
@@ -192,6 +192,7 @@ private fun BookInfoScreenContent(
modifier = Modifier.fillMaxSize(),
isRefreshing = state.isTocLoading,
onRefresh = { onIntent(BookInfoIntent.MenuAction(BookInfoMenuAction.Refresh)) },
topPadding = paddingValues.calculateTopPadding()
) {
LazyColumn(
state = listState,
@@ -692,7 +693,9 @@ private fun BookInfoHeader(
author = book.author,
path = book.getDisplayCover(),
sourceOrigin = book.origin,
modifier = Modifier.width(112.dp).aspectRatio(5f / 7f),
modifier = Modifier
.width(112.dp)
.aspectRatio(5f / 7f),
showLoadingPlaceholder = sharedCoverKey == null,
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
@@ -13,22 +13,19 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.clickable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -59,7 +56,6 @@ import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.ImportExport
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Save
@@ -67,16 +63,12 @@ import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material.icons.filled.UploadFile
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material3.ExperimentalMaterial3Api
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.FloatingToolbarDefaults.ScreenOffset
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -87,26 +79,23 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.zIndex
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.BaseRuleEvent
import io.legado.app.ui.about.AppLogSheet
import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.about.AppLogSheet
import io.legado.app.ui.book.group.GroupEditSheet
import io.legado.app.ui.book.info.GroupSelectSheet
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
@@ -114,33 +103,31 @@ import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.bookCoverSharedElementKey
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveContentPaddingBookshelf
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SelectionActions
import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
@@ -173,20 +160,6 @@ fun BookshelfScreen(
ReportDrawnWhen { uiState.groups.isNotEmpty() }
val activeOverlay = uiState.activeOverlay
val showGroupMenu = activeOverlay == BookshelfOverlay.GroupMenu
val isEditMode = uiState.isEditMode
val selectedBookUrls = uiState.selectedBookUrls
val isInFolderRoot = uiState.isInFolderRoot
val bookGroupStyle = uiState.bookGroupStyle
val transitionState = remember { SeekableTransitionState(isInFolderRoot) }
LaunchedEffect(isInFolderRoot) {
if (transitionState.targetState != isInFolderRoot) {
transitionState.animateTo(isInFolderRoot)
}
}
val clipboardManager = LocalClipboard.current
val snackbarHostState = remember { SnackbarHostState() }
@@ -277,6 +250,21 @@ fun BookshelfScreen(
return
}
val activeOverlay = uiState.activeOverlay
val showGroupMenu = activeOverlay == BookshelfOverlay.GroupMenu
val isEditMode = uiState.isEditMode
val selectedBookUrls = uiState.selectedBookUrls
val isInFolderRoot = uiState.isInFolderRoot
val bookGroupStyle = uiState.bookGroupStyle
val transitionState = remember { SeekableTransitionState(isInFolderRoot) }
val folderTransition = rememberTransition(transitionState, label = "FolderTransition")
LaunchedEffect(isInFolderRoot) {
if (transitionState.targetState != isInFolderRoot) {
transitionState.animateTo(isInFolderRoot)
}
}
val pagerState = rememberPagerState(
initialPage = uiState.selectedGroupIndex,
pageCount = { uiState.groups.size }
@@ -742,25 +730,36 @@ fun BookshelfScreen(
isRefreshing = uiState.isRefreshing,
onRefresh = { viewModel.refreshBooks(uiState.items) },
enabled = pullToRefreshEnabled,
topPadding = paddingValues.calculateTopPadding()
) {
val transition = rememberTransition(
transitionState,
label = "FolderTransition"
)
transition.AnimatedContent(
folderTransition.AnimatedContent(
transitionSpec = {
val easing = FastOutSlowInEasing
val duration = 480
if (targetState) {
(fadeIn(animationSpec = tween(duration, easing = easing)) +
scaleIn(initialScale = 1.05f, animationSpec = tween(duration, easing = easing)))
.togetherWith(fadeOut(animationSpec = tween(duration, easing = easing)) +
scaleOut(targetScale = 0.95f, animationSpec = tween(duration, easing = easing)))
(fadeIn(animationSpec = tween(duration, easing = easing)) +
scaleIn(
initialScale = 1.2f,
animationSpec = tween(duration, easing = easing)
))
.togetherWith(fadeOut(animationSpec = tween(duration, easing = easing)) +
scaleOut(
targetScale = 0.8f,
animationSpec = tween(duration, easing = easing)
)
)
} else {
(fadeIn(animationSpec = tween(duration, easing = easing)) +
scaleIn(initialScale = 0.95f, animationSpec = tween(duration, easing = easing)))
.togetherWith(fadeOut(animationSpec = tween(duration, easing = easing)) +
scaleOut(targetScale = 1.05f, animationSpec = tween(duration, easing = easing)))
(fadeIn(animationSpec = tween(duration, easing = easing)) +
scaleIn(
initialScale = 0.8f,
animationSpec = tween(duration, easing = easing)
))
.togetherWith(fadeOut(animationSpec = tween(duration, easing = easing)) +
scaleOut(
targetScale = 1.2f,
animationSpec = tween(duration, easing = easing)
)
)
}
}
) { isRoot ->
@@ -67,16 +67,15 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -160,7 +159,11 @@ class BookshelfViewModel(
val events = _eventChannel.receiveAsFlow()
val groupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowShow()
.onEach { isInitialLoadingFlow.value = false }
.onEach {
if (it.isNotEmpty()) {
isInitialLoadingFlow.value = false
}
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val allGroupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowAll()
@@ -477,6 +480,10 @@ class BookshelfViewModel(
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())
init {
viewModelScope.launch {
delay(500)
isInitialLoadingFlow.value = false
}
viewModelScope.launch {
FlowEventBus.with<Unit>(EventBus.UP_ALL_BOOK_TOC).collect {
upAllBookToc()
@@ -33,7 +33,6 @@ import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridS
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import io.legado.app.ui.widget.components.AppPullToRefresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -59,16 +58,15 @@ import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssSource
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.AppPullToRefresh
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.utils.toastOnUi
import io.legado.app.ui.config.themeConfig.ThemeConfig
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import org.koin.compose.koinInject
private enum class RssArticleLayout {
@@ -136,6 +134,7 @@ fun RssArticlesPage(
isRefreshing = loadState.isRefreshing,
onRefresh = { rssSource?.let(viewModel::loadArticles) },
modifier = modifier.fillMaxSize(),
topPadding = paddingValues.calculateTopPadding()
) {
when (layout) {
RssArticleLayout.List, RssArticleLayout.LargeCard -> {
@@ -392,7 +391,9 @@ private fun RssArticleItem(
}
RssArticleLayout.LargeCard -> {
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(8.dp)) {
RssArticleImage(
article = article,
showPlaceholder = false,
@@ -421,7 +422,9 @@ private fun RssArticleItem(
}
RssArticleLayout.GridCard -> {
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(8.dp)) {
RssArticleImage(
article = article,
showPlaceholder = true,
@@ -450,7 +453,9 @@ private fun RssArticleItem(
}
RssArticleLayout.Waterfall -> {
Column(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) {
Column(modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)) {
RssArticleImage(
article = article,
showPlaceholder = false,
@@ -1,6 +1,7 @@
package io.legado.app.ui.widget.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -8,10 +9,18 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme.composeEngine
import io.legado.app.ui.theme.ThemeResolver
import kotlinx.coroutines.delay
import top.yukonga.miuix.kmp.basic.PullToRefresh as MiuixPullToRefresh
import top.yukonga.miuix.kmp.basic.rememberPullToRefreshState as miuixRememberPullToRefreshState
@@ -22,20 +31,38 @@ fun AppPullToRefresh(
onRefresh: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
topPadding: Dp = 0.dp,
content: @Composable () -> Unit,
) {
if (ThemeResolver.isMiuixEngine(composeEngine)) {
if (enabled) {
// Let the miuix library handle gesture/animation, but skip the persistent
// Refreshing state that blocks all scroll events.
// localIsRefreshing is true only briefly so the indicator shows then hides,
// while the actual refresh runs independently.
var localIsRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(localIsRefreshing) {
if (localIsRefreshing) {
delay(300)
localIsRefreshing = false
}
}
MiuixPullToRefresh(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
isRefreshing = localIsRefreshing,
onRefresh = {
localIsRefreshing = true
onRefresh()
},
modifier = modifier,
contentPadding = PaddingValues(top = topPadding + 16.dp),
pullToRefreshState = miuixRememberPullToRefreshState(),
refreshTexts = listOf(
"下拉刷新",
"松开刷新",
"正在刷新",
"刷新成功"
"正在刷新"
)
) {
content()
@@ -57,7 +84,9 @@ fun AppPullToRefresh(
PullToRefreshDefaults.LoadingIndicator(
state = state,
isRefreshing = isRefreshing,
modifier = Modifier.align(Alignment.TopCenter),
modifier = Modifier
.padding(top = topPadding)
.align(Alignment.TopCenter),
)
}
) {