Просмотр исходного кода

feat: 分组管理样式优化

HapeLee 1 месяц назад
Родитель
Сommit
da2e40b318

+ 12 - 11
app/src/main/java/io/legado/app/data/AppDatabase.kt

@@ -252,6 +252,7 @@ abstract class AppDatabase : RoomDatabase() {
             }
 
             override fun onOpen(db: SupportSQLiteDatabase) {
+                // 伴生分组默认隐藏,仅「全部」默认显示;已有数据库不受影响,用户仍可在分组管理中手动开启
                 @Language("sql")
                 val insertBookGroupAllSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
@@ -262,70 +263,70 @@ abstract class AppDatabase : RoomDatabase() {
                 @Language("sql")
                 val insertBookGroupLocalSql = """
                     insert into book_groups(groupId, groupName, 'order', enableRefresh, show) 
-                    select ${BookGroup.IdLocal}, '本地', -9, 0, 1
+                    select ${BookGroup.IdLocal}, '本地', -9, 0, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdLocal})
                 """.trimIndent()
                 db.execSQL(insertBookGroupLocalSql)
                 @Language("sql")
                 val insertBookGroupTextSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdText}, '小说', -26, 1
+                    select ${BookGroup.IdText}, '小说', -26, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdText})
                 """.trimIndent()
                 db.execSQL(insertBookGroupTextSql)
                 @Language("sql")
                 val insertBookGroupMangaSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdManga}, '漫画', -25, 1
+                    select ${BookGroup.IdManga}, '漫画', -25, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdManga})
                 """.trimIndent()
                 db.execSQL(insertBookGroupMangaSql)
                 @Language("sql")
                 val insertBookGroupMusicSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdAudio}, '音频', -8, 1
+                    select ${BookGroup.IdAudio}, '音频', -8, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdAudio})
                 """.trimIndent()
                 db.execSQL(insertBookGroupMusicSql)
                 Language("sql")
                 val insertGroupReading = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdReading}, '在读', -30, 1
+                    select ${BookGroup.IdReading}, '在读', -30, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdReading})
                 """.trimIndent()
                 db.execSQL(insertGroupReading)
                 @Language("sql")
                 val insertGroupUnread = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdUnread}, '未读', -29, 1
+                    select ${BookGroup.IdUnread}, '未读', -29, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdUnread})
                 """.trimIndent()
                 db.execSQL(insertGroupUnread)
                 @Language("sql")
                 val insertGroupReadFinished = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdReadFinished}, '已读', -28, 1
+                    select ${BookGroup.IdReadFinished}, '已读', -28, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdReadFinished})
                 """.trimIndent()
                 db.execSQL(insertGroupReadFinished)
                 @Language("sql")
                 val insertGroupReadFinishedUpdate = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdReadFinishedUpdate}, '连载已读', -27, 1
+                    select ${BookGroup.IdReadFinishedUpdate}, '连载已读', -27, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdReadFinishedUpdate})
                 """.trimIndent()
                 db.execSQL(insertGroupReadFinishedUpdate)
                 @Language("sql")
                 val insertGroupReadFinishedComplete = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdReadFinishedComplete}, '完本已读', -26, 1
+                    select ${BookGroup.IdReadFinishedComplete}, '完本已读', -26, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdReadFinishedComplete})
                 """.trimIndent()
                 db.execSQL(insertGroupReadFinishedComplete)
                 @Language("sql")
                 val insertBookGroupNetNoneGroupSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdNetNone}, '网络未分组', -7, 1
+                    select ${BookGroup.IdNetNone}, '网络未分组', -7, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdNetNone})
                 """.trimIndent()
                 db.execSQL(insertBookGroupNetNoneGroupSql)
@@ -339,7 +340,7 @@ abstract class AppDatabase : RoomDatabase() {
                 @Language("sql")
                 val insertBookGroupErrorSql = """
                     insert into book_groups(groupId, groupName, 'order', show) 
