pull/1866/head
kunfei 3 years ago
parent f1ad8cb94d
commit 75f5cd1178
  1. 3
      app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
  2. 19
      app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt
  3. 21
      app/src/main/java/io/legado/app/help/config/ThemeConfig.kt
  4. 46
      app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt
  5. 38
      app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt
  6. 25
      app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt
  7. 11
      app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt
  8. 179
      app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt
  9. 106
      app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt
  10. 179
      app/src/main/java/io/legado/app/ui/association/ImportTxtRuleDialog.kt
  11. 107
      app/src/main/java/io/legado/app/ui/association/ImportTxtRuleViewModel.kt
  12. 6
      app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt
  13. 19
      app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt

@ -19,6 +19,9 @@ interface TxtTocRuleDao {
@get:Query("select * from txtTocRules where enable != 1 order by serialNumber") @get:Query("select * from txtTocRules where enable != 1 order by serialNumber")
val disabled: List<TxtTocRule> val disabled: List<TxtTocRule>
@Query("select * from txtTocRules where id = :id")
fun get(id: Long): TxtTocRule?
@get:Query("select ifNull(min(serialNumber), 0) from txtTocRules") @get:Query("select ifNull(min(serialNumber), 0) from txtTocRules")
val minOrder: Int val minOrder: Int

@ -1,8 +1,8 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.legado.app.utils.GSON
@Entity(tableName = "txtTocRules") @Entity(tableName = "txtTocRules")
@ -15,7 +15,20 @@ data class TxtTocRule(
var enable: Boolean = true var enable: Boolean = true
) { ) {
@Ignore override fun hashCode(): Int {
constructor() : this(name = "") return GSON.toJson(this).hashCode()
}
override fun equals(other: Any?): Boolean {
other ?: return false
if (other is TxtTocRule) {
return id == other.id
&& name == other.name
&& rule == other.rule
&& serialNumber == other.serialNumber
&& enable == other.enable
}
return false
}
} }

@ -256,6 +256,25 @@ object ThemeConfig {
var accentColor: String, var accentColor: String,
var backgroundColor: String, var backgroundColor: String,
var bottomBackground: String var bottomBackground: String
) ) {
override fun hashCode(): Int {
return GSON.toJson(this).hashCode()
}
override fun equals(other: Any?): Boolean {
other ?: return false
if (other is Config) {
return other.themeName == themeName
&& other.isNightTheme == isNightTheme
&& other.primaryColor == primaryColor
&& other.accentColor == accentColor
&& other.backgroundColor == backgroundColor
&& other.bottomBackground == bottomBackground
}
return false
}
}
} }

