|
@@ -5,6 +5,7 @@ import android.content.Intent
|
|
|
import android.os.Bundle
|
|
import android.os.Bundle
|
|
|
import android.view.Menu
|
|
import android.view.Menu
|
|
|
import android.view.MenuItem
|
|
import android.view.MenuItem
|
|
|
|
|
+import android.widget.ArrayAdapter
|
|
|
import android.widget.EditText
|
|
import android.widget.EditText
|
|
|
import androidx.activity.viewModels
|
|
import androidx.activity.viewModels
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.lifecycle.lifecycleScope
|
|
@@ -20,6 +21,9 @@ import io.legado.app.utils.sendToClip
|
|
|
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
|
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
|
|
import io.legado.app.utils.showHelp
|
|
import io.legado.app.utils.showHelp
|
|
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
|
|
|
+import androidx.core.content.edit
|
|
|
|
|
+import io.legado.app.lib.dialogs.alert
|
|
|
|
|
+import io.legado.app.utils.toastOnUi
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 编辑替换规则
|
|
* 编辑替换规则
|
|
@@ -28,28 +32,74 @@ class ReplaceEditActivity :
|
|
|
VMBaseActivity<ActivityReplaceEditBinding, ReplaceEditViewModel>(),
|
|
VMBaseActivity<ActivityReplaceEditBinding, ReplaceEditViewModel>(),
|
|
|
KeyboardToolPop.CallBack {
|
|
KeyboardToolPop.CallBack {
|
|
|
|
|
|
|
|
- companion object {
|
|
|
|
|
|
|
+ object GroupManager {
|
|
|
|
|
+ private const val PREF_NAME = "replace_groups"
|
|
|
|
|
+ private const val KEY_CUSTOM_GROUPS = "custom_groups"
|
|
|
|
|
+ private const val KEY_LAST_SELECTED = "last_selected_group"
|
|
|
|
|
+
|
|
|
|
|
+ private val presetGroups = listOf(
|
|
|
|
|
+ "默认", "地名", "国家", "企业", "人名", "敏感词", "生僻字", "乱码或广告"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ private fun prefs(context: Context) =
|
|
|
|
|
+ context.getSharedPreferences(PREF_NAME, MODE_PRIVATE)
|
|
|
|
|
+
|
|
|
|
|
+ fun getAllGroups(context: Context): List<String> =
|
|
|
|
|
+ (getCustomGroups(context) + presetGroups).distinct()
|
|
|
|
|
+
|
|
|
|
|
+ fun getCustomGroups(context: Context): List<String> {
|
|
|
|
|
+ val json = prefs(context).getString(KEY_CUSTOM_GROUPS, "[]") ?: "[]"
|
|
|
|
|
+ return GSON.fromJson(json, Array<String>::class.java).toList()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun saveCustomGroups(context: Context, groups: List<String>) {
|
|
|
|
|
+ prefs(context).edit { putString(KEY_CUSTOM_GROUPS, GSON.toJson(groups)) }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun addCustomGroup(context: Context, group: String) {
|
|
|
|
|
+ if (group.isBlank() || presetGroups.contains(group)) return
|
|
|
|
|
+ val custom = getCustomGroups(context).toMutableList()
|
|
|
|
|
+ if (group !in custom) {
|
|
|
|
|
+ custom += group
|
|
|
|
|
+ saveCustomGroups(context, custom)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun removeCustomGroups(context: Context, groupsToRemove: List<String>) {
|
|
|
|
|
+ if (groupsToRemove.isEmpty()) return
|
|
|
|
|
+ val updated = getCustomGroups(context).filterNot { it in groupsToRemove }
|
|
|
|
|
+ saveCustomGroups(context, updated)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fun getLastSelectedGroup(context: Context): String =
|
|
|
|
|
+ prefs(context).getString(KEY_LAST_SELECTED, "默认") ?: "默认"
|
|
|
|
|
|
|
|
|
|
+ fun setLastSelectedGroup(context: Context, group: String) {
|
|
|
|
|
+ prefs(context).edit { putString(KEY_LAST_SELECTED, group) }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ companion object {
|
|
|
fun startIntent(
|
|
fun startIntent(
|
|
|
context: Context,
|
|
context: Context,
|
|
|
id: Long = -1,
|
|
id: Long = -1,
|
|
|
pattern: String? = null,
|
|
pattern: String? = null,
|
|
|
isRegex: Boolean = false,
|
|
isRegex: Boolean = false,
|
|
|
- scope: String? = null
|
|
|
|
|
- ): Intent {
|
|
|
|
|
- val intent = Intent(context, ReplaceEditActivity::class.java)
|
|
|
|
|
- intent.putExtra("id", id)
|
|
|
|
|
- intent.putExtra("pattern", pattern)
|
|
|
|
|
- intent.putExtra("isRegex", isRegex)
|
|
|
|
|
- intent.putExtra("scope", scope)
|
|
|
|
|
- return intent
|
|
|
|
|
|
|
+ scope: String? = null,
|
|
|
|
|
+ isScopeTitle: Boolean = false,
|
|
|
|
|
+ isScopeContent: Boolean = false
|
|
|
|
|
+ ): Intent = Intent(context, ReplaceEditActivity::class.java).apply {
|
|
|
|
|
+ putExtra("id", id)
|
|
|
|
|
+ putExtra("pattern", pattern)
|
|
|
|
|
+ putExtra("isRegex", isRegex)
|
|
|
|
|
+ putExtra("scope", scope)
|
|
|
|
|
+ putExtra("isScopeTitle", isScopeTitle)
|
|
|
|
|
+ putExtra("isScopeContent", isScopeContent)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override val binding by viewBinding(ActivityReplaceEditBinding::inflate)
|
|
override val binding by viewBinding(ActivityReplaceEditBinding::inflate)
|
|
|
override val viewModel by viewModels<ReplaceEditViewModel>()
|
|
override val viewModel by viewModels<ReplaceEditViewModel>()
|
|
|
-
|
|
|
|
|
private val softKeyboardTool by lazy {
|
|
private val softKeyboardTool by lazy {
|
|
|
KeyboardToolPop(this, lifecycleScope, binding.root, this)
|
|
KeyboardToolPop(this, lifecycleScope, binding.root, this)
|
|
|
}
|
|
}
|
|
@@ -58,9 +108,12 @@ class ReplaceEditActivity :
|
|
|
super.onCreate(savedInstanceState)
|
|
super.onCreate(savedInstanceState)
|
|
|
softKeyboardTool.attachToWindow(window)
|
|
softKeyboardTool.attachToWindow(window)
|
|
|
initView()
|
|
initView()
|
|
|
- viewModel.initData(intent) {
|
|
|
|
|
- upReplaceView(it)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ viewModel.initData(intent, ::upReplaceView)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onDestroy() {
|
|
|
|
|
+ super.onDestroy()
|
|
|
|
|
+ softKeyboardTool.dismiss()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
|
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
|
@@ -76,66 +129,126 @@ class ReplaceEditActivity :
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
R.id.menu_copy_rule -> sendToClip(GSON.toJson(getReplaceRule()))
|
|
R.id.menu_copy_rule -> sendToClip(GSON.toJson(getReplaceRule()))
|
|
|
- R.id.menu_paste_rule -> viewModel.pasteRule {
|
|
|
|
|
- upReplaceView(it)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ R.id.menu_paste_rule -> viewModel.pasteRule(::upReplaceView)
|
|
|
}
|
|
}
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- override fun onDestroy() {
|
|
|
|
|
- super.onDestroy()
|
|
|
|
|
- softKeyboardTool.dismiss()
|
|
|
|
|
|
|
+ private fun initView() = binding.run {
|
|
|
|
|
+ ivHelp.setOnClickListener { showHelp("regexHelp") }
|
|
|
|
|
+ ivManageGroups.setOnClickListener { showGroupManageDialog() }
|
|
|
|
|
+
|
|
|
|
|
+ root.setOnApplyWindowInsetsListenerCompat { _, insets ->
|
|
|
|
|
+ softKeyboardTool.initialPadding = insets.imeHeight
|
|
|
|
|
+ insets
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setupGroupAutoComplete()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun setupGroupAutoComplete() = binding.run {
|
|
|
|
|
+ val allGroups = GroupManager.getAllGroups(this@ReplaceEditActivity)
|
|
|
|
|
+ val adapter = ArrayAdapter(
|
|
|
|
|
+ this@ReplaceEditActivity,
|
|
|
|
|
+ android.R.layout.simple_dropdown_item_1line,
|
|
|
|
|
+ allGroups
|
|
|
|
|
+ )
|
|
|
|
|
+ etGroup.setAdapter(adapter)
|
|
|
|
|
+
|
|
|
|
|
+ etGroup.setText(GroupManager.getLastSelectedGroup(this@ReplaceEditActivity), false)
|
|
|
|
|
+
|
|
|
|
|
+ etGroup.setOnFocusChangeListener { _, hasFocus ->
|
|
|
|
|
+ if (!hasFocus) saveCurrentGroup()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ etGroup.setOnItemClickListener { parent, _, position, _ ->
|
|
|
|
|
+ val selected = parent.getItemAtPosition(position) as String
|
|
|
|
|
+ GroupManager.setLastSelectedGroup(this@ReplaceEditActivity, selected)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun refreshGroupList() {
|
|
|
|
|
+ val allGroups = GroupManager.getAllGroups(this)
|
|
|
|
|
+ val adapter = ArrayAdapter(
|
|
|
|
|
+ this,
|
|
|
|
|
+ android.R.layout.simple_dropdown_item_1line,
|
|
|
|
|
+ allGroups
|
|
|
|
|
+ )
|
|
|
|
|
+ binding.etGroup.setAdapter(adapter)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private fun initView() {
|
|
|
|
|
- binding.ivHelp.setOnClickListener {
|
|
|
|
|
- showHelp("regexHelp")
|
|
|
|
|
|
|
+ private fun saveCurrentGroup() {
|
|
|
|
|
+ val current = binding.etGroup.text.toString().trim()
|
|
|
|
|
+ if (current.isNotBlank()) {
|
|
|
|
|
+ GroupManager.addCustomGroup(this, current)
|
|
|
|
|
+ GroupManager.setLastSelectedGroup(this, current)
|
|
|
}
|
|
}
|
|
|
- binding.root.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
|
|
|
|
|
- softKeyboardTool.initialPadding = windowInsets.imeHeight
|
|
|
|
|
- windowInsets
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun showGroupManageDialog() {
|
|
|
|
|
+ val allGroups = GroupManager.getCustomGroups(this).toMutableList()
|
|
|
|
|
+ if (allGroups.isEmpty()) {
|
|
|
|
|
+ toastOnUi("暂无自定义分组")
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ val checked = BooleanArray(allGroups.size)
|
|
|
|
|
+ alert(title = getString(R.string.group_manage)) {
|
|
|
|
|
+ multiChoiceItems(allGroups.toTypedArray(), checked) { _, _, _ -> }
|
|
|
|
|
+ negativeButton("关闭")
|
|
|
|
|
+ neutralButton("删除所选") {
|
|
|
|
|
+ val toDelete = allGroups.filterIndexed { i, _ -> checked[i] }
|
|
|
|
|
+ if (toDelete.isNotEmpty()) {
|
|
|
|
|
+ GroupManager.removeCustomGroups(this@ReplaceEditActivity, toDelete)
|
|
|
|
|
+ refreshGroupList()
|
|
|
|
|
+ toastOnUi("已删除 ${toDelete.size} 个分组")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ toastOnUi("未选择任何分组")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private fun upReplaceView(replaceRule: ReplaceRule) = binding.run {
|
|
|
|
|
- etName.setText(replaceRule.name)
|
|
|
|
|
- etGroup.setText(replaceRule.group)
|
|
|
|
|
- etReplaceRule.setText(replaceRule.pattern)
|
|
|
|
|
- cbUseRegex.isChecked = replaceRule.isRegex
|
|
|
|
|
- etReplaceTo.setText(replaceRule.replacement)
|
|
|
|
|
- cbScopeTitle.isChecked = replaceRule.scopeTitle
|
|
|
|
|
- cbScopeContent.isChecked = replaceRule.scopeContent
|
|
|
|
|
- etScope.setText(replaceRule.scope)
|
|
|
|
|
- etExcludeScope.setText(replaceRule.excludeScope)
|
|
|
|
|
- etTimeout.setText(replaceRule.timeoutMillisecond.toString())
|
|
|
|
|
|
|
+ private fun upReplaceView(rule: ReplaceRule) = binding.run {
|
|
|
|
|
+ etName.setText(rule.name)
|
|
|
|
|
+ val group = rule.group.takeUnless { it.isNullOrBlank() }
|
|
|
|
|
+ ?: GroupManager.getLastSelectedGroup(this@ReplaceEditActivity)
|
|
|
|
|
+ etGroup.setText(group, false)
|
|
|
|
|
+ etReplaceRule.setText(rule.pattern)
|
|
|
|
|
+ cbUseRegex.isChecked = rule.isRegex
|
|
|
|
|
+ etReplaceTo.setText(rule.replacement)
|
|
|
|
|
+ cbScopeTitle.isChecked = rule.scopeTitle
|
|
|
|
|
+ cbScopeContent.isChecked = rule.scopeContent
|
|
|
|
|
+ etScope.setText(rule.scope)
|
|
|
|
|
+ etExcludeScope.setText(rule.excludeScope)
|
|
|
|
|
+ etTimeout.setText(rule.timeoutMillisecond.toString())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private fun getReplaceRule(): ReplaceRule = binding.run {
|
|
private fun getReplaceRule(): ReplaceRule = binding.run {
|
|
|
- val replaceRule: ReplaceRule = viewModel.replaceRule ?: ReplaceRule()
|
|
|
|
|
- replaceRule.name = etName.text.toString()
|
|
|
|
|
- replaceRule.group = etGroup.text.toString()
|
|
|
|
|
- replaceRule.pattern = etReplaceRule.text.toString()
|
|
|
|
|
- replaceRule.isRegex = cbUseRegex.isChecked
|
|
|
|
|
- replaceRule.replacement = etReplaceTo.text.toString()
|
|
|
|
|
- replaceRule.scopeTitle = cbScopeTitle.isChecked
|
|
|
|
|
- replaceRule.scopeContent = cbScopeContent.isChecked
|
|
|
|
|
- replaceRule.scope = etScope.text.toString()
|
|
|
|
|
- replaceRule.excludeScope = etExcludeScope.text.toString()
|
|
|
|
|
- replaceRule.timeoutMillisecond = etTimeout.text.toString().ifEmpty { "3000" }.toLong()
|
|
|
|
|
- return replaceRule
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun helpActions(): List<SelectItem<String>> {
|
|
|
|
|
- return arrayListOf(
|
|
|
|
|
- SelectItem("正则教程", "regexHelp")
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ saveCurrentGroup()
|
|
|
|
|
+ val rule = viewModel.replaceRule ?: ReplaceRule()
|
|
|
|
|
+
|
|
|
|
|
+ rule.name = etName.text.toString()
|
|
|
|
|
+ rule.group = etGroup.text.toString().trim().ifBlank {
|
|
|
|
|
+ GroupManager.getLastSelectedGroup(this@ReplaceEditActivity)
|
|
|
|
|
+ }
|
|
|
|
|
+ rule.pattern = etReplaceRule.text.toString()
|
|
|
|
|
+ rule.isRegex = cbUseRegex.isChecked
|
|
|
|
|
+ rule.replacement = etReplaceTo.text.toString()
|
|
|
|
|
+ rule.scopeTitle = cbScopeTitle.isChecked
|
|
|
|
|
+ rule.scopeContent = cbScopeContent.isChecked
|
|
|
|
|
+ rule.scope = etScope.text.toString()
|
|
|
|
|
+ rule.excludeScope = etExcludeScope.text.toString()
|
|
|
|
|
+ rule.timeoutMillisecond = etTimeout.text.toString().ifEmpty { "3000" }.toLong()
|
|
|
|
|
+
|
|
|
|
|
+ rule
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ override fun helpActions(): List<SelectItem<String>> =
|
|
|
|
|
+ listOf(SelectItem("正则教程", "regexHelp"))
|
|
|
|
|
+
|
|
|
override fun onHelpActionSelect(action: String) {
|
|
override fun onHelpActionSelect(action: String) {
|
|
|
- when (action) {
|
|
|
|
|
- "regexHelp" -> showHelp("regexHelp")
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (action == "regexHelp") showHelp("regexHelp")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun sendText(text: String) {
|
|
override fun sendText(text: String) {
|
|
@@ -144,15 +257,12 @@ class ReplaceEditActivity :
|
|
|
if (view is EditText) {
|
|
if (view is EditText) {
|
|
|
val start = view.selectionStart
|
|
val start = view.selectionStart
|
|
|
val end = view.selectionEnd
|
|
val end = view.selectionEnd
|
|
|
- //获取EditText的文字
|
|
|
|
|
val edit = view.editableText
|
|
val edit = view.editableText
|
|
|
if (start < 0 || start >= edit.length) {
|
|
if (start < 0 || start >= edit.length) {
|
|
|
edit.append(text)
|
|
edit.append(text)
|
|
|
} else {
|
|
} else {
|
|
|
- //光标所在位置插入文字
|
|
|
|
|
edit.replace(start, end, text)
|
|
edit.replace(start, end, text)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
}
|
|
}
|