优化webFile导入
This commit is contained in:
@@ -96,6 +96,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions.add("mode")
|
flavorDimensions.add("mode")
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
app {
|
app {
|
||||||
dimension "mode"
|
dimension "mode"
|
||||||
@@ -107,6 +108,7 @@ android {
|
|||||||
manifestPlaceholders.put("APP_CHANNEL_VALUE", "google")
|
manifestPlaceholders.put("APP_CHANNEL_VALUE", "google")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
// Flag to enable support for the new language APIs
|
// Flag to enable support for the new language APIs
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
@@ -115,6 +117,11 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
// fix found with path 'META-INF/INDEX.LIST' from inputs
|
||||||
|
exclude 'META-INF/INDEX.LIST'
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
// Adds exported schema location as test app assets.
|
// Adds exported schema location as test app assets.
|
||||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ data class Server(
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class WebDavConfig(
|
data class WebDavConfig(
|
||||||
var url: String? = null,
|
var url: String,
|
||||||
var username: String? = null,
|
var username: String,
|
||||||
var password: String? = null,
|
var password: String
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -18,49 +18,39 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
|
|
||||||
|
|
||||||
val Book.isAudio: Boolean
|
val Book.isAudio: Boolean
|
||||||
get() {
|
get() = isType(BookType.audio)
|
||||||
return type and BookType.audio > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val Book.isImage: Boolean
|
val Book.isImage: Boolean
|
||||||
get() {
|
get() = isType(BookType.image)
|
||||||
return type and BookType.image > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val Book.isLocal: Boolean
|
val Book.isLocal: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
return origin == BookType.localTag || origin.startsWith(BookType.webDavTag)
|
return origin == BookType.localTag || origin.startsWith(BookType.webDavTag)
|
||||||
}
|
}
|
||||||
return type and BookType.local > 0
|
return isType(BookType.local)
|
||||||
}
|
}
|
||||||
|
|
||||||
val Book.isLocalTxt: Boolean
|
val Book.isLocalTxt: Boolean
|
||||||
get() {
|
get() = isLocal && originName.endsWith(".txt", true)
|
||||||
return isLocal && originName.endsWith(".txt", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Book.isEpub: Boolean
|
val Book.isEpub: Boolean
|
||||||
get() {
|
get() = isLocal && originName.endsWith(".epub", true)
|
||||||
return isLocal && originName.endsWith(".epub", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Book.isUmd: Boolean
|
val Book.isUmd: Boolean
|
||||||
get() {
|
get() = isLocal && originName.endsWith(".umd", true)
|
||||||
return isLocal && originName.endsWith(".umd", true)
|
|
||||||
}
|
|
||||||
val Book.isPdf: Boolean
|
val Book.isPdf: Boolean
|
||||||
get() {
|
get() = isLocal && originName.endsWith(".pdf", true)
|
||||||
return isLocal && originName.endsWith(".pdf", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Book.isOnLineTxt: Boolean
|
val Book.isOnLineTxt: Boolean
|
||||||
get() {
|
get() = !isLocal && isType(BookType.text)
|
||||||
return !isLocal && type and BookType.text > 0
|
|
||||||
}
|
val Book.isWebFile: Boolean
|
||||||
|
get() = isType(BookType.webFile)
|
||||||
|
|
||||||
val Book.isUpError: Boolean
|
val Book.isUpError: Boolean
|
||||||
get() = type and BookType.updateError > 0
|
get() = isType(BookType.updateError)
|
||||||
|
|
||||||
fun Book.contains(word: String?): Boolean {
|
fun Book.contains(word: String?): Boolean {
|
||||||
if (word.isNullOrEmpty()) {
|
if (word.isNullOrEmpty()) {
|
||||||
@@ -172,6 +162,8 @@ fun Book.clearType() {
|
|||||||
type = 0
|
type = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Book.isType(@BookType.Type bookType: Int): Boolean = type and bookType > 0
|
||||||
|
|
||||||
fun Book.upType() {
|
fun Book.upType() {
|
||||||
if (type < 8) {
|
if (type < 8) {
|
||||||
type = when (type) {
|
type = when (type) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ data class Authorization(
|
|||||||
|
|
||||||
constructor(serverID: Long?): this("","") {
|
constructor(serverID: Long?): this("","") {
|
||||||
serverID ?: throw NoStackTraceException("Unexpected server ID")
|
serverID ?: throw NoStackTraceException("Unexpected server ID")
|
||||||
appDb.serverDao.get(serverID)?.getWebDavConfig().run {
|
appDb.serverDao.get(serverID)?.getWebDavConfig()?.run {
|
||||||
data = Credentials.basic(username, password, charset)
|
data = Credentials.basic(username, password, charset)
|
||||||
} ?: throw WebDavException("Unexpected WebDav Authorization")
|
} ?: throw WebDavException("Unexpected WebDav Authorization")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import io.legado.app.data.entities.Book
|
|||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
|
import io.legado.app.help.book.isWebFile
|
||||||
import io.legado.app.model.Debug
|
import io.legado.app.model.Debug
|
||||||
import io.legado.app.model.analyzeRule.AnalyzeRule
|
import io.legado.app.model.analyzeRule.AnalyzeRule
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
@@ -137,7 +138,7 @@ object BookInfo {
|
|||||||
Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}")
|
Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}")
|
||||||
DebugLog.e("获取封面出错", e)
|
DebugLog.e("获取封面出错", e)
|
||||||
}
|
}
|
||||||
if (book.type and BookType.webFile == 0) {
|
if (book.isWebFile) {
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
|
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
|
||||||
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
|
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
package io.legado.app.ui.association
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
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.lib.dialogs.alert
|
|
||||||
import io.legado.app.lib.theme.primaryColor
|
|
||||||
import io.legado.app.ui.widget.dialog.WaitDialog
|
|
||||||
import io.legado.app.utils.openFileUri
|
|
||||||
import io.legado.app.utils.setLayout
|
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
|
||||||
import io.legado.app.utils.visible
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入在线书籍文件弹出窗口
|
|
||||||
*/
|
|
||||||
class ImportOnLineBookFileDialog : BaseDialogFragment(R.layout.dialog_recycler_view) {
|
|
||||||
|
|
||||||
|
|
||||||
private val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
|
||||||
private val viewModel by viewModels<ImportOnLineBookFileViewModel>()
|
|
||||||
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.visible()
|
|
||||||
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
binding.recyclerView.adapter = adapter
|
|
||||||
viewModel.errorLiveData.observe(this) {
|
|
||||||
binding.rotateLoading.gone()
|
|
||||||
binding.tvMsg.apply {
|
|
||||||
text = it
|
|
||||||
visible()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModel.successLiveData.observe(this) {
|
|
||||||
binding.rotateLoading.gone()
|
|
||||||
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<Triple<String, String, Boolean>
|
|
||||||
, ItemBookFileImportBinding>(context) {
|
|
||||||
|
|
||||||
override fun getViewBinding(parent: ViewGroup): ItemBookFileImportBinding {
|
|
||||||
return ItemBookFileImportBinding.inflate(inflater, parent, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun convert(
|
|
||||||
holder: ItemViewHolder,
|
|
||||||
binding: ItemBookFileImportBinding,
|
|
||||||
item: Triple<String, String, Boolean>,
|
|
||||||
payloads: MutableList<Any>
|
|
||||||
) {
|
|
||||||
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)
|
|
||||||
) {
|
|
||||||
yesButton {
|
|
||||||
importFileAndUpdate(selectFile.first, selectFile.second)
|
|
||||||
}
|
|
||||||
neutralButton(R.string.open_fun) {
|
|
||||||
downloadFile(selectFile.first, selectFile.second)
|
|
||||||
}
|
|
||||||
noButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package io.legado.app.ui.association
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import io.legado.app.base.BaseViewModel
|
|
||||||
import io.legado.app.constant.AppPattern
|
|
||||||
import io.legado.app.constant.EventBus
|
|
||||||
import io.legado.app.data.appDb
|
|
||||||
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.postEvent
|
|
||||||
import io.legado.app.utils.toastOnUi
|
|
||||||
|
|
||||||
class ImportOnLineBookFileViewModel(app: Application) : BaseViewModel(app) {
|
|
||||||
|
|
||||||
val allBookFiles = arrayListOf<Triple<String, String, Boolean>>()
|
|
||||||
val errorLiveData = MutableLiveData<String>()
|
|
||||||
val successLiveData = MutableLiveData<Int>()
|
|
||||||
val savedFileUriData = MutableLiveData<Uri>()
|
|
||||||
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.FILE_SOURCE_DOWNLOAD_DONE, it.bookUrl)
|
|
||||||
}
|
|
||||||
}.onSuccess {
|
|
||||||
success.invoke()
|
|
||||||
}.onError {
|
|
||||||
context.toastOnUi("下载书籍文件失败\n${it.localizedMessage}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -21,10 +21,7 @@ import io.legado.app.databinding.ActivityBookInfoBinding
|
|||||||
import io.legado.app.databinding.DialogEditTextBinding
|
import io.legado.app.databinding.DialogEditTextBinding
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.help.AppWebDav
|
||||||
import io.legado.app.help.book.isAudio
|
import io.legado.app.help.book.*
|
||||||
import io.legado.app.help.book.isLocal
|
|
||||||
import io.legado.app.help.book.isLocalTxt
|
|
||||||
import io.legado.app.help.book.getRemoteUrl
|
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
import io.legado.app.lib.theme.backgroundColor
|
import io.legado.app.lib.theme.backgroundColor
|
||||||
@@ -33,7 +30,6 @@ import io.legado.app.lib.theme.getPrimaryTextColor
|
|||||||
import io.legado.app.model.BookCover
|
import io.legado.app.model.BookCover
|
||||||
import io.legado.app.model.remote.RemoteBookWebDav
|
import io.legado.app.model.remote.RemoteBookWebDav
|
||||||
import io.legado.app.ui.about.AppLogDialog
|
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.audio.AudioPlayActivity
|
||||||
import io.legado.app.ui.book.changecover.ChangeCoverDialog
|
import io.legado.app.ui.book.changecover.ChangeCoverDialog
|
||||||
import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
|
import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
|
||||||
@@ -231,7 +227,6 @@ class BookInfoActivity :
|
|||||||
waitDialog.setText("上传中.....")
|
waitDialog.setText("上传中.....")
|
||||||
waitDialog.show()
|
waitDialog.show()
|
||||||
try {
|
try {
|
||||||
|
|
||||||
bookWebDav
|
bookWebDav
|
||||||
?.upload(book)
|
?.upload(book)
|
||||||
?: throw NoStackTraceException("未配置webDav")
|
?: throw NoStackTraceException("未配置webDav")
|
||||||
@@ -253,6 +248,7 @@ class BookInfoActivity :
|
|||||||
tvOrigin.text = getString(R.string.origin_show, book.originName)
|
tvOrigin.text = getString(R.string.origin_show, book.originName)
|
||||||
tvLasted.text = getString(R.string.lasted_show, book.latestChapterTitle)
|
tvLasted.text = getString(R.string.lasted_show, book.latestChapterTitle)
|
||||||
tvIntro.text = book.getDisplayIntro()
|
tvIntro.text = book.getDisplayIntro()
|
||||||
|
tvToc.visible(!book.isWebFile)
|
||||||
upTvBookshelf()
|
upTvBookshelf()
|
||||||
val kinds = book.getKindList()
|
val kinds = book.getKindList()
|
||||||
if (kinds.isEmpty()) {
|
if (kinds.isEmpty()) {
|
||||||
@@ -278,11 +274,10 @@ class BookInfoActivity :
|
|||||||
binding.tvToc.text = getString(R.string.toc_s, getString(R.string.loading))
|
binding.tvToc.text = getString(R.string.toc_s, getString(R.string.loading))
|
||||||
}
|
}
|
||||||
chapterList.isNullOrEmpty() -> {
|
chapterList.isNullOrEmpty() -> {
|
||||||
binding.tvToc.text =
|
binding.tvToc.text = getString(
|
||||||
if (viewModel.isImportBookOnLine) getString(R.string.click_read_button_load) else getString(
|
R.string.toc_s,
|
||||||
R.string.toc_s,
|
getString(R.string.error_load_toc)
|
||||||
getString(R.string.error_load_toc)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
viewModel.bookData.value?.let {
|
viewModel.bookData.value?.let {
|
||||||
@@ -333,9 +328,9 @@ class BookInfoActivity :
|
|||||||
}
|
}
|
||||||
tvRead.setOnClickListener {
|
tvRead.setOnClickListener {
|
||||||
viewModel.bookData.value?.let { book ->
|
viewModel.bookData.value?.let { book ->
|
||||||
if (viewModel.isImportBookOnLine) {
|
if (book.isWebFile) {
|
||||||
showDialogFragment<ImportOnLineBookFileDialog> {
|
showWebFileDownloadAlert {
|
||||||
putString("bookUrl", book.bookUrl)
|
readBook(book)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
readBook(book)
|
readBook(book)
|
||||||
@@ -343,11 +338,21 @@ class BookInfoActivity :
|
|||||||
} ?: toastOnUi("Book is null")
|
} ?: toastOnUi("Book is null")
|
||||||
}
|
}
|
||||||
tvShelf.setOnClickListener {
|
tvShelf.setOnClickListener {
|
||||||
if (viewModel.inBookshelf) {
|
viewModel.bookData.value?.let { book ->
|
||||||
deleteBook()
|
if (viewModel.inBookshelf) {
|
||||||
} else {
|
deleteBook()
|
||||||
viewModel.addToBookshelf {
|
} else {
|
||||||
upTvBookshelf()
|
if (book.isWebFile) {
|
||||||
|
showWebFileDownloadAlert {
|
||||||
|
viewModel.addToBookshelf {
|
||||||
|
upTvBookshelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.addToBookshelf {
|
||||||
|
upTvBookshelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,6 +498,34 @@ class BookInfoActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showWebFileDownloadAlert(onClick: (() -> Unit)?) {
|
||||||
|
viewModel.webFileData ?: return
|
||||||
|
alert(titleResource = R.string.download_and_import_file) {
|
||||||
|
val webFileData = viewModel.webFileData
|
||||||
|
webFileData?.let {
|
||||||
|
items<BookInfoViewModel.WebFile>(it) { _, webFile, _ ->
|
||||||
|
if (webFile.isSupported) {
|
||||||
|
viewModel.importOrDownloadWebFile(webFile) {
|
||||||
|
onClick?.invoke()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
title = getString(R.string.draw),
|
||||||
|
message = getString(R.string.file_not_supported, webFile.name)
|
||||||
|
) {
|
||||||
|
neutralButton(R.string.open_fun) {
|
||||||
|
viewModel.importOrDownloadWebFile(webFile) { uri ->
|
||||||
|
openFileUri(uri!!, "*/*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun readBook(book: Book) {
|
private fun readBook(book: Book) {
|
||||||
if (!viewModel.inBookshelf) {
|
if (!viewModel.inBookshelf) {
|
||||||
viewModel.saveBook(book) {
|
viewModel.saveBook(book) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.book.info
|
package io.legado.app.ui.book.info
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -7,6 +8,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.constant.AppPattern
|
||||||
import io.legado.app.constant.BookSourceType
|
import io.legado.app.constant.BookSourceType
|
||||||
import io.legado.app.constant.BookType
|
import io.legado.app.constant.BookType
|
||||||
import io.legado.app.constant.EventBus
|
import io.legado.app.constant.EventBus
|
||||||
@@ -19,6 +21,8 @@ import io.legado.app.help.AppWebDav
|
|||||||
import io.legado.app.help.book.*
|
import io.legado.app.help.book.*
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.lib.webdav.ObjectNotFoundException
|
import io.legado.app.lib.webdav.ObjectNotFoundException
|
||||||
|
import io.legado.app.model.analyzeRule.AnalyzeRule
|
||||||
|
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||||
import io.legado.app.model.BookCover
|
import io.legado.app.model.BookCover
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.model.localBook.LocalBook
|
import io.legado.app.model.localBook.LocalBook
|
||||||
@@ -32,11 +36,10 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||||||
class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
||||||
val bookData = MutableLiveData<Book>()
|
val bookData = MutableLiveData<Book>()
|
||||||
val chapterListData = MutableLiveData<List<BookChapter>>()
|
val chapterListData = MutableLiveData<List<BookChapter>>()
|
||||||
|
val webFileData: MutableList<WebFile>? = null
|
||||||
var inBookshelf = false
|
var inBookshelf = false
|
||||||
var bookSource: BookSource? = null
|
var bookSource: BookSource? = null
|
||||||
private var changeSourceCoroutine: Coroutine<*>? = null
|
private var changeSourceCoroutine: Coroutine<*>? = null
|
||||||
val isImportBookOnLine: Boolean
|
|
||||||
get() = bookSource?.bookSourceType == BookSourceType.file
|
|
||||||
|
|
||||||
fun initData(intent: Intent) {
|
fun initData(intent: Intent) {
|
||||||
execute {
|
execute {
|
||||||
@@ -82,8 +85,6 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
appDb.bookSourceDao.getBookSource(book.origin)
|
appDb.bookSourceDao.getBookSource(book.origin)
|
||||||
if (book.tocUrl.isEmpty() && !book.isLocal) {
|
if (book.tocUrl.isEmpty() && !book.isLocal) {
|
||||||
loadBookInfo(book)
|
loadBookInfo(book)
|
||||||
} else if (isImportBookOnLine) {
|
|
||||||
chapterListData.postValue(emptyList())
|
|
||||||
} else {
|
} else {
|
||||||
val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)
|
val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)
|
||||||
if (chapterList.isNotEmpty()) {
|
if (chapterList.isNotEmpty()) {
|
||||||
@@ -156,13 +157,14 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
inBookshelf = true
|
inBookshelf = true
|
||||||
}
|
}
|
||||||
bookData.postValue(book)
|
bookData.postValue(book)
|
||||||
if (isImportBookOnLine) {
|
|
||||||
appDb.searchBookDao.update(book.toSearchBook())
|
|
||||||
}
|
|
||||||
if (inBookshelf) {
|
if (inBookshelf) {
|
||||||
appDb.bookDao.update(book)
|
appDb.bookDao.update(book)
|
||||||
}
|
}
|
||||||
loadChapter(it, scope)
|
if (it.isWebFile) {
|
||||||
|
loadWebFile(book, bookSource, scope)
|
||||||
|
} else {
|
||||||
|
loadChapter(it, scope)
|
||||||
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
|
AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
|
||||||
context.toastOnUi(R.string.error_get_book_info)
|
context.toastOnUi(R.string.error_get_book_info)
|
||||||
@@ -187,7 +189,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
appDb.bookChapterDao.insert(*it.toTypedArray())
|
appDb.bookChapterDao.insert(*it.toTypedArray())
|
||||||
chapterListData.postValue(it)
|
chapterListData.postValue(it)
|
||||||
}
|
}
|
||||||
} else if (isImportBookOnLine) {
|
} else if (book.isWebFile) {
|
||||||
chapterListData.postValue(emptyList())
|
chapterListData.postValue(emptyList())
|
||||||
} else {
|
} else {
|
||||||
bookSource?.let { bookSource ->
|
bookSource?.let { bookSource ->
|
||||||
@@ -224,6 +226,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun loadGroup(groupId: Long, success: ((groupNames: String?) -> Unit)) {
|
fun loadGroup(groupId: Long, success: ((groupNames: String?) -> Unit)) {
|
||||||
execute {
|
execute {
|
||||||
appDb.bookGroupDao.getGroupNames(groupId).joinToString(",")
|
appDb.bookGroupDao.getGroupNames(groupId).joinToString(",")
|
||||||
@@ -232,6 +235,54 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadWebFile(
|
||||||
|
book: Book,
|
||||||
|
bookSource: BookSource,
|
||||||
|
scope: CoroutineScope = viewModelScope
|
||||||
|
) {
|
||||||
|
execute(scope) {
|
||||||
|
webFileData?.clear()
|
||||||
|
val fileName = "${book.name} 作者:${book.author}"
|
||||||
|
if (book.downloadUrls.isNullOrEmpty()) {
|
||||||
|
val ruleDownloadUrls = bookSource.getBookInfoRule().downloadUrls
|
||||||
|
val content = AnalyzeUrl(book.bookUrl, source = bookSource).getStrResponse().body
|
||||||
|
val analyzeRule = AnalyzeRule(book, bookSource)
|
||||||
|
analyzeRule.setContent(content).setBaseUrl(book.bookUrl)
|
||||||
|
analyzeRule.getStringList(ruleDownloadUrls, isUrl = true)?.let {
|
||||||
|
parseDownloadUrls(it, fileName)
|
||||||
|
} ?: throw NoStackTraceException("Unexpected ruleDownloadUrls")
|
||||||
|
} else {
|
||||||
|
parseDownloadUrls(book.downloadUrls, fileName)
|
||||||
|
}
|
||||||
|
}.onError {
|
||||||
|
context.toastOnUi("LoadWebFileError\n${it.localizedMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDownloadUrls(downloadUrls: List<String>?, fileName: String) {
|
||||||
|
val urls = downloadUrls
|
||||||
|
urls?.forEach { url ->
|
||||||
|
val mFileName = "${fileName}.${LocalBook.parseFileSuffix(url)}"
|
||||||
|
val isSupportedFile = AppPattern.bookFileRegex.matches(mFileName)
|
||||||
|
webFileData?.add(WebFile(url, mFileName, isSupportedFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importOrDownloadWebFile(webFile: WebFile, success: ((Uri?) -> Unit)?) {
|
||||||
|
bookSource ?: return
|
||||||
|
execute {
|
||||||
|
if (webFile.isSupported) {
|
||||||
|
LocalBook.importFileOnLine(webFile.url, webFile.name, bookSource)
|
||||||
|
} else {
|
||||||
|
LocalBook.saveBookFile(webFile.url, webFile.name, bookSource)
|
||||||
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
success?.invoke(it as? Uri)
|
||||||
|
}.onError {
|
||||||
|
context.toastOnUi("ImportWebFileError\n${it.localizedMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
|
fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
|
||||||
changeSourceCoroutine?.cancel()
|
changeSourceCoroutine?.cancel()
|
||||||
changeSourceCoroutine = execute {
|
changeSourceCoroutine = execute {
|
||||||
@@ -356,4 +407,14 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class WebFile(
|
||||||
|
val url: String,
|
||||||
|
val name: String,
|
||||||
|
val isSupported: Boolean
|
||||||
|
) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user