同步上游

This commit is contained in:
HapeLee
2025-11-15 17:11:06 +08:00
parent f42777d02a
commit 146b59dc96
28 changed files with 190 additions and 82 deletions
+2
View File
@@ -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()
@@ -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("(第)(.+?)(章)")
@@ -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<BookProgress>(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<BookProgress>(json).getOrNull()
}
}.onFailure {
coroutineContext.ensureActive()
AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it)
}
}.onFailure {
currentCoroutineContext().ensureActive()
AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it)
}
return null
}
@@ -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()
}
// 根据当前日期计算章节总数
@@ -77,7 +77,7 @@ object Backup {
"backup${backupDate}-${deviceName}.zip"
} else {
"backup${backupDate}.zip"
}
}.normalizeFileName()
}
private fun shouldBackup(): Boolean {
@@ -142,7 +142,7 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<Ale
}
override fun build(): AlertDialog {
var dialog = builder.create()
val dialog = builder.create()
if (AppConfig.isEInkMode) {
dialog.window?.run {
val attr = attributes
@@ -156,7 +156,7 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<Ale
}
override fun show(): AlertDialog {
var dialog = builder.show()
val dialog = builder.show()
if (AppConfig.isEInkMode) {
dialog.window?.run {
val attr = attributes
@@ -34,7 +34,9 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.SupervisorJob
@@ -44,9 +46,13 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import splitties.init.appCtx
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.max
import kotlin.math.min
@@ -69,6 +75,10 @@ object ReadBook : CoroutineScope by MainScope() {
var msg: String? = null
private val loadingChapters = arrayListOf<Int>()
private val readRecord = ReadRecord()
private val chapterLoadingJobs = ConcurrentHashMap<Int, Coroutine<*>>()
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()
}
@@ -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()
@@ -172,6 +172,7 @@ class ExportBookService : BaseService() {
val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let {
notificationContentText = "导出完成"
upExportNotification(true)
stopSelf()
return@launch
}
exportProgress[bookUrl] = 0
@@ -171,7 +171,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
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<ActivityCacheBookBinding, CacheViewModel>()
}
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<ActivityCacheBookBinding, CacheViewModel>()
}
}
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) {
@@ -187,6 +187,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
binding.llRetry.isGone = true
ReadManga.loadOrUpContent()
}
binding.flLoading.isVisible = !AppConfig.isEInkMode
mAdapter.addFooterView {
ViewLoadMoreBinding.bind(loadMoreView)
}
@@ -313,10 +314,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Looper.myQueue().addIdleHandler {
// viewModel.initData(intent)
// false
// }
viewModel.initData(intent)
justInitData = true
}
@@ -19,7 +19,7 @@ import java.security.MessageDigest
* 仅当applyBinarization为true时有效。
*/
class EpaperTransformation(
@IntRange(0, 255) private val threshold: Int = 128,
@param:IntRange(0, 255) private val threshold: Int = 128,
) : BitmapTransformation() {
private val ID get() = "io.legado.app.model.EpaperTransformation.$threshold"
@@ -671,7 +671,7 @@ class ReadBookActivity : BaseReadBookActivity(),
}
R.id.menu_cover_progress -> 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()
}
@@ -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)?) {
@@ -459,7 +459,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
return visiblePage
}
fun getCurVisibleFirstLine(): TextLine? {
fun getReadAloudPos(): Pair<Int, TextLine>? {
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
}
}
}
@@ -496,8 +496,8 @@ class PageView(context: Context) : FrameLayout(context) {
return binding.contentTextView.getCurVisiblePage()
}
fun getCurVisibleFirstLine(): TextLine? {
return binding.contentTextView.getCurVisibleFirstLine()
fun getReadAloudPos(): Pair<Int, TextLine>? {
return binding.contentTextView.getReadAloudPos()
}
fun markAsMainView() {
@@ -673,8 +673,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
return curPage.getCurVisiblePage()
}
fun getCurVisibleFirstLine(): TextLine? {
return curPage.getCurVisibleFirstLine()
fun getReadAloudPos(): Pair<Int, TextLine>? {
return curPage.getReadAloudPos()
}
fun invalidateTextPage() {
@@ -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()
@@ -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 {
@@ -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 ->
@@ -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()
@@ -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
}
@@ -140,3 +140,7 @@ fun String.escapeRegex(): String {
}
fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this)
fun String.normalizeFileName(): String {
return replace(AppPattern.fileNameRegex2, "_")
}