Rename
This commit is contained in:
1 parent
b86d90021c
commit
6c0757c91b
972 files changed
+6885
-7321
No files matched your search
@@ -0,0 +1,144 @@
|
||||
package io.legato.kazusa.ui.replace
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseDialogFragment
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.databinding.DialogEditTextBinding
|
||||
import io.legato.kazusa.databinding.DialogRecyclerViewBinding
|
||||
import io.legato.kazusa.databinding.ItemGroupManageBinding
|
||||
import io.legato.kazusa.lib.dialogs.alert
|
||||
//import io.legado.app.lib.theme.backgroundColor
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
import io.legato.kazusa.ui.widget.recycler.VerticalDivider
|
||||
import io.legato.kazusa.utils.applyTint
|
||||
import io.legato.kazusa.utils.requestInputMethod
|
||||
import io.legato.kazusa.utils.setLayout
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private val viewModel: ReplaceRuleViewModel by activityViewModels()
|
||||
private val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
||||
private val adapter by lazy { GroupAdapter(requireContext()) }
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setLayout(0.9f, 0.9f)
|
||||
}
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// view.setBackgroundColor(backgroundColor)
|
||||
// binding.toolBar.setBackgroundColor(primaryColor)
|
||||
initView()
|
||||
initData()
|
||||
}
|
||||
|
||||
private fun initView() = binding.run {
|
||||
toolBar.title = getString(R.string.group_manage)
|
||||
toolBar.inflateMenu(R.menu.group_manage)
|
||||
toolBar.menu.applyTint(requireContext())
|
||||
toolBar.setOnMenuItemClickListener(this@GroupManageDialog)
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.addItemDecoration(VerticalDivider(requireContext()))
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
lifecycleScope.launch {
|
||||
appDb.replaceRuleDao.flowGroups().conflate().collect {
|
||||
adapter.setItems(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
R.id.menu_add -> addGroup()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private fun addGroup() {
|
||||
alert(title = getString(R.string.add_group)) {
|
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
||||
editView.setHint(R.string.group_name)
|
||||
}
|
||||
customView { alertBinding.root }
|
||||
okButton {
|
||||
alertBinding.editView.text?.toString()?.let {
|
||||
if (it.isNotBlank()) {
|
||||
viewModel.addGroup(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}.requestInputMethod()
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private fun editGroup(group: String) {
|
||||
alert(title = getString(R.string.group_edit)) {
|
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
||||
editView.setHint(R.string.group_name)
|
||||
editView.setText(group)
|
||||
}
|
||||
customView { alertBinding.root }
|
||||
okButton {
|
||||
viewModel.upGroup(group, alertBinding.editView.text?.toString())
|
||||
}
|
||||
cancelButton()
|
||||
}.requestInputMethod()
|
||||
}
|
||||
|
||||
private inner class GroupAdapter(context: Context) :
|
||||
RecyclerAdapter<String, ItemGroupManageBinding>(context) {
|
||||
|
||||
override fun getViewBinding(parent: ViewGroup): ItemGroupManageBinding {
|
||||
return ItemGroupManageBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: ItemViewHolder,
|
||||
binding: ItemGroupManageBinding,
|
||||
item: String,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
binding.run {
|
||||
//root.setBackgroundColor(context.backgroundColor)
|
||||
tvGroup.text = item
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemGroupManageBinding) {
|
||||
binding.apply {
|
||||
tvEdit.setOnClickListener {
|
||||
getItem(holder.layoutPosition)?.let {
|
||||
editGroup(it)
|
||||
}
|
||||
}
|
||||
|
||||
tvDel.setOnClickListener {
|
||||
getItem(holder.layoutPosition)?.let { viewModel.delGroup(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
package io.legato.kazusa.ui.replace
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.SubMenu
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.VMBaseActivity
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.databinding.ActivityReplaceRuleBinding
|
||||
import io.legato.kazusa.databinding.DialogEditTextBinding
|
||||
import io.legato.kazusa.help.DirectLinkUpload
|
||||
import io.legato.kazusa.help.book.ContentProcessor
|
||||
import io.legato.kazusa.help.coroutine.Coroutine
|
||||
import io.legato.kazusa.lib.dialogs.alert
|
||||
//import io.legado.app.lib.theme.primaryColor
|
||||
//import io.legado.app.lib.theme.primaryTextColor
|
||||
import io.legato.kazusa.ui.association.ImportReplaceRuleDialog
|
||||
import io.legato.kazusa.ui.file.HandleFileContract
|
||||
import io.legato.kazusa.ui.qrcode.QrCodeResult
|
||||
import io.legato.kazusa.ui.replace.edit.ReplaceEditActivity
|
||||
import io.legato.kazusa.ui.widget.SelectActionBar
|
||||
import io.legato.kazusa.ui.widget.recycler.DragSelectTouchHelper
|
||||
import io.legato.kazusa.ui.widget.recycler.ItemTouchCallback
|
||||
import io.legato.kazusa.ui.widget.recycler.VerticalDivider
|
||||
import io.legato.kazusa.utils.ACache
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.hideSoftInput
|
||||
import io.legato.kazusa.utils.isAbsUrl
|
||||
import io.legato.kazusa.utils.launch
|
||||
import io.legato.kazusa.utils.readText
|
||||
import io.legato.kazusa.utils.sendToClip
|
||||
import io.legato.kazusa.utils.shouldHideSoftInput
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.showHelp
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import io.legato.kazusa.utils.transaction
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 替换规则管理
|
||||
*/
|
||||
class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRuleViewModel>(),
|
||||
SearchView.OnQueryTextListener,
|
||||
PopupMenu.OnMenuItemClickListener,
|
||||
SelectActionBar.CallBack,
|
||||
ReplaceRuleAdapter.CallBack {
|
||||
override val binding by viewBinding(ActivityReplaceRuleBinding::inflate)
|
||||
override val viewModel by viewModels<ReplaceRuleViewModel>()
|
||||
private val importRecordKey = "replaceRuleRecordKey"
|
||||
private val adapter by lazy { ReplaceRuleAdapter(this, this) }
|
||||
private val searchView: SearchView by lazy {
|
||||
binding.titleBar.findViewById(R.id.search_view)
|
||||
}
|
||||
private var groups = hashSetOf<String>()
|
||||
private var groupMenu: SubMenu? = null
|
||||
private var replaceRuleFlowJob: Job? = null
|
||||
private var dataInit = false
|
||||
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
showDialogFragment(
|
||||
ImportReplaceRuleDialog(it)
|
||||
)
|
||||
}
|
||||
private val editActivity =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
setResult(RESULT_OK)
|
||||
}
|
||||
}
|
||||
private val importDoc = registerForActivityResult(HandleFileContract()) {
|
||||
kotlin.runCatching {
|
||||
it.uri?.readText(this)?.let {
|
||||
showDialogFragment(
|
||||
ImportReplaceRuleDialog(it)
|
||||
)
|
||||
}
|
||||
}.onFailure {
|
||||
toastOnUi("readTextError:${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
private val exportResult = registerForActivityResult(HandleFileContract()) {
|
||||
it.uri?.let { uri ->
|
||||
alert(R.string.export_success) {
|
||||
if (uri.toString().isAbsUrl()) {
|
||||
setMessage(DirectLinkUpload.getSummary())
|
||||
}
|
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
||||
editView.hint = getString(R.string.path)
|
||||
editView.setText(uri.toString())
|
||||
}
|
||||
customView { alertBinding.root }
|
||||
okButton {
|
||||
sendToClip(uri.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
initRecyclerView()
|
||||
initSearchView()
|
||||
initSelectActionView()
|
||||
observeReplaceRuleData()
|
||||
observeGroupData()
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
if (ev.action == MotionEvent.ACTION_DOWN) {
|
||||
currentFocus?.let {
|
||||
if (it.shouldHideSoftInput(ev)) {
|
||||
it.post {
|
||||
it.clearFocus()
|
||||
it.hideSoftInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.replace_rule, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
groupMenu = menu.findItem(R.id.menu_group)?.subMenu
|
||||
upGroupMenu()
|
||||
return super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
private fun initRecyclerView() {
|
||||
//binding.recyclerView.setEdgeEffectColor(primaryColor)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.recyclerView.addItemDecoration(VerticalDivider(this))
|
||||
val itemTouchCallback = ItemTouchCallback(adapter)
|
||||
itemTouchCallback.isCanDrag = true
|
||||
val dragSelectTouchHelper: DragSelectTouchHelper =
|
||||
DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)
|
||||
dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)
|
||||
// When this page is opened, it is in selection mode
|
||||
dragSelectTouchHelper.activeSlideSelect()
|
||||
|
||||
// Note: need judge selection first, so add ItemTouchHelper after it.
|
||||
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
//searchView.applyTint(primaryTextColor)
|
||||
searchView.queryHint = getString(R.string.replace_purify_search)
|
||||
searchView.setOnQueryTextListener(this)
|
||||
}
|
||||
|
||||
override fun selectAll(selectAll: Boolean) {
|
||||
if (selectAll) {
|
||||
adapter.selectAll()
|
||||
} else {
|
||||
adapter.revertSelection()
|
||||
}
|
||||
}
|
||||
|
||||
override fun revertSelection() {
|
||||
adapter.revertSelection()
|
||||
}
|
||||
|
||||
override fun onClickSelectBarMainAction() {
|
||||
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
|
||||
yesButton { viewModel.delSelection(adapter.selection) }
|
||||
noButton()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSelectActionView() {
|
||||
binding.selectActionBar.setMainActionText(R.string.delete)
|
||||
binding.selectActionBar.inflateMenu(R.menu.replace_rule_sel)
|
||||
binding.selectActionBar.setOnMenuItemClickListener(this)
|
||||
binding.selectActionBar.setCallBack(this)
|
||||
}
|
||||
|
||||
private fun observeReplaceRuleData(searchKey: String? = null) {
|
||||
dataInit = false
|
||||
replaceRuleFlowJob?.cancel()
|
||||
replaceRuleFlowJob = lifecycleScope.launch {
|
||||
when {
|
||||
searchKey.isNullOrEmpty() -> {
|
||||
appDb.replaceRuleDao.flowAll()
|
||||
}
|
||||
|
||||
searchKey == getString(R.string.no_group) -> {
|
||||
appDb.replaceRuleDao.flowNoGroup()
|
||||
}
|
||||
|
||||
searchKey.startsWith("group:") -> {
|
||||
val key = searchKey.substringAfter("group:")
|
||||
appDb.replaceRuleDao.flowGroupSearch("%$key%")
|
||||
}
|
||||
else -> {
|
||||
appDb.replaceRuleDao.flowSearch("%$searchKey%")
|
||||
}
|
||||
}.catch {
|
||||
AppLog.put("替换规则管理界面更新数据出错", it)
|
||||
}.flowOn(IO).conflate().collect {
|
||||
if (dataInit) {
|
||||
setResult(Activity.RESULT_OK)
|
||||
}
|
||||
adapter.setItems(it, adapter.diffItemCallBack)
|
||||
dataInit = true
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeGroupData() {
|
||||
lifecycleScope.launch {
|
||||
appDb.replaceRuleDao.flowGroups().collect {
|
||||
groups.clear()
|
||||
groups.addAll(it)
|
||||
upGroupMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_add_replace_rule ->
|
||||
editActivity.launch(ReplaceEditActivity.startIntent(this))
|
||||
R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()
|
||||
R.id.menu_del_selection -> viewModel.delSelection(adapter.selection)
|
||||
R.id.menu_import_onLine -> showImportDialog()
|
||||
R.id.menu_import_local -> importDoc.launch {
|
||||
mode = HandleFileContract.FILE
|
||||
allowExtensions = arrayOf("txt", "json")
|
||||
}
|
||||
R.id.menu_import_qr -> qrCodeResult.launch()
|
||||
R.id.menu_help -> showHelp("replaceRuleHelp")
|
||||
R.id.menu_group_null -> {
|
||||
searchView.setQuery(getString(R.string.no_group), true)
|
||||
}
|
||||
else -> if (item.groupId == R.id.replace_group) {
|
||||
searchView.setQuery("group:${item.title}", true)
|
||||
}
|
||||
}
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection)
|
||||
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection)
|
||||
R.id.menu_top_sel -> viewModel.topSelect(adapter.selection)
|
||||
R.id.menu_bottom_sel -> viewModel.bottomSelect(adapter.selection)
|
||||
R.id.menu_export_selection -> exportResult.launch {
|
||||
mode = HandleFileContract.EXPORT
|
||||
fileData = HandleFileContract.FileData(
|
||||
"exportReplaceRule.json",
|
||||
GSON.toJson(adapter.selection).toByteArray(),
|
||||
"application/json"
|
||||
)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun upGroupMenu() = groupMenu?.transaction { menu ->
|
||||
menu.removeGroup(R.id.replace_group)
|
||||
groups.forEach {
|
||||
menu.add(R.id.replace_group, Menu.NONE, Menu.NONE, it)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private fun showImportDialog() {
|
||||
val aCache = ACache.get(cacheDir = false)
|
||||
val cacheUrls: MutableList<String> = aCache
|
||||
.getAsString(importRecordKey)
|
||||
?.splitNotBlank(",")
|
||||
?.toMutableList() ?: mutableListOf()
|
||||
alert(titleResource = R.string.import_on_line) {
|
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
|
||||
editView.hint = "url"
|
||||
editView.setFilterValues(cacheUrls)
|
||||
editView.delCallBack = {
|
||||
cacheUrls.remove(it)
|
||||
aCache.put(importRecordKey, cacheUrls.joinToString(","))
|
||||
}
|
||||
}
|
||||
customView { alertBinding.root }
|
||||
okButton {
|
||||
val text = alertBinding.editView.text?.toString()
|
||||
text?.let {
|
||||
if (it.isAbsUrl() && !cacheUrls.contains(it)) {
|
||||
cacheUrls.add(0, it)
|
||||
aCache.put(importRecordKey, cacheUrls.joinToString(","))
|
||||
}
|
||||
showDialogFragment(
|
||||
ImportReplaceRuleDialog(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
observeReplaceRuleData(newText)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Coroutine.async { ContentProcessor.upReplaceRules() }
|
||||
}
|
||||
|
||||
override fun upCountView() {
|
||||
binding.selectActionBar.upCountView(
|
||||
adapter.selection.size,
|
||||
adapter.itemCount
|
||||
)
|
||||
}
|
||||
|
||||
override fun update(vararg rule: ReplaceRule) {
|
||||
setResult(RESULT_OK)
|
||||
viewModel.update(*rule)
|
||||
}
|
||||
|
||||
override fun delete(rule: ReplaceRule) {
|
||||
alert(R.string.draw) {
|
||||
setMessage(getString(R.string.sure_del) + "\n" + rule.name)
|
||||
noButton()
|
||||
yesButton {
|
||||
setResult(RESULT_OK)
|
||||
viewModel.delete(rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun edit(rule: ReplaceRule) {
|
||||
setResult(RESULT_OK)
|
||||
editActivity.launch(ReplaceEditActivity.startIntent(this, rule.id))
|
||||
}
|
||||
|
||||
override fun toTop(rule: ReplaceRule) {
|
||||
setResult(RESULT_OK)
|
||||
viewModel.toTop(rule)
|
||||
}
|
||||
|
||||
override fun toBottom(rule: ReplaceRule) {
|
||||
setResult(RESULT_OK)
|
||||
viewModel.toBottom(rule)
|
||||
}
|
||||
|
||||
override fun upOrder() {
|
||||
setResult(RESULT_OK)
|
||||
viewModel.upOrder()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package io.legato.kazusa.ui.replace
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupMenu
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.adapter.ItemViewHolder
|
||||
import io.legato.kazusa.base.adapter.RecyclerAdapter
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.databinding.ItemReplaceRuleBinding
|
||||
//import io.legado.app.lib.theme.backgroundColor
|
||||
import io.legato.kazusa.ui.widget.recycler.DragSelectTouchHelper
|
||||
import io.legato.kazusa.ui.widget.recycler.ItemTouchCallback
|
||||
|
||||
|
||||
class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
|
||||
RecyclerAdapter<ReplaceRule, ItemReplaceRuleBinding>(context),
|
||||
ItemTouchCallback.Callback {
|
||||
|
||||
private val selected = linkedSetOf<ReplaceRule>()
|
||||
|
||||
val selection: List<ReplaceRule>
|
||||
get() {
|
||||
return getItems().filter {
|
||||
selected.contains(it)
|
||||
}
|
||||
}
|
||||
|
||||
val diffItemCallBack = object : DiffUtil.ItemCallback<ReplaceRule>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: ReplaceRule, newItem: ReplaceRule): Any? {
|
||||
val payload = Bundle()
|
||||
if (oldItem.name != newItem.name
|
||||
|| oldItem.group != newItem.group
|
||||
) {
|
||||
payload.putBoolean("upName", true)
|
||||
}
|
||||
if (oldItem.isEnabled != newItem.isEnabled) {
|
||||
payload.putBoolean("enabled", newItem.isEnabled)
|
||||
}
|
||||
if (payload.isEmpty) {
|
||||
return null
|
||||
}
|
||||
return payload
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAll() {
|
||||
getItems().forEach {
|
||||
selected.add(it)
|
||||
}
|
||||
notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null)))
|
||||
callBack.upCountView()
|
||||
}
|
||||
|
||||
fun revertSelection() {
|
||||
getItems().forEach {
|
||||
if (selected.contains(it)) {
|
||||
selected.remove(it)
|
||||
} else {
|
||||
selected.add(it)
|
||||
}
|
||||
}
|
||||
notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null)))
|
||||
callBack.upCountView()
|
||||
}
|
||||
|
||||
override fun getViewBinding(parent: ViewGroup): ItemReplaceRuleBinding {
|
||||
return ItemReplaceRuleBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
|
||||
override fun onCurrentListChanged() {
|
||||
callBack.upCountView()
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: ItemViewHolder,
|
||||
binding: ItemReplaceRuleBinding,
|
||||
item: ReplaceRule,
|
||||
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 {
|
||||
for (i in payloads.indices) {
|
||||
val bundle = payloads[i] as Bundle
|
||||
bundle.keySet().forEach {
|
||||
when (it) {
|
||||
"selected" -> cbName.isChecked = selected.contains(item)
|
||||
"upName" -> cbName.text = item.getDisplayNameGroup()
|
||||
"enabled" -> swtEnabled.isChecked = item.isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemReplaceRuleBinding) {
|
||||
binding.apply {
|
||||
swtEnabled.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
if (buttonView.isPressed) {
|
||||
getItem(holder.layoutPosition)?.let {
|
||||
it.isEnabled = isChecked
|
||||
callBack.update(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
ivEdit.setOnClickListener {
|
||||
getItem(holder.layoutPosition)?.let {
|
||||
callBack.edit(it)
|
||||
}
|
||||
}
|
||||
cbName.setOnClickListener {
|
||||
getItem(holder.layoutPosition)?.let {
|
||||
if (cbName.isChecked) {
|
||||
selected.add(it)
|
||||
} else {
|
||||
selected.remove(it)
|
||||
}
|
||||
}
|
||||
callBack.upCountView()
|
||||
}
|
||||
ivMenuMore.setOnClickListener {
|
||||
showMenu(ivMenuMore, holder.layoutPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMenu(view: View, position: Int) {
|
||||
val item = getItem(position) ?: return
|
||||
val popupMenu = PopupMenu(context, view)
|
||||
popupMenu.inflate(R.menu.replace_rule_item)
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.menu_top -> callBack.toTop(item)
|
||||
R.id.menu_bottom -> callBack.toBottom(item)
|
||||
R.id.menu_del -> {
|
||||
callBack.delete(item)
|
||||
selected.remove(item)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
|
||||
val srcItem = getItem(srcPosition)
|
||||
val targetItem = getItem(targetPosition)
|
||||
if (srcItem != null && targetItem != null) {
|
||||
if (srcItem.order == targetItem.order) {
|
||||
callBack.upOrder()
|
||||
} else {
|
||||
val srcOrder = srcItem.order
|
||||
srcItem.order = targetItem.order
|
||||
targetItem.order = srcOrder
|
||||
movedItems.add(srcItem)
|
||||
movedItems.add(targetItem)
|
||||
}
|
||||
}
|
||||
swapItem(srcPosition, targetPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
private val movedItems = linkedSetOf<ReplaceRule>()
|
||||
|
||||
override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||
if (movedItems.isNotEmpty()) {
|
||||
callBack.update(*movedItems.toTypedArray())
|
||||
movedItems.clear()
|
||||
}
|
||||
}
|
||||
|
||||
val dragSelectCallback: DragSelectTouchHelper.Callback =
|
||||
object : DragSelectTouchHelper.AdvanceCallback<ReplaceRule>(Mode.ToggleAndReverse) {
|
||||
override fun currentSelectedId(): MutableSet<ReplaceRule> {
|
||||
return selected
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): ReplaceRule {
|
||||
return getItem(position)!!
|
||||
}
|
||||
|
||||
override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {
|
||||
getItem(position)?.let {
|
||||
if (isSelected) {
|
||||
selected.add(it)
|
||||
} else {
|
||||
selected.remove(it)
|
||||
}
|
||||
notifyItemChanged(position, bundleOf(Pair("selected", null)))
|
||||
callBack.upCountView()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
interface CallBack {
|
||||
fun update(vararg rule: ReplaceRule)
|
||||
fun delete(rule: ReplaceRule)
|
||||
fun edit(rule: ReplaceRule)
|
||||
fun toTop(rule: ReplaceRule)
|
||||
fun toBottom(rule: ReplaceRule)
|
||||
fun upOrder()
|
||||
fun upCountView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package io.legato.kazusa.ui.replace
|
||||
|
||||
import android.app.Application
|
||||
import android.text.TextUtils
|
||||
import io.legato.kazusa.base.BaseViewModel
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
|
||||
/**
|
||||
* 替换规则数据修改
|
||||
* 修改数据要copy,直接修改会导致界面不刷新
|
||||
*/
|
||||
class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
fun update(vararg rule: ReplaceRule) {
|
||||
execute {
|
||||
appDb.replaceRuleDao.update(*rule)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(rule: ReplaceRule) {
|
||||
execute {
|
||||
appDb.replaceRuleDao.delete(rule)
|
||||
}
|
||||
}
|
||||
|
||||
fun toTop(rule: ReplaceRule) {
|
||||
execute {
|
||||
rule.order = appDb.replaceRuleDao.minOrder - 1
|
||||
appDb.replaceRuleDao.update(rule)
|
||||
}
|
||||
}
|
||||
|
||||
fun topSelect(rules: List<ReplaceRule>) {
|
||||
execute {
|
||||
var minOrder = appDb.replaceRuleDao.minOrder - rules.size
|
||||
rules.forEach {
|
||||
it.order = ++minOrder
|
||||
}
|
||||
appDb.replaceRuleDao.update(*rules.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun toBottom(rule: ReplaceRule) {
|
||||
execute {
|
||||
rule.order = appDb.replaceRuleDao.maxOrder + 1
|
||||
appDb.replaceRuleDao.update(rule)
|
||||
}
|
||||
}
|
||||
|
||||
fun bottomSelect(rules: List<ReplaceRule>) {
|
||||
execute {
|
||||
var maxOrder = appDb.replaceRuleDao.maxOrder
|
||||
rules.forEach {
|
||||
it.order = maxOrder++
|
||||
}
|
||||
appDb.replaceRuleDao.update(*rules.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun upOrder() {
|
||||
execute {
|
||||
val rules = appDb.replaceRuleDao.all
|
||||
for ((index, rule) in rules.withIndex()) {
|
||||
rule.order = index + 1
|
||||
}
|
||||
appDb.replaceRuleDao.update(*rules.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun enableSelection(rules: List<ReplaceRule>) {
|
||||
execute {
|
||||
val array = Array(rules.size) {
|
||||
rules[it].copy(isEnabled = true)
|
||||
}
|
||||
appDb.replaceRuleDao.update(*array)
|
||||
}
|
||||
}
|
||||
|
||||
fun disableSelection(rules: List<ReplaceRule>) {
|
||||
execute {
|
||||
val array = Array(rules.size) {
|
||||
rules[it].copy(isEnabled = false)
|
||||
}
|
||||
appDb.replaceRuleDao.update(*array)
|
||||
}
|
||||
}
|
||||
|
||||
fun delSelection(rules: List<ReplaceRule>) {
|
||||
execute {
|
||||
appDb.replaceRuleDao.delete(*rules.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun addGroup(group: String) {
|
||||
execute {
|
||||
val sources = appDb.replaceRuleDao.noGroup
|
||||
sources.forEach { source ->
|
||||
source.group = group
|
||||
}
|
||||
appDb.replaceRuleDao.update(*sources.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun upGroup(oldGroup: String, newGroup: String?) {
|
||||
execute {
|
||||
val sources = appDb.replaceRuleDao.getByGroup(oldGroup)
|
||||
sources.forEach { source ->
|
||||
source.group?.splitNotBlank(",")?.toHashSet()?.let {
|
||||
it.remove(oldGroup)
|
||||
if (!newGroup.isNullOrEmpty())
|
||||
it.add(newGroup)
|
||||
source.group = TextUtils.join(",", it)
|
||||
}
|
||||
}
|
||||
appDb.replaceRuleDao.update(*sources.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun delGroup(group: String) {
|
||||
execute {
|
||||
execute {
|
||||
val sources = appDb.replaceRuleDao.getByGroup(group)
|
||||
sources.forEach { source ->
|
||||
source.group?.splitNotBlank(",")?.toHashSet()?.let {
|
||||
it.remove(group)
|
||||
source.group = TextUtils.join(",", it)
|
||||
}
|
||||
}
|
||||
appDb.replaceRuleDao.update(*sources.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package io.legato.kazusa.ui.replace.edit
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.EditText
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.VMBaseActivity
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.databinding.ActivityReplaceEditBinding
|
||||
import io.legato.kazusa.lib.dialogs.SelectItem
|
||||
import io.legato.kazusa.ui.widget.keyboard.KeyboardToolPop
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.imeHeight
|
||||
import io.legato.kazusa.utils.sendToClip
|
||||
import io.legato.kazusa.utils.setOnApplyWindowInsetsListenerCompat
|
||||
import io.legato.kazusa.utils.showHelp
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
|
||||
/**
|
||||
* 编辑替换规则
|
||||
*/
|
||||
class ReplaceEditActivity :
|
||||
VMBaseActivity<ActivityReplaceEditBinding, ReplaceEditViewModel>(),
|
||||
KeyboardToolPop.CallBack {
|
||||
|
||||
companion object {
|
||||
|
||||
fun startIntent(
|
||||
context: Context,
|
||||
id: Long = -1,
|
||||
pattern: String? = null,
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override val binding by viewBinding(ActivityReplaceEditBinding::inflate)
|
||||
override val viewModel by viewModels<ReplaceEditViewModel>()
|
||||
|
||||
private val softKeyboardTool by lazy {
|
||||
KeyboardToolPop(this, lifecycleScope, binding.root, this)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
softKeyboardTool.attachToWindow(window)
|
||||
initView()
|
||||
viewModel.initData(intent) {
|
||||
upReplaceView(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.replace_edit, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_save -> viewModel.save(getReplaceRule()) {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
R.id.menu_copy_rule -> sendToClip(GSON.toJson(getReplaceRule()))
|
||||
R.id.menu_paste_rule -> viewModel.pasteRule {
|
||||
upReplaceView(it)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
softKeyboardTool.dismiss()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.ivHelp.setOnClickListener {
|
||||
showHelp("regexHelp")
|
||||
}
|
||||
binding.root.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
|
||||
softKeyboardTool.initialPadding = windowInsets.imeHeight
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
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 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")
|
||||
)
|
||||
}
|
||||
|
||||
override fun onHelpActionSelect(action: String) {
|
||||
when (action) {
|
||||
"regexHelp" -> showHelp("regexHelp")
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendText(text: String) {
|
||||
if (text.isBlank()) return
|
||||
val view = window?.decorView?.findFocus()
|
||||
if (view is EditText) {
|
||||
val start = view.selectionStart
|
||||
val end = view.selectionEnd
|
||||
//获取EditText的文字
|
||||
val edit = view.editableText
|
||||
if (start < 0 || start >= edit.length) {
|
||||
edit.append(text)
|
||||
} else {
|
||||
//光标所在位置插入文字
|
||||
edit.replace(start, end, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.legato.kazusa.ui.replace.edit
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import io.legato.kazusa.base.BaseViewModel
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.ReplaceRule
|
||||
import io.legato.kazusa.exception.NoStackTraceException
|
||||
import io.legato.kazusa.utils.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
class ReplaceEditViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
var replaceRule: ReplaceRule? = null
|
||||
|
||||
fun initData(intent: Intent, finally: (replaceRule: ReplaceRule) -> Unit) {
|
||||
execute {
|
||||
val id = intent.getLongExtra("id", -1)
|
||||
replaceRule = if (id > 0) {
|
||||
appDb.replaceRuleDao.findById(id)
|
||||
} else {
|
||||
val pattern = intent.getStringExtra("pattern") ?: ""
|
||||
val isRegex = intent.getBooleanExtra("isRegex", false)
|
||||
val scope = intent.getStringExtra("scope")
|
||||
ReplaceRule(
|
||||
name = pattern,
|
||||
pattern = pattern,
|
||||
isRegex = isRegex,
|
||||
scope = scope
|
||||
)
|
||||
}
|
||||
}.onFinally {
|
||||
replaceRule?.let {
|
||||
finally(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pasteRule(success: (ReplaceRule) -> Unit) {
|
||||
execute(context = Dispatchers.Main) {
|
||||
val text = context.getClipText()
|
||||
if (text.isNullOrBlank()) {
|
||||
throw NoStackTraceException("剪贴板为空")
|
||||
}
|
||||
GSON.fromJsonObject<ReplaceRule>(text).getOrNull()
|
||||
?: throw NoStackTraceException("格式不对")
|
||||
}.onSuccess {
|
||||
success.invoke(it)
|
||||
}.onError {
|
||||
context.toastOnUi(it.localizedMessage ?: "Error")
|
||||
it.printOnDebug()
|
||||
}
|
||||
}
|
||||
|
||||
fun save(replaceRule: ReplaceRule, success: () -> Unit) {
|
||||
execute {
|
||||
replaceRule.checkValid()
|
||||
if (replaceRule.order == Int.MIN_VALUE) {
|
||||
replaceRule.order = appDb.replaceRuleDao.maxOrder + 1
|
||||
}
|
||||
appDb.replaceRuleDao.insert(replaceRule)
|
||||
}.onSuccess {
|
||||
success()
|
||||
}.onError {
|
||||
context.toastOnUi("save error, ${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user