Merge pull request #3118 from Discut/custom_export_dev

Custom export dev
This commit is contained in:
kunfei
2023-05-26 15:21:07 +08:00
committed by GitHub
14 changed files with 616 additions and 1 deletions
@@ -54,6 +54,7 @@ object PreferKey {
const val webDavAccount = "web_dav_account" const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password" const val webDavPassword = "web_dav_password"
const val webDavDir = "webDavDir" const val webDavDir = "webDavDir"
const val enableCustomExport = "enableCustomExport"
const val exportToWebDav = "webDavCacheBackup" const val exportToWebDav = "webDavCacheBackup"
const val exportNoChapterName = "exportNoChapterName" const val exportNoChapterName = "exportNoChapterName"
const val exportType = "exportType" const val exportType = "exportType"
@@ -243,4 +243,25 @@ fun Book.getExportFileName(suffix: String): String {
}.onFailure { }.onFailure {
AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it) AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it)
}.getOrDefault("${name} 作者:${getRealAuthor()}.$suffix") }.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)
} }
@@ -268,6 +268,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
set(value) { set(value) {
appCtx.putPrefBoolean(PreferKey.exportNoChapterName, value) appCtx.putPrefBoolean(PreferKey.exportNoChapterName, value)
} }
var enableCustomExport: Boolean
get() = appCtx.getPrefBoolean(PreferKey.enableCustomExport)
set(value) {
appCtx.putPrefBoolean(PreferKey.enableCustomExport, value)
}
var exportType: Int var exportType: Int
get() = appCtx.getPrefInt(PreferKey.exportType) get() = appCtx.getPrefInt(PreferKey.exportType)
set(value) { set(value) {
@@ -4,7 +4,9 @@ import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity 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.data.entities.BookGroup
import io.legado.app.databinding.ActivityCacheBookBinding import io.legado.app.databinding.ActivityCacheBookBinding
import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogSelectSectionExportBinding
import io.legado.app.help.book.isAudio import io.legado.app.help.book.isAudio
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.SelectItem import io.legado.app.lib.dialogs.SelectItem
@@ -33,6 +36,9 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
/**
* cache/download 缓存界面
*/
class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(), class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(),
CacheAdapter.CallBack { CacheAdapter.CallBack {
@@ -88,6 +94,8 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean { override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.findItem(R.id.menu_enable_replace)?.isChecked = AppConfig.exportUseReplace 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_no_chapter_name)?.isChecked = AppConfig.exportNoChapterName
menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav
menu.findItem(R.id.menu_export_pics_file)?.isChecked = AppConfig.exportPictureFile menu.findItem(R.id.menu_export_pics_file)?.isChecked = AppConfig.exportPictureFile
@@ -108,6 +116,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
} }
/**
* 菜单按下回调
*/
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_download -> { R.id.menu_download -> {
@@ -124,8 +135,11 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
CacheBook.stop(this@CacheActivity) CacheBook.stop(this@CacheActivity)
} }
} }
R.id.menu_export_all -> exportAll() R.id.menu_export_all -> exportAll()
R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked 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_no_chapter_name -> AppConfig.exportNoChapterName = !item.isChecked
R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked
R.id.menu_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked R.id.menu_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked
@@ -133,6 +147,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
R.id.menu_export_folder -> { R.id.menu_export_folder -> {
selectExportFolder(-1) selectExportFolder(-1)
} }
R.id.menu_export_file_name -> alertExportFileName() R.id.menu_export_file_name -> alertExportFileName()
R.id.menu_export_type -> showExportTypeConfig() R.id.menu_export_type -> showExportTypeConfig()
R.id.menu_export_charset -> showCharsetConfig() R.id.menu_export_charset -> showCharsetConfig()
@@ -170,6 +185,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
2 -> booksDownload.sortedWith { o1, o2 -> 2 -> booksDownload.sortedWith { o1, o2 ->
o1.name.cnCompare(o2.name) o1.name.cnCompare(o2.name)
} }
3 -> booksDownload.sortedBy { it.order } 3 -> booksDownload.sortedBy { it.order }
else -> booksDownload.sortedByDescending { it.durChapterTime } else -> booksDownload.sortedByDescending { it.durChapterTime }
} }
@@ -233,6 +249,8 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
val path = ACache.get().getAsString(exportBookPathKey) val path = ACache.get().getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) { if (path.isNullOrEmpty()) {
selectExportFolder(position) selectExportFolder(position)
} else if (AppConfig.enableCustomExport && AppConfig.exportType == 1) {// 启用自定义导出 and 导出类型为Epub
configExportSection(path, position)
} else { } else {
startExport(path, position) startExport(path, position)
} }
@@ -247,6 +265,89 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
} }
/**
* 配置自定义导出对话框
*
* @param path 导出路径
* @param position book位置
* @author Discut
* @since 1.0.0
*/
private fun configExportSection(path: String, position: Int) {
val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater)
.apply {
etEpubSize.setText("1")
tvAllExport.setOnClickListener {
cbAllExport.callOnClick()
}
tvSelectExport.setOnClickListener {
cbSelectExport.callOnClick()
}
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 = ""
}
}
cbAllExport.callOnClick()
}
val alertDialog = alert(titleResource = R.string.select_section_export) {
customView { alertBinding.root }
positiveButton(R.string.ok)
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 =
applicationContext.getString(R.string.error_scope_input)//"请输入正确的范围"
return@apply
}
etInputScope.error = null
val toInt = etEpubSize.text.toString().toInt()
startExport(path, position, toInt, text.toString())
alertDialog.hide()
}
}
}
/**
* 验证 输入的范围 是否正确
*
* @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) { private fun selectExportFolder(exportPosition: Int) {
val default = arrayListOf<SelectItem<Int>>() val default = arrayListOf<SelectItem<Int>>()
val path = ACache.get().getAsString(exportBookPathKey) val path = ACache.get().getAsString(exportBookPathKey)
@@ -259,6 +360,18 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
} }
private fun startExport(path: String, exportPosition: Int, size: Int, scope: String) {
if (exportPosition >= 0) {
adapter.getItem(exportPosition)?.let { book ->
when (AppConfig.exportType) {
1 -> viewModel.exportEPUBs(path, book, size, scope)
// 目前仅支持 epub
//else -> viewModel.export(path, book)
}
}
}
}
private fun startExport(path: String, exportPosition: Int) { private fun startExport(path: String, exportPosition: Int) {
if (exportPosition == -10) { if (exportPosition == -10) {
if (adapter.getItems().isNotEmpty()) { if (adapter.getItems().isNotEmpty()) {
@@ -284,7 +397,12 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun alertExportFileName() { private fun alertExportFileName() {
alert(R.string.export_file_name) { 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 { val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
editView.hint = "file name js" editView.hint = "file name js"
editView.setText(AppConfig.bookExportFileName) editView.setText(AppConfig.bookExportFileName)
@@ -4,6 +4,7 @@ import android.app.Application
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.util.ArraySet
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@@ -41,6 +42,7 @@ import java.nio.charset.Charset
import java.nio.file.* import java.nio.file.*
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.ArrayList
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
@@ -248,7 +250,56 @@ 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<Int>()
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){
AppLog.put("Error expression : $s; left > right")
continue
}
for (i in left..right)
result.add(i - 1)
}
return result.toIntArray()
}
//////////////////Start EPUB //////////////////Start EPUB
/**
* 导出Epub 根据自定义导出范围
*
* @param path 导出路径
* @param book 书籍
* @param size 每本Epub包含的章节
* @param scope 导出范围
* @since 2023/5/22
*/
fun exportEPUBs(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.export(path, book)
}
}
/** /**
* 导出Epub * 导出Epub
*/ */
@@ -373,6 +424,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
) )
) )
} }
else -> { else -> {
//其他格式文件当做资源文件 //其他格式文件当做资源文件
folder.listFiles().forEach { folder.listFiles().forEach {
@@ -575,4 +627,282 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
//////end of EPUB //////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
/**
* 导出Epub
*
* from [io.legado.app.ui.book.cache.CacheViewModel.exportEPUB]
* @param path 导出的路径
* @param book 书籍
*/
fun export(
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("获取导出文档失败")
exportEpub(doc, book)
} else {
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(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
*
* 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, ep ->
val (filename, epubBook) = ep
//设置正文
this.setEpubContent(contentModel, book, epubBook, index)
save2Drive(filename, epubBook, doc)
}
}
/**
* 设置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,
book: Book,
epubBook: EpubBook,
epubBookIndex: Int
) {
//正文
val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()
val contentProcessor = ContentProcessor.get(book.name, book.origin)
var chapterList: MutableList<BookChapter> = ArrayList()
appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->
if (scope.indexOf(index) >= 0) {
chapterList.add(chapter)
}
if (scope.size == chapterList.size) {
return@forEachIndexed
}
}
val totalChapterNum = book.totalChapterNum / scope.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)
context.exportProgress[book.bookUrl] =
totalChapterNum * (epubBookIndex * size + 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 对象
*
* 分割epub时,一个书籍需要创建多个epub对象
*
* @param doc 导出文档
* @param book 书籍
*
* @return <内容模板字符串, <epub文件名, epub对象>>
*/
private fun createEpubs(
doc: DocumentFile,
book: Book
): Pair<String, List<Pair<String, EpubBook>>> {
val paresNumOfEpub = paresNumOfEpub(scope.size, size)
val result: MutableList<Pair<String, EpubBook>> = ArrayList(paresNumOfEpub)
var contentModel = ""
for (i in 1..paresNumOfEpub) {
val filename = book.getExportFileName("epub", i)
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(Pair(filename, epubBook))
}
return Pair(contentModel, result)
}
/**
* 创建多个epub 对象
*
* 分割epub时,一个书籍需要创建多个epub对象
*
* @param book 书籍
*
* @return <内容模板字符串, <epub文件名, epub对象>>
*/
private fun createEpubs(
book: Book
): Pair<String, List<Pair<String, EpubBook>>> {
val paresNumOfEpub = paresNumOfEpub(scope.size, size)
val result: MutableList<Pair<String, EpubBook>> = 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)
}
/**
* 保存文件到 设备
*/
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)
}
}
}
/**
* 保存文件到 设备
*/
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
var result = total / size
if (i > 0) {
result++
}
return result
}
}
} }
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<io.legado.app.ui.widget.checkbox.SmoothCheckBox
android:id="@+id/cb_all_export"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/tv_all_export"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/export_all"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<io.legado.app.ui.widget.checkbox.SmoothCheckBox
android:id="@+id/cb_select_export"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="6dp"
android:layout_marginEnd="16dp" />
<TextView
android:id="@+id/tv_select_export"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/custom_export"
android:textSize="18sp" />
</LinearLayout>
<io.legado.app.ui.widget.text.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="@string/file_contains_number">
<io.legado.app.lib.theme.view.ThemeEditText
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" />
</io.legado.app.ui.widget.text.TextInputLayout>
<io.legado.app.ui.widget.text.TextInputLayout
android:id="@+id/ly_et_input_scope"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/export_chapter_index">
<io.legado.app.lib.theme.view.ThemeEditText
android:id="@+id/et_input_scope"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</io.legado.app.ui.widget.text.TextInputLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+7
View File
@@ -31,6 +31,13 @@
android:checkable="true" android:checkable="true"
app:showAsAction="never" /> app:showAsAction="never" />
<!--自定义导出章节 选择菜单-->
<item
android:id="@+id/menu_enable_custom_export"
android:title="@string/custom_export_section"
android:checkable="true"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_export_web_dav" android:id="@+id/menu_export_web_dav"
android:title="@string/export_to_web_dav" android:title="@string/export_to_web_dav"
@@ -33,6 +33,7 @@
<string name="replace">Reemplazar</string> <string name="replace">Reemplazar</string>
<string name="replace_purify">Reemplazo</string> <string name="replace_purify">Reemplazo</string>
<string name="replace_purify_desc">Configurar reglas de reemplazo</string> <string name="replace_purify_desc">Configurar reglas de reemplazo</string>
<string name="custom_export_section">Custom export chapter of epub</string>
<string name="not_available">No disponible</string> <string name="not_available">No disponible</string>
<string name="enable">Activar</string> <string name="enable">Activar</string>
<string name="replace_purify_search">Buscar reemplazo</string> <string name="replace_purify_search">Buscar reemplazo</string>
@@ -1104,4 +1105,9 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="select_section_export">Choose some chapters to be exported</string>
<string name="error_scope_input">Please enter the correct range</string>
<string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string>
</resources> </resources>
@@ -44,6 +44,7 @@
<string name="rss">Subscription</string> <string name="rss">Subscription</string>
<string name="all">All</string> <string name="all">All</string>
<string name="recent_reading">Recent reading</string> <string name="recent_reading">Recent reading</string>
<string name="custom_export_section">カスタムEPUBエクスポートの章</string>
<string name="last_read">Last reading</string> <string name="last_read">Last reading</string>
<string name="update_log">What\'s new</string> <string name="update_log">What\'s new</string>
<string name="bookshelf_empty">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!</string> <string name="bookshelf_empty">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!</string>
@@ -1107,4 +1108,9 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="select_section_export">Choose some chapters to be exported</string>
<string name="error_scope_input">Please enter the correct range</string>
<string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string>
</resources> </resources>
@@ -33,6 +33,7 @@
<string name="replace">Substituir</string> <string name="replace">Substituir</string>
<string name="replace_purify">Substituição</string> <string name="replace_purify">Substituição</string>
<string name="replace_purify_desc">Configurar regras de substituição</string> <string name="replace_purify_desc">Configurar regras de substituição</string>
<string name="custom_export_section">Custom export chapter of epub</string>
<string name="not_available">Indisponível</string> <string name="not_available">Indisponível</string>
<string name="enable">Ativar</string> <string name="enable">Ativar</string>
<string name="replace_purify_search">Procurar o substituto</string> <string name="replace_purify_search">Procurar o substituto</string>
@@ -1107,4 +1108,9 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="select_section_export">Choose some chapters to be exported</string>
<string name="error_scope_input">Please enter the correct range</string>
<string name="custom_export">Custom Export</string>
<string name="file_contains_number">The index of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string>
</resources> </resources>
@@ -33,6 +33,7 @@
<string name="not_available">暫無</string> <string name="not_available">暫無</string>
<string name="enable">啟用</string> <string name="enable">啟用</string>
<string name="replace_purify_search">替換淨化-搜尋</string> <string name="replace_purify_search">替換淨化-搜尋</string>
<string name="custom_export_section">自定義Epub導出章節</string>
<string name="bookshelf">書架</string> <string name="bookshelf">書架</string>
<string name="favorites">收藏夾</string> <string name="favorites">收藏夾</string>
<string name="favorite">收藏</string> <string name="favorite">收藏</string>
@@ -1104,4 +1105,9 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="select_section_export">選擇待導出章節</string>
<string name="error_scope_input">請輸入正確的範圍</string>
<string name="custom_export">自定義導出</string>
<string name="file_contains_number">每個文件包含的章節數量</string>
<string name="export_chapter_index">需要輸出的章節</string>
</resources> </resources>
@@ -32,6 +32,7 @@
<string name="replace">取代</string> <string name="replace">取代</string>
<string name="replace_purify">取代淨化</string> <string name="replace_purify">取代淨化</string>
<string name="replace_purify_desc">配置取代淨化規則</string> <string name="replace_purify_desc">配置取代淨化規則</string>
<string name="custom_export_section">自定義Epub導出章節</string>
<string name="not_available">暫無</string> <string name="not_available">暫無</string>
<string name="enable">啟用</string> <string name="enable">啟用</string>
<string name="replace_purify_search">取代淨化-搜尋</string> <string name="replace_purify_search">取代淨化-搜尋</string>
@@ -1106,4 +1107,9 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="select_section_export">選擇待導出章節</string>
<string name="error_scope_input">請輸入正確的範圍</string>
<string name="custom_export">自定義導出</string>
<string name="file_contains_number">每個文件包含的章節數量</string>
<string name="export_chapter_index">需要輸出的章節</string>
</resources> </resources>
+6
View File
@@ -31,6 +31,7 @@
<string name="delete_all">删除所有</string> <string name="delete_all">删除所有</string>
<string name="replace">替换</string> <string name="replace">替换</string>
<string name="replace_purify">替换净化</string> <string name="replace_purify">替换净化</string>
<string name="custom_export_section">自定义Epub导出章节</string>
<string name="replace_purify_desc">配置替换净化规则</string> <string name="replace_purify_desc">配置替换净化规则</string>
<string name="not_available">暂无</string> <string name="not_available">暂无</string>
<string name="enable">启用</string> <string name="enable">启用</string>
@@ -346,6 +347,7 @@
<string name="ps_show_all_find">关闭则只显示勾选源的发现</string> <string name="ps_show_all_find">关闭则只显示勾选源的发现</string>
<string name="update_toc">更新目录</string> <string name="update_toc">更新目录</string>
<string name="txt_toc_rule">TXT 目录规则</string> <string name="txt_toc_rule">TXT 目录规则</string>
<string name="select_section_export">选择待导出章节</string>
<string name="set_charset">设置编码</string> <string name="set_charset">设置编码</string>
<string name="swap_sort">倒序-顺序</string> <string name="swap_sort">倒序-顺序</string>
<string name="sort">排序</string> <string name="sort">排序</string>
@@ -1106,4 +1108,8 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="error_scope_input">请输入正确的范围</string>
<string name="custom_export">自定义导出</string>
<string name="file_contains_number">每个文件包含的章节数量</string>
<string name="export_chapter_index">需要输出的章节</string>
</resources> </resources>
+6
View File
@@ -30,6 +30,7 @@
<string name="edit">Edit</string> <string name="edit">Edit</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="delete_all">Delete all</string> <string name="delete_all">Delete all</string>
<string name="custom_export_section">Custom export chapter of epub</string>
<string name="replace">Replace</string> <string name="replace">Replace</string>
<string name="replace_purify">Replacement</string> <string name="replace_purify">Replacement</string>
<string name="replace_purify_desc">Configure replacement rules</string> <string name="replace_purify_desc">Configure replacement rules</string>
@@ -347,6 +348,7 @@
<string name="ps_show_all_find">Display the selected origin\'s Discovery if closed</string> <string name="ps_show_all_find">Display the selected origin\'s Discovery if closed</string>
<string name="update_toc">Update chapters</string> <string name="update_toc">Update chapters</string>
<string name="txt_toc_rule">Txt Chapters Rule</string> <string name="txt_toc_rule">Txt Chapters Rule</string>
<string name="select_section_export">Choose some chapters to be exported</string>
<string name="set_charset">Text encoding</string> <string name="set_charset">Text encoding</string>
<string name="swap_sort">Ascending/Descending order</string> <string name="swap_sort">Ascending/Descending order</string>
<string name="sort">Sort</string> <string name="sort">Sort</string>
@@ -1107,4 +1109,8 @@
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string> <string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
<string name="format_js_rule">格式化规则(formatJs)</string> <string name="format_js_rule">格式化规则(formatJs)</string>
<string name="adjust_pos">调整位置</string> <string name="adjust_pos">调整位置</string>
<string name="error_scope_input">Please enter the correct range</string>
<string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string>
</resources> </resources>