同步上游
This commit is contained in:
@@ -36,6 +36,7 @@ import io.legado.app.help.AppFreezeMonitor
|
|||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.help.AppWebDav
|
||||||
import io.legado.app.help.CrashHandler
|
import io.legado.app.help.CrashHandler
|
||||||
import io.legado.app.help.DefaultData
|
import io.legado.app.help.DefaultData
|
||||||
|
import io.legado.app.help.DispatchersMonitor
|
||||||
import io.legado.app.help.LifecycleHelp
|
import io.legado.app.help.LifecycleHelp
|
||||||
import io.legado.app.help.RuleBigDataHelp
|
import io.legado.app.help.RuleBigDataHelp
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
@@ -129,6 +130,7 @@ class App : Application() {
|
|||||||
.setLogger(EventLogger())
|
.setLogger(EventLogger())
|
||||||
DefaultData.upVersion()
|
DefaultData.upVersion()
|
||||||
AppFreezeMonitor.init(this@App)
|
AppFreezeMonitor.init(this@App)
|
||||||
|
DispatchersMonitor.init()
|
||||||
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
|
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
|
||||||
launch { installGmsTlsProvider(appCtx) }
|
launch { installGmsTlsProvider(appCtx) }
|
||||||
initRhino()
|
initRhino()
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ object AppPattern {
|
|||||||
val nameRegex = Regex("\\s+作\\s*者.*|\\s+\\S+\\s+著")
|
val nameRegex = Regex("\\s+作\\s*者.*|\\s+\\S+\\s+著")
|
||||||
val authorRegex = Regex("^\\s*作\\s*者[::\\s]+|\\s+著")
|
val authorRegex = Regex("^\\s*作\\s*者[::\\s]+|\\s+著")
|
||||||
val fileNameRegex = Regex("[\\\\/:*?\"<>|.]")
|
val fileNameRegex = Regex("[\\\\/:*?\"<>|.]")
|
||||||
|
val fileNameRegex2 = Regex("[\\\\/:*?\"<>|]")
|
||||||
val splitGroupRegex = Regex("[,;,;]")
|
val splitGroupRegex = Regex("[,;,;]")
|
||||||
val titleNumPattern: Pattern = Pattern.compile("(第)(.+?)(章)")
|
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.fromJsonObject
|
||||||
import io.legado.app.utils.getPrefString
|
import io.legado.app.utils.getPrefString
|
||||||
import io.legado.app.utils.isJson
|
import io.legado.app.utils.isJson
|
||||||
|
import io.legado.app.utils.normalizeFileName
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
@@ -243,7 +245,7 @@ object AppWebDav {
|
|||||||
WebDav(putUrl, it).upload(byteArray, "text/plain")
|
WebDav(putUrl, it).upload(byteArray, "text/plain")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("WebDav导出失败\n${e.localizedMessage}", e, true)
|
AppLog.put("WebDav导出失败\n${e.localizedMessage}", e, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,12 +259,16 @@ object AppWebDav {
|
|||||||
WebDav(putUrl, it).upload(uri, "text/plain")
|
WebDav(putUrl, it).upload(uri, "text/plain")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("WebDav导出失败\n${e.localizedMessage}", e, true)
|
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
|
val authorization = authorization ?: return
|
||||||
if (!AppConfig.syncBookProgress) return
|
if (!AppConfig.syncBookProgress) return
|
||||||
if (!NetworkUtils.isAvailable()) return
|
if (!NetworkUtils.isAvailable()) return
|
||||||
@@ -272,9 +278,10 @@ object AppWebDav {
|
|||||||
val url = getProgressUrl(book.name, book.author)
|
val url = getProgressUrl(book.name, book.author)
|
||||||
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
||||||
book.syncTime = System.currentTimeMillis()
|
book.syncTime = System.currentTimeMillis()
|
||||||
|
onSuccess?.invoke()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("上传进度失败\n${e.localizedMessage}", e)
|
AppLog.put("上传进度失败\n${e.localizedMessage}", e, toast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +295,7 @@ object AppWebDav {
|
|||||||
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
||||||
onSuccess?.invoke()
|
onSuccess?.invoke()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("上传进度失败\n${e.localizedMessage}", e)
|
AppLog.put("上传进度失败\n${e.localizedMessage}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,27 +305,30 @@ object AppWebDav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getProgressFileName(name: String, author: String): String {
|
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? {
|
suspend fun getBookProgress(book: Book): BookProgress? {
|
||||||
authorization?.let {
|
|
||||||
val url = getProgressUrl(book.name, book.author)
|
val url = getProgressUrl(book.name, book.author)
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
WebDav(url, it).download().let { byteArray ->
|
val authorization = authorization ?: return null
|
||||||
|
WebDav(url, authorization).download().let { byteArray ->
|
||||||
val json = String(byteArray)
|
val json = String(byteArray)
|
||||||
if (json.isJson()) {
|
if (json.isJson()) {
|
||||||
return GSON.fromJsonObject<BookProgress>(json).getOrNull()
|
return GSON.fromJsonObject<BookProgress>(json).getOrNull()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it)
|
AppLog.put("获取书籍进度失败\n${it.localizedMessage}", it)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import io.legado.app.utils.normalizeFileName
|
||||||
|
|
||||||
|
|
||||||
val Book.isAudio: Boolean
|
val Book.isAudio: Boolean
|
||||||
@@ -376,7 +377,7 @@ fun Book.getExportFileName(
|
|||||||
RhinoScriptEngine.eval(jsStr, bindings).toString() + "." + suffix
|
RhinoScriptEngine.eval(jsStr, bindings).toString() + "." + suffix
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it)
|
AppLog.put("导出书名规则错误,使用默认规则\n${it.localizedMessage}", it)
|
||||||
}.getOrDefault(default)
|
}.getOrDefault(default).normalizeFileName()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据当前日期计算章节总数
|
// 根据当前日期计算章节总数
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ object Backup {
|
|||||||
"backup${backupDate}-${deviceName}.zip"
|
"backup${backupDate}-${deviceName}.zip"
|
||||||
} else {
|
} else {
|
||||||
"backup${backupDate}.zip"
|
"backup${backupDate}.zip"
|
||||||
}
|
}.normalizeFileName()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldBackup(): Boolean {
|
private fun shouldBackup(): Boolean {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<Ale
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun build(): AlertDialog {
|
override fun build(): AlertDialog {
|
||||||
var dialog = builder.create()
|
val dialog = builder.create()
|
||||||
if (AppConfig.isEInkMode) {
|
if (AppConfig.isEInkMode) {
|
||||||
dialog.window?.run {
|
dialog.window?.run {
|
||||||
val attr = attributes
|
val attr = attributes
|
||||||
@@ -156,7 +156,7 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<Ale
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun show(): AlertDialog {
|
override fun show(): AlertDialog {
|
||||||
var dialog = builder.show()
|
val dialog = builder.show()
|
||||||
if (AppConfig.isEInkMode) {
|
if (AppConfig.isEInkMode) {
|
||||||
dialog.window?.run {
|
dialog.window?.run {
|
||||||
val attr = attributes
|
val attr = attributes
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ import io.legado.app.utils.postEvent
|
|||||||
import io.legado.app.utils.stackTraceStr
|
import io.legado.app.utils.stackTraceStr
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@@ -44,9 +46,13 @@ import kotlinx.coroutines.ensureActive
|
|||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -69,6 +75,10 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
var msg: String? = null
|
var msg: String? = null
|
||||||
private val loadingChapters = arrayListOf<Int>()
|
private val loadingChapters = arrayListOf<Int>()
|
||||||
private val readRecord = ReadRecord()
|
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()
|
var readStartTime: Long = System.currentTimeMillis()
|
||||||
|
|
||||||
/* 跳转进度前进度记录 */
|
/* 跳转进度前进度记录 */
|
||||||
@@ -210,9 +220,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearTextChapter() {
|
fun clearTextChapter() {
|
||||||
prevTextChapter?.cancelLayout()
|
clearExpiredChapterLoadingJob(true)
|
||||||
curTextChapter?.cancelLayout()
|
|
||||||
nextTextChapter?.cancelLayout()
|
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
curTextChapter = null
|
curTextChapter = null
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -224,13 +232,14 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
nextTextChapter?.clearSearchResult()
|
nextTextChapter?.clearSearchResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uploadProgress(successAction: (() -> Unit)? = null) {
|
fun uploadProgress(toast: Boolean = false, successAction: (() -> Unit)? = null) {
|
||||||
book?.let {
|
book?.let {
|
||||||
launch(IO) {
|
launch(IO) {
|
||||||
AppWebDav.uploadBookProgress(it)
|
AppWebDav.uploadBookProgress(it, toast) {
|
||||||
|
successAction?.invoke()
|
||||||
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
it.update()
|
it.update()
|
||||||
successAction?.invoke()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +333,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < simulatedChapterSize - 1) {
|
if (durChapterIndex < simulatedChapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
prevTextChapter?.cancelLayout()
|
clearExpiredChapterLoadingJob()
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -355,7 +364,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < simulatedChapterSize - 1) {
|
if (durChapterIndex < simulatedChapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
prevTextChapter?.cancelLayout()
|
clearExpiredChapterLoadingJob()
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -387,7 +396,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex > 0) {
|
if (durChapterIndex > 0) {
|
||||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||||
durChapterIndex--
|
durChapterIndex--
|
||||||
nextTextChapter?.cancelLayout()
|
clearExpiredChapterLoadingJob()
|
||||||
nextTextChapter = curTextChapter
|
nextTextChapter = curTextChapter
|
||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
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) {
|
if (index < chapterSize) {
|
||||||
clearTextChapter()
|
clearTextChapter()
|
||||||
callBack?.upContent()
|
if (upContent) callBack?.upContent()
|
||||||
durChapterIndex = index
|
durChapterIndex = index
|
||||||
ReadBook.durChapterPos = durChapterPos
|
ReadBook.durChapterPos = durChapterPos
|
||||||
saveRead()
|
saveRead()
|
||||||
@@ -671,6 +685,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
/**
|
/**
|
||||||
* 内容加载完成
|
* 内容加载完成
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun contentLoadFinish(
|
fun contentLoadFinish(
|
||||||
book: Book,
|
book: Book,
|
||||||
chapter: BookChapter,
|
chapter: BookChapter,
|
||||||
@@ -684,7 +699,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (canceled || chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {
|
if (canceled || chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Coroutine.async {
|
chapterLoadingJobs[chapter.index]?.cancel()
|
||||||
|
val job = Coroutine.async(this, start = CoroutineStart.LAZY) {
|
||||||
val contentProcessor = ContentProcessor.get(book.name, book.origin)
|
val contentProcessor = ContentProcessor.get(book.name, book.origin)
|
||||||
val displayTitle = chapter.getDisplayTitle(
|
val displayTitle = chapter.getDisplayTitle(
|
||||||
contentProcessor.getTitleReplaceRules(),
|
contentProcessor.getTitleReplaceRules(),
|
||||||
@@ -692,13 +708,16 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
)
|
)
|
||||||
val contents = contentProcessor
|
val contents = contentProcessor
|
||||||
.getContent(book, chapter, content, includeTitle = false)
|
.getContent(book, chapter, content, includeTitle = false)
|
||||||
|
ensureActive()
|
||||||
val textChapter = ChapterProvider.getTextChapterAsync(
|
val textChapter = ChapterProvider.getTextChapterAsync(
|
||||||
this@ReadBook, book, chapter, displayTitle, contents, simulatedChapterSize
|
this, book, chapter, displayTitle, contents, simulatedChapterSize
|
||||||
)
|
)
|
||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> curChapterLoadingLock.withLock {
|
||||||
curTextChapter?.cancelLayout()
|
withContext(Main) {
|
||||||
|
ensureActive()
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
|
}
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
var available = false
|
var available = false
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
@@ -721,16 +740,20 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
callBack?.contentLoadFinish()
|
callBack?.contentLoadFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> prevChapterLoadingLock.withLock {
|
||||||
prevTextChapter?.cancelLayout()
|
withContext(Main) {
|
||||||
|
ensureActive()
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
|
}
|
||||||
textChapter.layoutChannel.receiveAsFlow().collect()
|
textChapter.layoutChannel.receiveAsFlow().collect()
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> nextChapterLoadingLock.withLock {
|
||||||
nextTextChapter?.cancelLayout()
|
withContext(Main) {
|
||||||
|
ensureActive()
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
|
}
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
if (page.index > 1) {
|
if (page.index > 1) {
|
||||||
continue
|
continue
|
||||||
@@ -742,11 +765,16 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
return@async
|
return@async
|
||||||
}.onError {
|
}.onError {
|
||||||
|
if (it is CancellationException) {
|
||||||
|
return@onError
|
||||||
|
}
|
||||||
AppLog.put("ChapterProvider ERROR", it)
|
AppLog.put("ChapterProvider ERROR", it)
|
||||||
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
|
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success?.invoke()
|
success?.invoke()
|
||||||
}
|
}
|
||||||
|
chapterLoadingJobs[chapter.index] = job
|
||||||
|
job.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun contentLoadFinishAwait(
|
suspend fun contentLoadFinishAwait(
|
||||||
@@ -774,7 +802,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
curTextChapter?.cancelLayout()
|
curTextChapter?.cancelLayout()
|
||||||
|
withContext(Main) {
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
|
}
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
var available = false
|
var available = false
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
@@ -799,14 +829,18 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
prevTextChapter?.cancelLayout()
|
prevTextChapter?.cancelLayout()
|
||||||
|
withContext(Main) {
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
|
}
|
||||||
textChapter.layoutChannel.receiveAsFlow().collect()
|
textChapter.layoutChannel.receiveAsFlow().collect()
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
nextTextChapter?.cancelLayout()
|
nextTextChapter?.cancelLayout()
|
||||||
|
withContext(Main) {
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
|
}
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
if (page.index > 1) {
|
if (page.index > 1) {
|
||||||
continue
|
continue
|
||||||
@@ -818,6 +852,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
if (it is CancellationException) {
|
||||||
|
return@onFailure
|
||||||
|
}
|
||||||
AppLog.put("ChapterProvider ERROR", it)
|
AppLog.put("ChapterProvider ERROR", it)
|
||||||
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
|
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() {
|
fun cancelPreDownloadTask() {
|
||||||
if (contentLoadFinish) {
|
if (contentLoadFinish) {
|
||||||
preDownloadTask?.cancel()
|
preDownloadTask?.cancel()
|
||||||
@@ -956,6 +1004,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
downloadScope.coroutineContext.cancelChildren()
|
downloadScope.coroutineContext.cancelChildren()
|
||||||
coroutineContext.cancelChildren()
|
coroutineContext.cancelChildren()
|
||||||
ImageProvider.clear()
|
ImageProvider.clear()
|
||||||
|
clearExpiredChapterLoadingJob(true)
|
||||||
if (!CacheBookService.isRun) {
|
if (!CacheBookService.isRun) {
|
||||||
CacheBook.close()
|
CacheBook.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,7 +482,9 @@ object ReadManga : CoroutineScope by MainScope() {
|
|||||||
fun uploadProgress(successAction: (() -> Unit)? = null) {
|
fun uploadProgress(successAction: (() -> Unit)? = null) {
|
||||||
book?.let {
|
book?.let {
|
||||||
launch(IO) {
|
launch(IO) {
|
||||||
AppWebDav.uploadBookProgress(it)
|
AppWebDav.uploadBookProgress(it) {
|
||||||
|
successAction?.invoke()
|
||||||
|
}
|
||||||
ensureActive()
|
ensureActive()
|
||||||
it.update()
|
it.update()
|
||||||
successAction?.invoke()
|
successAction?.invoke()
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ class ExportBookService : BaseService() {
|
|||||||
val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let {
|
val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let {
|
||||||
notificationContentText = "导出完成"
|
notificationContentText = "导出完成"
|
||||||
upExportNotification(true)
|
upExportNotification(true)
|
||||||
|
stopSelf()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
exportProgress[bookUrl] = 0
|
exportProgress[bookUrl] = 0
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
|||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_download,
|
R.id.menu_download,
|
||||||
R.id.menu_download_after -> {
|
R.id.menu_download_after -> {
|
||||||
if (!CacheBook.isRun) {
|
if (!CacheBook.isRun) sureCacheBook {
|
||||||
adapter.getItems().forEach { book ->
|
adapter.getItems().forEach { book ->
|
||||||
val indices = (book.durChapterIndex..book.lastChapterIndex).toList()
|
val indices = (book.durChapterIndex..book.lastChapterIndex).toList()
|
||||||
CacheBook.start(this@CacheActivity, book, indices)
|
CacheBook.start(this@CacheActivity, book, indices)
|
||||||
@@ -182,7 +182,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_download_all -> {
|
R.id.menu_download_all -> {
|
||||||
if (!CacheBook.isRun) {
|
if (!CacheBook.isRun) sureCacheBook {
|
||||||
adapter.getItems().forEach { book ->
|
adapter.getItems().forEach { book ->
|
||||||
val indices = (0..book.lastChapterIndex).toList()
|
val indices = (0..book.lastChapterIndex).toList()
|
||||||
CacheBook.start(this@CacheActivity, book, indices)
|
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 {
|
private fun getTypeName(): String {
|
||||||
return exportTypes.getOrElse(AppConfig.exportType) {
|
return exportTypes.getOrElse(AppConfig.exportType) {
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
binding.llRetry.isGone = true
|
binding.llRetry.isGone = true
|
||||||
ReadManga.loadOrUpContent()
|
ReadManga.loadOrUpContent()
|
||||||
}
|
}
|
||||||
|
binding.flLoading.isVisible = !AppConfig.isEInkMode
|
||||||
mAdapter.addFooterView {
|
mAdapter.addFooterView {
|
||||||
ViewLoadMoreBinding.bind(loadMoreView)
|
ViewLoadMoreBinding.bind(loadMoreView)
|
||||||
}
|
}
|
||||||
@@ -313,10 +314,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
|
|
||||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||||
super.onPostCreate(savedInstanceState)
|
super.onPostCreate(savedInstanceState)
|
||||||
// Looper.myQueue().addIdleHandler {
|
viewModel.initData(intent)
|
||||||
// viewModel.initData(intent)
|
|
||||||
// false
|
|
||||||
// }
|
|
||||||
justInitData = true
|
justInitData = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import java.security.MessageDigest
|
|||||||
* 仅当applyBinarization为true时有效。
|
* 仅当applyBinarization为true时有效。
|
||||||
*/
|
*/
|
||||||
class EpaperTransformation(
|
class EpaperTransformation(
|
||||||
@IntRange(0, 255) private val threshold: Int = 128,
|
@param:IntRange(0, 255) private val threshold: Int = 128,
|
||||||
) : BitmapTransformation() {
|
) : BitmapTransformation() {
|
||||||
|
|
||||||
private val ID get() = "io.legado.app.model.EpaperTransformation.$threshold"
|
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 {
|
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 -> {
|
R.id.menu_same_title_removed -> {
|
||||||
@@ -1415,10 +1415,17 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
ReadAloud.upReadAloudClass()
|
ReadAloud.upReadAloudClass()
|
||||||
val scrollPageAnim = ReadBook.pageAnim() == 3
|
val scrollPageAnim = ReadBook.pageAnim() == 3
|
||||||
if (scrollPageAnim) {
|
if (scrollPageAnim) {
|
||||||
val line = binding.readView.getCurVisibleFirstLine()
|
val pos = binding.readView.getReadAloudPos()
|
||||||
if (line != null) {
|
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.durChapterPos = line.chapterPosition
|
||||||
ReadBook.readAloud(startPos = line.pagePosition)
|
ReadBook.readAloud(startPos = line.pagePosition)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ReadBook.readAloud()
|
ReadBook.readAloud()
|
||||||
}
|
}
|
||||||
@@ -1431,10 +1438,17 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
val scrollPageAnim = ReadBook.pageAnim() == 3
|
val scrollPageAnim = ReadBook.pageAnim() == 3
|
||||||
if (scrollPageAnim && pageChanged) {
|
if (scrollPageAnim && pageChanged) {
|
||||||
pageChanged = false
|
pageChanged = false
|
||||||
val line = binding.readView.getCurVisibleFirstLine()
|
val pos = binding.readView.getReadAloudPos()
|
||||||
if (line != null) {
|
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.durChapterPos = line.chapterPosition
|
||||||
ReadBook.readAloud(startPos = line.pagePosition)
|
ReadBook.readAloud(startPos = line.pagePosition)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ReadBook.readAloud()
|
ReadBook.readAloud()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) {
|
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)?) {
|
fun removeFromBookshelf(success: (() -> Unit)?) {
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
return visiblePage
|
return visiblePage
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurVisibleFirstLine(): TextLine? {
|
fun getReadAloudPos(): Pair<Int, TextLine>? {
|
||||||
var relativeOffset: Float
|
var relativeOffset: Float
|
||||||
for (relativePos in 0..2) {
|
for (relativePos in 0..2) {
|
||||||
relativeOffset = relativeOffset(relativePos)
|
relativeOffset = relativeOffset(relativePos)
|
||||||
@@ -477,7 +477,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
lineTop += relativeOffset
|
lineTop += relativeOffset
|
||||||
lineBottom += relativeOffset
|
lineBottom += relativeOffset
|
||||||
}
|
}
|
||||||
return visibleLine
|
return textPage.chapterIndex to visibleLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,8 +496,8 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
return binding.contentTextView.getCurVisiblePage()
|
return binding.contentTextView.getCurVisiblePage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurVisibleFirstLine(): TextLine? {
|
fun getReadAloudPos(): Pair<Int, TextLine>? {
|
||||||
return binding.contentTextView.getCurVisibleFirstLine()
|
return binding.contentTextView.getReadAloudPos()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markAsMainView() {
|
fun markAsMainView() {
|
||||||
|
|||||||
@@ -673,8 +673,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
return curPage.getCurVisiblePage()
|
return curPage.getCurVisiblePage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurVisibleFirstLine(): TextLine? {
|
fun getReadAloudPos(): Pair<Int, TextLine>? {
|
||||||
return curPage.getCurVisibleFirstLine()
|
return curPage.getReadAloudPos()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidateTextPage() {
|
fun invalidateTextPage() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
@@ -120,6 +121,8 @@ class TextChapterLayout(
|
|||||||
}.onError {
|
}.onError {
|
||||||
exception = it
|
exception = it
|
||||||
onException(it)
|
onException(it)
|
||||||
|
}.onCancel {
|
||||||
|
channel.cancel()
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
isCompleted = true
|
isCompleted = true
|
||||||
}
|
}
|
||||||
@@ -304,7 +307,7 @@ class TextChapterLayout(
|
|||||||
var isSetTypedImage = false
|
var isSetTypedImage = false
|
||||||
var wordCount = 0
|
var wordCount = 0
|
||||||
contents.forEach { content ->
|
contents.forEach { content ->
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
if (isTextImageStyle) {
|
if (isTextImageStyle) {
|
||||||
//图片样式为文字嵌入类型
|
//图片样式为文字嵌入类型
|
||||||
var text = content.replace(ChapterProvider.srcReplaceChar, "丨") //▣
|
var text = content.replace(ChapterProvider.srcReplaceChar, "丨") //▣
|
||||||
@@ -342,7 +345,7 @@ class TextChapterLayout(
|
|||||||
val text = content.replace(ChapterProvider.srcReplaceChar, "丨")
|
val text = content.replace(ChapterProvider.srcReplaceChar, "丨")
|
||||||
val matcher = AppPattern.imgPattern.matcher(text)
|
val matcher = AppPattern.imgPattern.matcher(text)
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
val imgSrc = matcher.group(1)!!
|
val imgSrc = matcher.group(1)!!
|
||||||
var iStyle = imageStyle
|
var iStyle = imageStyle
|
||||||
var isSmallImage = true
|
var isSmallImage = true
|
||||||
@@ -450,7 +453,7 @@ class TextChapterLayout(
|
|||||||
textPage.height += endPadding
|
textPage.height += endPadding
|
||||||
}
|
}
|
||||||
textPage.text = stringBuilder.toString()
|
textPage.text = stringBuilder.toString()
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
onPageCompleted()
|
onPageCompleted()
|
||||||
onCompleted()
|
onCompleted()
|
||||||
}
|
}
|
||||||
@@ -915,7 +918,7 @@ class TextChapterLayout(
|
|||||||
textPage.leftLineSize = textPage.lineSize
|
textPage.leftLineSize = textPage.lineSize
|
||||||
}
|
}
|
||||||
textPage.text = stringBuilder.toString()
|
textPage.text = stringBuilder.toString()
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
onPageCompleted()
|
onPageCompleted()
|
||||||
//新建页面
|
//新建页面
|
||||||
pendingTextPage = TextPage()
|
pendingTextPage = TextPage()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import io.legado.app.R
|
|||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.databinding.DialogEditTextBinding
|
import io.legado.app.databinding.DialogEditTextBinding
|
||||||
import io.legado.app.help.AppFreezeMonitor
|
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.AppConfig
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
@@ -203,6 +204,7 @@ class OtherConfigFragment : PreferenceFragment(),
|
|||||||
LogUtils.logDeviceInfo()
|
LogUtils.logDeviceInfo()
|
||||||
LiveEventBus.config().enableLogger(AppConfig.recordLog)
|
LiveEventBus.config().enableLogger(AppConfig.recordLog)
|
||||||
AppFreezeMonitor.init(appCtx)
|
AppFreezeMonitor.init(appCtx)
|
||||||
|
DispatchersMonitor.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferKey.processText -> sharedPreferences?.let {
|
PreferKey.processText -> sharedPreferences?.let {
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import io.legado.app.utils.onEachParallel
|
|||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
@@ -175,6 +177,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
ReadBook.onChapterListUpdated(book)
|
ReadBook.onChapterListUpdated(book)
|
||||||
addDownload(source, book)
|
addDownload(source, book)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("${book.name} 更新目录失败\n${it.localizedMessage}", it)
|
AppLog.put("${book.name} 更新目录失败\n${it.localizedMessage}", it)
|
||||||
//这里可能因为时间太长书籍信息已经更改,所以重新获取
|
//这里可能因为时间太长书籍信息已经更改,所以重新获取
|
||||||
appDb.bookDao.getBook(book.bookUrl)?.let { book ->
|
appDb.bookDao.getBook(book.bookUrl)?.let { book ->
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
|
|||||||
booksFlowJob?.cancel()
|
booksFlowJob?.cancel()
|
||||||
booksFlowJob = viewLifecycleOwner.lifecycleScope.launch {
|
booksFlowJob = viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
|
||||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
|
||||||
appDb.bookDao.flowByGroup(groupId)
|
appDb.bookDao.flowByGroup(groupId)
|
||||||
.map { list ->
|
.map { list ->
|
||||||
@@ -250,7 +250,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
|
|||||||
if (!AppConfig.showLastUpdateTime || (bookshelfLayoutMode != 0 && bookshelfLayoutMode != 4)) {
|
if (!AppConfig.showLastUpdateTime || (bookshelfLayoutMode != 0 && bookshelfLayoutMode != 4)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
upLastUpdateTimeJob = lifecycleScope.launch {
|
upLastUpdateTimeJob = viewLifecycleOwner.lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
booksAdapter.upLastUpdateTime()
|
booksAdapter.upLastUpdateTime()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.legado.app.ui.widget.recycler
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
@@ -26,7 +27,7 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
super.setOnClickListener {
|
super.setOnClickListener {
|
||||||
if (!showErrorDialog()) {
|
if (!showErrorDialog(it)) {
|
||||||
onClickListener?.onClick(it)
|
onClickListener?.onClick(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +39,7 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout(
|
|||||||
|
|
||||||
override fun onAttachedToWindow() {
|
override fun onAttachedToWindow() {
|
||||||
super.onAttachedToWindow()
|
super.onAttachedToWindow()
|
||||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
layoutParams.width = LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startLoad() {
|
fun startLoad() {
|
||||||
@@ -79,12 +80,17 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout(
|
|||||||
binding.tvText.visible()
|
binding.tvText.visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showErrorDialog(): Boolean {
|
private fun showErrorDialog(view: View): Boolean {
|
||||||
if (errorMsg.isBlank()) {
|
if (errorMsg.isBlank()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
context.alert(R.string.error) {
|
context.alert(R.string.error) {
|
||||||
setMessage(errorMsg)
|
setMessage(errorMsg)
|
||||||
|
if (onClickListener != null) {
|
||||||
|
neutralButton(R.string.retry) {
|
||||||
|
onClickListener?.onClick(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,3 +140,7 @@ fun String.escapeRegex(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this)
|
fun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this)
|
||||||
|
|
||||||
|
fun String.normalizeFileName(): String {
|
||||||
|
return replace(AppPattern.fileNameRegex2, "_")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1211,4 +1211,5 @@
|
|||||||
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
||||||
<string name="refresh_explore">刷新发现</string>
|
<string name="refresh_explore">刷新发现</string>
|
||||||
<string name="padding_display_cutouts">填充刘海区域</string>
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
|
<string name="sure_cache_book">是否确认开始缓存?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1213,4 +1213,5 @@
|
|||||||
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
<string name="hide_manga_title">隐藏漫画列表标题</string>
|
||||||
<string name="refresh_explore">刷新发现</string>
|
<string name="refresh_explore">刷新发现</string>
|
||||||
<string name="padding_display_cutouts">填充刘海区域</string>
|
<string name="padding_display_cutouts">填充刘海区域</string>
|
||||||
|
<string name="sure_cache_book">是否确认开始缓存?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1345,4 +1345,5 @@
|
|||||||
<string name="dotted_line_while">空隙比例</string>
|
<string name="dotted_line_while">空隙比例</string>
|
||||||
<string name="underline_height">线段高度</string>
|
<string name="underline_height">线段高度</string>
|
||||||
<string name="title_bar_mode">菜单标题显示方式</string>
|
<string name="title_bar_mode">菜单标题显示方式</string>
|
||||||
|
<string name="sure_cache_book">是否确认开始缓存?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1346,4 +1346,5 @@
|
|||||||
<string name="dotted_line_while">空隙比例</string>
|
<string name="dotted_line_while">空隙比例</string>
|
||||||
<string name="underline_height">线段高度</string>
|
<string name="underline_height">线段高度</string>
|
||||||
<string name="title_bar_mode">菜单标题显示方式</string>
|
<string name="title_bar_mode">菜单标题显示方式</string>
|
||||||
|
<string name="sure_cache_book">是否确认开始缓存?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ liveeventbus = { module = "com.github.michaellee123:LiveEventBus", version.ref =
|
|||||||
material = { module = "com.google.android.material:material", version.ref = "material" }
|
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||||
|
|
||||||
mozilla-rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
|
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" }
|
zxing-lite = { module = "com.github.jenly1314:zxing-lite", version.ref = "zxingLite" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
|||||||
Reference in New Issue
Block a user