fix: 合并标签分组与传统分组编辑界面,支持在分组编辑中查看和修改匹配规则
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package io.legado.app.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import io.legado.app.data.entities.TagGroupRule
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TagGroupRuleDao {
|
||||
|
||||
@Query("SELECT * FROM tag_group_rules ORDER BY `order`")
|
||||
fun flowAll(): Flow<List<TagGroupRule>>
|
||||
|
||||
@Query("SELECT * FROM tag_group_rules ORDER BY `order` ASC")
|
||||
fun getAll(): List<TagGroupRule>
|
||||
|
||||
@Query("SELECT * FROM tag_group_rules WHERE id = :id")
|
||||
suspend fun getById(id: Long): TagGroupRule?
|
||||
|
||||
@Query("SELECT * FROM tag_group_rules WHERE id IN (:ids)")
|
||||
suspend fun getByIds(ids: Set<Long>): List<TagGroupRule>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(vararg rule: TagGroupRule)
|
||||
|
||||
@Update
|
||||
suspend fun update(vararg rule: TagGroupRule)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(vararg rule: TagGroupRule)
|
||||
|
||||
@Query("SELECT * FROM tag_group_rules WHERE groupName = :groupName LIMIT 1")
|
||||
suspend fun getByGroupName(groupName: String): TagGroupRule?
|
||||
|
||||
@get:Query("SELECT COALESCE(MAX(`order`), -1) FROM tag_group_rules")
|
||||
val maxOrder: Int
|
||||
|
||||
@Query("DELETE FROM tag_group_rules")
|
||||
suspend fun deleteAll()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.legado.app.data.repository
|
||||
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.TagGroupRule
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class TagGroupRuleRepository {
|
||||
|
||||
private val dao = appDb.tagGroupRuleDao
|
||||
|
||||
fun flowAll(): Flow<List<TagGroupRule>> {
|
||||
return dao.flowAll()
|
||||
}
|
||||
|
||||
suspend fun insert(vararg rule: TagGroupRule) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.insert(*rule)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(vararg rule: TagGroupRule) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.delete(*rule)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getByGroupName(groupName: String): TagGroupRule? = withContext(Dispatchers.IO) {
|
||||
dao.getByGroupName(groupName)
|
||||
}
|
||||
|
||||
suspend fun update(vararg rule: TagGroupRule) {
|
||||
withContext(Dispatchers.IO) {
|
||||
dao.update(*rule)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun findById(id: Long): TagGroupRule? = withContext(Dispatchers.IO) {
|
||||
dao.getById(id)
|
||||
}
|
||||
|
||||
suspend fun getByIds(ids: Set<Long>): List<TagGroupRule> =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (ids.isEmpty()) emptyList() else dao.getByIds(ids)
|
||||
}
|
||||
|
||||
suspend fun deleteByIds(ids: Set<Long>) = withContext(Dispatchers.IO) {
|
||||
if (ids.isEmpty()) return@withContext
|
||||
val rules = dao.getByIds(ids)
|
||||
dao.delete(*rules.toTypedArray())
|
||||
}
|
||||
|
||||
suspend fun moveOrder(rules: List<TagGroupRule>) = withContext(Dispatchers.IO) {
|
||||
val updatedRules = rules.mapIndexed { index, rule ->
|
||||
rule.copy(order = index)
|
||||
}
|
||||
dao.update(*updatedRules.toTypedArray())
|
||||
}
|
||||
|
||||
fun getMaxOrder(): Int = dao.maxOrder
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.data.entities.TagGroupRule
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppTextField
|
||||
import io.legado.app.ui.widget.components.alert.AppAlertDialog
|
||||
@@ -98,6 +99,7 @@ fun GroupEditContent(
|
||||
onDismissRequest: () -> Unit,
|
||||
coverPath: String?,
|
||||
onCoverPathChange: (String?) -> Unit,
|
||||
tagGroupRule: TagGroupRule? = null,
|
||||
viewModel: GroupViewModel = koinViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -106,6 +108,8 @@ fun GroupEditContent(
|
||||
var isPrivate by remember(group) { mutableStateOf(group?.isPrivate ?: false) }
|
||||
var showDisablePrivateDialog by remember(group) { mutableStateOf(false) }
|
||||
var selectedSortIndex by remember(group) { mutableIntStateOf(group?.bookSort ?: -1) }
|
||||
var pattern by remember(tagGroupRule) { mutableStateOf(tagGroupRule?.pattern ?: "") }
|
||||
var showPattern by remember(tagGroupRule) { mutableStateOf(tagGroupRule != null || pattern.isNotBlank()) }
|
||||
|
||||
val sortOptions = stringArrayResource(R.array.book_sort)
|
||||
val sortEntryValues = remember(sortOptions) {
|
||||
@@ -210,6 +214,28 @@ fun GroupEditContent(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
CompactSwitchSettingItem(
|
||||
title = "标签匹配规则",
|
||||
description = if (showPattern) "启用后分组将根据书籍标签自动匹配" else "关闭后分组为手动管理",
|
||||
checked = showPattern,
|
||||
onCheckedChange = { showPattern = it }
|
||||
)
|
||||
|
||||
if (showPattern) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
AppTextField(
|
||||
value = pattern,
|
||||
onValueChange = { pattern = it },
|
||||
backgroundColor = LegadoTheme.colorScheme.onSheetContent,
|
||||
label = stringResource(R.string.tag_group_pattern),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
minLines = 3
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ConfirmDismissButtonsRow(
|
||||
@@ -228,16 +254,36 @@ fun GroupEditContent(
|
||||
enableRefresh = enableRefresh,
|
||||
isPrivate = isPrivate
|
||||
)
|
||||
) {
|
||||
onDismissRequest()
|
||||
)
|
||||
if (showPattern && pattern.isNotBlank()) {
|
||||
val existingRule = tagGroupRule
|
||||
if (existingRule != null) {
|
||||
viewModel.saveTagGroupRule(
|
||||
existingRule.copy(
|
||||
groupName = groupName,
|
||||
pattern = pattern
|
||||
)
|
||||
)
|
||||
} else {
|
||||
viewModel.saveTagGroupRule(
|
||||
TagGroupRule(
|
||||
groupName = groupName,
|
||||
pattern = pattern
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (!showPattern && tagGroupRule != null) {
|
||||
viewModel.deleteTagGroupRule(tagGroupRule)
|
||||
}
|
||||
onDismissRequest()
|
||||
} else {
|
||||
viewModel.addGroup(
|
||||
groupName,
|
||||
selectedSortIndex,
|
||||
enableRefresh,
|
||||
isPrivate,
|
||||
coverPath
|
||||
coverPath,
|
||||
pattern = pattern.takeIf { showPattern && it.isNotBlank() }
|
||||
) {
|
||||
onDismissRequest()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package io.legado.app.ui.book.group
|
||||
import android.app.Application
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.data.entities.TagGroupRule
|
||||
import io.legado.app.data.repository.BookGroupRepository
|
||||
import io.legado.app.data.repository.TagGroupRuleRepository
|
||||
import io.legado.app.domain.usecase.RemoveBookGroupAssignmentUseCase
|
||||
|
||||
class GroupViewModel(
|
||||
@@ -12,6 +14,8 @@ class GroupViewModel(
|
||||
private val removeBookGroupAssignmentUseCase: RemoveBookGroupAssignmentUseCase
|
||||
) : BaseViewModel(application) {
|
||||
|
||||
private val tagGroupRuleRepository = TagGroupRuleRepository()
|
||||
|
||||
fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
|
||||
execute {
|
||||
bookGroupRepository.update(*bookGroup)
|
||||
@@ -26,6 +30,7 @@ class GroupViewModel(
|
||||
enableRefresh: Boolean,
|
||||
isPrivate: Boolean,
|
||||
cover: String?,
|
||||
pattern: String? = null,
|
||||
finally: () -> Unit
|
||||
) {
|
||||
execute {
|
||||
@@ -41,6 +46,14 @@ class GroupViewModel(
|
||||
)
|
||||
bookGroupRepository.getByID(groupId) ?: removeBookGroupAssignmentUseCase.execute(groupId)
|
||||
bookGroupRepository.insert(bookGroup)
|
||||
if (!pattern.isNullOrBlank()) {
|
||||
val tagRule = TagGroupRule(
|
||||
groupName = groupName,
|
||||
pattern = pattern,
|
||||
order = tagGroupRuleRepository.getMaxOrder()
|
||||
)
|
||||
tagGroupRuleRepository.insert(tagRule)
|
||||
}
|
||||
}.onFinally {
|
||||
finally()
|
||||
}
|
||||
@@ -63,4 +76,29 @@ class GroupViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getTagGroupRule(groupName: String): TagGroupRule? {
|
||||
return tagGroupRuleRepository.getByGroupName(groupName)
|
||||
}
|
||||
|
||||
fun saveTagGroupRule(rule: TagGroupRule, finally: (() -> Unit)? = null) {
|
||||
execute {
|
||||
val existing = tagGroupRuleRepository.getByGroupName(rule.groupName)
|
||||
if (existing != null) {
|
||||
tagGroupRuleRepository.update(rule.copy(id = existing.id))
|
||||
} else {
|
||||
tagGroupRuleRepository.insert(rule)
|
||||
}
|
||||
}.onFinally {
|
||||
finally?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteTagGroupRule(rule: TagGroupRule, finally: (() -> Unit)? = null) {
|
||||
execute {
|
||||
tagGroupRuleRepository.delete(rule)
|
||||
}.onFinally {
|
||||
finally?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,17 @@ import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Sync
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@@ -30,9 +34,14 @@ import io.legado.app.ui.book.group.GroupDeleteAction
|
||||
import io.legado.app.ui.book.group.GroupEditContent
|
||||
import io.legado.app.ui.book.group.GroupResetCoverAction
|
||||
import io.legado.app.ui.book.group.GroupViewModel
|
||||
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.button.series.SmallPlainButton
|
||||
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
|
||||
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.utils.move
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -44,14 +53,27 @@ fun GroupManageSheet(
|
||||
show: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
viewModel: GroupViewModel = koinViewModel(),
|
||||
bookshelfViewModel: BookshelfViewModel = koinViewModel()
|
||||
bookshelfViewModel: BookshelfViewModel = koinViewModel(),
|
||||
tagGroupRuleViewModel: TagGroupRuleViewModel = koinViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val groups by bookshelfViewModel.allGroupsFlow.collectAsState()
|
||||
|
||||
var editingGroup by remember { mutableStateOf<BookGroup?>(null) }
|
||||
var isEditing by remember { mutableStateOf(false) }
|
||||
var coverPath by remember(editingGroup) { mutableStateOf(editingGroup?.cover) }
|
||||
|
||||
var editingTagRule by remember { mutableStateOf<io.legado.app.data.entities.TagGroupRule?>(null) }
|
||||
var showTagRuleEdit by remember { mutableStateOf(false) }
|
||||
|
||||
var editingGroupTagRule by remember { mutableStateOf<io.legado.app.data.entities.TagGroupRule?>(null) }
|
||||
|
||||
LaunchedEffect(editingGroup) {
|
||||
editingGroupTagRule = editingGroup?.let { group ->
|
||||
viewModel.getTagGroupRule(group.groupName)
|
||||
}
|
||||
}
|
||||
|
||||
var listData by remember { mutableStateOf(groups) }
|
||||
val listState = rememberLazyListState()
|
||||
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
||||
@@ -95,14 +117,42 @@ fun GroupManageSheet(
|
||||
},
|
||||
endAction = {
|
||||
if (!isEditing) {
|
||||
SmallPlainButton(
|
||||
onClick = {
|
||||
editingGroup = null
|
||||
coverPath = null
|
||||
isEditing = true
|
||||
},
|
||||
icon = Icons.Default.Add
|
||||
)
|
||||
Box {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
SmallPlainButton(
|
||||
onClick = { showMenu = true },
|
||||
icon = Icons.Default.Add
|
||||
)
|
||||
RoundDropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.group_add),
|
||||
leadingIcon = { Icon(Icons.Default.Add, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
editingGroup = null
|
||||
coverPath = null
|
||||
isEditing = true
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.tag_group_add_rule),
|
||||
leadingIcon = { Icon(Icons.Default.Add, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
editingTagRule = null
|
||||
showTagRuleEdit = true
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.tag_group_sync),
|
||||
leadingIcon = { Icon(Icons.Default.Sync, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
tagGroupRuleViewModel.onIntent(TagGroupRuleIntent.SyncGroups)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GroupResetCoverAction(
|
||||
group = editingGroup,
|
||||
@@ -128,6 +178,7 @@ fun GroupManageSheet(
|
||||
},
|
||||
coverPath = coverPath,
|
||||
onCoverPathChange = { coverPath = it },
|
||||
tagGroupRule = editingGroupTagRule,
|
||||
viewModel = viewModel
|
||||
)
|
||||
} else {
|
||||
@@ -158,6 +209,26 @@ fun GroupManageSheet(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TagGroupRuleEditSheet(
|
||||
show = showTagRuleEdit,
|
||||
rule = editingTagRule,
|
||||
onDismissRequest = {
|
||||
showTagRuleEdit = false
|
||||
editingTagRule = null
|
||||
},
|
||||
onSave = { rule ->
|
||||
val isNew = editingTagRule == null
|
||||
tagGroupRuleViewModel.onIntent(
|
||||
TagGroupRuleIntent.SaveRule(rule, isNew)
|
||||
)
|
||||
showTagRuleEdit = false
|
||||
editingTagRule = null
|
||||
},
|
||||
onCopy = { rule ->
|
||||
tagGroupRuleViewModel.onIntent(TagGroupRuleIntent.CopyRule(rule))
|
||||
},
|
||||
onPaste = { tagGroupRuleViewModel.pasteRule() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user