-                    select ${BookGroup.IdError}, '更新失败', -1, 1
+                    select ${BookGroup.IdError}, '更新失败', -1, 0
                     where not exists (select * from book_groups where groupId = ${BookGroup.IdError})
                 """.trimIndent()
                 db.execSQL(insertBookGroupErrorSql)

+ 129 - 16
app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt

@@ -2,19 +2,27 @@ package io.legado.app.ui.main.bookshelf
 
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.clickable
 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.fillMaxWidth
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
+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.LazyGridItemScope
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.itemsIndexed
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.AutoAwesome
-import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.material.icons.filled.Sync
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
@@ -26,10 +34,15 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
 import io.legado.app.R
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.book.group.GroupDeleteAction
@@ -41,14 +54,24 @@ import io.legado.app.ui.tagGroupRule.TagGroupRuleEditSheet
 import io.legado.app.ui.tagGroupRule.TagGroupRuleIntent
 import io.legado.app.ui.tagGroupRule.TagGroupRuleViewModel
 import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.TinySwitch
 import io.legado.app.ui.widget.components.button.series.MediumTonalButton
-import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
+import io.legado.app.ui.widget.components.card.GlassCard
+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.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.reorderAccessibility
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.move
 import org.koin.androidx.compose.koinViewModel
-import sh.calvin.reorderable.rememberReorderableLazyListState
+import sh.calvin.reorderable.ReorderableItem
+import sh.calvin.reorderable.ReorderableLazyGridState
+import sh.calvin.reorderable.rememberReorderableLazyGridState
+
+/** 分组卡片最小宽度,常规手机为两列,更宽的屏幕按可用宽度自动增加列数。 */
+private val GroupCardMinWidth = 160.dp
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -80,8 +103,8 @@ fun GroupManageSheet(
     }
 
     var listData by remember { mutableStateOf(groups) }
-    val listState = rememberLazyListState()
-    val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
+    val gridState = rememberLazyGridState()
+    val reorderableState = rememberReorderableLazyGridState(gridState) { from, to ->
         listData = listData.toMutableList().apply {
             move(from.index, to.index)
         }
@@ -197,29 +220,30 @@ fun GroupManageSheet(
                     viewModel = viewModel
                 )
             } else {
-                LazyColumn(
-                    state = listState,
+                LazyVerticalGrid(
+                    state = gridState,
+                    columns = GridCells.Adaptive(GroupCardMinWidth),
                     modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                     verticalArrangement = Arrangement.spacedBy(8.dp)
                 ) {
-                    items(listData, key = { it.groupId }) { group ->
+                    itemsIndexed(listData, key = { _, group -> group.groupId }) { index, group ->
                         val manageNameInfo = remember(group) { group.getManageName(context) }
-                        ReorderableSelectionItem(
+                        GroupManageGridItem(
                             state = reorderableState,
                             key = group.groupId,
-                            reorderIndex = listData.indexOf(group),
+                            reorderIndex = index,
                             reorderItemCount = listData.size,
                             onMoveItem = { from, to ->
                                 listData = listData.toMutableList().apply { move(from, to) }
-                                val updatedGroups = listData.mapIndexed { index, item ->
-                                    item.copy(order = index)
+                                val updatedGroups = listData.mapIndexed { i, item ->
+                                    item.copy(order = i)
                                 }
                                 viewModel.upGroup(*updatedGroups.toTypedArray())
                             },
                             title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
                             subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
                             isEnabled = group.show,
-                            containerColor = LegadoTheme.colorScheme.onSheetContent,
                             onEnabledChange = { isChecked ->
                                 viewModel.upGroup(group.copy(show = isChecked))
                             },
@@ -262,3 +286,92 @@ fun GroupManageSheet(
         )
     }
 }
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun LazyGridItemScope.GroupManageGridItem(
+    state: ReorderableLazyGridState,
+    key: Any,
+    title: String,
+    subtitle: String?,
+    isEnabled: Boolean,
+    onEnabledChange: (Boolean) -> Unit,
+    onClickEdit: () -> Unit,
+    reorderIndex: Int,
+    reorderItemCount: Int,
+    onMoveItem: (from: Int, to: Int) -> Unit
+) {
+    val hapticFeedback = LocalHapticFeedback.current
+    ReorderableItem(state = state, key = key) { isDragging ->
+        val elevation by animateDpAsState(
+            targetValue = if (isDragging) 8.dp else 0.dp,
+            label = "DragElevation"
+        )
+        GlassCard(
+            cornerRadius = 12.dp,
+            containerColor = LegadoTheme.colorScheme.onSheetContent,
+            elevation = elevation,
+            modifier = Modifier
+                .reorderAccessibility(
+                    index = reorderIndex,
+                    itemCount = reorderItemCount,
+                    onMove = onMoveItem
+                )
+                .zIndex(if (isDragging) 1f else 0f)
+                .longPressDraggableHandle(
+                    onDragStarted = {
+                        hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureThresholdActivate)
+                    },
+                    onDragStopped = {
+                        hapticFeedback.performHapticFeedback(HapticFeedbackType.GestureEnd)
+                    }
+                )
+                .animateItem()
+        ) {
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(start = 12.dp, end = 8.dp, top = 12.dp, bottom = 12.dp),
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.SpaceBetween
+            ) {
+                Column() {
+                    Row(
+                        modifier = Modifier.clickable(
+                            onClick = onClickEdit
+                        ),
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.spacedBy(2.dp)
+                    ) {
+                        AppText(
+                            text = title,
+                            style = LegadoTheme.typography.titleSmall,
+                            maxLines = 2,
+                            overflow = TextOverflow.Ellipsis
+                        )
+                        AppIcon(
+                            modifier = Modifier
+                                .size(12.dp),
+                            imageVector = AppIcons.Edit,
+                            contentDescription = stringResource(R.string.edit)
+                        )
+                    }
+                    if (!subtitle.isNullOrBlank()) {
+                        AppText(
+                            text = subtitle,
+                            style = LegadoTheme.typography.bodySmall,
+                            color = LegadoTheme.colorScheme.onSurfaceVariant,
+                            maxLines = 1,
+                            overflow = TextOverflow.Ellipsis
+                        )
+                    }
+                }
+                TinySwitch(
+                    checked = isEnabled,
+                    onCheckedChange = onEnabledChange
+                )
+            }
+
+        }
+    }
+}

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/IconSwitch.kt

@@ -11,8 +11,8 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.semantics.toggleableState
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
 import androidx.compose.ui.state.ToggleableState
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ThemeResolver
@@ -86,7 +86,7 @@ fun TinySwitch(
         )
     } else {
         IconSwitch(
-            modifier = modifier.scale(0.8f),
+            modifier = modifier.scale(0.72f),
             checked = checked,
             onCheckedChange = onCheckedChange,
             enabled = enabled,