[新增] 基于Compose的替换规则页面,功能仍不完善
This commit is contained in:
10 files changed
+1174
-632
No files matched your search
@@ -120,6 +120,21 @@ interface ReplaceRuleDao {
|
|||||||
)
|
)
|
||||||
fun findEnabledByTitleScope(name: String, origin: String): List<ReplaceRule>
|
fun findEnabledByTitleScope(name: String, origin: String): List<ReplaceRule>
|
||||||
|
|
||||||
|
@Query("UPDATE replace_rules SET isEnabled = :enabled WHERE id = :id")
|
||||||
|
suspend fun updateEnabled(id: Long, enabled: Boolean)
|
||||||
|
|
||||||
|
@Query("UPDATE replace_rules SET isEnabled = :enabled WHERE id IN (:ids)")
|
||||||
|
suspend fun updateEnabled(ids: List<Long>, enabled: Boolean)
|
||||||
|
|
||||||
|
@Query("DELETE FROM replace_rules WHERE id IN (:ids)")
|
||||||
|
suspend fun deleteByIds(ids: List<Long>)
|
||||||
|
|
||||||
|
@Query("UPDATE replace_rules SET sortOrder = :order WHERE id = :id")
|
||||||
|
suspend fun updateOrder(id: Long, order: Int)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM replace_rules WHERE id IN (:ids)")
|
||||||
|
fun getByIds(ids: Set<Long>): List<ReplaceRule>
|
||||||
|
|
||||||
@Query("UPDATE replace_rules SET `group` = NULL WHERE `group` IN (:groups)")
|
@Query("UPDATE replace_rules SET `group` = NULL WHERE `group` IN (:groups)")
|
||||||
suspend fun clearGroups(groups: List<String>)
|
suspend fun clearGroups(groups: List<String>)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import io.legado.app.data.appDb
|
||||||
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ReplaceRuleRepository {
|
||||||
|
|
||||||
|
fun flowGroups(): Flow<List<String>> {
|
||||||
|
return appDb.replaceRuleDao.flowGroups().flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flowAll(): Flow<List<ReplaceRule>> {
|
||||||
|
return appDb.replaceRuleDao.flowAll().flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flowNoGroup(): Flow<List<ReplaceRule>> {
|
||||||
|
return appDb.replaceRuleDao.flowNoGroup().flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flowGroupSearch(key: String): Flow<List<ReplaceRule>> {
|
||||||
|
return appDb.replaceRuleDao.flowGroupSearch(key).flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flowSearch(key: String): Flow<List<ReplaceRule>> {
|
||||||
|
return appDb.replaceRuleDao.flowSearch(key).flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun update(vararg rule: ReplaceRule) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
appDb.replaceRuleDao.update(*rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun delete(rule: ReplaceRule) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
appDb.replaceRuleDao.delete(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun toTop(rule: ReplaceRule) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
rule.order = -1
|
||||||
|
appDb.replaceRuleDao.update(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun toBottom(rule: ReplaceRule) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
rule.order = -2
|
||||||
|
appDb.replaceRuleDao.update(rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun upOrder() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val rules = appDb.replaceRuleDao.all
|
||||||
|
var normalOrder = 1
|
||||||
|
rules.forEach { rule ->
|
||||||
|
if (rule.order >= 0) {
|
||||||
|
rule.order = normalOrder++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appDb.replaceRuleDao.update(*rules.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun enableSelection(rules: List<ReplaceRule>) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val array = Array(rules.size) {
|
||||||
|
rules[it].copy(isEnabled = true)
|
||||||
|
}
|
||||||
|
appDb.replaceRuleDao.update(*array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun disableSelection(rules: List<ReplaceRule>) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val array = Array(rules.size) {
|
||||||
|
rules[it].copy(isEnabled = false)
|
||||||
|
}
|
||||||
|
appDb.replaceRuleDao.update(*array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addGroup(group: String) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val sources = appDb.replaceRuleDao.noGroup
|
||||||
|
sources.forEach { source ->
|
||||||
|
source.group = group
|
||||||
|
}
|
||||||
|
appDb.replaceRuleDao.update(*sources.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun upGroup(oldGroup: String, newGroup: String?) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun delGroup(group: String) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun enableByIds(ids: Set<Long>) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (ids.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
val rules = appDb.replaceRuleDao.getByIds(ids)
|
||||||
|
val updated = rules.map { it.copy(isEnabled = true) }
|
||||||
|
appDb.replaceRuleDao.update(*updated.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun disableByIds(ids: Set<Long>) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (ids.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
val rules = appDb.replaceRuleDao.getByIds(ids)
|
||||||
|
val updated = rules.map { it.copy(isEnabled = false) }
|
||||||
|
appDb.replaceRuleDao.update(*updated.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteByIds(ids: Set<Long>) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (ids.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
val rules = appDb.replaceRuleDao.getByIds(ids)
|
||||||
|
appDb.replaceRuleDao.delete(*rules.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun topByIds(ids: Set<Long>) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (ids.isEmpty()) return@withContext
|
||||||
|
|
||||||
|
val rules = appDb.replaceRuleDao.getByIds(ids)
|
||||||
|
val updated = rules.map { it.copy(order = -1) }
|
||||||
|
appDb.replaceRuleDao.update(*updated.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bottomByIds(ids: Set<Long>) =
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import io.legado.app.data.repository.ReadRecordRepository
|
|||||||
import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
|
import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
|
||||||
import io.legado.app.ui.book.explore.ExploreShowViewModel
|
import io.legado.app.ui.book.explore.ExploreShowViewModel
|
||||||
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
||||||
|
import io.legado.app.ui.replace.ReplaceRuleViewModel
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
import io.legado.app.ui.replace.edit.ReplaceEditViewModel
|
||||||
import org.koin.android.ext.koin.androidApplication
|
import org.koin.android.ext.koin.androidApplication
|
||||||
import org.koin.core.module.dsl.viewModel
|
import org.koin.core.module.dsl.viewModel
|
||||||
@@ -16,6 +17,8 @@ val appModule = module {
|
|||||||
|
|
||||||
viewModel { ReplaceEditViewModel(get(), get(), get()) }
|
viewModel { ReplaceEditViewModel(get(), get(), get()) }
|
||||||
|
|
||||||
|
viewModel { ReplaceRuleViewModel(androidApplication()) }
|
||||||
|
|
||||||
// ReadRecord
|
// ReadRecord
|
||||||
single { get<AppDatabase>().readRecordDao }
|
single { get<AppDatabase>().readRecordDao }
|
||||||
single { get<AppDatabase>().bookDao }
|
single { get<AppDatabase>().bookDao }
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
package io.legado.app.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.legado.app.R
|
|
||||||
import io.legado.app.base.BaseDialogFragment
|
|
||||||
import io.legado.app.base.adapter.ItemViewHolder
|
|
||||||
import io.legado.app.base.adapter.RecyclerAdapter
|
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.databinding.DialogEditTextBinding
|
|
||||||
import io.legado.app.databinding.DialogRecyclerViewBinding
|
|
||||||
import io.legado.app.databinding.ItemGroupManageBinding
|
|
||||||
import io.legado.app.lib.dialogs.alert
|
|
||||||
//import io.legado.app.lib.theme.backgroundColor
|
|
||||||
//import io.legado.app.lib.theme.primaryColor
|
|
||||||
import io.legado.app.ui.widget.recycler.VerticalDivider
|
|
||||||
import io.legado.app.utils.applyTint
|
|
||||||
import io.legado.app.utils.requestInputMethod
|
|
||||||
import io.legado.app.utils.setLayout
|
|
||||||
import io.legado.app.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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,362 +1,19 @@
|
|||||||
package io.legado.app.ui.replace
|
package io.legado.app.ui.replace
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import androidx.activity.compose.setContent
|
||||||
import android.view.MenuItem
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.view.MotionEvent
|
import io.legado.app.base.AppTheme
|
||||||
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.legado.app.R
|
|
||||||
import io.legado.app.base.VMBaseActivity
|
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.ReplaceRule
|
|
||||||
import io.legado.app.databinding.ActivityReplaceRuleBinding
|
|
||||||
import io.legado.app.databinding.DialogEditTextBinding
|
|
||||||
import io.legado.app.help.DirectLinkUpload
|
|
||||||
import io.legado.app.help.book.ContentProcessor
|
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
|
||||||
import io.legado.app.lib.dialogs.alert
|
|
||||||
import io.legado.app.ui.association.ImportReplaceRuleDialog
|
|
||||||
import io.legado.app.ui.file.HandleFileContract
|
|
||||||
import io.legado.app.ui.qrcode.QrCodeResult
|
|
||||||
import io.legado.app.ui.replace.edit.ReplaceEditActivity
|
|
||||||
import io.legado.app.ui.widget.SelectActionBar
|
|
||||||
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
|
|
||||||
import io.legado.app.ui.widget.recycler.ItemTouchCallback
|
|
||||||
import io.legado.app.ui.widget.recycler.VerticalDivider
|
|
||||||
import io.legado.app.utils.ACache
|
|
||||||
import io.legado.app.utils.GSON
|
|
||||||
import io.legado.app.utils.hideSoftInput
|
|
||||||
import io.legado.app.utils.isAbsUrl
|
|
||||||
import io.legado.app.utils.launch
|
|
||||||
import io.legado.app.utils.readText
|
|
||||||
import io.legado.app.utils.sendToClip
|
|
||||||
import io.legado.app.utils.shouldHideSoftInput
|
|
||||||
import io.legado.app.utils.showDialogFragment
|
|
||||||
import io.legado.app.utils.showHelp
|
|
||||||
import io.legado.app.utils.splitNotBlank
|
|
||||||
import io.legado.app.utils.toastOnUi
|
|
||||||
import io.legado.app.utils.transaction
|
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
class ReplaceRuleActivity : AppCompatActivity() {
|
||||||
* 替换规则管理
|
|
||||||
*/
|
|
||||||
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 = arrayListOf<String>()
|
|
||||||
private var groupMenu: SubMenu? = null
|
|
||||||
private var searchKey: String? = null
|
|
||||||
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 onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
initRecyclerView()
|
setContent {
|
||||||
initSearchView()
|
AppTheme {
|
||||||
initSelectActionView()
|
ReplaceRuleScreen(onBackClick = { finish() })
|
||||||
observeReplaceRuleData()
|
|
||||||
observeGroupData()
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
// 标记当前选中的排序方式
|
|
||||||
when (viewModel.sortMode.value) {
|
|
||||||
"asc" -> menu.findItem(R.id.sort_order_asc)?.isChecked = true
|
|
||||||
"desc" -> menu.findItem(R.id.sort_order_desc)?.isChecked = true
|
|
||||||
"name_asc" -> menu.findItem(R.id.sort_name_asc)?.isChecked = true
|
|
||||||
"name_desc" -> menu.findItem(R.id.sort_name_desc)?.isChecked = true
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
viewModel.rulesFlow.collectLatest { rules ->
|
|
||||||
adapter.setItems(rules, adapter.diffItemCallBack)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
R.id.sort_order_asc -> {
|
|
||||||
viewModel.setSortMode("asc")
|
|
||||||
item.isChecked = true
|
|
||||||
}
|
|
||||||
R.id.sort_order_desc -> {
|
|
||||||
viewModel.setSortMode("desc")
|
|
||||||
item.isChecked = true
|
|
||||||
}
|
|
||||||
R.id.sort_name_asc -> {
|
|
||||||
viewModel.setSortMode("name_asc")
|
|
||||||
item.isChecked = true
|
|
||||||
}
|
|
||||||
R.id.sort_name_desc -> {
|
|
||||||
viewModel.setSortMode("name_desc")
|
|
||||||
item.isChecked = true
|
|
||||||
}
|
|
||||||
else -> if (item.groupId == R.id.replace_group) {
|
|
||||||
searchView.setQuery("group:${item.title}", true)
|
|
||||||
viewModel.setSearchKey("group:${item.title}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observeReplaceRuleData()
|
|
||||||
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 {
|
|
||||||
searchKey = newText
|
|
||||||
viewModel.setSearchKey(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)
|
|||||||