From d95376448d2d53041f830ac7f21b8f0ae13b7c38 Mon Sep 17 00:00:00 2001 From: Discut Date: Mon, 22 May 2023 21:00:37 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat(Cache/Export):=20=E2=9C=A8=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E5=BC=80=E5=90=AF=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=AF=BC=E5=87=BA=20=E8=8F=9C=E5=8D=95=E4=B8=8E?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/constant/PreferKey.kt | 1 + .../io/legado/app/help/config/AppConfig.kt | 6 + .../legado/app/ui/book/cache/CacheActivity.kt | 124 ++++++++++++++++++ .../book/cache/CustomExportSectionDialog.kt | 16 +++ .../layout/dialog_select_section_export.xml | 110 ++++++++++++++++ app/src/main/res/menu/book_cache.xml | 7 + app/src/main/res/values-zh/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 268 insertions(+) create mode 100644 app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt create mode 100644 app/src/main/res/layout/dialog_select_section_export.xml diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 47a5c7c64..5cf81230b 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -54,6 +54,7 @@ object PreferKey { const val webDavAccount = "web_dav_account" const val webDavPassword = "web_dav_password" const val webDavDir = "webDavDir" + const val enableCustomExport = "enableCustomExport" const val exportToWebDav = "webDavCacheBackup" const val exportNoChapterName = "exportNoChapterName" const val exportType = "exportType" diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index dec18efa9..369a06236 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -268,6 +268,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { set(value) { appCtx.putPrefBoolean(PreferKey.exportNoChapterName, value) } + var enableCustomExport: Boolean + get() = appCtx.getPrefBoolean(PreferKey.enableCustomExport) + set(value) { + appCtx.putPrefBoolean(PreferKey.enableCustomExport, value) + } + var exportType: Int get() = appCtx.getPrefInt(PreferKey.exportType) set(value) { diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index f98802f25..3d48b3136 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -4,7 +4,9 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.R import io.legado.app.base.VMBaseActivity @@ -17,6 +19,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookGroup import io.legado.app.databinding.ActivityCacheBookBinding import io.legado.app.databinding.DialogEditTextBinding +import io.legado.app.databinding.DialogSelectSectionExportBinding import io.legado.app.help.book.isAudio import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.SelectItem @@ -32,7 +35,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import androidx.core.widget.doOnTextChanged +/** + * cache/download 缓存界面 + */ class CacheActivity : VMBaseActivity(), CacheAdapter.CallBack { @@ -88,6 +95,8 @@ class CacheActivity : VMBaseActivity() override fun onMenuOpened(featureId: Int, menu: Menu): Boolean { menu.findItem(R.id.menu_enable_replace)?.isChecked = AppConfig.exportUseReplace + // 菜单打开时读取状态[enableCustomExport] + menu.findItem(R.id.menu_enable_custom_export)?.isChecked = AppConfig.enableCustomExport menu.findItem(R.id.menu_export_no_chapter_name)?.isChecked = AppConfig.exportNoChapterName menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav menu.findItem(R.id.menu_export_pics_file)?.isChecked = AppConfig.exportPictureFile @@ -108,6 +117,9 @@ class CacheActivity : VMBaseActivity() } } + /** + * 菜单按下回调 + */ override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menu_download -> { @@ -126,6 +138,8 @@ class CacheActivity : VMBaseActivity() } R.id.menu_export_all -> exportAll() R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked + // 更改菜单状态[enableCustomExport] + R.id.menu_enable_custom_export -> AppConfig.enableCustomExport = !item.isChecked R.id.menu_export_no_chapter_name -> AppConfig.exportNoChapterName = !item.isChecked R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked R.id.menu_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked @@ -233,6 +247,8 @@ class CacheActivity : VMBaseActivity() val path = ACache.get().getAsString(exportBookPathKey) if (path.isNullOrEmpty()) { selectExportFolder(position) + } else if (AppConfig.enableCustomExport) {// 启用自定义导出 + configExportSection(path, position); } else { startExport(path, position) } @@ -247,6 +263,102 @@ class CacheActivity : VMBaseActivity() } } + /** + * 配置自定义导出对话框 + * + * @param path 导出路径 + * @param position book位置 + * @author Discut + * @since 1.0.0 + */ + private fun configExportSection(path: String, position: Int) { + if (AppConfig.enableCustomExport) { + val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater) + .apply { + cbAllExport.isChecked = true + cbSelectExport.isChecked = false + etEpubSize.isEnabled = false + etInputScope.isEnabled = false + tvAllExport.setOnClickListener { + cbAllExport.isChecked = true + } + tvSelectExport.setOnClickListener { + cbSelectExport.isChecked = true + } + cbSelectExport.onCheckedChangeListener = { _, isChecked -> + if (isChecked) { + etEpubSize.isEnabled = true + etInputScope.isEnabled = true + cbAllExport.isChecked = false + } + } + cbAllExport.onCheckedChangeListener = { _, isChecked -> + if (isChecked) { + etEpubSize.isEnabled = false + etInputScope.isEnabled = false + cbSelectExport.isChecked = false + } + } + + etInputScope.onFocusChangeListener = + View.OnFocusChangeListener { v, hasFocus -> + if (hasFocus) { + etInputScope.hint = "例如:1-5,8,10-18" + } else { + etInputScope.hint = "" + } + } + etInputScope.doOnTextChanged { text, start, before, count -> + var a: Int = 1 + a = 2 + } + + } + val alertDialog = alert(titleResource = R.string.select_section_export) { + customView { alertBinding.root } + positiveButton("确认") + //okButton { +// alertBinding.apply { +// it.dismiss() +// if (cbAllExport.isChecked) { +// // export all sections of the book. +// startExport(path, position) +// } else if (cbSelectExport.isChecked) { +// +// } +// } + //} + cancelButton() + } + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + alertBinding.apply { + val text = etInputScope.text + if (!verificationField(text.toString())) { + etInputScope.error = "请输入正确的范围" + } else { + etInputScope.error = null + val toInt = etEpubSize.text.toString().toInt() + startExport(path, position, toInt, text.toString()) + } + } + + } + + } + } + + /** + * 验证 输入的范围 是否正确 + * + * @since 1.0.0 + * @author Discut + * @param text 输入的范围 字符串 + * @return 是否正确 + */ + private fun verificationField(text: String): Boolean { + return text.matches(Regex("\\d+(-\\d+)?(,\\d+(-\\d+)?)*")) + } + private fun selectExportFolder(exportPosition: Int) { val default = arrayListOf>() val path = ACache.get().getAsString(exportBookPathKey) @@ -259,6 +371,18 @@ class CacheActivity : VMBaseActivity() } } + private fun startExport(path: String, exportPosition: Int, size: Int, scope: String) { + if (exportPosition >= 0) { + adapter.getItem(exportPosition)?.let { book -> + when (AppConfig.exportType) { + 1 -> viewModel.exportEPUB(path, book, size, scope) + // 目前仅支持 epub + //else -> viewModel.export(path, book) + } + } + } + } + private fun startExport(path: String, exportPosition: Int) { if (exportPosition == -10) { if (adapter.getItems().isNotEmpty()) { diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt b/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt new file mode 100644 index 000000000..b26f54b43 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt @@ -0,0 +1,16 @@ +package io.legado.app.ui.book.cache + +import android.os.Bundle +import android.view.View +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.databinding.DialogSelectSectionExportBinding +import io.legado.app.utils.viewbindingdelegate.viewBinding + +class CustomExportSectionDialog() : BaseDialogFragment(R.layout.dialog_select_section_export) { + + private val binding by viewBinding(DialogSelectSectionExportBinding::bind) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_select_section_export.xml b/app/src/main/res/layout/dialog_select_section_export.xml new file mode 100644 index 000000000..b22c69f9f --- /dev/null +++ b/app/src/main/res/layout/dialog_select_section_export.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/book_cache.xml b/app/src/main/res/menu/book_cache.xml index 8e06e17b4..a943d2669 100644 --- a/app/src/main/res/menu/book_cache.xml +++ b/app/src/main/res/menu/book_cache.xml @@ -31,6 +31,13 @@ android:checkable="true" app:showAsAction="never" /> + + + 删除所有 替换 替换净化 + 自定义导出章节 配置替换净化规则 暂无 启用 @@ -346,6 +347,7 @@ 关闭则只显示勾选源的发现 更新目录 TXT 目录规则 + 选择待导出章节 设置编码 倒序-顺序 排序 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb06feb3e..18df6b071 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Edit Delete Delete all + Custom export chapter Replace Replacement Configure replacement rules @@ -347,6 +348,7 @@ Display the selected origin\'s Discovery if closed Update chapters Txt Chapters Rule + Choose some chapters to be exported Text encoding Ascending/Descending order Sort From c950a4bfe5bf6880b1cadef84ceaf5b255932574 Mon Sep 17 00:00:00 2001 From: Discut Date: Mon, 22 May 2023 21:28:42 +0800 Subject: [PATCH 02/12] =?UTF-8?q?fix(CustomExportDialog):=20=F0=9F=90=9B?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=20=E7=A1=AE=E8=AE=A4=E5=90=8E=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E6=A1=86=E4=B8=8D=E6=B6=88=E5=A4=B1=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 3d48b3136..02ec43c73 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -339,6 +339,7 @@ class CacheActivity : VMBaseActivity() etInputScope.error = null val toInt = etEpubSize.text.toString().toInt() startExport(path, position, toInt, text.toString()) + alertDialog.hide(); } } From 82f0d0af17e5502904a55bd5b407c103f640160a Mon Sep 17 00:00:00 2001 From: Discut Date: Mon, 22 May 2023 22:52:30 +0800 Subject: [PATCH 03/12] =?UTF-8?q?fix(CustomExportDialog):=20=E6=9A=82?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/cache/CacheViewModel.kt | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index acd859b5c..8fbd74c7d 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri +import android.util.ArraySet import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.MutableLiveData import com.bumptech.glide.Glide @@ -41,6 +42,7 @@ import java.nio.charset.Charset import java.nio.file.* import java.util.* import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.ArrayList import kotlin.coroutines.coroutineContext @@ -248,7 +250,55 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } } + /** + * 解析范围字符串 + * + * @param scope 范围字符串 + * @return 范围 + * + * @since 2023/5/22 + * @author Discut + */ + private fun paresScope(scope: String): IntArray { + val split = scope.split(",") + val result = ArraySet() + for (s in split) { + val v = s.split("-") + if (v.size != 2) { + result.add(s.toInt() - 1) + continue + } + val left = v[0].toInt(); + val right = v[1].toInt(); + if (left > right) + return IntArray(0) + for (i in left..right) + result.add(i - 1) + } + return result.toIntArray() + } + + //////////////////Start EPUB + /** + * 导出Epub 自定义导出范围 + * + * @param path 导出路径 + * @param book 书籍 + * @param size 每本Epub包含的章节 + * @param scope 导出范围 + * @author Discut + * @since 2023/5/22 + */ + fun exportEPUB(path: String, book: Book, size: Int = 1, scope: String) { + if (exportProgress.contains(book.bookUrl)) return + CustomExporter(this).let { + it.scope = paresScope(scope) + it.size = size + it.exportEPUB(path, book) + } + } + /** * 导出Epub */ @@ -373,6 +423,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { ) ) } + else -> { //其他格式文件当做资源文件 folder.listFiles().forEach { @@ -575,4 +626,183 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } //////end of EPUB + + /** + * 自定义Exporter + */ + class CustomExporter(private val context: CacheViewModel) { + var scope: IntArray = IntArray(0); + var size: Int = 1; + + fun exportEPUB( + path: String, + book: Book + ) { + context.exportProgress[book.bookUrl] = 0 + context.exportMsg.remove(book.bookUrl) + context.upAdapterLiveData.sendValue(book.bookUrl) + context.execute { + context.mutex.withLock { + while (context.exportNumber > 0) { + delay(1000) + } + context.exportNumber++ + } + if (path.isContentScheme()) { + val uri = Uri.parse(path) + val doc = DocumentFile.fromTreeUri(context.context, uri) + ?: throw NoStackTraceException("获取导出文档失败") + book.totalChapterNum + // TODO + exportEpub(doc, book) + } else { + context.exportEpub(File(path).createFolderIfNotExist(), book) + } + }.onError { + context.exportProgress.remove(book.bookUrl) + context.exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR" + context.upAdapterLiveData.postValue(book.bookUrl) + it.printStackTrace() + AppLog.put("导出epub书籍<${book.name}>出错\n${it.localizedMessage}", it) + }.onSuccess { + context.exportProgress.remove(book.bookUrl) + context.exportMsg[book.bookUrl] = context.context.getString(R.string.export_success) + context.upAdapterLiveData.postValue(book.bookUrl) + }.onFinally { + context.exportNumber-- + } + } + + /** + * 导出 epub + * + * from [io.legado.app.ui.book.cache.CacheViewModel.exportEpub] + */ + private suspend fun exportEpub(doc: DocumentFile, book: Book) { + val (contentModel, epubList) = createEpubs(doc, book) + epubList.forEachIndexed { index, epubBook -> + //设置正文 + this.setEpubContent(contentModel, book, epubBook, index) + save2Drive(book.name+index+".epub", epubBook, doc) + } + + } + + /** + * 设置epub正文 + * + * from [io.legado.app.ui.book.cache.CacheViewModel.setEpubContent] + */ + private suspend fun setEpubContent( + contentModel: String, + book: Book, + epubBook: EpubBook, + epubBookIndex: Int + ) { + + //正文 + val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() + val contentProcessor = ContentProcessor.get(book.name, book.origin) + var chapterList: MutableList = ArrayList(); + appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> + if (scope.indexOf(index) >= 0) { + chapterList.add(chapter) + } + } + chapterList = chapterList.subList(epubBookIndex * size, (epubBookIndex + 1) * size) + chapterList.forEachIndexed { index, chapter -> + coroutineContext.ensureActive() + context.upAdapterLiveData.postValue(book.bookUrl) + context.exportProgress[book.bookUrl] = index + BookHelp.getContent(book, chapter).let { content -> + var content1 = context.fixPic( + epubBook, + book, + content ?: if (chapter.isVolume) "" else "null", + chapter + ) + content1 = contentProcessor + .getContent( + book, + chapter, + content1, + includeTitle = false, + useReplace = useReplace, + chineseConvert = false, + reSegment = false + ).toString() + val title = chapter.run { + // 不导出vip标识 + isVip = false + getDisplayTitle( + contentProcessor.getTitleReplaceRules(), + useReplace = useReplace + ) + } + epubBook.addSection( + title, + ResourceUtil.createChapterResource( + title.replace("\uD83D\uDD12", ""), + content1, + contentModel, + "Text/chapter_${index}.html" + ) + ) + } + } + } + + /** + * 创建多个epub 对象 + */ + private fun createEpubs(doc: DocumentFile, book: Book): Pair> { + val paresNumOfEpub = paresNumOfEpub(scope.size, size) + val result: MutableList = ArrayList(paresNumOfEpub) + var contentModel = ""; + for (i in 1..paresNumOfEpub) { + val filename = book.getExportFileName("epub") + DocumentUtils.delete(doc, filename) + val epubBook = EpubBook() + epubBook.version = "2.0" + //set metadata + context.setEpubMetadata(book, epubBook) + //set cover + context.setCover(book, epubBook) + //set css + contentModel = context.setAssets(doc, book, epubBook) + + // add epubBook + result.add(epubBook) + } + return Pair(contentModel, result) + } + + /** + * 保存文件到 设备 + */ + private suspend fun save2Drive(filename: String, epubBook: EpubBook, doc: DocumentFile) { + DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> + context.context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> + EpubWriter().write(epubBook, bookOs) + } + if (AppConfig.exportToWebDav) { + // 导出到webdav + AppWebDav.exportWebDav(bookDoc.uri, filename) + } + } + + } + + /** + * 解析 分割epub后的数量 + */ + private fun paresNumOfEpub(total: Int, size: Int): Int { + val i = total % size + var result = total / size; + if (i > 0) { + result++; + } + return result; + } + } } \ No newline at end of file From 4bda1c400ec019b29133354d8f319c63eadc4d21 Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 10:06:49 +0800 Subject: [PATCH 04/12] =?UTF-8?q?fix(CustomExport):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AF=BC=E5=87=BA=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/cache/CacheActivity.kt | 129 ++++++++---------- .../app/ui/book/cache/CacheViewModel.kt | 36 +++-- .../book/cache/CustomExportSectionDialog.kt | 16 --- app/src/main/res/values-zh/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 5 files changed, 84 insertions(+), 101 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 02ec43c73..16246645c 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -35,7 +35,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import androidx.core.widget.doOnTextChanged /** * cache/download 缓存界面 @@ -136,6 +135,7 @@ class CacheActivity : VMBaseActivity() CacheBook.stop(this@CacheActivity) } } + R.id.menu_export_all -> exportAll() R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked // 更改菜单状态[enableCustomExport] @@ -147,6 +147,7 @@ class CacheActivity : VMBaseActivity() R.id.menu_export_folder -> { selectExportFolder(-1) } + R.id.menu_export_file_name -> alertExportFileName() R.id.menu_export_type -> showExportTypeConfig() R.id.menu_export_charset -> showCharsetConfig() @@ -184,6 +185,7 @@ class CacheActivity : VMBaseActivity() 2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) } + 3 -> booksDownload.sortedBy { it.order } else -> booksDownload.sortedByDescending { it.durChapterTime } } @@ -247,8 +249,8 @@ class CacheActivity : VMBaseActivity() val path = ACache.get().getAsString(exportBookPathKey) if (path.isNullOrEmpty()) { selectExportFolder(position) - } else if (AppConfig.enableCustomExport) {// 启用自定义导出 - configExportSection(path, position); + } else if (AppConfig.enableCustomExport && AppConfig.exportType == 1) {// 启用自定义导出 and 导出类型为Epub + configExportSection(path, position) } else { startExport(path, position) } @@ -272,76 +274,63 @@ class CacheActivity : VMBaseActivity() * @since 1.0.0 */ private fun configExportSection(path: String, position: Int) { - if (AppConfig.enableCustomExport) { - val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater) - .apply { + val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater) + .apply { + cbAllExport.isChecked = true + cbSelectExport.isChecked = false + etEpubSize.isEnabled = false + etInputScope.isEnabled = false + tvAllExport.setOnClickListener { cbAllExport.isChecked = true - cbSelectExport.isChecked = false - etEpubSize.isEnabled = false - etInputScope.isEnabled = false - tvAllExport.setOnClickListener { - cbAllExport.isChecked = true - } - tvSelectExport.setOnClickListener { - cbSelectExport.isChecked = true - } - cbSelectExport.onCheckedChangeListener = { _, isChecked -> - if (isChecked) { - etEpubSize.isEnabled = true - etInputScope.isEnabled = true - cbAllExport.isChecked = false - } - } - cbAllExport.onCheckedChangeListener = { _, isChecked -> - if (isChecked) { - etEpubSize.isEnabled = false - etInputScope.isEnabled = false - cbSelectExport.isChecked = false - } - } - - etInputScope.onFocusChangeListener = - View.OnFocusChangeListener { v, hasFocus -> - if (hasFocus) { - etInputScope.hint = "例如:1-5,8,10-18" - } else { - etInputScope.hint = "" - } - } - etInputScope.doOnTextChanged { text, start, before, count -> - var a: Int = 1 - a = 2 - } - } - val alertDialog = alert(titleResource = R.string.select_section_export) { - customView { alertBinding.root } - positiveButton("确认") - //okButton { -// alertBinding.apply { -// it.dismiss() -// if (cbAllExport.isChecked) { -// // export all sections of the book. -// startExport(path, position) -// } else if (cbSelectExport.isChecked) { -// -// } -// } - //} - cancelButton() + tvSelectExport.setOnClickListener { + cbSelectExport.isChecked = true + } + cbSelectExport.onCheckedChangeListener = { _, isChecked -> + if (isChecked) { + etEpubSize.isEnabled = true + etInputScope.isEnabled = true + cbAllExport.isChecked = false + } + } + cbAllExport.onCheckedChangeListener = { _, isChecked -> + if (isChecked) { + etEpubSize.isEnabled = false + etInputScope.isEnabled = false + cbSelectExport.isChecked = false + } + } + + etInputScope.onFocusChangeListener = + View.OnFocusChangeListener { _, hasFocus -> + if (hasFocus) { + etInputScope.hint = "例如:1-5,8,10-18" + } else { + etInputScope.hint = "" + } + } } - alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - alertBinding.apply { - val text = etInputScope.text - if (!verificationField(text.toString())) { - etInputScope.error = "请输入正确的范围" - } else { - etInputScope.error = null - val toInt = etEpubSize.text.toString().toInt() - startExport(path, position, toInt, text.toString()) - alertDialog.hide(); - } + val alertDialog = alert(titleResource = R.string.select_section_export) { + customView { alertBinding.root } + positiveButton("确认") + cancelButton() + } + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + alertBinding.apply { + if (cbAllExport.isChecked) { + startExport(path, position) + alertDialog.hide() + return@apply } + val text = etInputScope.text + if (!verificationField(text.toString())) { + etInputScope.error = "请输入正确的范围" + return@apply + } + etInputScope.error = null + val toInt = etEpubSize.text.toString().toInt() + startExport(path, position, toInt, text.toString()) + alertDialog.hide() } @@ -376,7 +365,7 @@ class CacheActivity : VMBaseActivity() if (exportPosition >= 0) { adapter.getItem(exportPosition)?.let { book -> when (AppConfig.exportType) { - 1 -> viewModel.exportEPUB(path, book, size, scope) + 1 -> viewModel.exportEPUBs(path, book, size, scope) // 目前仅支持 epub //else -> viewModel.export(path, book) } diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index 8fbd74c7d..c4d378964 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -268,8 +268,8 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { result.add(s.toInt() - 1) continue } - val left = v[0].toInt(); - val right = v[1].toInt(); + val left = v[0].toInt() + val right = v[1].toInt() if (left > right) return IntArray(0) for (i in left..right) @@ -290,7 +290,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { * @author Discut * @since 2023/5/22 */ - fun exportEPUB(path: String, book: Book, size: Int = 1, scope: String) { + fun exportEPUBs(path: String, book: Book, size: Int = 1, scope: String) { if (exportProgress.contains(book.bookUrl)) return CustomExporter(this).let { it.scope = paresScope(scope) @@ -627,13 +627,23 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { //////end of EPUB + //////start of custom exporter /** * 自定义Exporter + * + * @since 2023/5/23 */ class CustomExporter(private val context: CacheViewModel) { - var scope: IntArray = IntArray(0); - var size: Int = 1; + var scope: IntArray = IntArray(0) + var size: Int = 1 + /** + * 导出Epub + * + * from [io.legado.app.ui.book.cache.CacheViewModel.exportEPUB] + * @param path 导出的路径 + * @param book 书籍 + */ fun exportEPUB( path: String, book: Book @@ -653,7 +663,6 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val doc = DocumentFile.fromTreeUri(context.context, uri) ?: throw NoStackTraceException("获取导出文档失败") book.totalChapterNum - // TODO exportEpub(doc, book) } else { context.exportEpub(File(path).createFolderIfNotExist(), book) @@ -683,7 +692,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { epubList.forEachIndexed { index, epubBook -> //设置正文 this.setEpubContent(contentModel, book, epubBook, index) - save2Drive(book.name+index+".epub", epubBook, doc) + save2Drive(book.name + index + ".epub", epubBook, doc) } } @@ -703,17 +712,18 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { //正文 val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() val contentProcessor = ContentProcessor.get(book.name, book.origin) - var chapterList: MutableList = ArrayList(); + var chapterList: MutableList = ArrayList() appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> if (scope.indexOf(index) >= 0) { chapterList.add(chapter) } } + val totalChapterNum = book.totalChapterNum / scope.size chapterList = chapterList.subList(epubBookIndex * size, (epubBookIndex + 1) * size) chapterList.forEachIndexed { index, chapter -> coroutineContext.ensureActive() context.upAdapterLiveData.postValue(book.bookUrl) - context.exportProgress[book.bookUrl] = index + context.exportProgress[book.bookUrl] = totalChapterNum * (epubBookIndex * size + index) BookHelp.getContent(book, chapter).let { content -> var content1 = context.fixPic( epubBook, @@ -758,7 +768,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { private fun createEpubs(doc: DocumentFile, book: Book): Pair> { val paresNumOfEpub = paresNumOfEpub(scope.size, size) val result: MutableList = ArrayList(paresNumOfEpub) - var contentModel = ""; + var contentModel = "" for (i in 1..paresNumOfEpub) { val filename = book.getExportFileName("epub") DocumentUtils.delete(doc, filename) @@ -798,11 +808,11 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { */ private fun paresNumOfEpub(total: Int, size: Int): Int { val i = total % size - var result = total / size; + var result = total / size if (i > 0) { - result++; + result++ } - return result; + return result } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt b/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt deleted file mode 100644 index b26f54b43..000000000 --- a/app/src/main/java/io/legado/app/ui/book/cache/CustomExportSectionDialog.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.legado.app.ui.book.cache - -import android.os.Bundle -import android.view.View -import io.legado.app.R -import io.legado.app.base.BaseDialogFragment -import io.legado.app.databinding.DialogSelectSectionExportBinding -import io.legado.app.utils.viewbindingdelegate.viewBinding - -class CustomExportSectionDialog() : BaseDialogFragment(R.layout.dialog_select_section_export) { - - private val binding by viewBinding(DialogSelectSectionExportBinding::bind) - override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 9020237a3..8435a625e 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -31,7 +31,7 @@ 删除所有 替换 替换净化 - 自定义导出章节 + 自定义Epub导出章节 配置替换净化规则 暂无 启用 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18df6b071..d4227405a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ Edit Delete Delete all - Custom export chapter + Custom export chapter of epub Replace Replacement Configure replacement rules From ee0e085519d4493400bf5a0f0bf10cc086360db6 Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 13:11:20 +0800 Subject: [PATCH 05/12] =?UTF-8?q?fix(CustomExport):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E7=AB=A0=E8=8A=82=E5=88=97=E8=A1=A8=E8=B6=8A?= =?UTF-8?q?=E7=95=8C=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/book/BookExtensions.kt | 21 ++++++++++++++ .../app/ui/book/cache/CacheViewModel.kt | 29 ++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index d7a820246..a5a95db19 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -243,4 +243,25 @@ fun Book.getExportFileName(suffix: String): String { }.onFailure { AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it) }.getOrDefault("${name} 作者:${getRealAuthor()}.$suffix") +} + +/** + * 获取分割文件后的文件名 + */ +fun Book.getExportFileName(suffix: String, epubIndex: Int): String { + val jsStr = AppConfig.bookExportFileName + // 默认规则 + val default = "$name 作者:${getRealAuthor()} [${epubIndex}].$suffix" + if (jsStr.isNullOrBlank()) { + return default + } + val bindings = SimpleBindings() + bindings["name"] = name + bindings["author"] = getRealAuthor() + bindings["epubIndex"] = epubIndex + return kotlin.runCatching { + RhinoScriptEngine.eval(jsStr, bindings).toString() + "." + suffix + }.onFailure { + AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it) + }.getOrDefault(default) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index c4d378964..8d13e2176 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -689,10 +689,11 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { */ private suspend fun exportEpub(doc: DocumentFile, book: Book) { val (contentModel, epubList) = createEpubs(doc, book) - epubList.forEachIndexed { index, epubBook -> + epubList.forEachIndexed { index, ep -> + val (filename, epubBook) = ep //设置正文 this.setEpubContent(contentModel, book, epubBook, index) - save2Drive(book.name + index + ".epub", epubBook, doc) + save2Drive(filename, epubBook, doc) } } @@ -717,13 +718,17 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { if (scope.indexOf(index) >= 0) { chapterList.add(chapter) } + if (scope.size == chapterList.size) { + return@forEachIndexed + } } val totalChapterNum = book.totalChapterNum / scope.size - chapterList = chapterList.subList(epubBookIndex * size, (epubBookIndex + 1) * size) + chapterList = chapterList.subList(epubBookIndex * size, if((epubBookIndex + 1) * size > scope.size) scope.size else (epubBookIndex + 1) * size) chapterList.forEachIndexed { index, chapter -> coroutineContext.ensureActive() context.upAdapterLiveData.postValue(book.bookUrl) - context.exportProgress[book.bookUrl] = totalChapterNum * (epubBookIndex * size + index) + context.exportProgress[book.bookUrl] = + totalChapterNum * (epubBookIndex * size + index) BookHelp.getContent(book, chapter).let { content -> var content1 = context.fixPic( epubBook, @@ -764,13 +769,21 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { /** * 创建多个epub 对象 + * + * @param doc 导出文档 + * @param book 书籍 + * + * @return <内容模板字符串, > */ - private fun createEpubs(doc: DocumentFile, book: Book): Pair> { + private fun createEpubs( + doc: DocumentFile, + book: Book + ): Pair>> { val paresNumOfEpub = paresNumOfEpub(scope.size, size) - val result: MutableList = ArrayList(paresNumOfEpub) + val result: MutableList> = ArrayList(paresNumOfEpub) var contentModel = "" for (i in 1..paresNumOfEpub) { - val filename = book.getExportFileName("epub") + val filename = book.getExportFileName("epub", i) DocumentUtils.delete(doc, filename) val epubBook = EpubBook() epubBook.version = "2.0" @@ -782,7 +795,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { contentModel = context.setAssets(doc, book, epubBook) // add epubBook - result.add(epubBook) + result.add(Pair(filename, epubBook)) } return Pair(contentModel, result) } From dc4f1e03d7d916bf064c8ddee56ff9a7de3bb166 Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 14:01:21 +0800 Subject: [PATCH 06/12] =?UTF-8?q?feat(CustomExport):=20=E2=9C=A8=20?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E5=AF=BC=E5=87=BA=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8F=98=E9=87=8F=20epubIndex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/cache/CacheActivity.kt | 6 +- .../app/ui/book/cache/CacheViewModel.kt | 83 +++++++++++++++++-- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 16246645c..499c144c6 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -398,7 +398,11 @@ class CacheActivity : VMBaseActivity() @SuppressLint("SetTextI18n") private fun alertExportFileName() { alert(R.string.export_file_name) { - setMessage("js内有name和author变量,返回书名") + var message = "js内有name和author变量,返回书名\n启用自定义epub导出章节时包含额外变量[epubIndex]" + if (AppConfig.bookExportFileName.isNullOrBlank()) { + message += "\n例如:\nname+\"-\"+author+(epubIndex?\"(\"+epubIndex+\")\":\"\")" + } + setMessage(message) val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { editView.hint = "file name js" editView.setText(AppConfig.bookExportFileName) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index 8d13e2176..6b4072e61 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -281,13 +281,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { //////////////////Start EPUB /** - * 导出Epub 自定义导出范围 + * 导出Epub 根据自定义导出范围 * * @param path 导出路径 * @param book 书籍 * @param size 每本Epub包含的章节 * @param scope 导出范围 - * @author Discut * @since 2023/5/22 */ fun exportEPUBs(path: String, book: Book, size: Int = 1, scope: String) { @@ -295,7 +294,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { CustomExporter(this).let { it.scope = paresScope(scope) it.size = size - it.exportEPUB(path, book) + it.export(path, book) } } @@ -644,7 +643,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { * @param path 导出的路径 * @param book 书籍 */ - fun exportEPUB( + fun export( path: String, book: Book ) { @@ -662,10 +661,9 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val uri = Uri.parse(path) val doc = DocumentFile.fromTreeUri(context.context, uri) ?: throw NoStackTraceException("获取导出文档失败") - book.totalChapterNum exportEpub(doc, book) } else { - context.exportEpub(File(path).createFolderIfNotExist(), book) + exportEpub(File(path).createFolderIfNotExist(), book) } }.onError { context.exportProgress.remove(book.bookUrl) @@ -682,6 +680,22 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } } + /** + * 导出 epub + * + * from [io.legado.app.ui.book.cache.CacheViewModel.exportEpub] + */ + private suspend fun exportEpub(file: File, book: Book) { + val (contentModel, epubList) = createEpubs(book) + epubList.forEachIndexed { index, ep -> + val (filename, epubBook) = ep + //设置正文 + this.setEpubContent(contentModel, book, epubBook, index) + save2Drive(filename, epubBook, file) + } + + } + /** * 导出 epub * @@ -698,10 +712,16 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } + /** * 设置epub正文 * * from [io.legado.app.ui.book.cache.CacheViewModel.setEpubContent] + * + * @param contentModel 正文模板 + * @param book 书籍 + * @param epubBook 分割后的epub + * @param epubBookIndex 分割后的epub序号 */ private suspend fun setEpubContent( contentModel: String, @@ -709,7 +729,6 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { epubBook: EpubBook, epubBookIndex: Int ) { - //正文 val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() val contentProcessor = ContentProcessor.get(book.name, book.origin) @@ -770,6 +789,8 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { /** * 创建多个epub 对象 * + * 分割epub时,一个书籍需要创建多个epub对象 + * * @param doc 导出文档 * @param book 书籍 * @@ -800,6 +821,38 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { return Pair(contentModel, result) } + /** + * 创建多个epub 对象 + * + * 分割epub时,一个书籍需要创建多个epub对象 + * + * @param book 书籍 + * + * @return <内容模板字符串, > + */ + private fun createEpubs( + book: Book + ): Pair>> { + val paresNumOfEpub = paresNumOfEpub(scope.size, size) + val result: MutableList> = ArrayList(paresNumOfEpub) + var contentModel = "" + for (i in 1..paresNumOfEpub) { + val filename = book.getExportFileName("epub", i) + val epubBook = EpubBook() + epubBook.version = "2.0" + //set metadata + context.setEpubMetadata(book, epubBook) + //set cover + context.setCover(book, epubBook) + //set css + contentModel = context.setAssets(book, epubBook) + + // add epubBook + result.add(Pair(filename, epubBook)) + } + return Pair(contentModel, result) + } + /** * 保存文件到 设备 */ @@ -813,11 +866,27 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { AppWebDav.exportWebDav(bookDoc.uri, filename) } } + } + /** + * 保存文件到 设备 + */ + private suspend fun save2Drive(filename: String, epubBook: EpubBook, file: File) { + val bookPath = FileUtils.getPath(file, filename) + val bookFile = FileUtils.createFileWithReplace(bookPath) + @Suppress("BlockingMethodInNonBlockingContext") + EpubWriter().write(epubBook, FileOutputStream(bookFile)) + if (AppConfig.exportToWebDav) { + // 导出到webdav + AppWebDav.exportWebDav(Uri.fromFile(bookFile), filename) + } } /** * 解析 分割epub后的数量 + * + * @param total 章节总数 + * @param size 每个epub文件包含多少章节 */ private fun paresNumOfEpub(total: Int, size: Int): Int { val i = total % size From 10c229e6096f87c054492defaf2064098443a25e Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 15:38:32 +0800 Subject: [PATCH 07/12] =?UTF-8?q?feat(i18n):=20=E2=9C=A8=20=E5=A4=9A?= =?UTF-8?q?=E5=9B=BD=E8=AF=AD=E8=A8=80=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values-es-rES/strings.xml | 6 ++++++ app/src/main/res/values-ja-rJP/strings.xml | 6 ++++++ app/src/main/res/values-pt-rBR/strings.xml | 6 ++++++ app/src/main/res/values-zh-rHK/strings.xml | 6 ++++++ app/src/main/res/values-zh-rTW/strings.xml | 6 ++++++ app/src/main/res/values-zh/strings.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 ++++ 7 files changed, 38 insertions(+) diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index c48072554..ef327d25d 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -33,6 +33,7 @@ Reemplazar Reemplazo Configurar reglas de reemplazo + Custom export chapter of epub No disponible Activar Buscar reemplazo @@ -1104,4 +1105,9 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + Choose some chapters to be exported + Please enter the correct range + Custom Export + The number of chapters contained in each file + The section index that needs to be exported diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index a3d2beb15..a3856efbe 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -44,6 +44,7 @@ Subscription All Recent reading + カスタムEPUBエクスポートの章 Last reading What\'s new The bookshelf is still empty. Search for books or add them from discovery! \n if you use it for the first time, please open legado.top get help! @@ -1107,4 +1108,9 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + Choose some chapters to be exported + Please enter the correct range + Custom Export + The number of chapters contained in each file + The section index that needs to be exported diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5d1ddc05d..0735f2ff0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -33,6 +33,7 @@ Substituir Substituição Configurar regras de substituição + Custom export chapter of epub Indisponível Ativar Procurar o substituto @@ -1107,4 +1108,9 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + Choose some chapters to be exported + Please enter the correct range + Custom Export + The index of chapters contained in each file + The section index that needs to be exported diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 0394474f2..ba211f181 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -33,6 +33,7 @@ 暫無 啟用 替換淨化-搜尋 + 自定義Epub導出章節 書架 收藏夾 收藏 @@ -1104,4 +1105,9 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + 選擇待導出章節 + 請輸入正確的範圍 + 自定義導出 + 每個文件包含的章節數量 + 需要輸出的章節 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 180d998c1..96926e0cc 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -32,6 +32,7 @@ 取代 取代淨化 配置取代淨化規則 + 自定義Epub導出章節 暫無 啟用 取代淨化-搜尋 @@ -1106,4 +1107,9 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + 選擇待導出章節 + 請輸入正確的範圍 + 自定義導出 + 每個文件包含的章節數量 + 需要輸出的章節 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 8435a625e..cf8435b52 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1108,4 +1108,8 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + 请输入正确的范围 + 自定义导出 + 每个文件包含的章节数量 + 需要输出的章节 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4227405a..1b04c5ec4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1109,4 +1109,8 @@ 排除范围,选填书名或者书源 URL 格式化规则(formatJs) 调整位置 + Please enter the correct range + Custom Export + The number of chapters contained in each file + The section index that needs to be exported From 1f2ba63865255776a21dd8151cd1e99d36c07a3e Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 15:39:33 +0800 Subject: [PATCH 08/12] =?UTF-8?q?perf(dialog=5Fselect=5Fsection=5Fexport):?= =?UTF-8?q?=20=E2=9A=A1=20=E4=BC=98=E5=8C=96=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/dialog_select_section_export.xml | 149 ++++++++---------- 1 file changed, 64 insertions(+), 85 deletions(-) diff --git a/app/src/main/res/layout/dialog_select_section_export.xml b/app/src/main/res/layout/dialog_select_section_export.xml index b22c69f9f..48e395bdf 100644 --- a/app/src/main/res/layout/dialog_select_section_export.xml +++ b/app/src/main/res/layout/dialog_select_section_export.xml @@ -1,6 +1,5 @@ - + + + + + + + + android:layout_marginTop="12dp" + android:orientation="horizontal"> - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:text="@string/custom_export" + android:textSize="18sp" /> - + + + + + + + + + + + + From 0483bfae88bccb24ee1ce41908d6340c9999cfff Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 15:40:14 +0800 Subject: [PATCH 09/12] =?UTF-8?q?perf(CustomDialog):=20=E2=9A=A1=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E6=95=88=E6=9E=9C=E4=B8=8E=E8=BF=90=E8=A1=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/cache/CacheActivity.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 499c144c6..5f67bbd3d 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -276,15 +276,12 @@ class CacheActivity : VMBaseActivity() private fun configExportSection(path: String, position: Int) { val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater) .apply { - cbAllExport.isChecked = true - cbSelectExport.isChecked = false - etEpubSize.isEnabled = false - etInputScope.isEnabled = false + etEpubSize.setText("1") tvAllExport.setOnClickListener { - cbAllExport.isChecked = true + cbAllExport.callOnClick() } tvSelectExport.setOnClickListener { - cbSelectExport.isChecked = true + cbSelectExport.callOnClick() } cbSelectExport.onCheckedChangeListener = { _, isChecked -> if (isChecked) { @@ -304,15 +301,16 @@ class CacheActivity : VMBaseActivity() etInputScope.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (hasFocus) { - etInputScope.hint = "例如:1-5,8,10-18" + etInputScope.hint = "1-5,8,10-18" } else { etInputScope.hint = "" } } + cbAllExport.callOnClick() } val alertDialog = alert(titleResource = R.string.select_section_export) { customView { alertBinding.root } - positiveButton("确认") + positiveButton(R.string.ok) cancelButton() } alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { @@ -324,7 +322,8 @@ class CacheActivity : VMBaseActivity() } val text = etInputScope.text if (!verificationField(text.toString())) { - etInputScope.error = "请输入正确的范围" + etInputScope.error = + applicationContext.getString(R.string.error_scope_input)//"请输入正确的范围" return@apply } etInputScope.error = null @@ -398,7 +397,8 @@ class CacheActivity : VMBaseActivity() @SuppressLint("SetTextI18n") private fun alertExportFileName() { alert(R.string.export_file_name) { - var message = "js内有name和author变量,返回书名\n启用自定义epub导出章节时包含额外变量[epubIndex]" + var message = + "js内有name和author变量,返回书名\n启用自定义epub导出章节时包含额外变量[epubIndex]" if (AppConfig.bookExportFileName.isNullOrBlank()) { message += "\n例如:\nname+\"-\"+author+(epubIndex?\"(\"+epubIndex+\")\":\"\")" } From a6c69e5e058118651af18e8ceabd3a562e80c657 Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 20:59:51 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fix(CustomDialog):=20=F0=9F=90=9B=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E9=94=99=E8=AF=AF=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E4=BC=9A=E5=AF=BC=E8=87=B4=E6=B5=81=E7=A8=8B=E4=B8=AD?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复后行为为: + 丢弃错误表达式 + 尝试解析剩下的表达式 --- .../main/java/io/legado/app/ui/book/cache/CacheViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index 6b4072e61..546cd3076 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -270,8 +270,10 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } val left = v[0].toInt() val right = v[1].toInt() - if (left > right) - return IntArray(0) + if (left > right){ + AppLog.put("Error expression : $s; left > right") + continue + } for (i in left..right) result.add(i - 1) } From 835c3df175d9bf4629a493143e56ed24d6405efc Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 21:58:42 +0800 Subject: [PATCH 11/12] =?UTF-8?q?fix(CustomDialog):=20=F0=9F=90=9B=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20EpubSize=E5=8F=AF=E8=83=BD=E4=BC=9A?= =?UTF-8?q?=E6=BA=A2=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 无章节错误信息 --- .../io/legado/app/ui/book/cache/CacheViewModel.kt | 11 +++++++++-- .../main/res/layout/dialog_select_section_export.xml | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index 546cd3076..ef51c38b0 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -270,7 +270,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } val left = v[0].toInt() val right = v[1].toInt() - if (left > right){ + if (left > right) { AppLog.put("Error expression : $s; left > right") continue } @@ -735,6 +735,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() val contentProcessor = ContentProcessor.get(book.name, book.origin) var chapterList: MutableList = ArrayList() + appDb.bookChapterDao.getChapterList(book.bookUrl) appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> if (scope.indexOf(index) >= 0) { chapterList.add(chapter) @@ -744,7 +745,13 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } } val totalChapterNum = book.totalChapterNum / scope.size - chapterList = chapterList.subList(epubBookIndex * size, if((epubBookIndex + 1) * size > scope.size) scope.size else (epubBookIndex + 1) * size) + if (chapterList.size == 0) { + throw RuntimeException("书籍<${book.name}>(${epubBookIndex + 1})未找到章节信息") + } + chapterList = chapterList.subList( + epubBookIndex * size, + if ((epubBookIndex + 1) * size > scope.size) scope.size else (epubBookIndex + 1) * size + ) chapterList.forEachIndexed { index, chapter -> coroutineContext.ensureActive() context.upAdapterLiveData.postValue(book.bookUrl) diff --git a/app/src/main/res/layout/dialog_select_section_export.xml b/app/src/main/res/layout/dialog_select_section_export.xml index 48e395bdf..6bff4baff 100644 --- a/app/src/main/res/layout/dialog_select_section_export.xml +++ b/app/src/main/res/layout/dialog_select_section_export.xml @@ -64,6 +64,7 @@ android:id="@+id/et_epub_size" android:layout_width="match_parent" android:layout_height="wrap_content" + android:maxLength="6" android:inputType="number" tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" /> From 6a41cd6cbe963f20eef9decde190056cc05c6408 Mon Sep 17 00:00:00 2001 From: Discut Date: Tue, 23 May 2023 22:07:40 +0800 Subject: [PATCH 12/12] =?UTF-8?q?style(CustomDialog):=20=F0=9F=8E=A8=20?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/ui/book/cache/CacheViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index ef51c38b0..7d4a26759 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -270,7 +270,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } val left = v[0].toInt() val right = v[1].toInt() - if (left > right) { + if (left > right){ AppLog.put("Error expression : $s; left > right") continue } @@ -735,7 +735,6 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule() val contentProcessor = ContentProcessor.get(book.name, book.origin) var chapterList: MutableList = ArrayList() - appDb.bookChapterDao.getChapterList(book.bookUrl) appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> if (scope.indexOf(index) >= 0) { chapterList.add(chapter)