From 788a5b5d08613a181eb62f037d76d04aa9be788b Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Wed, 23 Aug 2023 12:31:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/model/ReadBook.kt | 14 ++++ .../app/service/BaseReadAloudService.kt | 55 ++++++++++++++-- .../app/service/HttpReadAloudService.kt | 16 +++-- .../legado/app/service/TTSReadAloudService.kt | 9 ++- .../book/import/remote/RemoteBookActivity.kt | 14 +++- .../book/import/remote/RemoteBookAdapter.kt | 11 +++- .../book/manage/BookshelfManageViewModel.kt | 26 ++++---- .../ui/book/read/page/entities/TextChapter.kt | 65 +++++++++++++++++++ .../ui/book/read/page/entities/TextLine.kt | 4 ++ .../ui/book/read/page/entities/TextPage.kt | 17 +++++ .../book/read/page/entities/TextParagraph.kt | 18 +++++ .../read/page/provider/ChapterProvider.kt | 13 ++++ 12 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/book/read/page/entities/TextParagraph.kt diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 2328e97be..9bbe13852 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -177,6 +177,20 @@ object ReadBook : CoroutineScope by MainScope() { return hasNextPage } + fun moveToPrevPage(): Boolean { + var hasPrevPage = false + curTextChapter?.let { + val prevPagePos = it.getPrevPageLength(durChapterPos) + if (prevPagePos >= 0) { + hasPrevPage = true + durChapterPos = prevPagePos + callBack?.upContent() + saveRead() + } + } + return hasPrevPage + } + fun moveToNextChapter(upContent: Boolean): Boolean { if (durChapterIndex < chapterSize - 1) { durChapterPos = 0 diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index f024fe517..37f3757f2 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -86,6 +86,9 @@ abstract class BaseReadAloudService : BaseService(), BitmapFactory.decodeResource(appCtx.resources, R.drawable.icon_read_book) var pageChanged = false private var ttsProgress = 0 + private var toLast = false + var paragraphStartPos = 0 + private var readAloudByPage = false private val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -164,19 +167,40 @@ abstract class BaseReadAloudService : BaseService(), } private fun newReadAloud(play: Boolean, pageIndex: Int, startPos: Int) { - lifecycleScope.launch(IO) { + execute(executeContext = IO) { this@BaseReadAloudService.pageIndex = pageIndex textChapter = ReadBook.curTextChapter - val textChapter = textChapter ?: return@launch - nowSpeak = 0 + val textChapter = textChapter ?: return@execute readAloudNumber = textChapter.getReadLength(pageIndex) + startPos - val readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage) - contentList = textChapter.getNeedReadAloud(pageIndex, readAloudByPage, startPos) + readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage) + contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0) .split("\n") .filter { it.isNotEmpty() } + var pos = startPos + val page = textChapter.getPage(pageIndex)!! + if (pos > 0) { + for (paragraph in page.paragraphs) { + val tmp = pos - paragraph.length - 1 + if (tmp < 0) break + pos = tmp + } + } + nowSpeak = textChapter.getParagraphNum(readAloudNumber + 1, readAloudByPage) - 1 + if (!readAloudByPage && pos == 0) { + pos = page.lines.first().chapterPosition - + textChapter.paragraphs[nowSpeak].chapterPosition + } + paragraphStartPos = pos + if (toLast) { + toLast = false + readAloudNumber = textChapter.getLastParagraphPosition() + nowSpeak = contentList.lastIndex + } launch(Main) { if (play) play() else pageChanged = true } + }.onError { + AppLog.put("启动朗读出错\n${it.localizedMessage}", it, true) } } @@ -225,9 +249,24 @@ abstract class BaseReadAloudService : BaseService(), if (nowSpeak > 0) { playStop() nowSpeak-- - readAloudNumber -= contentList[nowSpeak].length.minus(1) + readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos + paragraphStartPos = 0 + textChapter?.let { + val paragraphs = if (readAloudByPage) { + it.pageParagraphs + } else { + it.paragraphs + } + if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber++ + if (readAloudNumber < it.getReadLength(pageIndex)) { + pageIndex-- + ReadBook.moveToPrevPage() + } + } + upTtsProgress(readAloudNumber + 1) play() } else { + toLast = true ReadBook.moveToPrevChapter(true) } } @@ -235,8 +274,10 @@ abstract class BaseReadAloudService : BaseService(), private fun nextP() { if (nowSpeak < contentList.size - 1) { playStop() - readAloudNumber += contentList[nowSpeak].length.plus(1) + readAloudNumber += contentList[nowSpeak].length.plus(1) - paragraphStartPos + paragraphStartPos = 0 nowSpeak++ + upTtsProgress(readAloudNumber + 1) play() } else { nextChapter() diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index cb4b3a3ec..c0f8a785b 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -11,7 +11,6 @@ import com.script.ScriptException import io.legado.app.R import io.legado.app.constant.AppLog import io.legado.app.constant.AppPattern -import io.legado.app.constant.EventBus import io.legado.app.data.entities.HttpTTS import io.legado.app.exception.ConcurrentException import io.legado.app.exception.NoStackTraceException @@ -22,7 +21,6 @@ import io.legado.app.model.ReadBook import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.FileUtils import io.legado.app.utils.MD5Utils -import io.legado.app.utils.postEvent import io.legado.app.utils.printOnDebug import io.legado.app.utils.servicePendingIntent import io.legado.app.utils.toastOnUi @@ -90,10 +88,12 @@ class HttpReadAloudService : BaseReadAloudService(), override fun playStop() { exoPlayer.stop() + playIndexJob?.cancel() } private fun playNext() { - readAloudNumber += contentList[nowSpeak].length + 1 + readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos + paragraphStartPos = 0 if (nowSpeak < contentList.lastIndex) { nowSpeak++ } else { @@ -111,10 +111,14 @@ class HttpReadAloudService : BaseReadAloudService(), contentList.forEachIndexed { index, content -> ensureActive() if (index < nowSpeak) return@forEachIndexed - val fileName = md5SpeakFileName(content) - val speakText = content.replace(AppPattern.notReadAloudRegex, "") + var text = content + if (paragraphStartPos > 0 && index == nowSpeak) { + text = text.substring(paragraphStartPos) + } + val fileName = md5SpeakFileName(text) + val speakText = text.replace(AppPattern.notReadAloudRegex, "") if (speakText.isEmpty()) { - AppLog.put("阅读段落内容为空,使用无声音频代替。\n朗读文本:$content") + AppLog.put("阅读段落内容为空,使用无声音频代替。\n朗读文本:$text") createSilentSound(fileName) } else if (!hasSpeakFile(fileName)) { runCatching { diff --git a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt index 83560bbfe..a92bb00b6 100644 --- a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt @@ -7,7 +7,6 @@ import io.legado.app.R import io.legado.app.constant.AppConst import io.legado.app.constant.AppLog import io.legado.app.constant.AppPattern -import io.legado.app.constant.EventBus import io.legado.app.exception.NoStackTraceException import io.legado.app.help.MediaHelp import io.legado.app.help.config.AppConfig @@ -100,7 +99,10 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener val contentList = contentList for (i in nowSpeak until contentList.size) { ensureActive() - val text = contentList[i] + var text = contentList[i] + if (paragraphStartPos > 0 && i == nowSpeak) { + text = text.substring(paragraphStartPos) + } if (text.matches(AppPattern.notReadAloudRegex)) { continue } @@ -178,7 +180,8 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener override fun onDone(s: String) { //跳过全标点段落 do { - readAloudNumber += contentList[nowSpeak].length + 1 + readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos + paragraphStartPos = 0 nowSpeak++ if (nowSpeak >= contentList.size) { nextChapter() diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt index 8f384f2ca..17c19bb06 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt @@ -247,4 +247,16 @@ class RemoteBookActivity : BaseImportBookActivity(), } } -} \ No newline at end of file + override fun addToBookShelfAgain(remoteBook: RemoteBook) { + alert(getString(R.string.sure), "是否重新加入书架?") { + yesButton { + binding.refreshProgressBar.isAutoLoading = true + viewModel.addToBookshelf(hashSetOf(remoteBook)) { + binding.refreshProgressBar.isAutoLoading = false + } + } + noButton() + } + } + +} diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookAdapter.kt index 863367c33..64ce19c65 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookAdapter.kt @@ -90,6 +90,14 @@ class RemoteBookAdapter(context: Context, val callBack: CallBack) : } } } + holder.itemView.setOnLongClickListener { + getItem(holder.layoutPosition)?.let { remoteBook -> + if (remoteBook.isOnBookShelf) { + callBack.addToBookShelfAgain(remoteBook) + } + } + true + } } private fun upCheckableCount() { @@ -144,5 +152,6 @@ class RemoteBookAdapter(context: Context, val callBack: CallBack) : fun openDir(remoteBook: RemoteBook) fun upCountView() fun startRead(remoteBook: RemoteBook) + fun addToBookShelfAgain(remoteBook: RemoteBook) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt index f9abc296d..fae2f2fa6 100644 --- a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt @@ -3,6 +3,7 @@ package io.legado.app.ui.book.manage import android.app.Application import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppLog import io.legado.app.constant.BookType import io.legado.app.data.appDb import io.legado.app.data.entities.Book @@ -52,22 +53,21 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica batchChangeSourceCoroutine?.cancel() batchChangeSourceCoroutine = execute { books.forEachIndexed { index, book -> - batchChangeSourceProcessLiveData.postValue("${index + 1}/${books.size}") + batchChangeSourceProcessLiveData.postValue("${index + 1} / ${books.size}") if (book.isLocal) return@forEachIndexed if (book.origin == source.bookSourceUrl) return@forEachIndexed - WebBook.preciseSearchAwait(this, source, book.name, book.author) + val newBook = WebBook.preciseSearchAwait(this, source, book.name, book.author) .onFailure { - context.toastOnUi("获取书籍出错\n${it.localizedMessage}") - }.getOrNull()?.let { newBook -> - WebBook.getChapterListAwait(source, newBook) - .onFailure { - context.toastOnUi("获取目录出错\n${it.localizedMessage}") - }.getOrNull()?.let { toc -> - book.migrateTo(newBook, toc) - book.removeType(BookType.updateError) - appDb.bookDao.insert(newBook) - appDb.bookChapterDao.insert(*toc.toTypedArray()) - } + AppLog.put("获取书籍出错\n${it.localizedMessage}", it, true) + }.getOrNull() ?: return@forEachIndexed + WebBook.getChapterListAwait(source, newBook) + .onFailure { + AppLog.put("获取目录出错\n${it.localizedMessage}", it, true) + }.getOrNull()?.let { toc -> + book.migrateTo(newBook, toc) + book.removeType(BookType.updateError) + appDb.bookDao.insert(newBook) + appDb.bookChapterDao.insert(*toc.toTypedArray()) } } }.onStart { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt index 2121bf103..09faed73a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt @@ -40,6 +40,38 @@ data class TextChapter( val pageSize: Int get() = pages.size + val paragraphs by lazy { + paragraphsInternal + } + + val pageParagraphs by lazy { + pageParagraphsInternal + } + + val paragraphsInternal: ArrayList + get() { + val paragraphs = arrayListOf() + pages.forEach { + it.lines.forEach loop@{ line -> + if (line.paragraphNum <= 0) return@loop + if (paragraphs.lastIndex < line.paragraphNum - 1) { + paragraphs.add(TextParagraph(line.paragraphNum)) + } + paragraphs[line.paragraphNum - 1].textLines.add(line) + } + } + return paragraphs + } + + val pageParagraphsInternal: List + get() = pages.map { + it.paragraphs + }.flatten().also { + it.forEachIndexed { index, textParagraph -> + textParagraph.num = index + 1 + } + } + /** * @param index 页数 * @return 是否是最后一页 @@ -73,6 +105,18 @@ data class TextChapter( return getReadLength(pageIndex + 1) } + /** + * @param length 当前页面文字在章节中的位置 + * @return 上一页位置,如果没有上一页返回-1 + */ + fun getPrevPageLength(length: Int): Int { + val pageIndex = getPageIndexByCharIndex(length) + if (pageIndex - 1 < 0) { + return -1 + } + return getReadLength(pageIndex - 1) + } + /** * 获取内容 */ @@ -116,6 +160,27 @@ data class TextChapter( return stringBuilder.substring(startPos).toString() } + fun getParagraphNum( + position: Int, + pageSplit: Boolean, + ): Int { + val paragraphs = if (pageSplit) { + pageParagraphs + } else { + paragraphs + } + paragraphs.forEach { paragraph -> + if (position in paragraph.chapterIndices) { + return paragraph.num + } + } + return -1 + } + + fun getLastParagraphPosition(): Int { + return pageParagraphs.last().chapterPosition + } + /** * @return 根据索引位置获取所在页 */ diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index e96cfaea1..1cb279ca1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -18,6 +18,9 @@ data class TextLine( var lineBase: Float = 0f, var lineBottom: Float = 0f, var indentWidth: Float = 0f, + var paragraphNum: Int = 0, + var chapterPosition: Int = 0, + var pagePosition: Int = 0, val isTitle: Boolean = false, var isParagraphEnd: Boolean = false, var isReadAloud: Boolean = false, @@ -28,6 +31,7 @@ data class TextLine( val charSize: Int get() = textColumns.size val lineStart: Float get() = textColumns.firstOrNull()?.start ?: 0f val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f + val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize fun addColumn(column: BaseColumn) { textColumns.add(column) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index f28bcc4ec..79fcc5544 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -36,6 +36,23 @@ data class TextPage( val searchResult = hashSetOf() var isMsgPage: Boolean = false + val paragraphs by lazy { + paragraphsInternal + } + + val paragraphsInternal: ArrayList get() { + val paragraphs = arrayListOf() + val lines = textLines.filter { it.paragraphNum > 0 } + val offset = lines.first().paragraphNum - 1 + lines.forEach { line -> + if (paragraphs.lastIndex < line.paragraphNum - offset - 1) { + paragraphs.add(TextParagraph(0)) + } + paragraphs[line.paragraphNum - offset - 1].textLines.add(line) + } + return paragraphs + } + fun addLine(line: TextLine) { textLines.add(line) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextParagraph.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextParagraph.kt new file mode 100644 index 000000000..d338016b4 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextParagraph.kt @@ -0,0 +1,18 @@ +package io.legado.app.ui.book.read.page.entities + +@Suppress("unused") +data class TextParagraph( + var num: Int, + val textLines: ArrayList = arrayListOf(), +) : Iterable { + val text: String get() = textLines.joinToString("") { it.text } + val length: Int get() = text.length + val chapterIndices: IntRange get() = first().chapterPosition..last().chapterPosition + last().charSize + val chapterPosition: Int get() = first().chapterPosition + val realNum: Int get() = first().paragraphNum + val isParagraphEnd: Boolean get() = last().isParagraphEnd + + override fun iterator(): Iterator { + return textLines.iterator() + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index b1350be7e..120b2e654 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -417,10 +417,23 @@ object ChapterProvider { } } } + val sbLength = stringBuilder.length stringBuilder.append(words) if (textLine.isParagraphEnd) { stringBuilder.append("\n") } + val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 } + ?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 } + val paragraphNum = when { + lastLine == null -> 1 + lastLine.isParagraphEnd -> lastLine.paragraphNum + 1 + else -> lastLine.paragraphNum + } + textLine.paragraphNum = paragraphNum + textLine.chapterPosition = textPages.foldIndexed(sbLength) { index, acc, textPage -> + acc + if (index == textPages.lastIndex) 0 else textPage.text.length + } + textLine.pagePosition = sbLength textPages.last().addLine(textLine) textLine.upTopBottom(durY, textPaint) durY += textPaint.textHeight * lineSpacingExtra