commit
						aca8d21d0c
					
				@ -1,7 +1,7 @@ | 
				
			||||
# 本地书籍解析 | 
				
			||||
# 书籍文件导入解析 | 
				
			||||
 | 
				
			||||
* BaseLocalBookParse.kt 本地书籍解析接口 | 
				
			||||
* LocalBook.kt 总入口 | 
				
			||||
* LocalBook.kt 导入解析总入口 | 
				
			||||
* TextFile.kt 解析txt | 
				
			||||
* EpubFile.kt 解析epub | 
				
			||||
* UmdFile.kt 解析umd | 
				
			||||
@ -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<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.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<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) | 
				
			||||
                        ) { | 
				
			||||
                            okButton { | 
				
			||||
                                importFileAndUpdate(selectFile.first, selectFile.second) | 
				
			||||
                            } | 
				
			||||
                            neutralButton(R.string.open_fun) { | 
				
			||||
                                downloadFile(selectFile.first, selectFile.second) | 
				
			||||
                            } | 
				
			||||
                            cancelButton() | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -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<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.BOOK_URL_CHANGED, it.bookUrl) | 
				
			||||
            } | 
				
			||||
        }.onSuccess { | 
				
			||||
           success.invoke() | 
				
			||||
        }.onError { | 
				
			||||
            context.toastOnUi("下载书籍文件失败\n${it.localizedMessage}") | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,23 @@ | 
				
			||||
<?xml version="1.0" encoding="utf-8"?> | 
				
			||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | 
				
			||||
    xmlns:app="http://schemas.android.com/apk/res-auto" | 
				
			||||
    xmlns:tools="http://schemas.android.com/tools" | 
				
			||||
    android:layout_width="match_parent" | 
				
			||||
    android:layout_height="wrap_content" | 
				
			||||
    android:padding="8dp"> | 
				
			||||
 | 
				
			||||
    <TextView | 
				
			||||
        android:id="@+id/cb_file_name" | 
				
			||||
        android:layout_width="wrap_content" | 
				
			||||
        android:layout_height="wrap_content" | 
				
			||||
        android:textColor="@color/primaryText" | 
				
			||||
        android:textSize="14sp" | 
				
			||||
        app:layout_constrainedWidth="true" | 
				
			||||
        app:layout_constraintBottom_toBottomOf="parent" | 
				
			||||
        app:layout_constraintHorizontal_bias="0" | 
				
			||||
        app:layout_constraintHorizontal_chainStyle="packed" | 
				
			||||
        app:layout_constraintLeft_toLeftOf="parent" | 
				
			||||
        app:layout_constraintTop_toTopOf="parent" | 
				
			||||
        tools:ignore="TouchTargetSizeCheck" /> | 
				
			||||
 | 
				
			||||
</androidx.constraintlayout.widget.ConstraintLayout> | 
				
			||||
					Loading…
					
					
				
		Reference in new issue