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/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/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index c5fb22f40..68b95c9e3 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 @@ -92,7 +92,7 @@ object LocalBook { * 下载在线的文件并自动导入到阅读(txt umd epub) * 压缩文件请先提示用户解压 */ - fun importFile( + fun importFileOnLine( str: String, fileName: String? = null, source: BaseSource? = null, @@ -236,7 +236,7 @@ object LocalBook { val fileName = when { onLineBook != null -> "${onLineBook.name}_${onLineBook.author}_${onLineBook.origin}" type == null-> lastPath - else -> lastPath.substringBeforeAfter(".") + else -> lastPath.substringBeforeLast(".") } val fileSuffix = fileType ?: type ?: "unknown" return "${fileName}.${fileSuffix}" 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..53847ea52 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,24 @@ 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) + 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/ui/association/ImportOnLineBookFileDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt new file mode 100644 index 000000000..bfe435c0d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt @@ -0,0 +1,152 @@ +package io.legado.app.ui.association + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import 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 +import splitties.views.onClick + + +/** + * 导入在线书籍文件弹出窗口 + */ +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) + } + + @SuppressLint("NotifyDataSetChanged") + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + val bookUrl = arguments?.getString("bookUrl") + val infoHtml = arguments?.getString("infoHtml") + viewModel.initData(bookUrl, infoHtml) + binding.toolBar.setBackgroundColor(primaryColor) + //标题 + binding.toolBar.setTitle("导入在线书籍文件") + binding.rotateLoading.show() + binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.recyclerView.adapter = adapter + binding.tvCancel.visible() + binding.tvCancel.setOnClickListener { + dismissAllowingStateLoss() + } + binding.tvOk.visible() + binding.tvOk.setOnClickListener { + val waitDialog = WaitDialog(requireContext()) + waitDialog.show() + viewModel.importSelect { + waitDialog.dismiss() + dismissAllowingStateLoss() + } + } + binding.tvFooterLeft.visible() + binding.tvFooterLeft.setOnClickListener { + val selectAll = viewModel.isSelectAll + viewModel.selectStatus.forEachIndexed { index, b -> + if (b != !selectAll) { + viewModel.selectStatus[index] = !selectAll + } + } + adapter.notifyDataSetChanged() + upSelectText() + } + adapter.setItems(viewModel.allBookFiles) + upSelectText() + } + + private fun upSelectText() { + if (viewModel.isSelectAll) { + binding.tvFooterLeft.text = getString( + R.string.select_cancel_count, + viewModel.selectCount, + viewModel.allBookFiles.size + ) + } else { + binding.tvFooterLeft.text = getString( + R.string.select_all_count, + viewModel.selectCount, + viewModel.allBookFiles.size + ) + } + } + + 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.isChecked = viewModel.selectStatus[holder.layoutPosition] + cbFileName.text = item.second + if (item.third) { + tvOpen.invisible() + } else { + tvOpen.visible() + } + } + } + + override fun registerListener(holder: ItemViewHolder, binding: ItemBookFileImportBinding) { + binding.apply { + cbFileName.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed) { + val selectFile = viewModel.allBookFiles[holder.layoutPosition] + if (selectFile.third) { + viewModel.selectStatus[holder.layoutPosition] = isChecked + } else { + toastOnUi("不支持直接导入") + } + upSelectText() + } + } + root.onClick { + cbFileName.isChecked = !cbFileName.isChecked + viewModel.selectStatus[holder.layoutPosition] = cbFileName.isChecked + upSelectText() + } + tvOpen.setOnClickListener { + val bookFile = viewModel.allBookFiles[holder.layoutPosition] + //intent解压 + viewModel.downloadUrl(bookFile.first, bookFile.second) { + //openFileUri(it) + } + } + } + } + + } + +} \ 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..a05c34e93 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileViewModel.kt @@ -0,0 +1,112 @@ +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.data.appDb +import io.legado.app.data.entities.Book +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 selectStatus = arrayListOf() + + fun initData(bookUrl: String?, infoHtml: String?) { + execute { + bookUrl ?: throw NoStackTraceException("书籍详情页链接为空") + val book = appDb.searchBookDao.getSearchBook(bookUrl)?.toBook() + ?: throw NoStackTraceException("book is null") + val bookSource = appDb.bookSourceDao.getBookSource(book.origin) + ?: throw NoStackTraceException("bookSource is null") + val ruleDownloadUrls = bookSource?.getBookInfoRule()?.downloadUrls + var content = infoHtml + if (content.isNullOrBlank()) { + content = AnalyzeUrl(bookUrl, source = bookSource).getStrResponse().body + } + val analyzeRule = AnalyzeRule(book, bookSource) + analyzeRule.setContent(content).setBaseUrl(bookUrl) + analyzeRule.getStringList(ruleDownloadUrls, isUrl = true)?.let { + it.forEach { url -> + val fileName = LocalBook.extractDownloadName(url, book) + val isSupportedFile = AppPattern.bookFileRegex.matches(fileName) + allBookFiles.add(Triple(url, fileName, isSupportedFile)) + selectStatus.add(isSupportedFile) + } + } ?: throw NoStackTraceException("下载链接规则解析为空") + }.onError { + context.toastOnUi("获取书籍下载链接失败\n${it.localizedMessage}") + } + + } + + val isSelectAll: Boolean + get() { + selectStatus.forEach { + if (!it) { + return false + } + } + return true + } + + val selectCount: Int + get() { + var count = 0 + selectStatus.forEach { + if (it) { + count++ + } + } + return count + } + + fun importSelect(success: () -> Unit) { + execute { + selectStatus.forEachIndexed { index, selected -> + if (selected) { + val selectedFile = allBookFiles[index] + val isSupportedFile = selectedFile.third + val fileUrl: String = selectedFile.first + val fileName: String = selectedFile.second + when { + isSupportedFile -> importOnLineBookFile(fileUrl, fileName) + else -> { + downloadUrl(fileUrl, fileName) { + // AppLog.putDebug("下载文件路径: ${it.toString()}") + } + } + } + } + } + }.onSuccess { + success.invoke() + }.onError { + + } + } + + + fun downloadUrl(url: String, fileName: String, success: () -> Unit) { + execute { + LocalBook.saveBookFile(url, fileName) + }.onSuccess { + success.invoke() + }.onError { + context.toastOnUi("下载书籍文件失败\n${it.localizedMessage}") + } + } + + fun importOnLineBookFile(url: String, fileName: String) { + LocalBook.importFileOnLine(url, fileName) + } + +} 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..f94345da5 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 @@ -25,6 +25,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 +279,14 @@ class BookInfoActivity : true } tvRead.setOnClickListener { - viewModel.bookData.value?.let { + viewModel.bookData.value?.let { book -> if (viewModel.isImportBookOnLine) { - viewModel.importBookFileOnLine() + showDialogFragment { + putString("bookUrl", book.bookUrl) + putString("infoHtml", book.infoHtml) + } } else { - readBook(it) + readBook(book) } } ?: toastOnUi("Book is null") } 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..f8506ece9 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 @@ -290,27 +291,13 @@ 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) - } - isImportBookOnLine = false - inBookshelf = true - }.onError { - context.toastOnUi("自动导入出错\n${it.localizedMessage}") + private fun changeToLocalBook(book: Book) { + bookData.postValue(book) + LocalBook.getChapterList(book).let { + chapterListData.postValue(it) } + isImportBookOnLine = false + inBookshelf = true } } \ No newline at end of file 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..88291196c --- /dev/null +++ b/app/src/main/res/layout/item_book_file_import.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file