[优化] 优化了替换净化置顶置底逻辑,回归了时间排序下的拖动排序

This commit is contained in:
HapeLee
2025-12-27 04:21:36 +08:00
parent e2e980269c
commit 9d9edb2376
11 changed files with 218 additions and 132 deletions
+2
View File
@@ -382,4 +382,6 @@ dependencies {
implementation(libs.koin.compose) implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel) implementation(libs.koin.compose.viewmodel)
implementation(libs.reorderable)
} }
@@ -35,16 +35,16 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules ORDER BY name COLLATE NOCASE DESC") @Query("SELECT * FROM replace_rules ORDER BY name COLLATE NOCASE DESC")
fun flowAllNameDesc(): Flow<List<ReplaceRule>> fun flowAllNameDesc(): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key ORDER BY sortOrder ASC") @Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY sortOrder ASC")
fun flowSearchAsc(key: String): Flow<List<ReplaceRule>> fun flowSearchAsc(key: String): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key ORDER BY sortOrder DESC") @Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY sortOrder DESC")
fun flowSearchDesc(key: String): Flow<List<ReplaceRule>> fun flowSearchDesc(key: String): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key ORDER BY name COLLATE NOCASE ASC") @Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY name COLLATE NOCASE ASC")
fun flowSearchNameAsc(key: String): Flow<List<ReplaceRule>> fun flowSearchNameAsc(key: String): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key ORDER BY name COLLATE NOCASE DESC") @Query("SELECT * FROM replace_rules WHERE `group` LIKE :key OR name LIKE :key OR pattern LIKE :key OR replacement LIKE :key OR scope LIKE :key ORDER BY name COLLATE NOCASE DESC")
fun flowSearchNameDesc(key: String): Flow<List<ReplaceRule>> fun flowSearchNameDesc(key: String): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY sortOrder ASC") @Query("SELECT * FROM replace_rules WHERE `group` LIKE :key ORDER BY sortOrder ASC")
@@ -72,7 +72,7 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules WHERE `group` IS NULL OR trim(`group`) = '' OR trim(`group`) LIKE '%未分组%' ORDER BY name COLLATE NOCASE DESC") @Query("SELECT * FROM replace_rules WHERE `group` IS NULL OR trim(`group`) = '' OR trim(`group`) LIKE '%未分组%' ORDER BY name COLLATE NOCASE DESC")
fun flowNoGroupNameDesc(): Flow<List<ReplaceRule>> fun flowNoGroupNameDesc(): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules where `group` like :key or name like :key ORDER BY sortOrder ASC") @Query("SELECT * FROM replace_rules where `group` like :key or name like :key or pattern like :key or replacement like :key or scope like :key ORDER BY sortOrder ASC")
fun flowSearch(key: String): Flow<List<ReplaceRule>> fun flowSearch(key: String): Flow<List<ReplaceRule>>
@Query("SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC") @Query("SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC")
@@ -186,4 +186,4 @@ interface ReplaceRuleDao {
dealGroups(list) dealGroups(list)
}.flowOn(IO) }.flowOn(IO)
} }
} }
@@ -45,16 +45,24 @@ class ReplaceRuleRepository {
} }
} }
suspend fun toTop(rule: ReplaceRule) { suspend fun toTop(rule: ReplaceRule, isDesc: Boolean = false) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
rule.order = -1 if (isDesc) {
rule.order = appDb.replaceRuleDao.maxOrder + 1
} else {
rule.order = appDb.replaceRuleDao.minOrder - 1
}
appDb.replaceRuleDao.update(rule) appDb.replaceRuleDao.update(rule)
} }
} }
suspend fun toBottom(rule: ReplaceRule) { suspend fun toBottom(rule: ReplaceRule, isDesc: Boolean = false) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
rule.order = -2 if (isDesc) {
rule.order = appDb.replaceRuleDao.minOrder - 1
} else {
rule.order = appDb.replaceRuleDao.maxOrder + 1
}
appDb.replaceRuleDao.update(rule) appDb.replaceRuleDao.update(rule)
} }
} }
@@ -154,32 +162,59 @@ class ReplaceRuleRepository {
appDb.replaceRuleDao.delete(*rules.toTypedArray()) appDb.replaceRuleDao.delete(*rules.toTypedArray())
} }
suspend fun topByIds(ids: Set<Long>) = suspend fun topByIds(ids: Set<Long>, isDesc: Boolean = false) =
withContext(Dispatchers.IO) {
if (ids.isEmpty()) return@withContext
val rules = appDb.replaceRuleDao.getByIds(ids)
if (isDesc) {
var maxOrder = appDb.replaceRuleDao.maxOrder
val updated = rules.map {
maxOrder++
it.copy(order = maxOrder)
}
appDb.replaceRuleDao.update(*updated.toTypedArray())
} else {
var minOrder = appDb.replaceRuleDao.minOrder
val updated = rules.map {
minOrder--
it.copy(order = minOrder)
}
appDb.replaceRuleDao.update(*updated.toTypedArray())
}
}
suspend fun bottomByIds(ids: Set<Long>, isDesc: Boolean = false) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (ids.isEmpty()) return@withContext if (ids.isEmpty()) return@withContext
val rules = appDb.replaceRuleDao.getByIds(ids) val rules = appDb.replaceRuleDao.getByIds(ids)
val updated = rules.map { it.copy(order = -1) } if (isDesc) {
appDb.replaceRuleDao.update(*updated.toTypedArray()) var minOrder = appDb.replaceRuleDao.minOrder
val updated = rules.map {
minOrder--
it.copy(order = minOrder)
}
appDb.replaceRuleDao.update(*updated.toTypedArray())
} else {
var maxOrder = appDb.replaceRuleDao.maxOrder
val updated = rules.map {
maxOrder++
it.copy(order = maxOrder)
}
appDb.replaceRuleDao.update(*updated.toTypedArray())
}
} }
suspend fun bottomByIds(ids: Set<Long>) = suspend fun moveOrder(currentRules: List<ReplaceRuleItemUi>, isDesc: Boolean = false) {
withContext(Dispatchers.IO) {
if (ids.isEmpty()) return@withContext
val rules = appDb.replaceRuleDao.getByIds(ids)
val updated = rules.map { it.copy(order = -2) }
appDb.replaceRuleDao.update(*updated.toTypedArray())
}
suspend fun moveOrder(currentRules: List<ReplaceRuleItemUi>) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val size = currentRules.size
val updatedRules = currentRules.mapIndexed { index, itemUi -> val updatedRules = currentRules.mapIndexed { index, itemUi ->
itemUi.rule.copy(order = index + 1) val order = if (isDesc) size - index else index + 1
itemUi.rule.copy(order = order)
} }
appDb.replaceRuleDao.update(*updatedRules.toTypedArray()) appDb.replaceRuleDao.update(*updatedRules.toTypedArray())
} }
} }
} }
@@ -14,7 +14,6 @@ import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
@@ -34,8 +33,8 @@ import androidx.compose.ui.text.font.FontWeight
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 io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.ui.widget.components.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.Scroller import io.legado.app.ui.widget.components.lazylist.Scroller
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBarSection
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -9,12 +9,10 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
@@ -43,11 +41,9 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
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.window.Dialog import androidx.compose.ui.window.Dialog
@@ -59,7 +55,7 @@ import io.legado.app.data.repository.UploadRepository
import io.legado.app.ui.replace.edit.ReplaceEditActivity import io.legado.app.ui.replace.edit.ReplaceEditActivity
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.AnimatedText import io.legado.app.ui.widget.components.AnimatedText
import io.legado.app.ui.widget.components.FastScrollLazyColumn import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.SelectionBottomBar import io.legado.app.ui.widget.components.SelectionBottomBar
import io.legado.app.ui.widget.components.exportComponents.FilePickerSheet import io.legado.app.ui.widget.components.exportComponents.FilePickerSheet
@@ -71,6 +67,8 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject import org.koin.compose.koinInject
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
ExperimentalMaterial3ExpressiveApi::class ExperimentalMaterial3ExpressiveApi::class
@@ -118,6 +116,16 @@ fun ReplaceRuleScreen(
var isUploading by remember { mutableStateOf(false) } var isUploading by remember { mutableStateOf(false) }
val clipboardManager = LocalClipboard.current val clipboardManager = LocalClipboard.current
val hapticFeedback = LocalHapticFeedback.current
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
viewModel.moveItemInList(from.index, to.index)
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
}
val canReorder = remember(uiState.sortMode) {
uiState.sortMode == "asc" || uiState.sortMode == "desc"
}
val importDoc = rememberLauncherForActivityResult( val importDoc = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(), contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> onResult = { uri ->
@@ -177,11 +185,9 @@ fun ReplaceRuleScreen(
.map { it.rule } .map { it.rule }
val json = Gson().toJson(rulesToExport) val json = Gson().toJson(rulesToExport)
// [新增] 设置加载状态
isUploading = true isUploading = true
try { try {
// 使用 runCatching 处理上传
runCatching { runCatching {
uploadRepository.upload( uploadRepository.upload(
fileName = "exportReplaceRule.json", fileName = "exportReplaceRule.json",
@@ -246,6 +252,12 @@ fun ReplaceRuleScreen(
} }
} }
LaunchedEffect(reorderableState.isAnyItemDragging) {
if (!reorderableState.isAnyItemDragging) {
viewModel.saveSortOrder()
}
}
if (showGroupManageSheet) { if (showGroupManageSheet) {
GroupManageBottomSheet( GroupManageBottomSheet(
groups = groups, groups = groups,
@@ -386,11 +398,23 @@ fun ReplaceRuleScreen(
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("名称升序") }, text = { Text("名称升序") },
onClick = { viewModel.setSortMode("name_asc"); showMenu = false } onClick = {
viewModel.setSortMode("name_asc")
showMenu = false
scope.launch {
snackbarHostState.showSnackbar("非时间排序模式下将禁用拖动")
}
}
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text("名称降序") }, text = { Text("名称降序") },
onClick = { viewModel.setSortMode("name_desc"); showMenu = false } onClick = {
viewModel.setSortMode("name_desc")
showMenu = false
scope.launch {
snackbarHostState.showSnackbar("非时间排序模式下将禁用拖动")
}
}
) )
} }
} }
@@ -432,7 +456,7 @@ fun ReplaceRuleScreen(
text = title, text = title,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 12.dp), modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant color = MaterialTheme.colorScheme.onSurfaceVariant
) )
} }
@@ -473,30 +497,43 @@ fun ReplaceRuleScreen(
) { ) {
items(rules, key = { it.id }) { ui -> items(rules, key = { it.id }) { ui ->
val isSelected = selectedRuleIds.contains(ui.id) val isSelected = selectedRuleIds.contains(ui.id)
ReorderableItem(
ReplaceRuleItem( state = reorderableState,
modifier = Modifier key = ui.id
.padding(horizontal = 12.dp) ) { _ ->
.animateItem(), ReplaceRuleItem(
name = ui.name, modifier = Modifier
isEnabled = ui.isEnabled, .padding(horizontal = 12.dp)
isSelected = isSelected, .then(
inSelectionMode = inSelectionMode, if (canReorder) {
onEnabledChange = { enabled -> Modifier.longPressDraggableHandle(
viewModel.update(ui.rule.copy(isEnabled = enabled)) interactionSource = remember { MutableInteractionSource() }
}, )
onDelete = { showDeleteRuleDialog = ui.rule }, } else {
onToTop = { viewModel.toTop(ui.rule) }, Modifier
onToBottom = { viewModel.toBottom(ui.rule) }, }
onToggleSelection = { )
viewModel.toggleSelection(ui.id) .animateItem(),
}, name = ui.name,
onClickEdit = { isEnabled = ui.isEnabled,
context.startActivity( isSelected = isSelected,
ReplaceEditActivity.startIntent(context, ui.id) inSelectionMode = inSelectionMode,
) onEnabledChange = { enabled ->
} viewModel.update(ui.rule.copy(isEnabled = enabled))
) },
onDelete = { showDeleteRuleDialog = ui.rule },
onToTop = { viewModel.toTop(ui.rule) },
onToBottom = { viewModel.toBottom(ui.rule) },
onToggleSelection = {
viewModel.toggleSelection(ui.id)
},
onClickEdit = {
context.startActivity(
ReplaceEditActivity.startIntent(context, ui.id)
)
}
)
}
} }
} }
if (inSelectionMode) { if (inSelectionMode) {
@@ -692,42 +729,65 @@ private fun GroupManageBottomSheet(
sheetState = sheetState sheetState = sheetState
) { ) {
Column( Column(
modifier = Modifier.padding(16.dp), modifier = Modifier
verticalArrangement = Arrangement.spacedBy(16.dp) .padding(horizontal = 16.dp)
.padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text(stringResource(R.string.group_manage), style = MaterialTheme.typography.titleLarge) Text(
modifier = Modifier.padding(bottom = 16.dp),
text = stringResource(R.string.group_manage),
style = MaterialTheme.typography.titleMedium
)
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
items(groups) { group -> items(groups) { group ->
Row( Card(modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
if (editingGroup == group) { if (editingGroup == group) {
OutlinedTextField( Row(
value = updatedGroupName, modifier = Modifier
onValueChange = { updatedGroupName = it }, .fillMaxWidth()
modifier = Modifier.weight(1f) .padding(horizontal = 16.dp, vertical = 4.dp),
) verticalAlignment = Alignment.CenterVertically,
IconButton(onClick = { horizontalArrangement = Arrangement.SpaceBetween
viewModel.upGroup(group, updatedGroupName) ) {
editingGroup = null OutlinedTextField(
}) { value = updatedGroupName,
Icon(Icons.Default.Check, contentDescription = stringResource(id = R.string.ok)) onValueChange = { updatedGroupName = it },
modifier = Modifier.weight(1f)
)
IconButton(onClick = {
viewModel.upGroup(group, updatedGroupName)
editingGroup = null
}) {
Icon(
Icons.Default.Check,
contentDescription = stringResource(id = R.string.ok)
)
}
} }
} else { } else {
Text(group, modifier = Modifier.weight(1f)) ListItem(
Row { headlineContent = { Text(group) },
IconButton(onClick = { trailingContent = {
editingGroup = group Row {
updatedGroupName = group IconButton(onClick = {
}) { editingGroup = group
Icon(Icons.Default.Edit, contentDescription = stringResource(id = R.string.edit)) updatedGroupName = group
}) {
Icon(
Icons.Default.Edit,
contentDescription = stringResource(id = R.string.edit)
)
}
IconButton(onClick = { viewModel.delGroup(group) }) {
Icon(
Icons.Default.Delete,
contentDescription = stringResource(id = R.string.delete)
)
}
}
} }
IconButton(onClick = { viewModel.delGroup(group) }) { )
Icon(Icons.Default.Delete, contentDescription = stringResource(id = R.string.delete))
}
}
} }
} }
} }
@@ -785,6 +845,7 @@ fun ReplaceRuleItem(
AnimatedContent(targetState = name, label = "RuleNameAnimation") { targetName -> AnimatedContent(targetState = name, label = "RuleNameAnimation") { targetName ->
Text( Text(
text = targetName, text = targetName,
style = MaterialTheme.typography.titleMedium,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -102,34 +102,10 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
baseFlow baseFlow
.map { rules -> .map { rules ->
val comparator = when (sortMode) { val comparator = when (sortMode) {
"asc" -> compareBy<ReplaceRule> { "asc" -> compareBy<ReplaceRule> { it.order.toLong() }
when (it.order) { "desc" -> compareByDescending<ReplaceRule> { it.order.toLong() }
-1 -> Long.MIN_VALUE "name_asc" -> compareBy<ReplaceRule> { it.name.lowercase() }
-2 -> Long.MAX_VALUE "name_desc" -> compareByDescending<ReplaceRule> { it.name.lowercase() }
else -> it.order.toLong()
}
}
"desc" -> compareByDescending<ReplaceRule> {
when (it.order) {
-1 -> Long.MAX_VALUE
-2 -> Long.MIN_VALUE
else -> it.order.toLong()
}
}
"name_asc" -> compareBy<ReplaceRule> {
when (it.order) {
-1 -> Long.MIN_VALUE
-2 -> Long.MAX_VALUE
else -> 0L
}
}.thenBy { it.name.lowercase() }
"name_desc" -> compareBy<ReplaceRule> {
when (it.order) {
-1 -> Long.MIN_VALUE
-2 -> Long.MAX_VALUE
else -> 0L
}
}.thenByDescending { it.name.lowercase() }
else -> null else -> null
} }
@@ -154,7 +130,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
_sortMode, _sortMode,
_searchKey, _searchKey,
repository.flowGroups(), repository.flowGroups(),
ruleUiFlow _uiRules
) { sortMode, searchKey, groups, rules -> ) { sortMode, searchKey, groups, rules ->
ReplaceRuleUiState( ReplaceRuleUiState(
sortMode = sortMode, sortMode = sortMode,
@@ -169,6 +145,14 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
initialValue = ReplaceRuleUiState(isLoading = true) initialValue = ReplaceRuleUiState(isLoading = true)
) )
init {
viewModelScope.launch {
ruleUiFlow.collect { rules ->
_uiRules.value = rules
}
}
}
fun setSortMode(mode: String) { fun setSortMode(mode: String) {
_sortMode.value = mode _sortMode.value = mode
context.putPrefString(PreferKey.replaceSortMode, mode) context.putPrefString(PreferKey.replaceSortMode, mode)
@@ -196,13 +180,13 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun toTop(rule: ReplaceRule) { fun toTop(rule: ReplaceRule) {
execute { execute {
repository.toTop(rule) repository.toTop(rule, _sortMode.value == "desc")
} }
} }
fun toBottom(rule: ReplaceRule) { fun toBottom(rule: ReplaceRule) {
execute { execute {
repository.toBottom(rule) repository.toBottom(rule, _sortMode.value == "desc")
} }
} }
@@ -244,13 +228,13 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun topSelectByIds(ids: Set<Long>) { fun topSelectByIds(ids: Set<Long>) {
execute { execute {
repository.topByIds(ids) repository.topByIds(ids, _sortMode.value == "desc")
} }
} }
fun bottomSelectByIds(ids: Set<Long>) { fun bottomSelectByIds(ids: Set<Long>) {
execute { execute {
repository.bottomByIds(ids) repository.bottomByIds(ids, _sortMode.value == "desc")
} }
} }
@@ -284,8 +268,9 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun saveSortOrder() { fun saveSortOrder() {
val currentRules = _uiRules.value val currentRules = _uiRules.value
val isDesc = _sortMode.value == "desc"
execute { execute {
repository.moveOrder(currentRules) repository.moveOrder(currentRules, isDesc)
} }
} }
@@ -420,4 +405,4 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
} }
} }
} }
@@ -40,14 +40,14 @@ fun FilePickerSheet(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.padding(bottom = 32.dp), .padding(bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Text(
modifier = Modifier.padding(bottom = 16.dp),
text = stringResource(R.string.select_operation), text = stringResource(R.string.select_operation),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 16.dp)
) )
Row( Row(
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components.lazylist
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -1,4 +1,4 @@
package io.legado.app.ui.widget.components package io.legado.app.ui.widget.components.lazylist
import android.view.ViewConfiguration import android.view.ViewConfiguration
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
@@ -46,7 +46,7 @@ import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastLastOrNull import androidx.compose.ui.util.fastLastOrNull
import androidx.compose.ui.util.fastMaxBy import androidx.compose.ui.util.fastMaxBy
import io.legado.app.ui.widget.components.Scroller.STICKY_HEADER_KEY_PREFIX import io.legado.app.ui.widget.components.lazylist.Scroller.STICKY_HEADER_KEY_PREFIX
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
+1 -1
View File
@@ -42,7 +42,7 @@
<string name="replace_purify_desc">配置替换净化规则</string> <string name="replace_purify_desc">配置替换净化规则</string>
<string name="not_available">暂无</string> <string name="not_available">暂无</string>
<string name="enable">启用</string> <string name="enable">启用</string>
<string name="replace_purify_search">替换净化 - 搜索</string> <string name="replace_purify_search">搜索</string>
<string name="bookshelf">书架</string> <string name="bookshelf">书架</string>
<string name="favorites">收藏夹</string> <string name="favorites">收藏夹</string>
<string name="favorite">收藏</string> <string name="favorite">收藏</string>
+4
View File
@@ -72,6 +72,8 @@ zxingLite = "3.3.0"
koin-bom = "4.1.1" koin-bom = "4.1.1"
biometricKtx = "1.1.0" biometricKtx = "1.1.0"
reorderable = "3.0.0"
[libraries] [libraries]
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" } accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanistWebview" }
@@ -214,6 +216,8 @@ renderscript-intrinsics-replacement-toolkit = { module = "com.github.TomasValent
zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" } zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" }
androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric-ktx", version.ref = "biometricKtx" } androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric-ktx", version.ref = "biometricKtx" }
reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" }
[bundles] [bundles]
coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"] coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android"]
androidTest = ["androidx-espresso-core", "androidx-junit", "androidx-runner"] androidTest = ["androidx-espresso-core", "androidx-junit", "androidx-runner"]