@ -1,10 +1,10 @@
package io.legado.app.ui.association package io.legado.app.ui.association
import android.app.Application import android.app.Application
import androidx.lifecycle.MutableLiveData
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.HttpTTS
import io.legado.app.data.entities.TxtTocRule import io.legado.app.data.entities.TxtTocRule
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.ThemeConfig import io.legado.app.help.config.ThemeConfig
@ -15,6 +15,27 @@ import io.legado.app.utils.isJsonArray
abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) { abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) {
val successLive = MutableLiveData<Pair<String, String>>()
val errorLive = MutableLiveData<String>()
fun importJson(json: String) {
//暂时根据文件内容判断属于什么
when {
json.contains("bookSourceUrl") ->
successLive.postValue(Pair("bookSource", json))
json.contains("sourceUrl") ->
successLive.postValue(Pair("rssSource", json))
json.contains("pattern") ->
successLive.postValue(Pair("replaceRule", json))
json.contains("themeName") ->
successLive.postValue(Pair("theme", json))
json.contains("name") && json.contains("rule") ->
successLive.postValue(Pair("txtRule", json))
json.contains("name") && json.contains("url") ->
successLive.postValue(Pair("httpTts", json))
else -> errorLive.postValue("格式不对")
}
}
fun importTextTocRule(json: String, finally: (title: String, msg: String) -> Unit) { fun importTextTocRule(json: String, finally: (title: String, msg: String) -> Unit) {
execute { execute {
@ -37,29 +58,6 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode
} }
} }
fun importHttpTTS(json: String, finally: (title: String, msg: String) -> Unit) {
execute {
if (json.isJsonArray()) {
HttpTTS.fromJsonArray(json).getOrThrow().let {
appDb.httpTTSDao.insert(*it.toTypedArray())
return@execute it.size
}
} else {
HttpTTS.fromJson(json).getOrThrow().let {
appDb.httpTTSDao.insert(it)
return@execute 1
}
}
}.onSuccess {
finally.invoke(context.getString(R.string.success), "导入${it}朗读引擎")
}.onError {
finally.invoke(
context.getString(R.string.error),
it.localizedMessage ?: context.getString(R.string.unknown_error)
)
}
}
fun importTheme(json: String, finally: (title: String, msg: String) -> Unit) { fun importTheme(json: String, finally: (title: String, msg: String) -> Unit) {
execute { execute {
if (json.isJsonArray()) { if (json.isJsonArray()) {

@ -70,19 +70,29 @@ class FileAssociationActivity :
} }
finish() finish()
} }
viewModel.importBookSourceLive.observe(this) { viewModel.successLive.observe(this) {
binding.rotateLoading.hide() when (it.first) {
showDialogFragment(ImportBookSourceDialog(it, true)) "bookSource" -> showDialogFragment(
} ImportBookSourceDialog(it.second, true)
viewModel.importRssSourceLive.observe(this) { )
binding.rotateLoading.hide() "rssSource" -> showDialogFragment(
showDialogFragment(ImportRssSourceDialog(it, true)) ImportRssSourceDialog(it.second, true)
} )
viewModel.importReplaceRuleLive.observe(this) { "replaceRule" -> showDialogFragment(
binding.rotateLoading.hide() ImportReplaceRuleDialog(it.second, true)
showDialogFragment(ImportReplaceRuleDialog(it, true)) )
} "httpTts" -> showDialogFragment(
viewModel.errorLiveData.observe(this) { ImportHttpTtsDialog(it.second, true)
)
"theme" -> showDialogFragment(
ImportThemeDialog(it.second, true)
)
"txtRule" -> showDialogFragment(
ImportTxtRuleDialog(it.second, true)
)
}
}
viewModel.errorLive.observe(this) {
binding.rotateLoading.hide() binding.rotateLoading.hide()
toastOnUi(it) toastOnUi(it)
finish() finish()
@ -95,7 +105,7 @@ class FileAssociationActivity :
finish() finish()
} }
intent.data?.let { data -> intent.data?.let { data ->
viewModel.dispatchIndent(data, this::finallyDialog) viewModel.dispatchIndent(data)
} }
} }

@ -15,14 +15,10 @@ import java.io.File
class FileAssociationViewModel(application: Application) : BaseAssociationViewModel(application) { class FileAssociationViewModel(application: Application) : BaseAssociationViewModel(application) {
val importBookLiveData = MutableLiveData<Uri>() val importBookLiveData = MutableLiveData<Uri>()
val onLineImportLive = MutableLiveData<Uri>() val onLineImportLive = MutableLiveData<Uri>()
val importBookSourceLive = MutableLiveData<String>()
val importRssSourceLive = MutableLiveData<String>()
val importReplaceRuleLive = MutableLiveData<String>()
val openBookLiveData = MutableLiveData<String>() val openBookLiveData = MutableLiveData<String>()
val errorLiveData = MutableLiveData<String>()
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
fun dispatchIndent(uri: Uri, finally: (title: String, msg: String) -> Unit) { fun dispatchIndent(uri: Uri) {
execute { execute {
lateinit var fileName: String lateinit var fileName: String
lateinit var content: String lateinit var content: String
@ -39,22 +35,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
} }
when { when {
content.isJson() -> { content.isJson() -> {
//暂时根据文件内容判断属于什么 importJson(content)
when {
content.contains("bookSourceUrl") ->
importBookSourceLive.postValue(content)
content.contains("sourceUrl") ->
importRssSourceLive.postValue(content)
content.contains("pattern") ->
importReplaceRuleLive.postValue(content)
content.contains("themeName") ->
importTheme(content, finally)
content.contains("name") && content.contains("rule") ->
importTextTocRule(content, finally)
content.contains("name") && content.contains("url") ->
importHttpTTS(content, finally)
else -> errorLiveData.postValue("格式不对")
}
} }
fileName.matches(bookFileRegex) -> { fileName.matches(bookFileRegex) -> {
importBookLiveData.postValue(uri) importBookLiveData.postValue(uri)
@ -68,7 +49,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
} }
}.onError { }.onError {
it.printOnDebug() it.printOnDebug()
errorLiveData.postValue(it.localizedMessage) errorLive.postValue(it.localizedMessage)
} }
} }

@ -2,13 +2,11 @@ package io.legado.app.ui.association
import android.app.Application import android.app.Application
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.jayway.jsonpath.JsonPath
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.HttpTTS import io.legado.app.data.entities.HttpTTS
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.ContentProcessor
import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.newCallResponseBody
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text import io.legado.app.help.http.text
@ -56,7 +54,6 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {
} }
} }
appDb.httpTTSDao.insert(*selectSource.toTypedArray()) appDb.httpTTSDao.insert(*selectSource.toTypedArray())
ContentProcessor.upReplaceRules()
}.onFinally { }.onFinally {
finally.invoke() finally.invoke()
} }
@ -67,18 +64,10 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {
val mText = text.trim() val mText = text.trim()
when { when {
mText.isJsonObject() -> { mText.isJsonObject() -> {
val json = JsonPath.parse(mText)
val urls = json.read<List<String>>("$.sourceUrls")
if (!urls.isNullOrEmpty()) {
urls.forEach {
importSourceUrl(it)
}
} else {
HttpTTS.fromJson(mText).getOrThrow().let { HttpTTS.fromJson(mText).getOrThrow().let {
allSources.add(it) allSources.add(it)
} }
} }
}
mText.isJsonArray() -> HttpTTS.fromJsonArray(mText).getOrThrow().let { items -> mText.isJsonArray() -> HttpTTS.fromJsonArray(mText).getOrThrow().let { items ->
allSources.addAll(items) allSources.addAll(items)
} }

@ -0,0 +1,179 @@
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.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.help.config.ThemeConfig
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.GSON
import io.legado.app.utils.setLayout
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import splitties.views.onClick
class ImportThemeDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {
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<ImportThemeViewModel>()
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_book_source)
binding.rotateLoading.show()
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.hide()
binding.tvMsg.apply {
text = it
visible()
}
}
viewModel.successLiveData.observe(this) {
binding.rotateLoading.hide()
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<ThemeConfig.Config, ItemSourceImportBinding>(context) {
override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {
return ItemSourceImportBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: ItemSourceImportBinding,
item: ThemeConfig.Config,
payloads: MutableList<Any>
) {
binding.apply {
cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]
cbSourceName.text = item.themeName
val localSource = viewModel.checkSources[holder.layoutPosition]
tvSourceState.text = when {
localSource == null -> "新增"
localSource != item -> "更新"
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()
)
)
}
}
}
}
}

