فهرست منبع

feat: 优化主题管理

HapeLee 3 ماه پیش
والد
کامیت
3e92709d27

+ 96 - 143
app/src/main/java/io/legado/app/ui/config/themeManage/ThemeManageScreen.kt

@@ -1,20 +1,20 @@
 package io.legado.app.ui.config.themeManage
 
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
-import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material.icons.filled.Edit
@@ -22,19 +22,20 @@ import androidx.compose.material.icons.filled.Share
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.help.config.SavedTheme
-import io.legado.app.help.config.ThemePackageManager
+import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.AppTextField
+import io.legado.app.ui.widget.components.SearchBar
 import io.legado.app.ui.widget.components.SplicedColumnGroup
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.series.SmallPlainButton
@@ -54,6 +55,11 @@ fun ThemeManageScreen(
 ) {
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     val savedThemes = state.savedThemes
+    val filteredSavedThemes = remember(savedThemes, state.searchQuery) {
+        savedThemes.filter { theme ->
+            theme.name.contains(state.searchQuery, ignoreCase = true)
+        }
+    }
 
     AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -67,17 +73,15 @@ fun ThemeManageScreen(
             )
         }
     ) { paddingValues ->
-        LazyVerticalGrid(
-            columns = GridCells.Fixed(2),
+        LazyColumn(
             modifier = Modifier.fillMaxSize(),
             contentPadding = adaptiveContentPadding(
                 top = paddingValues.calculateTopPadding(),
                 bottom = 120.dp
             ),
-            horizontalArrangement = Arrangement.spacedBy(8.dp),
             verticalArrangement = Arrangement.spacedBy(8.dp)
         ) {
-            item(span = { GridItemSpan(maxLineSpan) }) {
+            item {
                 SplicedColumnGroup {
                     ClickableSettingItem(
                         title = stringResource(R.string.theme_manage_save_current),
@@ -114,7 +118,18 @@ fun ThemeManageScreen(
             }
 
             if (savedThemes.isNotEmpty()) {
-                item(span = { GridItemSpan(maxLineSpan) }) {
+                item {
+                    SearchBar(
+                        query = state.searchQuery,
+                        onQueryChange = {
+                            onIntent(ThemeManageIntent.UpdateSearchQuery(it))
+                        },
+                        placeholder = stringResource(R.string.search_placeholder),
+                        autoFocus = false,
+                    )
+                }
+
+                item {
                     AppText(
                         text = stringResource(R.string.theme_manage_saved_themes),
                         style = MaterialTheme.typography.titleSmall,
@@ -123,7 +138,7 @@ fun ThemeManageScreen(
                     )
                 }
 
-                items(savedThemes, key = { it.name }) { theme ->
+                items(filteredSavedThemes, key = { it.name }) { theme ->
                     SavedThemeItem(
                         theme = theme,
                         onApply = { onIntent(ThemeManageIntent.OpenApplyDialog(theme)) },
@@ -229,150 +244,88 @@ private fun SavedThemeItem(
     onExport: () -> Unit,
     onDelete: () -> Unit
 ) {
+    val data = theme.data
+    val lightPrimary = data.themeColor.takeIf { it != 0 }?.let(::Color)
+        ?: data.cPrimary.takeIf { it != 0 }?.let(::Color)
+        ?: MaterialTheme.colorScheme.primary
+    val darkPrimary = data.themeColorNight.takeIf { it != 0 }?.let(::Color)
+        ?: data.cNPrimary.takeIf { it != 0 }?.let(::Color)
+        ?: lightPrimary
+    val lightSecondary = data.secondaryThemeColor.takeIf { it != 0 }?.let(::Color)
+        ?: lightPrimary
+    val darkSecondary = data.secondaryThemeColorNight.takeIf { it != 0 }?.let(::Color)
+        ?: lightSecondary
+    val lightSurface = data.themeBackgroundColor.takeIf { it != 0 }?.let(::Color)
+        ?: Color(0xFFF7F2FA)
+    val darkSurface = data.themeBackgroundColorNight.takeIf { it != 0 }?.let(::Color)
+        ?: if (data.enableDeepPersonalization && data.themeBackgroundColor != 0) {
+            Color(data.themeBackgroundColor)
+        } else if (data.isPureBlack) {
+            Color.Black
+        } else {
+            Color(0xFF1C1B1F)
+        }
+
     GlassCard(
         onClick = onApply,
         modifier = Modifier.fillMaxWidth()
     ) {
-        Column(
-            Modifier.fillMaxWidth()
+        Row(
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(horizontal = 12.dp, vertical = 8.dp),
+            verticalAlignment = Alignment.CenterVertically,
         ) {
-            val lightPrimary = if (theme.data.themeColor != 0) Color(theme.data.themeColor)
-            else if (theme.data.cPrimary != 0) Color(theme.data.cPrimary)
-            else MaterialTheme.colorScheme.primary
-
-            val darkPrimary = if (theme.data.themeColorNight != 0) {
-                Color(theme.data.themeColorNight)
-            } else if (theme.data.cNPrimary != 0) {
-                Color(theme.data.cNPrimary)
-            } else {
-                lightPrimary
-            }
-
-            val lightBg =
-                if (theme.data.themeBackgroundColor != 0) Color(theme.data.themeBackgroundColor)
-                else Color(0xFFF7F2FA)
-
-            val darkBg = if (theme.data.themeBackgroundColorNight != 0) {
-                Color(theme.data.themeBackgroundColorNight)
-            } else if (theme.data.enableDeepPersonalization &&
-                theme.data.themeBackgroundColor != 0
-            ) {
-                Color(theme.data.themeBackgroundColor)
-            } else if (theme.data.isPureBlack) {
-                Color.Black
-            } else {
-                Color(0xFF1C1B1F)
-            }
             Column(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .aspectRatio(1.4f)
-            ) {
-                // 日间行
-                Box(
-                    modifier = Modifier
-                        .weight(1f)
-                        .fillMaxWidth()
-                        .background(lightBg)
-                ) {
-                    AppText(
-                        text = stringResource(R.string.theme_manage_preview_day),
-                        style = MaterialTheme.typography.labelMediumEmphasized,
-                        color = if (theme.data.primaryTextColor != 0) Color(theme.data.primaryTextColor).copy(
-                            alpha = 0.6f
-                        )
-                        else Color.Black.copy(alpha = 0.5f),
-                        modifier = Modifier
-                            .align(Alignment.CenterStart)
-                            .padding(start = 12.dp)
-                    )
-
-                    Box(
-                        modifier = Modifier
-                            .align(Alignment.CenterEnd)
-                            .padding(end = 16.dp)
-                            .size(28.dp)
-                            .shadow(
-                                elevation = 6.dp,
-                                shape = RoundedCornerShape(6.dp),
-                                ambientColor = lightPrimary,
-                                spotColor = lightPrimary
-                            )
-                            .background(lightPrimary, RoundedCornerShape(6.dp))
-                    )
-                }
-
-                // 夜间行
-                Box(
-                    modifier = Modifier
-                        .weight(1f)
-                        .fillMaxWidth()
-                        .background(darkBg)
-                ) {
-                    AppText(
-                        text = stringResource(R.string.theme_manage_preview_night),
-                        style = MaterialTheme.typography.labelMediumEmphasized,
-                        color = if (theme.data.primaryTextColorNight != 0) {
-                            Color(theme.data.primaryTextColorNight).copy(alpha = 0.6f)
-                        } else if (theme.data.enableDeepPersonalization &&
-                            theme.data.primaryTextColor != 0
-                        ) {
-                            Color(theme.data.primaryTextColor).copy(alpha = 0.6f)
-                        } else {
-                            Color.White.copy(alpha = 0.5f)
-                        },
-                        modifier = Modifier
-                            .align(Alignment.CenterStart)
-                            .padding(start = 12.dp)
-                    )
-
-                    Box(
-                        modifier = Modifier
-                            .align(Alignment.CenterEnd)
-                            .padding(end = 16.dp)
-                            .size(28.dp)
-                            .shadow(
-                                elevation = 6.dp,
-                                shape = RoundedCornerShape(6.dp),
-                                ambientColor = darkPrimary,
-                                spotColor = darkPrimary
-                            )
-                            .background(darkPrimary, RoundedCornerShape(6.dp))
-                    )
-                }
-            }
-
-            Column(
-                modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp)
+                modifier = Modifier.weight(1f)
             ) {
                 AppText(
                     text = theme.name,
-                    style = MaterialTheme.typography.titleSmall,
+                    style = LegadoTheme.typography.titleSmallEmphasized,
                     maxLines = 1
                 )
-
                 Row(
-                    modifier = Modifier.fillMaxWidth(),
-                    horizontalArrangement = Arrangement.End,
-                    verticalAlignment = Alignment.CenterVertically
+                    modifier = Modifier.padding(top = 4.dp),
+                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                 ) {
-                    SmallPlainButton(
-                        onClick = onEdit,
-                        icon = Icons.Default.Edit,
-                        contentDescription = stringResource(R.string.edit)
-                    )
-                    SmallPlainButton(
-                        onClick = onExport,
-                        icon = Icons.Default.Share,
-                        contentDescription = stringResource(R.string.share)
-                    )
-                    SmallPlainButton(
-                        onClick = onDelete,
-                        icon = Icons.Default.Delete,
-                        contentDescription = stringResource(R.string.delete)
-                    )
+                    ThemeColorGroup(lightPrimary, lightSecondary, lightSurface)
+                    Spacer(Modifier.width(8.dp))
+                    ThemeColorGroup(darkPrimary, darkSecondary, darkSurface)
                 }
             }
+            SmallPlainButton(
+                onClick = onEdit,
+                icon = Icons.Default.Edit,
+                contentDescription = stringResource(R.string.edit)
+            )
+            SmallPlainButton(
+                onClick = onExport,
+                icon = Icons.Default.Share,
+                contentDescription = stringResource(R.string.share)
+            )
+            SmallPlainButton(
+                onClick = onDelete,
+                icon = Icons.Default.Delete,
+                contentDescription = stringResource(R.string.delete)
+            )
+        }
+    }
+}
+
+@Composable
+private fun ThemeColorGroup(
+    primary: Color,
+    secondary: Color,
+    surface: Color,
+) {
+    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+        listOf(primary, secondary, surface).forEach { color ->
+            Box(
+                modifier = Modifier
+                    .size(12.dp)
+                    .background(color, CircleShape)
+                    .border(1.dp, MaterialTheme.colorScheme.outlineVariant, CircleShape)
+            )
         }
     }
 }

+ 6 - 1
app/src/main/java/io/legado/app/ui/config/themeManage/ThemeManageViewModel.kt

@@ -6,8 +6,8 @@ import androidx.compose.runtime.Stable
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import io.legado.app.R
-import io.legado.app.help.config.SavedTheme
 import io.legado.app.domain.model.settings.ThemeExportData
+import io.legado.app.help.config.SavedTheme
 import io.legado.app.help.config.ThemePackageManager
 import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.persistentListOf
@@ -52,6 +52,9 @@ class ThemeManageViewModel(
                 val dialog = state.dialog as? ThemeManageDialog.Save ?: return@update state
                 state.copy(dialog = dialog.copy(name = intent.value))
             }
+            is ThemeManageIntent.UpdateSearchQuery -> _uiState.update {
+                it.copy(searchQuery = intent.value)
+            }
             is ThemeManageIntent.OpenApplyDialog ->
                 _uiState.update { it.copy(dialog = ThemeManageDialog.Apply(intent.theme)) }
             is ThemeManageIntent.OpenDeleteDialog ->
@@ -239,6 +242,7 @@ class ThemeManageViewModel(
 data class ThemeManageUiState(
     val loading: Boolean = false,
     val savedThemes: ImmutableList<SavedTheme> = persistentListOf(),
+    val searchQuery: String = "",
     val hasLegacyThemes: Boolean = false,
     val dialog: ThemeManageDialog? = null,
 )
@@ -273,6 +277,7 @@ sealed interface ThemeManageIntent {
     data object MigrateLegacyThemes : ThemeManageIntent
     data object OpenSaveDialog : ThemeManageIntent
     data class UpdateSaveName(val value: String) : ThemeManageIntent
+    data class UpdateSearchQuery(val value: String) : ThemeManageIntent
     data class OpenApplyDialog(val theme: SavedTheme) : ThemeManageIntent
     data class OpenDeleteDialog(val theme: SavedTheme) : ThemeManageIntent
     data class OpenEditSheet(val theme: SavedTheme) : ThemeManageIntent