Преглед изворни кода

[优化] Compose界面的顶栏模糊和渐变模糊,可在外观处体验。初始版本,需要重启应用,可能存在bug,仅在发现页可用

HapeLee пре 8 месеци
родитељ
комит
fb020356b6

+ 153 - 122
app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt

@@ -42,17 +42,13 @@ import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
 import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
 import androidx.compose.material.icons.filled.Check
 import androidx.compose.material.icons.filled.Check
 import androidx.compose.material.icons.filled.FilterList
 import androidx.compose.material.icons.filled.FilterList
-import androidx.compose.material.icons.filled.Grid4x4
 import androidx.compose.material.icons.filled.GridView
 import androidx.compose.material.icons.filled.GridView
 import androidx.compose.material.icons.filled.Warning
 import androidx.compose.material.icons.filled.Warning
 import androidx.compose.material.icons.outlined.FilterAlt
 import androidx.compose.material.icons.outlined.FilterAlt
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.FilterChip
 import androidx.compose.material3.FilterChip
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Scaffold
@@ -61,7 +57,9 @@ import androidx.compose.material3.Slider
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.Text
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.material3.pulltorefresh.PullToRefreshBox
+import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
+import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -82,28 +80,34 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.unit.sp
-import dev.chrisbanes.haze.HazeProgressive
 import dev.chrisbanes.haze.HazeState
 import dev.chrisbanes.haze.HazeState
-import dev.chrisbanes.haze.hazeEffect
-import dev.chrisbanes.haze.hazeSource
 import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
 import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.rule.ExploreKind
 import io.legado.app.data.entities.rule.ExploreKind
 import io.legado.app.model.BookShelfState
 import io.legado.app.model.BookShelfState
+import io.legado.app.ui.theme.responsiveHazeEffect
+import io.legado.app.ui.theme.responsiveHazeSource
 import io.legado.app.ui.widget.components.AnimatedTextLine
 import io.legado.app.ui.widget.components.AnimatedTextLine
 import io.legado.app.ui.widget.components.Cover
 import io.legado.app.ui.widget.components.Cover
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.SearchBarSection
 import io.legado.app.ui.widget.components.SearchBarSection
 import io.legado.app.ui.widget.components.button.AnimatedTextButton
 import io.legado.app.ui.widget.components.button.AnimatedTextButton
-import io.legado.app.ui.widget.components.button.SmallTopBarButton
+import io.legado.app.ui.widget.components.button.TopBarActionButton
+import io.legado.app.ui.widget.components.button.TopbarNavigationButton
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.card.TextCard
+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.GlassModalBottomSheet
 import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.Flow
 import org.koin.androidx.compose.koinViewModel
 import org.koin.androidx.compose.koinViewModel
 
 
 @SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
 @SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