@ -0,0 +1,106 @@
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.exception.NoStackTraceException
import io.legado.app.help.config.ThemeConfig
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 ImportThemeViewModel(app: Application) : BaseViewModel(app) {
val errorLiveData = MutableLiveData<String>()
val successLiveData = MutableLiveData<Int>()
val allSources = arrayListOf<ThemeConfig.Config>()
val checkSources = arrayListOf<ThemeConfig.Config?>()
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 {
selectStatus.forEachIndexed { index, b ->
if (b) {
ThemeConfig.addConfig(allSources[index])
}
}
}.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<ThemeConfig.Config>(text).getOrThrow()?.let {
allSources.add(it)
}
}
text.isJsonArray() -> GSON.fromJsonArray<ThemeConfig.Config>(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 {
url(url)
}.text().let {
importSourceAwait(it)
}
}
private fun comparisonSource() {
execute {
allSources.forEach { config ->
val source = ThemeConfig.configList.find {
it.themeName == config.themeName
}
checkSources.add(source)
selectStatus.add(source == null || source != config)
}
successLiveData.postValue(allSources.size)
}
}
}

@ -0,0 +1,179 @@
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.TxtTocRule
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.GSON
import io.legado.app.utils.setLayout
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import splitties.views.onClick
class ImportTxtRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {
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<ImportTxtRuleViewModel>()
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_book_source)
binding.rotateLoading.show()
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.hide()
binding.tvMsg.apply {
text = it
visible()
}
}
viewModel.successLiveData.observe(this) {
binding.rotateLoading.hide()
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<TxtTocRule, ItemSourceImportBinding>(context) {
override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {
return ItemSourceImportBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: ItemSourceImportBinding,
item: TxtTocRule,
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 -> "新增"
item != localSource -> "更新"
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()
)
)
}
}
}
}
}

