diff --git a/app/src/main/assets/help/ruleHelp.md b/app/src/main/assets/help/ruleHelp.md index e3def4f4e..9d3c6ade9 100644 --- a/app/src/main/assets/help/ruleHelp.md +++ b/app/src/main/assets/help/ruleHelp.md @@ -10,6 +10,9 @@ @Json: json规则,直接写时以$.开头可省略@Json : regex规则,不可省略,只可以用在书籍列表和目录列表 ``` +* 书源类型: 文件 +> 对于类似知轩藏书提供文件整合下载的网站,可以'在书源详情的下载URL规则获取文件链接,支持多个链接,阅读会自动下载并导入 + * CookieJar > 启用后会自动保存每次返回头中的Set-Cookie中的值,适用于验证码图片一类需要session的网站 * 登录UI diff --git a/app/src/main/java/io/legado/app/api/controller/BookController.kt b/app/src/main/java/io/legado/app/api/controller/BookController.kt index 797212544..4c8e93123 100644 --- a/app/src/main/java/io/legado/app/api/controller/BookController.kt +++ b/app/src/main/java/io/legado/app/api/controller/BookController.kt @@ -244,7 +244,7 @@ object BookController { val fileData = parameters["fileData"]?.firstOrNull() ?: return returnData.setErrorMsg("fileData 不能为空") kotlin.runCatching { - LocalBook.importFile(fileData, fileName) + LocalBook.importFileOnLine(fileData, fileName) }.onFailure { return when (it) { is SecurityException -> returnData.setErrorMsg("需重新设置书籍保存位置!") diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index 18be386c5..df3d6017a 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -28,4 +28,5 @@ object EventBus { const val TIP_COLOR = "tipColor" const val SOURCE_CHANGED = "sourceChanged" const val SEARCH_RESULT = "searchResult" + const val BOOK_URL_CHANGED = "bookUrlChanged" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 072fbe9a3..449d6eacc 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -154,6 +154,10 @@ data class Book( @IgnoredOnParcel override var tocHtml: String? = null + @Ignore + @IgnoredOnParcel + var downloadUrls: List? = null + fun getRealAuthor() = author.replace(AppPattern.authorRegex, "") fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) diff --git a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt index 9f3a0c9d0..cddba2c4f 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt @@ -16,5 +16,6 @@ data class BookInfoRule( var coverUrl: String? = null, var tocUrl: String? = null, var wordCount: String? = null, - var canReName: String? = null + var canReName: String? = null, + var downloadUrls: String? = null ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 9be322adc..8cab89c29 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -2,6 +2,7 @@ package io.legado.app.model import android.annotation.SuppressLint import io.legado.app.constant.AppPattern +import io.legado.app.constant.BookType import io.legado.app.data.entities.* import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.model.rss.Rss @@ -238,7 +239,11 @@ object Debug { .onSuccess { log(debugSource, "︽详情页解析完成") log(debugSource, showTime = false) - tocDebug(scope, bookSource, book) + if (book.type != BookType.file) { + tocDebug(scope, bookSource, book) + } else { + log(debugSource, "≡文件类书源跳过解析目录", state = 1000) + } } .onError { log(debugSource, it.msg, state = -1) diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 9a1d57104..0cac6f128 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -24,6 +24,10 @@ import java.io.FileNotFoundException import java.io.InputStream import java.util.regex.Pattern +/** + * 书籍文件导入 目录正文解析 + * 支持在线文件(txt epub umd 压缩文件需要用户解压) 本地文件 + */ object LocalBook { private val nameAuthorPatterns = arrayOf( @@ -84,32 +88,23 @@ object LocalBook { } } - //导入在线的文件 - fun importFile( + /** + * 下载在线的文件并自动导入到阅读(txt umd epub) + * 压缩文件请先提示用户解压 + */ + fun importFileOnLine( str: String, fileName: String, source: BaseSource? = null, - onLineBook: Book? = null ): Book { - val bytes = when { - str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray() - str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT) - else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL") - } - val localBook = importFile(bytes, fileName) - return mergeBook(localBook, onLineBook) - } - - fun importFile( - bytes: ByteArray, - fileName: String - ): Book { - return saveBookFile(bytes, fileName).let { + return saveBookFile(str, fileName, source).let { importFile(it) } } - //导入本地文件 + /** + * 导入本地文件 + */ fun importFile(uri: Uri): Book { val bookUrl: String val updateTime: Long @@ -150,6 +145,9 @@ object LocalBook { return book } + /** + * 从文件分析书籍必要信息(书名 作者等) + */ private fun analyzeNameAuthor(fileName: String): Pair { val tempFileName = fileName.substringBeforeLast(".") var name: String @@ -203,6 +201,36 @@ object LocalBook { } } + /** + * 下载在线的文件 + */ + fun saveBookFile( + str: String, + fileName: String, + source: BaseSource? = null, + ): Uri { + val bytes = when { + str.isAbsUrl() -> AnalyzeUrl(str, source = source).getByteArray() + str.isDataUrl() -> Base64.decode(str.substringAfter("base64,"), Base64.DEFAULT) + else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL") + } + return saveBookFile(bytes, fileName) + } + + /** + * 分析下载文件类书源的下载链接的文件后缀 + * https://www.example.com/download/{fileName}.{type} 含有文件名和后缀 + * https://www.example.com/download/?fileid=1234, {type: "txt"} 规则设置 + */ + fun parseFileSuffix(url: String): String { + val analyzeUrl = AnalyzeUrl(url) + val urlNoOption = analyzeUrl.url + val lastPath = urlNoOption.substringAfterLast("/") + val fileType = lastPath.substringAfterLast(".") + val type = analyzeUrl.type + return type ?: fileType ?: "unknown" + } + private fun saveBookFile( bytes: ByteArray, fileName: String @@ -232,7 +260,7 @@ object LocalBook { } //文件类书源 合并在线书籍信息 在线 > 本地 - private fun mergeBook(localBook: Book, onLineBook: Book?): Book { + fun mergeBook(localBook: Book, onLineBook: Book?): Book { onLineBook ?: return localBook val mergeBook = localBook mergeBook.name = if (onLineBook.name.isBlank()) localBook.name else onLineBook.name diff --git a/app/src/main/java/io/legado/app/model/localBook/README.md b/app/src/main/java/io/legado/app/model/localBook/README.md index 8f080b25e..04aaa6c38 100644 --- a/app/src/main/java/io/legado/app/model/localBook/README.md +++ b/app/src/main/java/io/legado/app/model/localBook/README.md @@ -1,7 +1,7 @@ -# 本地书籍解析 +# 书籍文件导入解析 * BaseLocalBookParse.kt 本地书籍解析接口 -* LocalBook.kt 总入口 +* LocalBook.kt 导入解析总入口 * TextFile.kt 解析txt * EpubFile.kt 解析epub * UmdFile.kt 解析umd \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt index 1ff5f0356..9595b1079 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt @@ -1,6 +1,8 @@ package io.legado.app.model.webBook +import android.text.TextUtils import io.legado.app.R +import io.legado.app.constant.BookType import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookSource import io.legado.app.exception.NoStackTraceException @@ -137,14 +139,29 @@ object BookInfo { Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}") DebugLog.e("获取封面出错", e) } - scope.ensureActive() - Debug.log(bookSource.bookSourceUrl, "┌获取目录链接") - book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true) - if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl - if (book.tocUrl == baseUrl) { - book.tocHtml = body + if (book.type != BookType.file) { + scope.ensureActive() + Debug.log(bookSource.bookSourceUrl, "┌获取目录链接") + book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true) + if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl + if (book.tocUrl == baseUrl) { + book.tocHtml = body + } + Debug.log(bookSource.bookSourceUrl, "└${book.tocUrl}") + } else { + scope.ensureActive() + Debug.log(bookSource.bookSourceUrl, "┌获取文件下载链接") + book.downloadUrls = analyzeRule.getStringList(infoRule.downloadUrls, isUrl = true) + if (book.downloadUrls == null) { + Debug.log(bookSource.bookSourceUrl, "└") + throw NoStackTraceException("下载链接为空") + } else { + Debug.log( + bookSource.bookSourceUrl, + "└" + TextUtils.join(",\n", book.downloadUrls!!) + ) + } } - Debug.log(bookSource.bookSourceUrl, "└${book.tocUrl}") } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index a7a81b924..62637d428 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -6,6 +6,7 @@ import com.script.ScriptException import io.legado.app.R import io.legado.app.base.BaseService import io.legado.app.constant.AppConst +import io.legado.app.constant.BookType import io.legado.app.constant.EventBus import io.legado.app.constant.IntentAction import io.legado.app.data.appDb @@ -208,7 +209,9 @@ class CheckSourceService : BaseService() { mBook = WebBook.getBookInfoAwait(this, source, mBook) } //校验目录 - if (CheckSource.checkCategory) { + if (CheckSource.checkCategory && + source.bookSourceType != BookType.file + ) { val toc = WebBook.getChapterListAwait(this, source, mBook).getOrThrow() val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url //校验正文 diff --git a/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt new file mode 100644 index 000000000..667be6b10 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt @@ -0,0 +1,132 @@ +package io.legado.app.ui.association + +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.net.Uri +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.RecyclerAdapter +import io.legado.app.databinding.DialogRecyclerViewBinding +import io.legado.app.databinding.ItemBookFileImportBinding +import io.legado.app.help.config.AppConfig +import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.dialog.WaitDialog +import io.legado.app.utils.* +import io.legado.app.utils.viewbindingdelegate.viewBinding + + +/** + * 导入在线书籍文件弹出窗口 + */ +class ImportOnLineBookFileDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) { + + + private val binding by viewBinding(DialogRecyclerViewBinding::bind) + private val viewModel by viewModels() + private val adapter by lazy { BookFileAdapter(requireContext()) } + + override fun onStart() { + super.onStart() + setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + val bookUrl = arguments?.getString("bookUrl") + viewModel.initData(bookUrl) + binding.toolBar.setBackgroundColor(primaryColor) + binding.toolBar.setTitle(R.string.download_and_import_file) + binding.rotateLoading.show() + binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.recyclerView.adapter = adapter + viewModel.errorLiveData.observe(this) { + binding.rotateLoading.hide() + binding.tvMsg.apply { + text = it + visible() + } + } + viewModel.successLiveData.observe(this) { + binding.rotateLoading.hide() + if (it > 0) { + adapter.setItems(viewModel.allBookFiles) + } + } + viewModel.savedFileUriData.observe(this) { + requireContext().openFileUri(it, "*/*") + } + } + + private fun importFileAndUpdate(url: String, fileName: String) { + val waitDialog = WaitDialog(requireContext()) + waitDialog.show() + viewModel.importOnLineBookFile(url, fileName) { + waitDialog.dismiss() + dismissAllowingStateLoss() + } + } + + private fun downloadFile(url: String, fileName: String) { + val waitDialog = WaitDialog(requireContext()) + waitDialog.show() + viewModel.downloadUrl(url, fileName) { + waitDialog.dismiss() + dismissAllowingStateLoss() + } +} + + inner class BookFileAdapter(context: Context) : + RecyclerAdapter +, ItemBookFileImportBinding>(context) { + + override fun getViewBinding(parent: ViewGroup): ItemBookFileImportBinding { + return ItemBookFileImportBinding.inflate(inflater, parent, false) + } + + override fun convert( + holder: ItemViewHolder, + binding: ItemBookFileImportBinding, + item: Triple, + payloads: MutableList + ) { + binding.apply { + cbFileName.text = item.second + } + } + + override fun registerListener( + holder: ItemViewHolder, + binding: ItemBookFileImportBinding + ) { + binding.apply { + cbFileName.setOnClickListener { + val selectFile = viewModel.allBookFiles[holder.layoutPosition] + if (selectFile.third) { + importFileAndUpdate(selectFile.first, selectFile.second) + } else { + alert( + title = getString(R.string.draw), + message = getString(R.string.file_not_supported, selectFile.second) + ) { + okButton { + importFileAndUpdate(selectFile.first, selectFile.second) + } + neutralButton(R.string.open_fun) { + downloadFile(selectFile.first, selectFile.second) + } + cancelButton() + } + } + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileViewModel.kt new file mode 100644 index 000000000..d601cd20e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileViewModel.kt @@ -0,0 +1,80 @@ +package io.legado.app.ui.association + +import android.app.Application +import android.net.Uri +import androidx.lifecycle.MutableLiveData +import io.legado.app.R +import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppPattern +import io.legado.app.constant.AppLog +import io.legado.app.constant.EventBus +import io.legado.app.data.appDb +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookSource +import io.legado.app.exception.NoStackTraceException +import io.legado.app.model.analyzeRule.AnalyzeRule +import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.model.localBook.LocalBook +import io.legado.app.utils.* + +class ImportOnLineBookFileViewModel(app: Application) : BaseViewModel(app) { + + val allBookFiles = arrayListOf>() + val errorLiveData = MutableLiveData() + val successLiveData = MutableLiveData() + val savedFileUriData = MutableLiveData() + var bookSource: BookSource? = null + + fun initData(bookUrl: String?) { + execute { + bookUrl ?: throw NoStackTraceException("书籍详情页链接为空") + val book = appDb.searchBookDao.getSearchBook(bookUrl)?.toBook() + ?: throw NoStackTraceException("book is null") + bookSource = appDb.bookSourceDao.getBookSource(book.origin) + ?: throw NoStackTraceException("bookSource is null") + val ruleDownloadUrls = bookSource?.getBookInfoRule()?.downloadUrls + val content = AnalyzeUrl(bookUrl, source = bookSource).getStrResponse().body + val analyzeRule = AnalyzeRule(book, bookSource) + analyzeRule.setContent(content).setBaseUrl(bookUrl) + val fileName = "${book.name} 作者:${book.author}" + analyzeRule.getStringList(ruleDownloadUrls, isUrl = true)?.let { + it.forEach { url -> + val mFileName = "${fileName}.${LocalBook.parseFileSuffix(url)}" + val isSupportedFile = AppPattern.bookFileRegex.matches(mFileName) + allBookFiles.add(Triple(url, mFileName, isSupportedFile)) + } + } ?: throw NoStackTraceException("下载链接规则解析为空") + }.onSuccess { + successLiveData.postValue(allBookFiles.size) + }.onError { + errorLiveData.postValue(it.localizedMessage ?: "") + context.toastOnUi("获取书籍下载链接失败\n${it.localizedMessage}") + } + + } + + fun downloadUrl(url: String, fileName: String, success: () -> Unit) { + execute { + LocalBook.saveBookFile(url, fileName, bookSource).let { + savedFileUriData.postValue(it) + } + }.onSuccess { + success.invoke() + }.onError { + context.toastOnUi("下载书籍文件失败\n${it.localizedMessage}") + } + } + + fun importOnLineBookFile(url: String, fileName: String, success: () -> Unit) { + execute { + LocalBook.importFileOnLine(url, fileName, bookSource).let { + postEvent(EventBus.BOOK_URL_CHANGED, it.bookUrl) + } + }.onSuccess { + success.invoke() + }.onError { + context.toastOnUi("下载书籍文件失败\n${it.localizedMessage}") + } + } + +} diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index dfad88975..56e2bd5d7 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -12,6 +12,7 @@ import androidx.activity.viewModels import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.BookType +import io.legado.app.constant.EventBus import io.legado.app.constant.Theme import io.legado.app.data.appDb import io.legado.app.data.entities.Book @@ -25,6 +26,7 @@ import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.model.BookCover import io.legado.app.ui.about.AppLogDialog +import io.legado.app.ui.association.ImportOnLineBookFileDialog import io.legado.app.ui.book.audio.AudioPlayActivity import io.legado.app.ui.book.changecover.ChangeCoverDialog import io.legado.app.ui.book.changesource.ChangeBookSourceDialog @@ -278,11 +280,13 @@ class BookInfoActivity : true } tvRead.setOnClickListener { - viewModel.bookData.value?.let { + viewModel.bookData.value?.let { book -> if (viewModel.isImportBookOnLine) { - viewModel.importBookFileOnLine() + showDialogFragment { + putString("bookUrl", book.bookUrl) + } } else { - readBook(it) + readBook(book) } } ?: toastOnUi("Book is null") } @@ -486,4 +490,9 @@ class BookInfoActivity : } } + override fun observeLiveBus() { + observeEvent(EventBus.BOOK_URL_CHANGED) { + viewModel.changeToLocalBook(it) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 7a029f588..b60fbfa43 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.book.info import android.app.Application import android.content.Intent +import android.net.Uri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import io.legado.app.R @@ -118,6 +119,9 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { WebBook.getBookInfo(this, bookSource, book, canReName = canReName) .onSuccess(IO) { bookData.postValue(book) + if (isImportBookOnLine) { + appDb.searchBookDao.update(book.toSearchBook()) + } if (inBookshelf) { appDb.bookDao.update(book) } @@ -290,26 +294,14 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { } } - fun importBookFileOnLine() { - execute { - //下载类书源的目录链接视为文件链接 - val book = bookData.value!! - val fileUrl = book.tocUrl - //切下载链接获取文件名 - var fileName = fileUrl.substringAfterLast("/") - if (fileName.isEmpty()) { - fileName = book.name - } - LocalBook.importFile(fileUrl, fileName, bookSource, book) - }.onSuccess { - bookData.postValue(it) - LocalBook.getChapterList(it).let { toc -> - chapterListData.postValue(toc) - } + fun changeToLocalBook(bookUrl: String) { + appDb.bookDao.getBook(bookUrl)?.let { localBook -> isImportBookOnLine = false inBookshelf = true - }.onError { - context.toastOnUi("自动导入出错\n${it.localizedMessage}") + LocalBook.mergeBook(localBook, bookData.value).let { + bookData.postValue(it) + loadChapter(it) + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index ab93ccc0e..ed991ff58 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -266,6 +266,7 @@ class BookSourceEditActivity : add(EditEntity("coverUrl", ir?.coverUrl, R.string.rule_cover_url)) add(EditEntity("tocUrl", ir?.tocUrl, R.string.rule_toc_url)) add(EditEntity("canReName", ir?.canReName, R.string.rule_can_re_name)) + add(EditEntity("downloadUrls", ir?.downloadUrls, R.string.download_url_rule)) } //目录页 val tr = source?.getTocRule() @@ -396,6 +397,7 @@ class BookSourceEditActivity : "tocUrl" -> bookInfoRule.tocUrl = viewModel.ruleComplete(it.value, bookInfoRule.init, 2) "canReName" -> bookInfoRule.canReName = it.value + "downloadUrls" -> bookInfoRule.downloadUrls = viewModel.ruleComplete(it.value, bookInfoRule.init) } } tocEntities.forEach { diff --git a/app/src/main/res/layout/item_book_file_import.xml b/app/src/main/res/layout/item_book_file_import.xml new file mode 100644 index 000000000..8f584c1a4 --- /dev/null +++ b/app/src/main/res/layout/item_book_file_import.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 030c5335e..741265978 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -981,5 +981,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 + diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 1cfcb0d47..e24e9b362 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -984,5 +984,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1293cedc9..95fb2ec20 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -984,5 +984,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 + diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 94805b80d..3b97eaf2b 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -981,5 +981,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 7dea2e10e..975d7dbba 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -983,5 +983,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 + diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index b20df127c..018ccb9d8 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -983,5 +983,6 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6799fca04..d582253cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -984,5 +984,7 @@ CookieJar 点击阅读加载目录 清除cookie + 导入在线书籍文件 +