mirror of
https://github.com/gedoor/legado.git
synced 2024-07-06 23:47:49 +08:00
Merge pull request #3118 from Discut/custom_export_dev
Custom export dev
This commit is contained in:
commit
1b1e343f77
@ -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"
|
||||
|
@ -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)
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
@ -33,6 +36,9 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* cache/download 缓存界面
|
||||
*/
|
||||
class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(),
|
||||
CacheAdapter.CallBack {
|
||||
|
||||
@ -88,6 +94,8 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
|
||||
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 +116,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单按下回调
|
||||
*/
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_download -> {
|
||||
@ -124,8 +135,11 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
CacheBook.stop(this@CacheActivity)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -133,6 +147,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
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()
|
||||
@ -170,6 +185,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
2 -> booksDownload.sortedWith { o1, o2 ->
|
||||
o1.name.cnCompare(o2.name)
|
||||
}
|
||||
|
||||
3 -> booksDownload.sortedBy { it.order }
|
||||
else -> booksDownload.sortedByDescending { it.durChapterTime }
|
||||
}
|
||||
@ -233,6 +249,8 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
val path = ACache.get().getAsString(exportBookPathKey)
|
||||
if (path.isNullOrEmpty()) {
|
||||
selectExportFolder(position)
|
||||
} else if (AppConfig.enableCustomExport && AppConfig.exportType == 1) {// 启用自定义导出 and 导出类型为Epub
|
||||
configExportSection(path, position)
|
||||
} else {
|
||||
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) {
|
||||
val default = arrayListOf<SelectItem<Int>>()
|
||||
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) {
|
||||
if (exportPosition == -10) {
|
||||
if (adapter.getItems().isNotEmpty()) {
|
||||
@ -284,7 +397,12 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
@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)
|
||||
|
@ -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,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
|
||||
/**
|
||||
* 导出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
|
||||
*/
|
||||
@ -373,6 +424,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
//其他格式文件当做资源文件
|
||||
folder.listFiles().forEach {
|
||||
@ -575,4 +627,282 @@ 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
|
||||
|
||||
/**
|
||||
* 导出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
|
||||
}
|
||||
}
|
||||
}
|
90
app/src/main/res/layout/dialog_select_section_export.xml
Normal file
90
app/src/main/res/layout/dialog_select_section_export.xml
Normal file
@ -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>
|
@ -31,6 +31,13 @@
|
||||
android:checkable="true"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<!--自定义导出章节 选择菜单-->
|
||||
<item
|
||||
android:id="@+id/menu_enable_custom_export"
|
||||
android:title="@string/custom_export_section"
|
||||
android:checkable="true"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_export_web_dav"
|
||||
android:title="@string/export_to_web_dav"
|
||||
|
@ -33,6 +33,7 @@
|
||||
<string name="replace">Reemplazar</string>
|
||||
<string name="replace_purify">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="enable">Activar</string>
|
||||
<string name="replace_purify_search">Buscar reemplazo</string>
|
||||
@ -1104,4 +1105,9 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -44,6 +44,7 @@
|
||||
<string name="rss">Subscription</string>
|
||||
<string name="all">All</string>
|
||||
<string name="recent_reading">Recent reading</string>
|
||||
<string name="custom_export_section">カスタムEPUBエクスポートの章</string>
|
||||
<string name="last_read">Last reading</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>
|
||||
@ -1107,4 +1108,9 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -33,6 +33,7 @@
|
||||
<string name="replace">Substituir</string>
|
||||
<string name="replace_purify">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="enable">Ativar</string>
|
||||
<string name="replace_purify_search">Procurar o substituto</string>
|
||||
@ -1107,4 +1108,9 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -33,6 +33,7 @@
|
||||
<string name="not_available">暫無</string>
|
||||
<string name="enable">啟用</string>
|
||||
<string name="replace_purify_search">替換淨化-搜尋</string>
|
||||
<string name="custom_export_section">自定義Epub導出章節</string>
|
||||
<string name="bookshelf">書架</string>
|
||||
<string name="favorites">收藏夾</string>
|
||||
<string name="favorite">收藏</string>
|
||||
@ -1104,4 +1105,9 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -32,6 +32,7 @@
|
||||
<string name="replace">取代</string>
|
||||
<string name="replace_purify">取代淨化</string>
|
||||
<string name="replace_purify_desc">配置取代淨化規則</string>
|
||||
<string name="custom_export_section">自定義Epub導出章節</string>
|
||||
<string name="not_available">暫無</string>
|
||||
<string name="enable">啟用</string>
|
||||
<string name="replace_purify_search">取代淨化-搜尋</string>
|
||||
@ -1106,4 +1107,9 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -31,6 +31,7 @@
|
||||
<string name="delete_all">删除所有</string>
|
||||
<string name="replace">替换</string>
|
||||
<string name="replace_purify">替换净化</string>
|
||||
<string name="custom_export_section">自定义Epub导出章节</string>
|
||||
<string name="replace_purify_desc">配置替换净化规则</string>
|
||||
<string name="not_available">暂无</string>
|
||||
<string name="enable">启用</string>
|
||||
@ -346,6 +347,7 @@
|
||||
<string name="ps_show_all_find">关闭则只显示勾选源的发现</string>
|
||||
<string name="update_toc">更新目录</string>
|
||||
<string name="txt_toc_rule">TXT 目录规则</string>
|
||||
<string name="select_section_export">选择待导出章节</string>
|
||||
<string name="set_charset">设置编码</string>
|
||||
<string name="swap_sort">倒序-顺序</string>
|
||||
<string name="sort">排序</string>
|
||||
@ -1106,4 +1108,8 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
@ -30,6 +30,7 @@
|
||||
<string name="edit">Edit</string>
|
||||
<string name="delete">Delete</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_purify">Replacement</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="update_toc">Update chapters</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="swap_sort">Ascending/Descending order</string>
|
||||
<string name="sort">Sort</string>
|
||||
@ -1107,4 +1109,8 @@
|
||||
<string name="replace_exclude_scope">排除范围,选填书名或者书源 URL</string>
|
||||
<string name="format_js_rule">格式化规则(formatJs)</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>
|
||||
|
Loading…
Reference in New Issue
Block a user