From d03729f964968373dcb99c3ee7ba6f3019f92264 Mon Sep 17 00:00:00 2001 From: gedoor Date: Wed, 29 Sep 2021 16:40:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=B7=BB=E5=8A=A0=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/cache/CacheActivity.kt | 45 ++-- .../legado/app/ui/book/cache/CacheAdapter.kt | 29 ++- .../app/ui/book/cache/CacheViewModel.kt | 194 ++++++++++-------- app/src/main/res/layout/item_download.xml | 46 +++-- 4 files changed, 188 insertions(+), 126 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 61594c047..8790f19e1 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -6,7 +6,6 @@ import android.view.Menu import android.view.MenuItem import androidx.activity.viewModels import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.snackbar.Snackbar import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.AppConst @@ -208,6 +207,13 @@ class CacheActivity : VMBaseActivity() } override fun observeLiveBus() { + viewModel.upAdapterLiveData.observe(this) { + adapter.getItems().forEachIndexed { index, book -> + if (book.bookUrl == it) { + adapter.notifyItemChanged(index, true) + } + } + } observeEvent(EventBus.UP_DOWNLOAD) { if (!CacheBook.isRun) { menu?.findItem(R.id.menu_download)?.let { item -> @@ -263,25 +269,10 @@ class CacheActivity : VMBaseActivity() private fun startExport(path: String) { if (exportPosition == -10) { if (adapter.getItems().isNotEmpty()) { - Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE) - .show() - var exportSize = adapter.getItems().size adapter.getItems().forEach { book -> when (AppConfig.exportType) { - 1 -> viewModel.exportEPUB(path, book) { - exportSize-- - toastOnUi(it) - if (exportSize <= 0) { - binding.titleBar.snackbar(R.string.complete) - } - } - else -> viewModel.export(path, book) { - exportSize-- - toastOnUi(it) - if (exportSize <= 0) { - binding.titleBar.snackbar(R.string.complete) - } - } + 1 -> viewModel.exportEPUB(path, book) + else -> viewModel.export(path, book) } } } else { @@ -289,15 +280,9 @@ class CacheActivity : VMBaseActivity() } } else if (exportPosition >= 0) { adapter.getItem(exportPosition)?.let { book -> - Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE) - .show() when (AppConfig.exportType) { - 1 -> viewModel.exportEPUB(path, book) { - binding.titleBar.snackbar(it) - } - else -> viewModel.export(path, book) { - binding.titleBar.snackbar(it) - } + 1 -> viewModel.exportEPUB(path, book) + else -> viewModel.export(path, book) } } } @@ -346,4 +331,12 @@ class CacheActivity : VMBaseActivity() }.show() } + override fun exportProgress(bookUrl: String): Int? { + return viewModel.exportProgress[bookUrl] + } + + override fun exportMsg(bookUrl: String): String? { + return viewModel.exportMsg[bookUrl] + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt index 24119b873..45439f31b 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt @@ -3,12 +3,16 @@ package io.legado.app.ui.book.cache import android.content.Context import android.view.ViewGroup import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.data.entities.Book import io.legado.app.databinding.ItemDownloadBinding import io.legado.app.model.CacheBook +import io.legado.app.utils.gone +import io.legado.app.utils.visible class CacheAdapter(context: Context, private val callBack: CallBack) : RecyclerAdapter(context) { @@ -36,13 +40,13 @@ class CacheAdapter(context: Context, private val callBack: CallBack) : tvDownload.text = context.getString(R.string.download_count, cs.size, item.totalChapterNum) } - upDownloadIv(ivDownload, item) } else { val cacheSize = cacheChapters[item.bookUrl]?.size ?: 0 tvDownload.text = context.getString(R.string.download_count, cacheSize, item.totalChapterNum) - upDownloadIv(ivDownload, item) } + upDownloadIv(ivDownload, item) + upExportInfo(tvMsg, progressExport, item) } } @@ -79,7 +83,28 @@ class CacheAdapter(context: Context, private val callBack: CallBack) : } } + private fun upExportInfo(msgView: TextView, progressView: ProgressBar, book: Book) { + val msg = callBack.exportMsg(book.bookUrl) + if (msg != null) { + msgView.text = msg + msgView.visible() + progressView.gone() + return + } + msgView.gone() + val progress = callBack.exportProgress(book.bookUrl) + if (progress != null) { + progressView.max = book.totalChapterNum + progressView.progress = progress + progressView.visible() + return + } + progressView.gone() + } + interface CallBack { fun export(position: Int) + fun exportProgress(bookUrl: String): Int? + fun exportMsg(bookUrl: String): String? } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index c7d96f9e5..c667f8768 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.MutableLiveData import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition @@ -19,7 +20,10 @@ import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.ContentProcessor import io.legado.app.help.storage.AppWebDav +import io.legado.app.model.NoStackTraceException import io.legado.app.utils.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ensureActive import me.ag2s.epublib.domain.* import me.ag2s.epublib.epub.EpubWriter import me.ag2s.epublib.util.ResourceUtil @@ -28,10 +32,14 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream import java.nio.charset.Charset +import java.util.concurrent.ConcurrentHashMap import javax.script.SimpleBindings class CacheViewModel(application: Application) : BaseViewModel(application) { + val upAdapterLiveData = MutableLiveData() + val exportProgress = ConcurrentHashMap() + val exportMsg = ConcurrentHashMap() private fun getExportFileName(book: Book): String { val jsStr = AppConfig.bookExportFileName @@ -44,100 +52,111 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { return AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString() } - fun export(path: String, book: Book, finally: (msg: String) -> Unit) { + fun export(path: String, book: Book) { + if (exportProgress.contains(book.bookUrl)) return + exportProgress[book.bookUrl] = 0 + exportMsg.remove(book.bookUrl) + upAdapterLiveData.postValue(book.bookUrl) execute { if (path.isContentScheme()) { val uri = Uri.parse(path) - DocumentFile.fromTreeUri(context, uri)?.let { - export(it, book) - } + val doc = DocumentFile.fromTreeUri(context, uri) + ?: throw NoStackTraceException("获取导出文档失败") + export(this, doc, book) } else { - export(FileUtils.createFolderIfNotExist(path), book) + export(this, FileUtils.createFolderIfNotExist(path), book) } }.onError { - finally(it.localizedMessage ?: "ERROR") + exportProgress.remove(book.bookUrl) + exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR" + upAdapterLiveData.postValue(book.bookUrl) it.printOnDebug() }.onSuccess { - finally(context.getString(R.string.success)) + exportProgress.remove(book.bookUrl) + exportMsg[book.bookUrl] = context.getString(R.string.export_success) + upAdapterLiveData.postValue(book.bookUrl) } } @Suppress("BlockingMethodInNonBlockingContext") - private suspend fun export(doc: DocumentFile, book: Book) { + private suspend fun export(scope: CoroutineScope, doc: DocumentFile, book: Book) { val filename = "${getExportFileName(book)}.txt" DocumentUtils.delete(doc, filename) - DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> - val stringBuilder = StringBuilder() - context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> - getAllContents(book) { - bookOs.write(it.toByteArray(Charset.forName(AppConfig.exportCharset))) - stringBuilder.append(it) + val bookDoc = DocumentUtils.createFileIfNotExist(doc, filename) + ?: throw NoStackTraceException("创建文档失败") + val stringBuilder = StringBuilder() + context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> + getAllContents(scope, book) { text, srcList -> + bookOs.write(text.toByteArray(Charset.forName(AppConfig.exportCharset))) + stringBuilder.append(text) + srcList?.forEach { + val vFile = BookHelp.getImage(book, it.third) + if (vFile.exists()) { + DocumentUtils.createFileIfNotExist( + doc, + "${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg", + subDirs = arrayOf("${book.name}_${book.author}", "images", it.first) + )?.writeBytes(context, vFile.readBytes()) + } } } - if (AppConfig.exportToWebDav) { - // 导出到webdav - val byteArray = - stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset)) - AppWebDav.exportWebDav(byteArray, filename) - } } - getSrcList(book).forEach { - val vFile = BookHelp.getImage(book, it.third) - if (vFile.exists()) { - DocumentUtils.createFileIfNotExist( - doc, - "${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg", - subDirs = arrayOf("${book.name}_${book.author}", "images", it.first) - )?.writeBytes(context, vFile.readBytes()) - } + if (AppConfig.exportToWebDav) { + // 导出到webdav + val byteArray = + stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset)) + AppWebDav.exportWebDav(byteArray, filename) } } - private suspend fun export(file: File, book: Book) { + private suspend fun export(scope: CoroutineScope, file: File, book: Book) { val filename = "${getExportFileName(book)}.txt" val bookPath = FileUtils.getPath(file, filename) val bookFile = FileUtils.createFileWithReplace(bookPath) val stringBuilder = StringBuilder() - getAllContents(book) { - bookFile.appendText(it, Charset.forName(AppConfig.exportCharset)) - stringBuilder.append(it) + getAllContents(scope, book) { text, srcList -> + bookFile.appendText(text, Charset.forName(AppConfig.exportCharset)) + stringBuilder.append(text) + srcList?.forEach { + val vFile = BookHelp.getImage(book, it.third) + if (vFile.exists()) { + FileUtils.createFileIfNotExist( + file, + "${book.name}_${book.author}", + "images", + it.first, + "${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg" + ).writeBytes(vFile.readBytes()) + } + } } if (AppConfig.exportToWebDav) { val byteArray = stringBuilder.toString().toByteArray(Charset.forName(AppConfig.exportCharset)) AppWebDav.exportWebDav(byteArray, filename) // 导出到webdav } - getSrcList(book).forEach { - val vFile = BookHelp.getImage(book, it.third) - if (vFile.exists()) { - FileUtils.createFileIfNotExist( - file, - "${book.name}_${book.author}", - "images", - it.first, - "${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg" - ).writeBytes(vFile.readBytes()) - } - } } - private fun getAllContents(book: Book, append: (text: String) -> Unit) { + private fun getAllContents( + scope: CoroutineScope, + book: Book, + append: (text: String, srcList: ArrayList>?) -> Unit + ) { val useReplace = AppConfig.exportUseReplace val contentProcessor = ContentProcessor.get(book.name, book.origin) - append( - "${book.name}\n${ - context.getString( - R.string.author_show, - book.getRealAuthor() - ) - }\n${ - context.getString( - R.string.intro_show, - "\n" + HtmlFormatter.format(book.getDisplayIntro()) - ) - }" - ) - appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter -> + val qy = "${book.name}\n${ + context.getString(R.string.author_show, book.getRealAuthor()) + }\n${ + context.getString( + R.string.intro_show, + "\n" + HtmlFormatter.format(book.getDisplayIntro()) + ) + }" + append(qy, null) + appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> + scope.ensureActive() + upAdapterLiveData.postValue(book.bookUrl) + exportProgress[book.bookUrl] = index BookHelp.getContent(book, chapter).let { content -> val content1 = contentProcessor .getContent( @@ -149,16 +168,8 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { chineseConvert = false, reSegment = false ).joinToString("\n") - append.invoke("\n\n$content1") - } - } - } - - private fun getSrcList(book: Book): ArrayList> { - val srcList = arrayListOf>() - appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter -> - BookHelp.getContent(book, chapter)?.let { content -> - content.split("\n").forEachIndexed { index, text -> + val srcList = arrayListOf>() + content?.split("\n")?.forEachIndexed { index, text -> val matcher = AppPattern.imgPattern.matcher(text) while (matcher.find()) { matcher.group(1)?.let { @@ -167,34 +178,43 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } } } + append.invoke("\n\n$content1", srcList) } } - return srcList } + //////////////////Start EPUB /** * 导出Epub */ - fun exportEPUB(path: String, book: Book, finally: (msg: String) -> Unit) { + fun exportEPUB(path: String, book: Book) { + if (exportProgress.contains(book.bookUrl)) return + exportProgress[book.bookUrl] = 0 + exportMsg.remove(book.bookUrl) + upAdapterLiveData.postValue(book.bookUrl) execute { if (path.isContentScheme()) { val uri = Uri.parse(path) - DocumentFile.fromTreeUri(context, uri)?.let { - exportEpub(it, book) - } + val doc = DocumentFile.fromTreeUri(context, uri) + ?: throw NoStackTraceException("获取导出文档失败") + exportEpub(this, doc, book) } else { - exportEpub(FileUtils.createFolderIfNotExist(path), book) + exportEpub(this, FileUtils.createFolderIfNotExist(path), book) } }.onError { - finally(it.localizedMessage ?: "ERROR") + exportProgress.remove(book.bookUrl) + exportMsg[book.bookUrl] = it.localizedMessage ?: "ERROR" + upAdapterLiveData.postValue(book.bookUrl) it.printOnDebug() }.onSuccess { - finally(context.getString(R.string.success)) + exportProgress.remove(book.bookUrl) + exportMsg[book.bookUrl] = context.getString(R.string.export_success) + upAdapterLiveData.postValue(book.bookUrl) } } @Suppress("BlockingMethodInNonBlockingContext") - private fun exportEpub(doc: DocumentFile, book: Book) { + private fun exportEpub(scope: CoroutineScope, doc: DocumentFile, book: Book) { val filename = "${getExportFileName(book)}.epub" DocumentUtils.delete(doc, filename) val epubBook = EpubBook() @@ -207,7 +227,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val contentModel = setAssets(doc, book, epubBook) //设置正文 - setEpubContent(contentModel, book, epubBook) + setEpubContent(scope, contentModel, book, epubBook) DocumentUtils.createFileIfNotExist(doc, filename)?.let { bookDoc -> context.contentResolver.openOutputStream(bookDoc.uri, "wa")?.use { bookOs -> EpubWriter().write(epubBook, bookOs) @@ -217,7 +237,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } - private fun exportEpub(file: File, book: Book) { + private fun exportEpub(scope: CoroutineScope, file: File, book: Book) { val filename = "${getExportFileName(book)}.epub" val epubBook = EpubBook() epubBook.version = "2.0" @@ -231,7 +251,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { val bookPath = FileUtils.getPath(file, filename) val bookFile = FileUtils.createFileWithReplace(bookPath) //设置正文 - setEpubContent(contentModel, book, epubBook) + setEpubContent(scope, contentModel, book, epubBook) EpubWriter().write(epubBook, FileOutputStream(bookFile)) } @@ -386,11 +406,19 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { }) } - private fun setEpubContent(contentModel: String, book: Book, epubBook: EpubBook) { + private fun setEpubContent( + scope: CoroutineScope, + contentModel: String, + book: Book, + epubBook: EpubBook + ) { //正文 val useReplace = AppConfig.exportUseReplace val contentProcessor = ContentProcessor.get(book.name, book.origin) appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter -> + scope.ensureActive() + upAdapterLiveData.postValue(book.bookUrl) + exportProgress[book.bookUrl] = index BookHelp.getContent(book, chapter).let { content -> var content1 = fixPic(epubBook, book, content ?: "null", chapter) content1 = contentProcessor diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml index f4dce4213..0a0a78e8b 100644 --- a/app/src/main/res/layout/item_download.xml +++ b/app/src/main/res/layout/item_download.xml @@ -1,9 +1,9 @@ + tools:text="@string/name" /> + tools:text="@string/author" /> + tools:text="@string/action_download" /> + app:layout_constraintBottom_toBottomOf="@+id/tv_download" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@id/tv_name" /> + + + + \ No newline at end of file