替换规则排序

This commit is contained in:
HapeLee
2025-11-03 20:49:22 +08:00
parent 4c00917525
commit 963cc90621
6 changed files with 145 additions and 76 deletions
@@ -72,8 +72,6 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
}
private var groups = arrayListOf<String>()
private var groupMenu: SubMenu? = null
private var sortMode = "desc"
private var searchKey: String? = null
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
it ?: return@registerForActivityResult
@@ -16,6 +16,10 @@ import io.legado.app.databinding.ItemReplaceRuleBinding
//import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.utils.gone
import io.legado.app.utils.themeColor
import io.legado.app.utils.visible
import splitties.views.backgroundColor
class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
@@ -38,28 +42,24 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
}
override fun areContentsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean {
if (oldItem.name != newItem.name) {
return false
}
if (oldItem.group != newItem.group) {
return false
}
if (oldItem.isEnabled != newItem.isEnabled) {
return false
}
if (oldItem.name != newItem.name) return false
if (oldItem.group != newItem.group) return false
if (oldItem.isEnabled != newItem.isEnabled) return false
if (oldItem.order != newItem.order) return false // 添加 order 检查
return true
}
override fun getChangePayload(oldItem: ReplaceRule, newItem: ReplaceRule): Any? {
val payload = Bundle()
if (oldItem.name != newItem.name
|| oldItem.group != newItem.group
) {
if (oldItem.name != newItem.name || oldItem.group != newItem.group) {
payload.putBoolean("upName", true)
}
if (oldItem.isEnabled != newItem.isEnabled) {
payload.putBoolean("enabled", newItem.isEnabled)
}
if (oldItem.order != newItem.order) {
payload.putBoolean("orderChanged", true)
}
if (payload.isEmpty) {
return null
}
@@ -102,12 +102,9 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
payloads: MutableList<Any>
) {
binding.run {
if (payloads.isEmpty()) {
//root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
cbName.text = item.getDisplayNameGroup()
swtEnabled.isChecked = item.isEnabled
cbName.isChecked = selected.contains(item)
} else {
var needUpdatePin = payloads.isEmpty()
if (payloads.isNotEmpty()) {
for (i in payloads.indices) {
val bundle = payloads[i] as Bundle
bundle.keySet().forEach {
@@ -115,10 +112,35 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
"selected" -> cbName.isChecked = selected.contains(item)
"upName" -> cbName.text = item.getDisplayNameGroup()
"enabled" -> swtEnabled.isChecked = item.isEnabled
"orderChanged" -> needUpdatePin = true
}
}
}
}
if (payloads.isEmpty() || needUpdatePin) {
when (item.order) {
-1 -> {
ivPin.visible()
ivPin.setImageResource(R.drawable.ic_praise_filled)
ivPin.rotation = 0f
ivPin.setColorFilter(context.themeColor(com.google.android.material.R.attr.colorSecondary))
}
-2 -> {
ivPin.visible()
ivPin.setImageResource(R.drawable.ic_praise_filled)
ivPin.rotation = 180f
ivPin.setColorFilter(context.themeColor(com.google.android.material.R.attr.colorSecondary))
}
else -> ivPin.gone()
}
}
if (payloads.isEmpty()) {
cbName.text = item.getDisplayNameGroup()
swtEnabled.isChecked = item.isEnabled
cbName.isChecked = selected.contains(item)
}
}
}
@@ -221,6 +243,7 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
}
return false
}
}
interface CallBack {
@@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import splitties.init.appCtx
/**
@@ -43,39 +44,64 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
val rulesFlow = combine(_searchKey, _sortMode) { search, sort ->
Pair(search, sort)
}.flatMapLatest { (searchKey, sortMode) ->
when {
searchKey.isNullOrEmpty() -> when (sortMode) {
"asc" -> appDb.replaceRuleDao.flowAllAsc()
"name_asc" -> appDb.replaceRuleDao.flowAllNameAsc()
"name_desc" -> appDb.replaceRuleDao.flowAllNameDesc()
else -> appDb.replaceRuleDao.flowAllDesc()
}
searchKey == appCtx.getString(R.string.no_group) -> when (sortMode) {
"asc" -> appDb.replaceRuleDao.flowNoGroupAsc()
"name_asc" -> appDb.replaceRuleDao.flowNoGroupNameAsc()
"name_desc" -> appDb.replaceRuleDao.flowNoGroupNameDesc()
else -> appDb.replaceRuleDao.flowNoGroupDesc()
}
// 先获取基础数据
val baseFlow = when {
searchKey.isNullOrEmpty() -> appDb.replaceRuleDao.flowAll()
searchKey == appCtx.getString(R.string.no_group) -> appDb.replaceRuleDao.flowNoGroup()
searchKey.startsWith("group:") -> {
val key = searchKey.substringAfter("group:")
when (sortMode) {
"asc" -> appDb.replaceRuleDao.flowGroupSearchAsc("%$key%")
"name_asc" -> appDb.replaceRuleDao.flowGroupSearchNameAsc("%$key%")
"name_desc" -> appDb.replaceRuleDao.flowGroupSearchNameDesc("%$key%")
else -> appDb.replaceRuleDao.flowGroupSearchDesc("%$key%")
appDb.replaceRuleDao.flowGroupSearch("%$key%")
}
else -> appDb.replaceRuleDao.flowSearch("%$searchKey%")
}
baseFlow.map { rules ->
val comparator = when (sortMode) {
"asc" -> Comparator<ReplaceRule> { a, b ->
when {
// 置顶优先
a.order == -1 && b.order != -1 -> -1
a.order != -1 && b.order == -1 -> 1
// 置底最后
a.order == -2 && b.order != -2 -> 1
a.order != -2 && b.order == -2 -> -1
// 普通规则按order排序
else -> a.order.compareTo(b.order)
}
}
"desc" -> Comparator<ReplaceRule> { a, b ->
when {
a.order == -1 && b.order != -1 -> -1
a.order != -1 && b.order == -1 -> 1
a.order == -2 && b.order != -2 -> 1
a.order != -2 && b.order == -2 -> -1
else -> b.order.compareTo(a.order)
}
}
"name_asc" -> Comparator<ReplaceRule> { a, b ->
when {
a.order == -1 && b.order != -1 -> -1
a.order != -1 && b.order == -1 -> 1
a.order == -2 && b.order != -2 -> 1
a.order != -2 && b.order == -2 -> -1
else -> a.name.lowercase().compareTo(b.name.lowercase())
}
}
"name_desc" -> Comparator<ReplaceRule> { a, b ->
when {
a.order == -1 && b.order != -1 -> -1
a.order != -1 && b.order == -1 -> 1
a.order == -2 && b.order != -2 -> 1
a.order != -2 && b.order == -2 -> -1
else -> b.name.lowercase().compareTo(a.name.lowercase())
}
}
else -> null
}
else -> when (sortMode) {
"asc" -> appDb.replaceRuleDao.flowSearchAsc("%$searchKey%")
"name_asc" -> appDb.replaceRuleDao.flowSearchNameAsc("%$searchKey%")
"name_desc" -> appDb.replaceRuleDao.flowSearchNameDesc("%$searchKey%")
else -> appDb.replaceRuleDao.flowSearchDesc("%$searchKey%")
}
if (comparator != null) rules.sortedWith(comparator) else rules
}
}.flowOn(Dispatchers.IO)
}.flowOn(Dispatchers.Default)
fun update(vararg rule: ReplaceRule) {
execute {
@@ -91,16 +117,15 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun toTop(rule: ReplaceRule) {
execute {
rule.order = appDb.replaceRuleDao.minOrder - 1
rule.order = -1
appDb.replaceRuleDao.update(rule)
}
}
fun topSelect(rules: List<ReplaceRule>) {
execute {
var minOrder = appDb.replaceRuleDao.minOrder - rules.size
rules.forEach {
it.order = ++minOrder
it.order = -1
}
appDb.replaceRuleDao.update(*rules.toTypedArray())
}
@@ -108,16 +133,15 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun toBottom(rule: ReplaceRule) {
execute {
rule.order = appDb.replaceRuleDao.maxOrder + 1
rule.order = -2
appDb.replaceRuleDao.update(rule)
}
}
fun bottomSelect(rules: List<ReplaceRule>) {
execute {
var maxOrder = appDb.replaceRuleDao.maxOrder
rules.forEach {
it.order = maxOrder++
it.order = -2
}
appDb.replaceRuleDao.update(*rules.toTypedArray())
}
@@ -125,9 +149,13 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
fun upOrder() {
execute {
// 重置所有非特殊排序的规则
val rules = appDb.replaceRuleDao.all
for ((index, rule) in rules.withIndex()) {
rule.order = index + 1
var normalOrder = 1
rules.forEach { rule ->
if (rule.order >= 0) { // 只重置普通排序的规则
rule.order = normalOrder++
}
}
appDb.replaceRuleDao.update(*rules.toTypedArray())
}
@@ -211,8 +211,7 @@ class ReplaceEditActivity :
private fun upReplaceView(rule: ReplaceRule) = binding.run {
etName.setText(rule.name)
val group = rule.group.takeUnless { it.isNullOrBlank() }
?: GroupManager.getLastSelectedGroup(this@ReplaceEditActivity)
val group = rule.group.takeUnless { it.isNullOrBlank() } ?: "默认"
etGroup.setText(group, false)
etReplaceRule.setText(rule.pattern)
cbUseRegex.isChecked = rule.isRegex
@@ -229,8 +228,11 @@ class ReplaceEditActivity :
val rule = viewModel.replaceRule ?: ReplaceRule()
rule.name = etName.text.toString()
rule.group = etGroup.text.toString().trim().ifBlank {
GroupManager.getLastSelectedGroup(this@ReplaceEditActivity)
val groupText = etGroup.text.toString().trim()
rule.group = if (groupText == "默认") {
null
} else {
groupText.ifBlank { null }
}
rule.pattern = etReplaceRule.text.toString()
rule.isRegex = cbUseRegex.isChecked
@@ -39,26 +39,35 @@
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_group"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:hint="@string/group">
android:orientation="horizontal">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/et_group"
android:layout_width="match_parent"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/text_default"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</com.google.android.material.textfield.TextInputLayout>
android:layout_weight="1"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:hint="@string/group">
<com.google.android.material.button.MaterialButton
android:id="@+id/iv_manage_groups"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/group_manage"/>
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/et_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_default"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/iv_manage_groups"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3Expressive.Button.TextButton"
android:text="@string/group_manage"/>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_replace_rule"
+11 -2
View File
@@ -12,7 +12,7 @@
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/cb_name"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="8dp"
@@ -20,10 +20,19 @@
android:maxLines="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/swt_enabled"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/iv_pin"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_praise"
app:layout_constraintStart_toEndOf="@id/cb_name"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/swt_enabled"
android:name="@string/enable"