同步上游
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.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, "_")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user