[优化] 优化了替换净化置顶置底逻辑,回归了时间排序下的拖动排序
This commit is contained in:
@@ -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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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
-1
@@ -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
|
||||||
+2
-2
@@ -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
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
Reference in New Issue
Block a user