diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 89242e806..b7814e36e 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -36,6 +36,7 @@ import io.legado.app.help.AppFreezeMonitor import io.legado.app.help.AppWebDav import io.legado.app.help.CrashHandler import io.legado.app.help.DefaultData +import io.legado.app.help.DispatchersMonitor import io.legado.app.help.LifecycleHelp import io.legado.app.help.RuleBigDataHelp import io.legado.app.help.book.BookHelp @@ -129,6 +130,7 @@ class App : Application() { .setLogger(EventLogger()) DefaultData.upVersion() AppFreezeMonitor.init(this@App) + DispatchersMonitor.init() URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient)) launch { installGmsTlsProvider(appCtx) } initRhino() diff --git a/app/src/main/java/io/legado/app/constant/AppPattern.kt b/app/src/main/java/io/legado/app/constant/AppPattern.kt index ee3b8920b..c028fe350 100644 --- a/app/src/main/java/io/legado/app/constant/AppPattern.kt +++ b/app/src/main/java/io/legado/app/constant/AppPattern.kt @@ -29,6 +29,7 @@ object AppPattern { val nameRegex = Regex("\\s+作\\s*者.*|\\s+\\S+\\s+著") val authorRegex = Regex("^\\s*作\\s*者[::\\s]+|\\s+著") val fileNameRegex = Regex("[\\\\/:*?\"<>|.]") + val fileNameRegex2 = Regex("[\\\\/:*?\"<>|]") val splitGroupRegex = Regex("[,;,;]") val titleNumPattern: Pattern = Pattern.compile("(第)(.+?)(章)") diff --git a/app/src/main/java/io/legado/app/help/AppWebDav.kt b/app/src/main/java/io/legado/app/help/AppWebDav.kt index ad7043b3b..2aa968c7e 100644 --- a/app/src/main/java/io/legado/app/help/AppWebDav.kt +++ b/app/src/main/java/io/legado/app/help/AppWebDav.kt @@ -25,7 +25,9 @@ import io.legado.app.utils.compress.ZipUtils import io.legado.app.utils.fromJsonObject import io.legado.app.utils.getPrefString import io.legado.app.utils.isJson +import io.legado.app.utils.normalizeFileName import io.legado.app.utils.toastOnUi +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.runBlocking import splitties.init.appCtx @@ -243,7 +245,7 @@ object AppWebDav { WebDav(putUrl, it).upload(byteArray, "text/plain") } } catch (e: Exception) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() AppLog.put("WebDav导出失败\n${e.localizedMessage}", e, true) } } @@ -257,12 +259,16 @@ object AppWebDav { WebDav(putUrl, it).upload(uri, "text/plain") } } catch (e: Exception) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() AppLog.put("WebDav导出失败\n${e.localizedMessage}", e, true) } } - suspend fun uploadBookProgress(book: Book) { + suspend fun uploadBookProgress( + book: Book, + toast: Boolean = false, + onSuccess: (() -> Unit)? = null + ) { val authorization = authorization ?: return if (!AppConfig.syncBookProgress) return if (!NetworkUtils.isAvailable()) return @@ -272,9 +278,10 @@ object AppWebDav { val url = getProgressUrl(book.name, book.author) WebDav(url, authorization).upload(json.toByteArray(), "application/json") book.syncTime = System.currentTimeMillis() + onSuccess?.invoke() } catch (e: Exception) { - coroutineContext.ensureActive() - AppLog.put("上传进度失败\n${e.localizedMessage}", e) + currentCoroutineContext().ensureActive() + AppLog.put("上传进度失败\n${e.localizedMessage}", e, toast) } } @@ -288,7 +295,7 @@ object AppWebDav { WebDav(url, authorization).upload(json.toByteArray(), "application/json") onSuccess?.invoke() } catch (e: Exception) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() AppLog.put("上传进度失败\n${e.localizedMessage}", e) } } @@ -298,26 +305,29 @@ object AppWebDav { } private fun getProgressFileName(name: String, author: String): String { - return UrlUtil.replaceReservedChar("${name}_${author}") + ".json" + return UrlUtil.replaceReservedChar("${name}_${author}".normalizeFileName()) + ".json" } /** * 获取书籍进度 */ suspend fun getBookProgress(book: Book): BookProgress? { - authorization?.let { - val url = getProgressUrl(book.name, book.author) - kotlin.runCatching { - WebDav(url, it).download().let { byteArray -> - val json = String(byteArray) - if (json.isJson()) { - return GSON.fromJsonObject(json).getOrNull() - } + val url = getProgressUrl(book.name, book.author) + kotlin.runCatching { + val authorization = authorization ?: return null + WebDav(url, authorization).download().let { byteArray -> + val json = String(byteArray) + if (json.isJson()) { + return GSON.fromJsonObject(json).getOrNull() + } - }.onFailure { - coroutineContext.ensureActive() - AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it) + + + } + }.onFailure { + currentCoroutineContext().ensureActive() + AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it) } return null } diff --git a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt index d8d0a9901..47d088651 100644 --- a/app/src/main/java/io/legado/app/help/book/BookExtensions.kt +++ b/app/src/main/java/io/legado/app/help/book/BookExtensions.kt @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap import kotlin.math.max import kotlin.math.min import androidx.core.net.toUri +import io.legado.app.utils.normalizeFileName val Book.isAudio: Boolean @@ -376,7 +377,7 @@ fun Book.getExportFileName( RhinoScriptEngine.eval(jsStr, bindings).toString() + "." + suffix }.onFailure { AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it) - }.getOrDefault(default) + }.getOrDefault(default).normalizeFileName() } // 根据当前日期计算章节总数 diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index a60c6a231..cc1d92a28 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -77,7 +77,7 @@ object Backup { "backup${backupDate}-${deviceName}.zip" } else { "backup${backupDate}.zip" - } + }.normalizeFileName() } private fun shouldBackup(): Boolean { diff --git a/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt b/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt index 9febacd04..9a77ea035 100644 --- a/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt +++ b/app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt @@ -142,7 +142,7 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder() private val readRecord = ReadRecord() + private val chapterLoadingJobs = ConcurrentHashMap>() + private val prevChapterLoadingLock = Mutex() + private val curChapterLoadingLock = Mutex() + private val nextChapterLoadingLock = Mutex() var readStartTime: Long = System.currentTimeMillis() /* 跳转进度前进度记录 */ @@ -210,9 +220,7 @@ object ReadBook : CoroutineScope by MainScope() { } fun clearTextChapter() { - prevTextChapter?.cancelLayout() - curTextChapter?.cancelLayout() - nextTextChapter?.cancelLayout() + clearExpiredChapterLoadingJob(true) prevTextChapter = null curTextChapter = null nextTextChapter = null @@ -224,13 +232,14 @@ object ReadBook : CoroutineScope by MainScope() { nextTextChapter?.clearSearchResult() } - fun uploadProgress(successAction: (() -> Unit)? = null) { + fun uploadProgress(toast: Boolean = false, successAction: (() -> Unit)? = null) { book?.let { launch(IO) { - AppWebDav.uploadBookProgress(it) + AppWebDav.uploadBookProgress(it, toast) { + successAction?.invoke() + } ensureActive() it.update() - successAction?.invoke() } } } @@ -324,7 +333,7 @@ object ReadBook : CoroutineScope by MainScope() { if (durChapterIndex < simulatedChapterSize - 1) { durChapterPos = 0 durChapterIndex++ - prevTextChapter?.cancelLayout() + clearExpiredChapterLoadingJob() prevTextChapter = curTextChapter curTextChapter = nextTextChapter nextTextChapter = null @@ -355,7 +364,7 @@ object ReadBook : CoroutineScope by MainScope() { if (durChapterIndex < simulatedChapterSize - 1) { durChapterPos = 0 durChapterIndex++ - prevTextChapter?.cancelLayout() + clearExpiredChapterLoadingJob() prevTextChapter = curTextChapter curTextChapter = nextTextChapter nextTextChapter = null @@ -387,7 +396,7 @@ object ReadBook : CoroutineScope by MainScope() { if (durChapterIndex > 0) { durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0 durChapterIndex-- - nextTextChapter?.cancelLayout() + clearExpiredChapterLoadingJob() nextTextChapter = curTextChapter curTextChapter = prevTextChapter prevTextChapter = null @@ -438,10 +447,15 @@ object ReadBook : CoroutineScope by MainScope() { } } - fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) { + fun openChapter( + index: Int, + durChapterPos: Int = 0, + upContent: Boolean = true, + success: (() -> Unit)? = null + ) { if (index < chapterSize) { clearTextChapter() - callBack?.upContent() + if (upContent) callBack?.upContent() durChapterIndex = index ReadBook.durChapterPos = durChapterPos saveRead() @@ -671,6 +685,7 @@ object ReadBook : CoroutineScope by MainScope() { /** * 内容加载完成 */ + @Synchronized fun contentLoadFinish( book: Book, chapter: BookChapter, @@ -684,7 +699,8 @@ object ReadBook : CoroutineScope by MainScope() { if (canceled || chapter.index !in durChapterIndex - 1..durChapterIndex + 1) { return } - Coroutine.async { + chapterLoadingJobs[chapter.index]?.cancel() + val job = Coroutine.async(this, start = CoroutineStart.LAZY) { val contentProcessor = ContentProcessor.get(book.name, book.origin) val displayTitle = chapter.getDisplayTitle( contentProcessor.getTitleReplaceRules(), @@ -692,13 +708,16 @@ object ReadBook : CoroutineScope by MainScope() { ) val contents = contentProcessor .getContent(book, chapter, content, includeTitle = false) + ensureActive() val textChapter = ChapterProvider.getTextChapterAsync( - this@ReadBook, book, chapter, displayTitle, contents, simulatedChapterSize + this, book, chapter, displayTitle, contents, simulatedChapterSize ) when (val offset = chapter.index - durChapterIndex) { - 0 -> { - curTextChapter?.cancelLayout() - curTextChapter = textChapter + 0 -> curChapterLoadingLock.withLock { + withContext(Main) { + ensureActive() + curTextChapter = textChapter + } callBack?.upMenuView() var available = false for (page in textChapter.layoutChannel) { @@ -721,16 +740,20 @@ object ReadBook : CoroutineScope by MainScope() { callBack?.contentLoadFinish() } - -1 -> { - prevTextChapter?.cancelLayout() - prevTextChapter = textChapter + -1 -> prevChapterLoadingLock.withLock { + withContext(Main) { + ensureActive() + prevTextChapter = textChapter + } textChapter.layoutChannel.receiveAsFlow().collect() if (upContent) callBack?.upContent(offset, resetPageOffset) } - 1 -> { - nextTextChapter?.cancelLayout() - nextTextChapter = textChapter + 1 -> nextChapterLoadingLock.withLock { + withContext(Main) { + ensureActive() + nextTextChapter = textChapter + } for (page in textChapter.layoutChannel) { if (page.index > 1) { continue @@ -742,11 +765,16 @@ object ReadBook : CoroutineScope by MainScope() { return@async }.onError { + if (it is CancellationException) { + return@onError + } AppLog.put("ChapterProvider ERROR", it) appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}") }.onSuccess { success?.invoke() } + chapterLoadingJobs[chapter.index] = job + job.start() } suspend fun contentLoadFinishAwait( @@ -774,7 +802,9 @@ object ReadBook : CoroutineScope by MainScope() { when (val offset = chapter.index - durChapterIndex) { 0 -> { curTextChapter?.cancelLayout() - curTextChapter = textChapter + withContext(Main) { + curTextChapter = textChapter + } callBack?.upMenuView() var available = false for (page in textChapter.layoutChannel) { @@ -799,14 +829,18 @@ object ReadBook : CoroutineScope by MainScope() { -1 -> { prevTextChapter?.cancelLayout() - prevTextChapter = textChapter + withContext(Main) { + prevTextChapter = textChapter + } textChapter.layoutChannel.receiveAsFlow().collect() if (upContent) callBack?.upContent(offset, resetPageOffset) } 1 -> { nextTextChapter?.cancelLayout() - nextTextChapter = textChapter + withContext(Main) { + nextTextChapter = textChapter + } for (page in textChapter.layoutChannel) { if (page.index > 1) { continue @@ -818,6 +852,9 @@ object ReadBook : CoroutineScope by MainScope() { return }.onFailure { + if (it is CancellationException) { + return@onFailure + } AppLog.put("ChapterProvider ERROR", it) appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}") } @@ -913,6 +950,17 @@ object ReadBook : CoroutineScope by MainScope() { } } + private fun clearExpiredChapterLoadingJob(clearAll: Boolean = false) { + val iterator = chapterLoadingJobs.iterator() + while (iterator.hasNext()) { + val (index, job) = iterator.next() + if (clearAll || index !in durChapterIndex - 1..durChapterIndex + 1) { + job.cancel() + iterator.remove() + } + } + } + fun cancelPreDownloadTask() { if (contentLoadFinish) { preDownloadTask?.cancel() @@ -956,6 +1004,7 @@ object ReadBook : CoroutineScope by MainScope() { downloadScope.coroutineContext.cancelChildren() coroutineContext.cancelChildren() ImageProvider.clear() + clearExpiredChapterLoadingJob(true) if (!CacheBookService.isRun) { CacheBook.close() } diff --git a/app/src/main/java/io/legado/app/model/ReadManga.kt b/app/src/main/java/io/legado/app/model/ReadManga.kt index f017c489c..1f0940184 100644 --- a/app/src/main/java/io/legado/app/model/ReadManga.kt +++ b/app/src/main/java/io/legado/app/model/ReadManga.kt @@ -482,7 +482,9 @@ object ReadManga : CoroutineScope by MainScope() { fun uploadProgress(successAction: (() -> Unit)? = null) { book?.let { launch(IO) { - AppWebDav.uploadBookProgress(it) + AppWebDav.uploadBookProgress(it) { + successAction?.invoke() + } ensureActive() it.update() successAction?.invoke() diff --git a/app/src/main/java/io/legado/app/service/ExportBookService.kt b/app/src/main/java/io/legado/app/service/ExportBookService.kt index 20439e327..c94873036 100644 --- a/app/src/main/java/io/legado/app/service/ExportBookService.kt +++ b/app/src/main/java/io/legado/app/service/ExportBookService.kt @@ -172,6 +172,7 @@ class ExportBookService : BaseService() { val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let { notificationContentText = "导出完成" upExportNotification(true) + stopSelf() return@launch } exportProgress[bookUrl] = 0 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 da6bbd86c..77297d67a 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 @@ -171,7 +171,7 @@ class CacheActivity : VMBaseActivity() when (item.itemId) { R.id.menu_download, R.id.menu_download_after -> { - if (!CacheBook.isRun) { + if (!CacheBook.isRun) sureCacheBook { adapter.getItems().forEach { book -> val indices = (book.durChapterIndex..book.lastChapterIndex).toList() CacheBook.start(this@CacheActivity, book, indices) @@ -182,7 +182,7 @@ class CacheActivity : VMBaseActivity() } R.id.menu_download_all -> { - if (!CacheBook.isRun) { + if (!CacheBook.isRun) sureCacheBook { adapter.getItems().forEach { book -> val indices = (0..book.lastChapterIndex).toList() CacheBook.start(this@CacheActivity, book, indices) @@ -546,7 +546,15 @@ class CacheActivity : VMBaseActivity() } } - + private fun sureCacheBook(action: () -> Unit) { + alert(R.string.draw) { + setMessage(R.string.sure_cache_book) + noButton() + yesButton { + action.invoke() + } + } + } private fun getTypeName(): String { return exportTypes.getOrElse(AppConfig.exportType) { diff --git a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt index cd3cfb35f..c2a02230a 100644 --- a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt @@ -187,6 +187,7 @@ class ReadMangaActivity : VMBaseActivity ReadBook.book?.let { - ReadBook.uploadProgress { toastOnUi(R.string.upload_book_success) } + ReadBook.uploadProgress(true) { toastOnUi(R.string.upload_book_success) } } R.id.menu_same_title_removed -> { @@ -1415,10 +1415,17 @@ class ReadBookActivity : BaseReadBookActivity(), ReadAloud.upReadAloudClass() val scrollPageAnim = ReadBook.pageAnim() == 3 if (scrollPageAnim) { - val line = binding.readView.getCurVisibleFirstLine() - if (line != null) { - ReadBook.durChapterPos = line.chapterPosition - ReadBook.readAloud(startPos = line.pagePosition) + val pos = binding.readView.getReadAloudPos() + if (pos != null) { + val (index, line) = pos + if (ReadBook.durChapterIndex != index) { + ReadBook.openChapter(index, line.chapterPosition, false) { + ReadBook.readAloud(startPos = line.pagePosition) + } + } else { + ReadBook.durChapterPos = line.chapterPosition + ReadBook.readAloud(startPos = line.pagePosition) + } } else { ReadBook.readAloud() } @@ -1431,10 +1438,17 @@ class ReadBookActivity : BaseReadBookActivity(), val scrollPageAnim = ReadBook.pageAnim() == 3 if (scrollPageAnim && pageChanged) { pageChanged = false - val line = binding.readView.getCurVisibleFirstLine() - if (line != null) { - ReadBook.durChapterPos = line.chapterPosition - ReadBook.readAloud(startPos = line.pagePosition) + val pos = binding.readView.getReadAloudPos() + if (pos != null) { + val (index, line) = pos + if (ReadBook.durChapterIndex != index) { + ReadBook.openChapter(index, line.chapterPosition, false) { + ReadBook.readAloud(startPos = line.pagePosition) + } + } else { + ReadBook.durChapterPos = line.chapterPosition + ReadBook.readAloud(startPos = line.pagePosition) + } } else { ReadBook.readAloud() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 3da25f3de..fdf88560c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -321,7 +321,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) { - ReadBook.openChapter(index, durChapterPos, success) + ReadBook.openChapter(index, durChapterPos, success = success) } fun removeFromBookshelf(success: (() -> Unit)?) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index fa02606ef..f8ec4d0bc 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -459,7 +459,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at return visiblePage } - fun getCurVisibleFirstLine(): TextLine? { + fun getReadAloudPos(): Pair? { var relativeOffset: Float for (relativePos in 0..2) { relativeOffset = relativeOffset(relativePos) @@ -477,7 +477,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at lineTop += relativeOffset lineBottom += relativeOffset } - return visibleLine + return textPage.chapterIndex to visibleLine } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 5f7853784..cd43549f3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -496,8 +496,8 @@ class PageView(context: Context) : FrameLayout(context) { return binding.contentTextView.getCurVisiblePage() } - fun getCurVisibleFirstLine(): TextLine? { - return binding.contentTextView.getCurVisibleFirstLine() + fun getReadAloudPos(): Pair? { + return binding.contentTextView.getReadAloudPos() } fun markAsMainView() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index b0b55f9fb..f3ccb1845 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -673,8 +673,8 @@ class ReadView(context: Context, attrs: AttributeSet) : return curPage.getCurVisiblePage() } - fun getCurVisibleFirstLine(): TextLine? { - return curPage.getCurVisibleFirstLine() + fun getReadAloudPos(): Pair? { + return curPage.getReadAloudPos() } fun invalidateTextPage() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt index d750aefc2..5a4d5936e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import java.util.LinkedList @@ -120,6 +121,8 @@ class TextChapterLayout( }.onError { exception = it onException(it) + }.onCancel { + channel.cancel() }.onFinally { isCompleted = true } @@ -304,7 +307,7 @@ class TextChapterLayout( var isSetTypedImage = false var wordCount = 0 contents.forEach { content -> - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() if (isTextImageStyle) { //图片样式为文字嵌入类型 var text = content.replace(ChapterProvider.srcReplaceChar, "丨") //▣ @@ -342,7 +345,7 @@ class TextChapterLayout( val text = content.replace(ChapterProvider.srcReplaceChar, "丨") val matcher = AppPattern.imgPattern.matcher(text) while (matcher.find()) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() val imgSrc = matcher.group(1)!! var iStyle = imageStyle var isSmallImage = true @@ -450,7 +453,7 @@ class TextChapterLayout( textPage.height += endPadding } textPage.text = stringBuilder.toString() - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() onPageCompleted() onCompleted() } @@ -915,7 +918,7 @@ class TextChapterLayout( textPage.leftLineSize = textPage.lineSize } textPage.text = stringBuilder.toString() - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() onPageCompleted() //新建页面 pendingTextPage = TextPage() diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index a72ac23f9..c784dcd9c 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -20,6 +20,7 @@ import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.help.AppFreezeMonitor +import io.legado.app.help.DispatchersMonitor import io.legado.app.help.config.AppConfig import io.legado.app.help.config.LocalConfig import io.legado.app.lib.dialogs.alert @@ -203,6 +204,7 @@ class OtherConfigFragment : PreferenceFragment(), LogUtils.logDeviceInfo() LiveEventBus.config().enableLogger(AppConfig.recordLog) AppFreezeMonitor.init(appCtx) + DispatchersMonitor.init() } PreferKey.processText -> sharedPreferences?.let { diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index b7ac2e888..16d833abd 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -29,7 +29,9 @@ import io.legado.app.utils.onEachParallel import io.legado.app.utils.postEvent import kotlinx.coroutines.Job import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow @@ -175,6 +177,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) { ReadBook.onChapterListUpdated(book) addDownload(source, book) }.onFailure { + currentCoroutineContext().ensureActive() AppLog.put("${book.name} 更新目录失败\n${it.localizedMessage}", it) //这里可能因为时间太长书籍信息已经更改,所以重新获取 appDb.bookDao.getBook(book.bookUrl)?.let { book -> diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt index 6ea77a6e8..60ee2de36 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt @@ -201,7 +201,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books), booksFlowJob?.cancel() booksFlowJob = viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { appDb.bookDao.flowByGroup(groupId) .map { list -> @@ -250,7 +250,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books), if (!AppConfig.showLastUpdateTime || (bookshelfLayoutMode != 0 && bookshelfLayoutMode != 4)) { return } - upLastUpdateTimeJob = lifecycleScope.launch { + upLastUpdateTimeJob = viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { while (isActive) { booksAdapter.upLastUpdateTime() diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt index 8d9c2b6a3..ed55f384d 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt @@ -3,6 +3,7 @@ package io.legado.app.ui.widget.recycler import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import io.legado.app.R @@ -26,7 +27,7 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout( init { super.setOnClickListener { - if (!showErrorDialog()) { + if (!showErrorDialog(it)) { onClickListener?.onClick(it) } } @@ -38,7 +39,7 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout( override fun onAttachedToWindow() { super.onAttachedToWindow() - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT + layoutParams.width = LayoutParams.MATCH_PARENT } fun startLoad() { @@ -79,12 +80,17 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout( binding.tvText.visible() } - private fun showErrorDialog(): Boolean { + private fun showErrorDialog(view: View): Boolean { if (errorMsg.isBlank()) { return false } context.alert(R.string.error) { setMessage(errorMsg) + if (onClickListener != null) { + neutralButton(R.string.retry) { + onClickListener?.onClick(view) + } + } } return true } diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index 483ef96e3..5de0b9086 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -140,3 +140,7 @@ fun String.escapeRegex(): String { } fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this) + +fun String.normalizeFileName(): String { + return replace(AppPattern.fileNameRegex2, "_") +} diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index bc2316967..b51599bc6 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1211,4 +1211,5 @@ 隐藏漫画列表标题 刷新发现 填充刘海区域 + 是否确认开始缓存? diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5d7e73daf..7764a0571 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1213,4 +1213,5 @@ 隐藏漫画列表标题 刷新发现 填充刘海区域 + 是否确认开始缓存? diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 9a6b35f78..fa3921be0 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1345,4 +1345,5 @@ 空隙比例 线段高度 菜单标题显示方式 + 是否确认开始缓存? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9eb4fb986..37244a8fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1346,4 +1346,5 @@ 空隙比例 线段高度 菜单标题显示方式 + 是否确认开始缓存? diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4ea51bd1..a3c9f9560 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -169,7 +169,7 @@ liveeventbus = { module = "com.github.michaellee123:LiveEventBus", version.ref = material = { module = "com.google.android.material:material", version.ref = "material" } mozilla-rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } - +renderscript-intrinsics-replacement-toolkit = { module = "com.github.TomasValenta:renderscript-intrinsics-replacement-toolkit", version = "8eaa829ddd" } zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" } [bundles]