Browse Source

优化文件夹与书架切换时的动画与跟随手势

HapeLee 6 months ago
parent
commit
56d82d5e25

+ 52 - 8
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -5,18 +5,29 @@ import android.content.res.Configuration
 import android.net.Uri
 import android.net.Uri
 import androidx.activity.compose.BackHandler
 import androidx.activity.compose.BackHandler
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.compose.PredictiveBackHandler
 import androidx.activity.compose.ReportDrawnWhen
 import androidx.activity.compose.ReportDrawnWhen
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideOutVertically
 import androidx.compose.animation.slideOutVertically
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionScope
 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.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement
@@ -101,6 +112,7 @@ import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.book.group.GroupEditSheet
 import io.legado.app.ui.book.group.GroupEditSheet
 import io.legado.app.ui.book.info.GroupSelectSheet
 import io.legado.app.ui.book.info.GroupSelectSheet
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
+import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.main.bookCoverSharedElementKey
 import io.legado.app.ui.main.bookCoverSharedElementKey
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.ThemeResolver
@@ -133,6 +145,7 @@ import io.legado.app.ui.widget.components.text.AppText
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableSet
 import kotlinx.collections.immutable.ImmutableSet
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.persistentListOf
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
 import org.koin.androidx.compose.koinViewModel
@@ -141,7 +154,8 @@ import sh.calvin.reorderable.rememberReorderableLazyGridState
 
 
 @OptIn(
 @OptIn(
     ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
     ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
-    ExperimentalMaterial3ExpressiveApi::class, ExperimentalSharedTransitionApi::class
+    ExperimentalMaterial3ExpressiveApi::class, ExperimentalSharedTransitionApi::class,
+    ExperimentalAnimationApi::class
 )
 )
 @Composable
 @Composable
 fun BookshelfScreen(
 fun BookshelfScreen(
@@ -167,6 +181,13 @@ fun BookshelfScreen(
     val isInFolderRoot = uiState.isInFolderRoot
     val isInFolderRoot = uiState.isInFolderRoot
     val bookGroupStyle = uiState.bookGroupStyle
     val bookGroupStyle = uiState.bookGroupStyle
 
 
+    val transitionState = remember { SeekableTransitionState(isInFolderRoot) }
+    LaunchedEffect(isInFolderRoot) {
+        if (transitionState.targetState != isInFolderRoot) {
+            transitionState.animateTo(isInFolderRoot)
+        }
+    }
+
     val clipboardManager = LocalClipboard.current
     val clipboardManager = LocalClipboard.current
     val snackbarHostState = remember { SnackbarHostState() }
     val snackbarHostState = remember { SnackbarHostState() }
 
 
@@ -327,9 +348,15 @@ fun BookshelfScreen(
 
 
     val currentGroupName by remember { derivedStateOf { uiState.currentGroupName } }
     val currentGroupName by remember { derivedStateOf { uiState.currentGroupName } }
 
 
-    if (bookGroupStyle == 2 && !isInFolderRoot && !isEditMode) {
-        BackHandler {
+    PredictiveBackHandler(enabled = bookGroupStyle == 2 && !isInFolderRoot && !isEditMode) { progress ->
+        try {
+            progress.collect { backEvent ->
+                transitionState.seekTo(backEvent.progress, targetState = true)
+            }
             viewModel.setInFolderRoot(true)
             viewModel.setInFolderRoot(true)
+            transitionState.animateTo(true)
+        } catch (e: CancellationException) {
+            transitionState.animateTo(false)
         }
         }
     }
     }
 
 
@@ -720,9 +747,26 @@ fun BookshelfScreen(
                     enabled = pullToRefreshEnabled
                     enabled = pullToRefreshEnabled
                 )
                 )
         ) {
         ) {
-            AnimatedContent(
-                targetState = isInFolderRoot,
+            val transition = rememberTransition(
+                transitionState, 
                 label = "FolderTransition"
                 label = "FolderTransition"
+            )
+            transition.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)))
+                    } 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)))
+                    }
+                }
             ) { isRoot ->
             ) { isRoot ->
                 if (bookGroupStyle == 2 && isRoot && !isUsingStandaloneSearchGroup) {
                 if (bookGroupStyle == 2 && isRoot && !isUsingStandaloneSearchGroup) {
                     val folderColumns =
                     val folderColumns =
@@ -739,7 +783,7 @@ fun BookshelfScreen(
                             ),
                             ),
                         contentPadding = adaptiveContentPaddingBookshelf(
                         contentPadding = adaptiveContentPaddingBookshelf(
                             top = paddingValues.calculateTopPadding(),
                             top = paddingValues.calculateTopPadding(),
-                            bottom = 120.dp,
+                            bottom = if (ThemeConfig.useFloatingBottomBar) 120.dp else 8.dp,
                             horizontal = if (isGridMode) 8.dp else 4.dp
                             horizontal = if (isGridMode) 8.dp else 4.dp
                         ),
                         ),
                         verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
                         verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
@@ -830,7 +874,7 @@ fun BookshelfScreen(
                                         if (this != null) Modifier.skipToLookaheadSize() else Modifier
                                         if (this != null) Modifier.skipToLookaheadSize() else Modifier
                                     }
                                     }
                                 ),
                                 ),
-                            beyondViewportPageCount = 2,
+                            beyondViewportPageCount = 1,
                             key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
                             key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
                         ) { pageIndex ->
                         ) { pageIndex ->
                             val group = uiState.groups.getOrNull(pageIndex)
                             val group = uiState.groups.getOrNull(pageIndex)
@@ -1310,7 +1354,7 @@ fun BookshelfPage(
                 ),
                 ),
             contentPadding = adaptiveContentPaddingBookshelf(
             contentPadding = adaptiveContentPaddingBookshelf(
                 top = paddingValues.calculateTopPadding(),
                 top = paddingValues.calculateTopPadding(),
-                bottom = 120.dp,
+                bottom = if (ThemeConfig.useFloatingBottomBar) 120.dp else 8.dp,
                 horizontal = if (isGridMode) 8.dp else 4.dp
                 horizontal = if (isGridMode) 8.dp else 4.dp
             ),
             ),
             verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
             verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),