parent
							
								
									4fb534ccb7
								
							
						
					
					
						commit
						d4adbba776
					
				@ -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<ImportOnLineBookFileViewModel>() | 
				
			||||||
 | 
					    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<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.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) | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
@ -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<Triple<String, String, Boolean>>() | 
				
			||||||
 | 
					    val selectStatus = arrayListOf<Boolean>() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,34 @@ | 
				
			|||||||
 | 
					<?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"> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <io.legado.app.lib.theme.view.ThemeCheckBox | 
				
			||||||
 | 
					        android:id="@+id/cb_file_name" | 
				
			||||||
 | 
					        android:layout_width="wrap_content" | 
				
			||||||
 | 
					        android:layout_height="wrap_content" | 
				
			||||||
 | 
					        android:maxLines="1" | 
				
			||||||
 | 
					        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" /> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  | 
				
			||||||
 | 
					    <TextView | 
				
			||||||
 | 
					        android:id="@+id/tv_open" | 
				
			||||||
 | 
					        android:layout_width="wrap_content" | 
				
			||||||
 | 
					        android:layout_height="wrap_content" | 
				
			||||||
 | 
					        android:padding="8dp" | 
				
			||||||
 | 
					        android:text="@string/open" | 
				
			||||||
 | 
					        android:textColor="@color/secondaryText" | 
				
			||||||
 | 
					        app:layout_constraintBottom_toBottomOf="parent" | 
				
			||||||
 | 
					        app:layout_constraintRight_toRightOf="parent" | 
				
			||||||
 | 
					        app:layout_constraintTop_toTopOf="parent" /> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</androidx.constraintlayout.widget.ConstraintLayout> | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue