This commit is contained in:
kunfei 2023-02-21 09:46:07 +08:00
parent 08cbe844e0
commit 0d242b9bc0
6 changed files with 371 additions and 2 deletions

View File

@ -0,0 +1,7 @@
## 字典规则说明
* 字典规则是用在正文文字选择菜单字典里的规则,通常用来做翻译或者查找
* urlRule
* 同书源的url规则
* showRule
* 用来提取显示到对话框里面内容的规则

View File

@ -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") ->

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
)

View File

@ -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()
}
}
}