Преглед изворни кода

Revert "优化webFile导入"

This reverts commit ec9057be9b32259c3dc42a3e8e3e22454b6b336d.
Xwite пре 3 година
родитељ
комит
ef284ff362

+ 0 - 7
app/build.gradle

@@ -96,7 +96,6 @@ android {
     }
 
     flavorDimensions.add("mode")
-
     productFlavors {
         app {
             dimension "mode"
@@ -108,7 +107,6 @@ android {
             manifestPlaceholders.put("APP_CHANNEL_VALUE", "google")
         }
     }
-
     compileOptions {
         // Flag to enable support for the new language APIs
         coreLibraryDesugaringEnabled true
@@ -117,11 +115,6 @@ android {
         targetCompatibility JavaVersion.VERSION_11
     }
 
-    packagingOptions {
-        // fix found with path 'META-INF/INDEX.LIST' from inputs
-        exclude 'META-INF/INDEX.LIST'
-    }
-
     sourceSets {
         // Adds exported schema location as test app assets.
         androidTest.assets.srcDirs += files("$projectDir/schemas".toString())

+ 3 - 3
app/src/main/java/io/legado/app/data/entities/Server.kt

@@ -49,9 +49,9 @@ data class Server(
 
     @Parcelize
     data class WebDavConfig(
-        var url: String,
-        var username: String,
-        var password: String
+        var url: String? = null,
+        var username: String? = null,
+        var password: String? = null,
     ) : Parcelable
 
 }

+ 23 - 15
app/src/main/java/io/legado/app/help/book/BookExtensions.kt

@@ -18,39 +18,49 @@ import java.util.concurrent.ConcurrentHashMap
 
 
 val Book.isAudio: Boolean
-    get() = isType(BookType.audio)
+    get() {
+        return type and BookType.audio > 0
+    }
 
 val Book.isImage: Boolean
-    get() = isType(BookType.image)
+    get() {
+        return type and BookType.image > 0
+    }
 
 val Book.isLocal: Boolean
     get() {
         if (type == 0) {
             return origin == BookType.localTag || origin.startsWith(BookType.webDavTag)
         }
-        return isType(BookType.local)
+        return type and BookType.local > 0
     }
 
 val Book.isLocalTxt: Boolean
-    get() = isLocal && originName.endsWith(".txt", true)
+    get() {
+        return isLocal && originName.endsWith(".txt", true)
+    }
 
 val Book.isEpub: Boolean
-    get() = isLocal && originName.endsWith(".epub", true)
+    get() {
+        return isLocal && originName.endsWith(".epub", true)
+    }
 
 val Book.isUmd: Boolean
-    get() = isLocal && originName.endsWith(".umd", true)
-
+    get() {
+        return isLocal && originName.endsWith(".umd", true)
+    }
 val Book.isPdf: Boolean
-    get() = isLocal && originName.endsWith(".pdf", true)
+    get() {
+        return isLocal && originName.endsWith(".pdf", true)
+    }
 
 val Book.isOnLineTxt: Boolean
-    get() = !isLocal && isType(BookType.text)
-
-val Book.isWebFile: Boolean
-    get() = isType(BookType.webFile)
+    get() {
+        return !isLocal && type and BookType.text > 0
+    }
 
 val Book.isUpError: Boolean
-    get() = isType(BookType.updateError)
+    get() = type and BookType.updateError > 0
 
 fun Book.contains(word: String?): Boolean {
     if (word.isNullOrEmpty()) {
@@ -162,8 +172,6 @@ fun Book.clearType() {
     type = 0
 }
 
-fun Book.isType(@BookType.Type bookType: Int): Boolean = type and bookType > 0
-
 fun Book.upType() {
     if (type < 8) {
         type = when (type) {

+ 1 - 1
app/src/main/java/io/legado/app/lib/webdav/Authorization.kt

@@ -24,7 +24,7 @@ data class Authorization(
 
     constructor(serverID: Long?): this("","") {
         serverID ?: throw NoStackTraceException("Unexpected server ID")
-        appDb.serverDao.get(serverID)?.getWebDavConfig()?.run {
+        appDb.serverDao.get(serverID)?.getWebDavConfig().run {
             data = Credentials.basic(username, password, charset)
         } ?: throw WebDavException("Unexpected WebDav Authorization")
     }

+ 1 - 2
app/src/main/java/io/legado/app/model/webBook/BookInfo.kt

@@ -7,7 +7,6 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookSource
 import io.legado.app.exception.NoStackTraceException
 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.analyzeRule.AnalyzeRule
 import io.legado.app.utils.DebugLog
@@ -138,7 +137,7 @@ object BookInfo {
             Debug.log(bookSource.bookSourceUrl, "└${e.localizedMessage}")
             DebugLog.e("获取封面出错", e)
         }
-        if (book.isWebFile) {
+        if (book.type and BookType.webFile == 0) {
             coroutineContext.ensureActive()
             Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
             book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)

+ 131 - 0
app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileDialog.kt

@@ -0,0 +1,131 @@
+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()
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+}

+ 78 - 0
app/src/main/java/io/legado/app/ui/association/ImportOnLineBookFileViewModel.kt

@@ -0,0 +1,78 @@
+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}")
+        }
+    }
+
+}

+ 19 - 52
app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt

@@ -21,7 +21,10 @@ import io.legado.app.databinding.ActivityBookInfoBinding
 import io.legado.app.databinding.DialogEditTextBinding
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.AppWebDav
-import io.legado.app.help.book.*
+import io.legado.app.help.book.isAudio
+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.lib.dialogs.alert
 import io.legado.app.lib.theme.backgroundColor
@@ -30,6 +33,7 @@ import io.legado.app.lib.theme.getPrimaryTextColor
 import io.legado.app.model.BookCover
 import io.legado.app.model.remote.RemoteBookWebDav
 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
@@ -227,6 +231,7 @@ class BookInfoActivity :
             waitDialog.setText("上传中.....")
             waitDialog.show()
             try {
+                
                 bookWebDav
                     ?.upload(book)
                     ?: throw NoStackTraceException("未配置webDav")
@@ -248,7 +253,6 @@ class BookInfoActivity :
         tvOrigin.text = getString(R.string.origin_show, book.originName)
         tvLasted.text = getString(R.string.lasted_show, book.latestChapterTitle)
         tvIntro.text = book.getDisplayIntro()
-        tvToc.visible(!book.isWebFile)
         upTvBookshelf()
         val kinds = book.getKindList()
         if (kinds.isEmpty()) {
@@ -274,10 +278,11 @@ class BookInfoActivity :
                 binding.tvToc.text = getString(R.string.toc_s, getString(R.string.loading))
             }
             chapterList.isNullOrEmpty() -> {
-                binding.tvToc.text = getString(
-                    R.string.toc_s,
-                    getString(R.string.error_load_toc)
-                )
+                binding.tvToc.text =
+                    if (viewModel.isImportBookOnLine) getString(R.string.click_read_button_load) else getString(
+                        R.string.toc_s,
+                        getString(R.string.error_load_toc)
+                    )
             }
             else -> {
                 viewModel.bookData.value?.let {
@@ -328,9 +333,9 @@ class BookInfoActivity :
         }
         tvRead.setOnClickListener {
             viewModel.bookData.value?.let { book ->
-                if (book.isWebFile) {
-                    showWebFileDownloadAlert {
-                        readBook(book)
+                if (viewModel.isImportBookOnLine) {
+                    showDialogFragment<ImportOnLineBookFileDialog> {
+                        putString("bookUrl", book.bookUrl)
                     }
                 } else {
                     readBook(book)
@@ -338,21 +343,11 @@ class BookInfoActivity :
             } ?: toastOnUi("Book is null")
         }
         tvShelf.setOnClickListener {
-            viewModel.bookData.value?.let { book ->
-                if (viewModel.inBookshelf) {
-                    deleteBook()
-                } else {
-                    if (book.isWebFile) {
-                        showWebFileDownloadAlert {
-                            viewModel.addToBookshelf {
-                                upTvBookshelf()
-                            }
-                        }
-                    } else {
-                        viewModel.addToBookshelf {
-                            upTvBookshelf()
-                        }
-                    }
+            if (viewModel.inBookshelf) {
+                deleteBook()
+            } else {
+                viewModel.addToBookshelf {
+                    upTvBookshelf()
                 }
             }
         }
@@ -498,34 +493,6 @@ 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) {
         if (!viewModel.inBookshelf) {
             viewModel.saveBook(book) {

+ 9 - 70
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -1,6 +1,5 @@
 package io.legado.app.ui.book.info
 
-import android.net.Uri
 import android.app.Application
 import android.content.Intent
 import androidx.lifecycle.MutableLiveData
@@ -8,7 +7,6 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.R
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppLog
-import io.legado.app.constant.AppPattern
 import io.legado.app.constant.BookSourceType
 import io.legado.app.constant.BookType
 import io.legado.app.constant.EventBus
@@ -21,8 +19,6 @@ import io.legado.app.help.AppWebDav
 import io.legado.app.help.book.*
 import io.legado.app.help.coroutine.Coroutine
 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.ReadBook
 import io.legado.app.model.localBook.LocalBook
@@ -36,10 +32,11 @@ import kotlinx.coroutines.Dispatchers.IO
 class BookInfoViewModel(application: Application) : BaseViewModel(application) {
     val bookData = MutableLiveData<Book>()
     val chapterListData = MutableLiveData<List<BookChapter>>()
-    val webFileData: MutableList<WebFile>? = null
     var inBookshelf = false
     var bookSource: BookSource? = null
     private var changeSourceCoroutine: Coroutine<*>? = null
+    val isImportBookOnLine: Boolean
+        get() = bookSource?.bookSourceType == BookSourceType.file
 
     fun initData(intent: Intent) {
         execute {
@@ -85,6 +82,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
                 appDb.bookSourceDao.getBookSource(book.origin)
             if (book.tocUrl.isEmpty() && !book.isLocal) {
                 loadBookInfo(book)
+            } else if (isImportBookOnLine) {
+                chapterListData.postValue(emptyList())
             } else {
                 val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)
                 if (chapterList.isNotEmpty()) {
@@ -157,14 +156,13 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
                                 inBookshelf = true
                             }
                             bookData.postValue(book)
+                            if (isImportBookOnLine) {
+                                appDb.searchBookDao.update(book.toSearchBook())
+                            }
                             if (inBookshelf) {
                                 appDb.bookDao.update(book)
                             }
-                            if (it.isWebFile) {
-                                loadWebFile(book, bookSource, scope)
-                            } else {
-                                loadChapter(it, scope)
-                            }
+                            loadChapter(it, scope)
                         }.onError {
                             AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
                             context.toastOnUi(R.string.error_get_book_info)
@@ -189,7 +187,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
                     appDb.bookChapterDao.insert(*it.toTypedArray())
                     chapterListData.postValue(it)
                 }
-            } else if (book.isWebFile) {
+            } else if (isImportBookOnLine) {
                 chapterListData.postValue(emptyList())
             } else {
                 bookSource?.let { bookSource ->
@@ -226,7 +224,6 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         }
     }
 
-
     fun loadGroup(groupId: Long, success: ((groupNames: String?) -> Unit)) {
         execute {
             appDb.bookGroupDao.getGroupNames(groupId).joinToString(",")
@@ -235,54 +232,6 @@ 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>) {
         changeSourceCoroutine?.cancel()
         changeSourceCoroutine = execute {
@@ -407,14 +356,4 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
         }
     }
 
-    data class WebFile(
-        val url: String,
-        val name: String,
-        val isSupported: Boolean
-    ) {
-        override fun toString(): String {
-            return name
-        }
-    }
-
 }