diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 25a85a582..0c0be999a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -73,7 +73,15 @@ "WebFetch(domain:compose-miuix-ui.github.io)", "Bash(Select-Object -Last 60)", "PowerShell(.\\\\gradlew.bat :app:compileAppDebugKotlin 2>&1 | Select-Object -Last 60)", - "Bash(New-Item *)" + "Bash(New-Item *)", + "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\*miuix*\")", + "Bash(findstr /i \"jar\\\\|aar\")", + "Bash(dir /s /b \"%USERPROFILE%\\\\.gradle\\\\caches\\\\transforms-*\\\\*PullToRefresh*\")", + "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\build\\\\*PullToRefresh*\")", + "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\")", + "Bash(findstr /i \"miuix.*\\\\.kt\")", + "Bash(findstr /v \"\\\\\\\\build\\\\\\\\\")", + "Bash(dir /s /b \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\.gradle\\\\*miuix*\")" ] } } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt index b4ea46114..620444d58 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt index 115982a25..80488b2d9 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/local/ImportBookScreen.kt @@ -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() -> { diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt index 63544bace..d84aeff04 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookScreen.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt index b6e8fcb56..f014255b1 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index 15a5e863d..b2499e477 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -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 -> diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 1f8cbed90..13463f529 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -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> = bookGroupRepository.flowShow() - .onEach { isInitialLoadingFlow.value = false } + .onEach { + if (it.isNotEmpty()) { + isInitialLoadingFlow.value = false + } + } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) val allGroupsFlow: StateFlow> = 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(EventBus.UP_ALL_BOOK_TOC).collect { upAllBookToc() diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt index e134f9b41..83fc13e89 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/AppPullToRefresh.kt b/app/src/main/java/io/legado/app/ui/widget/components/AppPullToRefresh.kt index 4d3b696dc..c4a300acc 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/AppPullToRefresh.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/AppPullToRefresh.kt @@ -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), ) } ) {