+@OptIn(
+    ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class,
+    ExperimentalMaterial3ExpressiveApi::class
+)
 @Composable
 @Composable
 fun ExploreShowScreen(
 fun ExploreShowScreen(
     title: String,
     title: String,
@@ -133,6 +137,10 @@ fun ExploreShowScreen(
     val isGridMode = layoutState == 1
     val isGridMode = layoutState == 1
     var showGridCountSheet by remember { mutableStateOf(false) }
     var showGridCountSheet by remember { mutableStateOf(false) }
     val gridColumnCount by viewModel.gridCount.collectAsState()
     val gridColumnCount by viewModel.gridCount.collectAsState()
+
+    val isRefreshing by viewModel.isRefreshing.collectAsState()
+    val pullToRefreshState = rememberPullToRefreshState()
+
     val hazeState = remember { HazeState() }
     val hazeState = remember { HazeState() }
     val shouldLoadMoreList = remember {
     val shouldLoadMoreList = remember {
         derivedStateOf {
         derivedStateOf {
@@ -149,6 +157,7 @@ fun ExploreShowScreen(
             total > 0 && last >= total - 1
             total > 0 && last >= total - 1
         }
         }
     }
     }
+    var showMenu by remember { mutableStateOf(false) }
 
 
     LaunchedEffect(shouldLoadMoreList.value, isGridMode) {
     LaunchedEffect(shouldLoadMoreList.value, isGridMode) {
         if (!isGridMode && shouldLoadMoreList.value) viewModel.loadMore()
         if (!isGridMode && shouldLoadMoreList.value) viewModel.loadMore()
@@ -298,30 +307,130 @@ fun ExploreShowScreen(
 
 
     Scaffold(
     Scaffold(
         modifier = Modifier
         modifier = Modifier
-            .nestedScroll(scrollBehavior.nestedScrollConnection)
-            .hazeSource(hazeState),
+            .nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
         topBar = {
-            ExploreTopBar(
-                modifier = Modifier.hazeEffect(hazeState) {
-                    progressive =
-                        HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
+            GlassMediumFlexibleTopAppBar(
+                modifier = Modifier.responsiveHazeEffect(
+                    state = hazeState
+                ),
+                title = {
+                    AnimatedTextLine(
+                        selectedTitle ?: title,
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis
+                    )
+                },
+                colors = GlassTopAppBarDefaults.glassColors(),
+                navigationIcon = {
+                    TopbarNavigationButton(onClick = onBack)
+                },
+                actions = {
+                    Row(
+                        horizontalArrangement = Arrangement.End,
+                        modifier = Modifier.animateContentSize(tween(300))
+                    ) {
+                        TopBarActionButton(
+                            onClick = { showMenu = true },
+                            imageVector = Icons.Default.FilterList,
+                            contentDescription = "Filter"
+                        )
+
+                        TopBarActionButton(
+                            onClick = { showKindSheet = true },
+                            imageVector = Icons.Outlined.FilterAlt,
+                            contentDescription = "分类"
+                        )
+
+                        AnimatedVisibility(
+                            visible = isGridMode,
+                            enter = fadeIn(tween(300)),
+                            exit = fadeOut(tween(300))
+                        ) {
+                            TopBarActionButton(
+                                onClick = { showGridCountSheet = true },
+                                imageVector = Icons.AutoMirrored.Outlined.FormatListBulleted,
+                                contentDescription = "列数设置"
+                            )
+                        }
+                    }
+
+                    TopBarActionButton(
+                        onClick = { viewModel.setLayout() },
+                        imageVector = if (!isGridMode) Icons.AutoMirrored.Outlined.FormatListBulleted else Icons.Default.GridView,
+                        contentDescription = "切换布局"
+                    )
+
+                    RoundDropdownMenu(
+                        expanded = showMenu,
+                        onDismissRequest = { showMenu = false }
+                    ) {
+                        RoundDropdownMenuItem(
+                            text = { Text("全部显示") },
+                            onClick = {
+                                viewModel.setFilterState(BookFilterState.SHOW_ALL)
+                                showMenu = false
+                            },
+                            trailingIcon = {
+                                if (filterState == BookFilterState.SHOW_ALL)
+                                    Icon(Icons.Default.Check, null)
+                            }
+                        )
+
+                        RoundDropdownMenuItem(
+                            text = { Text("隐藏已在书架的同源书籍") },
+                            onClick = {
+                                viewModel.setFilterState(BookFilterState.HIDE_IN_SHELF)
+                                showMenu = false
+                            },
+                            trailingIcon = {
+                                if (filterState == BookFilterState.HIDE_IN_SHELF)
+                                    Icon(Icons.Default.Check, null)
+                            }
+                        )
+
+                        RoundDropdownMenuItem(
+                            text = { Text("隐藏已在书架的非同源书籍") },
+                            onClick = {
+                                viewModel.setFilterState(BookFilterState.HIDE_SAME_NAME_AUTHOR)
+                                showMenu = false
+                            },
+                            trailingIcon = {
+                                if (filterState == BookFilterState.HIDE_SAME_NAME_AUTHOR)
+                                    Icon(Icons.Default.Check, null)
+                            }
+                        )
+
+                        RoundDropdownMenuItem(
+                            text = { Text("只显示不在书架的书籍") },
+                            onClick = {
+                                viewModel.setFilterState(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
+                                showMenu = false
+                            },
+                            trailingIcon = {
+                                if (filterState == BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
+                                    Icon(Icons.Default.Check, null)
+                            }
+                        )
+                    }
                 },
                 },
-                title = selectedTitle ?: title,
-                filterState = filterState,
-                onBack = onBack,
-                onFilterSelect = viewModel::setFilterState,
-                onSelectKindClick = { showKindSheet = true },
-                onToggleGridMode = { viewModel.setLayout() },
-                isGridMode = isGridMode,
-                onGridCountClick = { showGridCountSheet = true },
                 scrollBehavior = scrollBehavior
                 scrollBehavior = scrollBehavior
             )
             )
         }
         }
     ) { paddingValues ->
     ) { paddingValues ->
-        Box(
-            modifier = Modifier
-                .fillMaxSize()
-                .padding(paddingValues)
+        PullToRefreshBox(
+            modifier = Modifier.fillMaxSize(),
+            isRefreshing = isRefreshing,
+            state = pullToRefreshState,
+            onRefresh = { viewModel.loadMore(isRefresh = true) },
+            indicator = {
+                PullToRefreshDefaults.LoadingIndicator(
+                    state = pullToRefreshState,
+                    isRefreshing = isRefreshing,
+                    modifier = Modifier
+                        .align(Alignment.TopCenter)
+                        .padding(top = paddingValues.calculateTopPadding())
+                )
+            }
         ) {
         ) {
             Crossfade(
             Crossfade(
                 targetState = isGridMode,
                 targetState = isGridMode,
@@ -331,9 +440,16 @@ fun ExploreShowScreen(
                 if (isGrid) {
                 if (isGrid) {
                     LazyVerticalGrid(
                     LazyVerticalGrid(
                         state = gridState,
                         state = gridState,
-                        modifier = Modifier.fillMaxSize(),
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .responsiveHazeSource(hazeState),
                         columns = GridCells.Fixed(gridColumnCount),
                         columns = GridCells.Fixed(gridColumnCount),
-                        contentPadding = PaddingValues(12.dp),
+                        contentPadding = PaddingValues(
+                            top = paddingValues.calculateTopPadding() + 12.dp,
+                            bottom = paddingValues.calculateBottomPadding() + 12.dp,
+                            start = 12.dp,
+                            end = 12.dp
+                        ),
                         horizontalArrangement = Arrangement.spacedBy(4.dp),
                         horizontalArrangement = Arrangement.spacedBy(4.dp),
                         verticalArrangement = Arrangement.spacedBy(8.dp)
                         verticalArrangement = Arrangement.spacedBy(8.dp)
                     ) {
                     ) {
@@ -360,8 +476,14 @@ fun ExploreShowScreen(
                     }
                     }
                 } else {
                 } else {
                     LazyColumn(
                     LazyColumn(
+                        modifier = Modifier
+                            .fillMaxSize()
+                            .responsiveHazeSource(hazeState),
                         state = listState,
                         state = listState,
-                        contentPadding = PaddingValues(bottom = 16.dp)
+                        contentPadding = PaddingValues(
+                            top = paddingValues.calculateTopPadding(),
+                            bottom = paddingValues.calculateBottomPadding() + 16.dp
+                        )
                     ) {
                     ) {
                         items(
                         items(
                             items = books,
                             items = books,
@@ -419,97 +541,6 @@ fun KindGridItem(
     )
     )
 }
 }
 
 
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
-@Composable
-fun ExploreTopBar(
-    modifier: Modifier = Modifier,
-    title: String,
-    filterState: BookFilterState,
-    onBack: () -> Unit,
-    onFilterSelect: (BookFilterState) -> Unit,
-    onSelectKindClick: () -> Unit,
-    onToggleGridMode: () -> Unit,
-    isGridMode: Boolean,
-    onGridCountClick: () -> Unit,
-    scrollBehavior: TopAppBarScrollBehavior? = null
-) {
-    var showMenu by remember { mutableStateOf(false) }
-
-    GlassMediumFlexibleTopAppBar(
-        modifier = modifier,
-        title = { AnimatedTextLine(title, maxLines = 1, overflow = TextOverflow.Ellipsis) },
-        navigationIcon = {
-            SmallTopBarButton(onClick = onBack)
-        },
-        actions = {
-            Row(
-                horizontalArrangement = Arrangement.End,
-                modifier = Modifier.animateContentSize(tween(300))
-            ) {
-                IconButton(onClick = { showMenu = true }) {
-                    Icon(Icons.Default.FilterList, contentDescription = "Filter")
-                }
-                IconButton(onClick = { onSelectKindClick() }) {
-                    Icon(Icons.Outlined.FilterAlt, contentDescription = "分类")
-                }
-                AnimatedVisibility(
-                    visible = isGridMode,
-                    enter = fadeIn(tween(300)),
-                    exit = fadeOut(tween(300))
-                ) {
-                    IconButton(onClick = onGridCountClick) {
-                        Icon(Icons.Default.Grid4x4, contentDescription = "列数设置")
-                    }
-                }
-            }
-            IconButton(onClick = { onToggleGridMode() }) {
-                Icon(
-                    imageVector = if (!isGridMode) Icons.AutoMirrored.Outlined.FormatListBulleted else Icons.Default.GridView,
-                    contentDescription = "切换布局"
-                )
-            }
-            DropdownMenu(
-                expanded = showMenu,
-                onDismissRequest = { showMenu = false }
-            ) {
-                DropdownMenuItem(
-                    text = { Text("全部显示") },
-                    onClick = {
-                        onFilterSelect(BookFilterState.SHOW_ALL)
-                        showMenu = false
-                    },
-                    trailingIcon = { if(filterState == BookFilterState.SHOW_ALL) Icon(Icons.Default.Check, null) }
-                )
-                DropdownMenuItem(
-                    text = { Text("隐藏已在书架的同源书籍") },
-                    onClick = {
-                        onFilterSelect(BookFilterState.HIDE_IN_SHELF)
-                        showMenu = false
-                    },
-                    trailingIcon = { if(filterState == BookFilterState.HIDE_IN_SHELF) Icon(Icons.Default.Check, null) }
-                )
-                DropdownMenuItem(
-                    text = { Text("隐藏已在书架的非同源书籍") },
-                    onClick = {
-                        onFilterSelect(BookFilterState.HIDE_SAME_NAME_AUTHOR)
-                        showMenu = false
-                    },
-                    trailingIcon = { if(filterState == BookFilterState.HIDE_SAME_NAME_AUTHOR) Icon(Icons.Default.Check, null) }
-                )
-                DropdownMenuItem(
-                    text = { Text("只显示不在书架的书籍") },
-                    onClick = {
-                        onFilterSelect(BookFilterState.SHOW_NOT_IN_SHELF_ONLY)
-                        showMenu = false
-                    },
-                    trailingIcon = { if(filterState == BookFilterState.SHOW_NOT_IN_SHELF_ONLY) Icon(Icons.Default.Check, null) }
-                )
-            }
-        },
-        scrollBehavior = scrollBehavior
-    )
-}
-
 @Composable
 @Composable
 fun ExploreBookItem(
 fun ExploreBookItem(
     book: SearchBook,
     book: SearchBook,

+ 44 - 0
app/src/main/java/io/legado/app/ui/theme/HazeStyle.kt

@@ -0,0 +1,44 @@
+package io.legado.app.ui.theme
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import dev.chrisbanes.haze.HazeProgressive
+import dev.chrisbanes.haze.HazeState
+import dev.chrisbanes.haze.hazeEffect
+import dev.chrisbanes.haze.hazeSource
+import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
+import dev.chrisbanes.haze.materials.HazeMaterials
+import io.legado.app.ui.config.themeConfig.ThemeConfig
+
+/**
+ * 自动感知全局配置的 HazeSource
+ */
+fun Modifier.responsiveHazeSource(state: HazeState): Modifier = this.then(
+    if (ThemeConfig.enableBlur) Modifier.hazeSource(state) else Modifier
+)
+
+/**
+ * 自动感知全局配置的 HazeEffect
+ */
+@OptIn(ExperimentalHazeMaterialsApi::class)
+@Composable
+fun Modifier.responsiveHazeEffect(
+    state: HazeState
+): Modifier {
+    val enableBlur = ThemeConfig.enableBlur
+    val enableProgressiveBlur = ThemeConfig.enableProgressiveBlur
+
+    if (!enableBlur) return this
+
+    return this.hazeEffect(
+        state = state,
+        style = HazeMaterials.ultraThin()
+    ) {
+        if (enableProgressiveBlur) {
+            progressive = HazeProgressive.verticalGradient(
+                startIntensity = 1f,
+                endIntensity = 0f
+            )
+        }
+    }
+}