@ -0,0 +1,107 @@
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.data.appDb
import io.legado.app.data.entities.TxtTocRule
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 ImportTxtRuleViewModel(app: Application) : BaseViewModel(app) {
val errorLiveData = MutableLiveData<String>()
val successLiveData = MutableLiveData<Int>()
val allSources = arrayListOf<TxtTocRule>()
val checkSources = arrayListOf<TxtTocRule?>()
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<TxtTocRule>()
selectStatus.forEachIndexed { index, b ->
if (b) {
selectSource.add(allSources[index])
}
}
appDb.txtTocRuleDao.insert(*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<TxtTocRule>(text).getOrThrow()?.let {
allSources.add(it)
}
}
text.isJsonArray() -> GSON.fromJsonArray<TxtTocRule>(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 {
url(url)
}.text().let {
importSourceAwait(it)
}
}
private fun comparisonSource() {
execute {
allSources.forEach {
val source = appDb.txtTocRuleDao.get(it.id)
checkSources.add(source)
selectStatus.add(source == null || it != source)
}
successLiveData.postValue(allSources.size)
}
}
}

@ -34,6 +34,12 @@ class OnLineImportActivity :
"httpTts" -> showDialogFragment( "httpTts" -> showDialogFragment(
ImportHttpTtsDialog(it.second, true) ImportHttpTtsDialog(it.second, true)
) )
"theme" -> showDialogFragment(
ImportThemeDialog(it.second, true)
)
"txtRule" -> showDialogFragment(
ImportTxtRuleDialog(it.second, true)
)
} }
} }
viewModel.errorLive.observe(this) { viewModel.errorLive.observe(this) {

@ -1,7 +1,6 @@
package io.legado.app.ui.association package io.legado.app.ui.association
import android.app.Application import android.app.Application
import androidx.lifecycle.MutableLiveData
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.newCallResponseBody
@ -10,8 +9,6 @@ import io.legado.app.help.http.text
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) { class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) {
val successLive = MutableLiveData<Pair<String, String>>()
val errorLive = MutableLiveData<String>()
fun getText(url: String, success: (text: String) -> Unit) { fun getText(url: String, success: (text: String) -> Unit) {
execute { execute {
@ -76,21 +73,7 @@ class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) {
} }
else -> { else -> {
val json = rs.text("utf-8") val json = rs.text("utf-8")
when { importJson(json)
json.contains("bookSourceUrl") ->
successLive.postValue(Pair("bookSource", json))
json.contains("sourceUrl") ->
successLive.postValue(Pair("rssSource", json))
json.contains("replacement") ->
successLive.postValue(Pair("replaceRule", json))
json.contains("themeName") ->
importTheme(json, finally)
json.contains("name") && json.contains("rule") ->
importTextTocRule(json, finally)
json.contains("name") && json.contains("url") ->
successLive.postValue(Pair("httpTts", json))
else -> errorLive.postValue("格式不对")
}
} }
} }
} }

Loading…
Cancel
Save