mirror of
https://github.com/gedoor/legado.git
synced 2024-07-17 00:58:29 +08:00
优化
This commit is contained in:
parent
08cbe844e0
commit
0d242b9bc0
7
app/src/main/assets/help/dictRuleHelp.md
Normal file
7
app/src/main/assets/help/dictRuleHelp.md
Normal file
@ -0,0 +1,7 @@
|
||||
## 字典规则说明
|
||||
|
||||
* 字典规则是用在正文文字选择菜单字典里的规则,通常用来做翻译或者查找
|
||||
* urlRule
|
||||
* 同书源的url规则
|
||||
* showRule
|
||||
* 用来提取显示到对话框里面内容的规则
|
@ -30,6 +30,8 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode
|
||||
successLive.postValue(Pair("replaceRule", json))
|
||||
json.contains("themeName") ->
|
||||
successLive.postValue(Pair("theme", json))
|
||||
json.contains("urlRule") && json.contains("showRule") ->
|
||||
successLive.postValue(Pair("dictRule", json))
|
||||
json.contains("name") && json.contains("rule") ->
|
||||
successLive.postValue(Pair("txtRule", json))
|
||||
json.contains("name") && json.contains("url") ->
|
||||
|
@ -0,0 +1,185 @@
|
||||
package io.legado.app.ui.association
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
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.entities.DictRule
|
||||
import io.legado.app.databinding.DialogRecyclerViewBinding
|
||||
import io.legado.app.databinding.ItemSourceImportBinding
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.ui.widget.dialog.CodeDialog
|
||||
import io.legado.app.ui.widget.dialog.WaitDialog
|
||||
import io.legado.app.utils.*
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import splitties.views.onClick
|
||||
|
||||
class ImportDictRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),
|
||||
CodeDialog.Callback {
|
||||
|
||||
constructor(source: String, finishOnDismiss: Boolean = false) : this() {
|
||||
arguments = Bundle().apply {
|
||||
putString("source", source)
|
||||
putBoolean("finishOnDismiss", finishOnDismiss)
|
||||
}
|
||||
}
|
||||
|
||||
private val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
||||
private val viewModel by viewModels<ImportDictRuleViewModel>()
|
||||
private val adapter by lazy { SourcesAdapter(requireContext()) }
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
if (arguments?.getBoolean("finishOnDismiss") == true) {
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.toolBar.setBackgroundColor(primaryColor)
|
||||
binding.toolBar.setTitle(R.string.import_tts)
|
||||
binding.rotateLoading.visible()
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.tvCancel.visible()
|
||||
binding.tvCancel.setOnClickListener {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
binding.tvOk.visible()
|
||||
binding.tvOk.setOnClickListener {
|
||||
val waitDialog = WaitDialog(requireContext())
|
||||
waitDialog.show()
|
||||
viewModel.importSelect {
|
||||
waitDialog.dismiss()
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
binding.tvFooterLeft.visible()
|
||||
binding.tvFooterLeft.setOnClickListener {
|
||||
val selectAll = viewModel.isSelectAll
|
||||
viewModel.selectStatus.forEachIndexed { index, b ->
|
||||
if (b != !selectAll) {
|
||||
viewModel.selectStatus[index] = !selectAll
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
upSelectText()
|
||||
}
|
||||
viewModel.errorLiveData.observe(this) {
|
||||
binding.rotateLoading.gone()
|
||||
binding.tvMsg.apply {
|
||||
text = it
|
||||
visible()
|
||||
}
|
||||
}
|
||||
viewModel.successLiveData.observe(this) {
|
||||
binding.rotateLoading.gone()
|
||||
if (it > 0) {
|
||||
adapter.setItems(viewModel.allSources)
|
||||
upSelectText()
|
||||
} else {
|
||||
binding.tvMsg.apply {
|
||||
setText(R.string.wrong_format)
|
||||
visible()
|
||||
}
|
||||
}
|
||||
}
|
||||
val source = arguments?.getString("source")
|
||||
if (source.isNullOrEmpty()) {
|
||||
dismiss()
|
||||
return
|
||||
}
|
||||
viewModel.importSource(source)
|
||||
}
|
||||
|
||||
private fun upSelectText() {
|
||||
if (viewModel.isSelectAll) {
|
||||
binding.tvFooterLeft.text = getString(
|
||||
R.string.select_cancel_count,
|
||||
viewModel.selectCount,
|
||||
viewModel.allSources.size
|
||||
)
|
||||
} else {
|
||||
binding.tvFooterLeft.text = getString(
|
||||
R.string.select_all_count,
|
||||
viewModel.selectCount,
|
||||
viewModel.allSources.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inner class SourcesAdapter(context: Context) :
|
||||
RecyclerAdapter<DictRule, ItemSourceImportBinding>(context) {
|
||||
|
||||
override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {
|
||||
return ItemSourceImportBinding.inflate(inflater, parent, false)
|
||||
}
|
||||
|
||||
override fun convert(
|
||||
holder: ItemViewHolder,
|
||||
binding: ItemSourceImportBinding,
|
||||
item: DictRule,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
binding.apply {
|
||||
cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]
|
||||
cbSourceName.text = item.name
|
||||
val localSource = viewModel.checkSources[holder.layoutPosition]
|
||||
tvSourceState.text = when (localSource) {
|
||||
null -> "新增"
|
||||
else -> "已有"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {
|
||||
binding.apply {
|
||||
cbSourceName.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
if (buttonView.isPressed) {
|
||||
viewModel.selectStatus[holder.layoutPosition] = isChecked
|
||||
upSelectText()
|
||||
}
|
||||
}
|
||||
root.onClick {
|
||||
cbSourceName.isChecked = !cbSourceName.isChecked
|
||||
viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked
|
||||
upSelectText()
|
||||
}
|
||||
tvOpen.setOnClickListener {
|
||||
val source = viewModel.allSources[holder.layoutPosition]
|
||||
showDialogFragment(
|
||||
CodeDialog(
|
||||
GSON.toJson(source),
|
||||
disableEdit = false,
|
||||
requestId = holder.layoutPosition.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCodeSave(code: String, requestId: String?) {
|
||||
requestId?.toInt()?.let {
|
||||
GSON.fromJsonObject<DictRule>(code).getOrNull()?.let { source ->
|
||||
viewModel.allSources[it] = source
|
||||
adapter.setItem(it, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package io.legado.app.ui.association
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.DictRule
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.help.http.newCallResponseBody
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.help.http.text
|
||||
import io.legado.app.utils.*
|
||||
|
||||
class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) {
|
||||
|
||||
val errorLiveData = MutableLiveData<String>()
|
||||
val successLiveData = MutableLiveData<Int>()
|
||||
|
||||
val allSources = arrayListOf<DictRule>()
|
||||
val checkSources = arrayListOf<DictRule?>()
|
||||
val selectStatus = arrayListOf<Boolean>()
|
||||
|
||||
val isSelectAll: Boolean
|
||||
get() {
|
||||
selectStatus.forEach {
|
||||
if (!it) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val selectCount: Int
|
||||
get() {
|
||||
var count = 0
|
||||
selectStatus.forEach {
|
||||
if (it) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
fun importSelect(finally: () -> Unit) {
|
||||
execute {
|
||||
val selectSource = arrayListOf<DictRule>()
|
||||
selectStatus.forEachIndexed { index, b ->
|
||||
if (b) {
|
||||
selectSource.add(allSources[index])
|
||||
}
|
||||
}
|
||||
appDb.dictRuleDao.upsert(*selectSource.toTypedArray())
|
||||
}.onFinally {
|
||||
finally.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun importSource(text: String) {
|
||||
execute {
|
||||
importSourceAwait(text.trim())
|
||||
}.onError {
|
||||
it.printOnDebug()
|
||||
errorLiveData.postValue(it.localizedMessage ?: "")
|
||||
}.onSuccess {
|
||||
comparisonSource()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun importSourceAwait(text: String) {
|
||||
when {
|
||||
text.isJsonObject() -> {
|
||||
GSON.fromJsonObject<DictRule>(text).getOrThrow()?.let {
|
||||
allSources.add(it)
|
||||
}
|
||||
}
|
||||
text.isJsonArray() -> GSON.fromJsonArray<DictRule>(text).getOrThrow()?.let { items ->
|
||||
allSources.addAll(items)
|
||||
}
|
||||
text.isAbsUrl() -> {
|
||||
importSourceUrl(text)
|
||||
}
|
||||
else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun importSourceUrl(url: String) {
|
||||
okHttpClient.newCallResponseBody {
|
||||
if (url.endsWith("#requestWithoutUA")) {
|
||||
url(url.substringBeforeLast("#requestWithoutUA"))
|
||||
header(AppConst.UA_NAME, "null")
|
||||
} else {
|
||||
url(url)
|
||||
}
|
||||
}.text().let {
|
||||
importSourceAwait(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun comparisonSource() {
|
||||
execute {
|
||||
allSources.forEach {
|
||||
val source = appDb.dictRuleDao.getByName(it.name)
|
||||
checkSources.add(source)
|
||||
selectStatus.add(source == null)
|
||||
}
|
||||
successLiveData.postValue(allSources.size)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -40,6 +40,9 @@ class OnLineImportActivity :
|
||||
"txtRule" -> showDialogFragment(
|
||||
ImportTxtTocRuleDialog(it.second, true)
|
||||
)
|
||||
"dictRule" -> showDialogFragment(
|
||||
ImportDictRuleDialog(it.second, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
viewModel.errorLive.observe(this) {
|
||||
@ -67,6 +70,9 @@ class OnLineImportActivity :
|
||||
"/httpTTS" -> showDialogFragment(
|
||||
ImportHttpTtsDialog(url, true)
|
||||
)
|
||||
"/dictRule" -> showDialogFragment(
|
||||
ImportDictRuleDialog(url, true)
|
||||
)
|
||||
"/theme" -> showDialogFragment(
|
||||
ImportThemeDialog(url, true)
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.legado.app.ui.dict.rule
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@ -16,7 +17,9 @@ import io.legado.app.databinding.DialogEditTextBinding
|
||||
import io.legado.app.help.DirectLinkUpload
|
||||
import io.legado.app.lib.dialogs.alert
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.ui.association.ImportDictRuleDialog
|
||||
import io.legado.app.ui.document.HandleFileContract
|
||||
import io.legado.app.ui.qrcode.QrCodeResult
|
||||
import io.legado.app.ui.widget.SelectActionBar
|
||||
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
|
||||
import io.legado.app.ui.widget.recycler.ItemTouchCallback
|
||||
@ -32,8 +35,25 @@ class DictRuleActivity : VMBaseActivity<ActivityDictRuleBinding, DictRuleViewMod
|
||||
|
||||
override val viewModel by viewModels<DictRuleViewModel>()
|
||||
override val binding by viewBinding(ActivityDictRuleBinding::inflate)
|
||||
|
||||
private val importRecordKey = "dictRuleUrls"
|
||||
private val adapter by lazy { DictRuleAdapter(this, this) }
|
||||
private val qrCodeResult = registerForActivityResult(QrCodeResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
showDialogFragment(
|
||||
ImportDictRuleDialog(it)
|
||||
)
|
||||
}
|
||||
private val importDoc = registerForActivityResult(HandleFileContract()) {
|
||||
kotlin.runCatching {
|
||||
it.uri?.readText(this)?.let {
|
||||
showDialogFragment(
|
||||
ImportDictRuleDialog(it)
|
||||
)
|
||||
}
|
||||
}.onFailure {
|
||||
toastOnUi("readTextError:${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
private val exportResult = registerForActivityResult(HandleFileContract()) {
|
||||
it.uri?.let { uri ->
|
||||
alert(R.string.export_success) {
|
||||
@ -102,6 +122,12 @@ class DictRuleActivity : VMBaseActivity<ActivityDictRuleBinding, DictRuleViewMod
|
||||
when (item.itemId) {
|
||||
R.id.menu_create -> showDialogFragment<DictRuleEditDialog>()
|
||||
R.id.menu_import_default -> viewModel.importDefault()
|
||||
R.id.menu_import_local -> importDoc.launch {
|
||||
mode = HandleFileContract.FILE
|
||||
allowExtensions = arrayOf("txt", "json")
|
||||
}
|
||||
R.id.menu_import_onLine -> showImportDialog()
|
||||
R.id.menu_import_qr -> qrCodeResult.launch()
|
||||
R.id.menu_help -> {}
|
||||
}
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
@ -162,5 +188,36 @@ class DictRuleActivity : VMBaseActivity<ActivityDictRuleBinding, DictRuleViewMod
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@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_replace_rule_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 (!cacheUrls.contains(it)) {
|
||||
cacheUrls.add(0, it)
|
||||
aCache.put(importRecordKey, cacheUrls.joinToString(","))
|
||||
}
|
||||
showDialogFragment(
|
||||
ImportDictRuleDialog(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user