From 75f5cd1178095ca24b3607657a88fdd353af7b1f Mon Sep 17 00:00:00 2001 From: kunfei Date: Mon, 9 May 2022 23:20:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/data/dao/TxtTocRuleDao.kt | 3 + .../io/legado/app/data/entities/TxtTocRule.kt | 19 +- .../io/legado/app/help/config/ThemeConfig.kt | 21 +- .../association/BaseAssociationViewModel.kt | 46 +++-- .../ui/association/FileAssociationActivity.kt | 36 ++-- .../association/FileAssociationViewModel.kt | 25 +-- .../ui/association/ImportHttpTtsViewModel.kt | 15 +- .../app/ui/association/ImportThemeDialog.kt | 179 ++++++++++++++++++ .../ui/association/ImportThemeViewModel.kt | 106 +++++++++++ .../app/ui/association/ImportTxtRuleDialog.kt | 179 ++++++++++++++++++ .../ui/association/ImportTxtRuleViewModel.kt | 107 +++++++++++ .../ui/association/OnLineImportActivity.kt | 6 + .../ui/association/OnLineImportViewModel.kt | 19 +- 13 files changed, 667 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt create mode 100644 app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt create mode 100644 app/src/main/java/io/legado/app/ui/association/ImportTxtRuleDialog.kt create mode 100644 app/src/main/java/io/legado/app/ui/association/ImportTxtRuleViewModel.kt diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt index 88ee47f9c..9e45ad26f 100644 --- a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -19,6 +19,9 @@ interface TxtTocRuleDao { @get:Query("select * from txtTocRules where enable != 1 order by serialNumber") val disabled: List + @Query("select * from txtTocRules where id = :id") + fun get(id: Long): TxtTocRule? + @get:Query("select ifNull(min(serialNumber), 0) from txtTocRules") val minOrder: Int diff --git a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt index 54429bbf3..b4c28b0fb 100644 --- a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt @@ -1,8 +1,8 @@ package io.legado.app.data.entities import androidx.room.Entity -import androidx.room.Ignore import androidx.room.PrimaryKey +import io.legado.app.utils.GSON @Entity(tableName = "txtTocRules") @@ -15,7 +15,20 @@ data class TxtTocRule( var enable: Boolean = true ) { - @Ignore - constructor() : this(name = "") + override fun hashCode(): Int { + 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 + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt b/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt index f29bcfc5d..01a012bc5 100644 --- a/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt @@ -256,6 +256,25 @@ object ThemeConfig { var accentColor: String, var backgroundColor: 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 + } + + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt index e996bd36b..50548f15a 100644 --- a/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt @@ -1,10 +1,10 @@ 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.HttpTTS import io.legado.app.data.entities.TxtTocRule import io.legado.app.exception.NoStackTraceException import io.legado.app.help.config.ThemeConfig @@ -15,6 +15,27 @@ import io.legado.app.utils.isJsonArray abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) { + val successLive = MutableLiveData>() + val errorLive = MutableLiveData() + + 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) { 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) { execute { if (json.isJsonArray()) { diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt index b6b6e240f..7c79d85fb 100644 --- a/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt @@ -70,19 +70,29 @@ class FileAssociationActivity : } finish() } - viewModel.importBookSourceLive.observe(this) { - binding.rotateLoading.hide() - showDialogFragment(ImportBookSourceDialog(it, true)) - } - viewModel.importRssSourceLive.observe(this) { - binding.rotateLoading.hide() - showDialogFragment(ImportRssSourceDialog(it, true)) - } - viewModel.importReplaceRuleLive.observe(this) { - binding.rotateLoading.hide() - showDialogFragment(ImportReplaceRuleDialog(it, true)) + viewModel.successLive.observe(this) { + when (it.first) { + "bookSource" -> showDialogFragment( + ImportBookSourceDialog(it.second, true) + ) + "rssSource" -> showDialogFragment( + ImportRssSourceDialog(it.second, true) + ) + "replaceRule" -> showDialogFragment( + ImportReplaceRuleDialog(it.second, true) + ) + "httpTts" -> showDialogFragment( + ImportHttpTtsDialog(it.second, true) + ) + "theme" -> showDialogFragment( + ImportThemeDialog(it.second, true) + ) + "txtRule" -> showDialogFragment( + ImportTxtRuleDialog(it.second, true) + ) + } } - viewModel.errorLiveData.observe(this) { + viewModel.errorLive.observe(this) { binding.rotateLoading.hide() toastOnUi(it) finish() @@ -95,7 +105,7 @@ class FileAssociationActivity : finish() } intent.data?.let { data -> - viewModel.dispatchIndent(data, this::finallyDialog) + viewModel.dispatchIndent(data) } } diff --git a/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt index 403412fd9..9a328c603 100644 --- a/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt @@ -15,14 +15,10 @@ import java.io.File class FileAssociationViewModel(application: Application) : BaseAssociationViewModel(application) { val importBookLiveData = MutableLiveData() val onLineImportLive = MutableLiveData() - val importBookSourceLive = MutableLiveData() - val importRssSourceLive = MutableLiveData() - val importReplaceRuleLive = MutableLiveData() val openBookLiveData = MutableLiveData() - val errorLiveData = MutableLiveData() @Suppress("BlockingMethodInNonBlockingContext") - fun dispatchIndent(uri: Uri, finally: (title: String, msg: String) -> Unit) { + fun dispatchIndent(uri: Uri) { execute { lateinit var fileName: String lateinit var content: String @@ -39,22 +35,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo } when { content.isJson() -> { - //暂时根据文件内容判断属于什么 - 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("格式不对") - } + importJson(content) } fileName.matches(bookFileRegex) -> { importBookLiveData.postValue(uri) @@ -68,7 +49,7 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo } }.onError { it.printOnDebug() - errorLiveData.postValue(it.localizedMessage) + errorLive.postValue(it.localizedMessage) } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt index 5cbacb09d..d4628cf0d 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt @@ -2,13 +2,11 @@ package io.legado.app.ui.association import android.app.Application import androidx.lifecycle.MutableLiveData -import com.jayway.jsonpath.JsonPath import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.appDb import io.legado.app.data.entities.HttpTTS 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.okHttpClient import io.legado.app.help.http.text @@ -56,7 +54,6 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) { } } appDb.httpTTSDao.insert(*selectSource.toTypedArray()) - ContentProcessor.upReplaceRules() }.onFinally { finally.invoke() } @@ -67,16 +64,8 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) { val mText = text.trim() when { mText.isJsonObject() -> { - val json = JsonPath.parse(mText) - val urls = json.read>("$.sourceUrls") - if (!urls.isNullOrEmpty()) { - urls.forEach { - importSourceUrl(it) - } - } else { - HttpTTS.fromJson(mText).getOrThrow().let { - allSources.add(it) - } + HttpTTS.fromJson(mText).getOrThrow().let { + allSources.add(it) } } mText.isJsonArray() -> HttpTTS.fromJsonArray(mText).getOrThrow().let { items -> diff --git a/app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt new file mode 100644 index 000000000..078bb00a0 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt @@ -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() + 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(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 + ) { + 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() + ) + ) + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt new file mode 100644 index 000000000..a611fe8a9 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt @@ -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() + val successLiveData = MutableLiveData() + + val allSources = arrayListOf() + val checkSources = arrayListOf() + val selectStatus = arrayListOf() + + 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(text).getOrThrow()?.let { + allSources.add(it) + } + } + text.isJsonArray() -> GSON.fromJsonArray(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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleDialog.kt new file mode 100644 index 000000000..20f0950e8 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleDialog.kt @@ -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() + 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(context) { + + override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding { + return ItemSourceImportBinding.inflate(inflater, parent, false) + } + + override fun convert( + holder: ItemViewHolder, + binding: ItemSourceImportBinding, + item: TxtTocRule, + payloads: MutableList + ) { + 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() + ) + ) + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleViewModel.kt new file mode 100644 index 000000000..76f6f0eff --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportTxtRuleViewModel.kt @@ -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() + val successLiveData = MutableLiveData() + + val allSources = arrayListOf() + val checkSources = arrayListOf() + val selectStatus = arrayListOf() + + 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() + 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(text).getOrThrow()?.let { + allSources.add(it) + } + } + text.isJsonArray() -> GSON.fromJsonArray(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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt b/app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt index d25f5007f..fe17159eb 100644 --- a/app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt +++ b/app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt @@ -34,6 +34,12 @@ class OnLineImportActivity : "httpTts" -> showDialogFragment( ImportHttpTtsDialog(it.second, true) ) + "theme" -> showDialogFragment( + ImportThemeDialog(it.second, true) + ) + "txtRule" -> showDialogFragment( + ImportTxtRuleDialog(it.second, true) + ) } } viewModel.errorLive.observe(this) { diff --git a/app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt b/app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt index 06387eace..a71af4620 100644 --- a/app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.association import android.app.Application -import androidx.lifecycle.MutableLiveData import io.legado.app.R import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.http.newCallResponseBody @@ -10,8 +9,6 @@ import io.legado.app.help.http.text import okhttp3.MediaType.Companion.toMediaType class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) { - val successLive = MutableLiveData>() - val errorLive = MutableLiveData() fun getText(url: String, success: (text: String) -> Unit) { execute { @@ -76,21 +73,7 @@ class OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) { } else -> { val json = rs.text("utf-8") - when { - 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("格式不对") - } + importJson(json) } } }