Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei
2023-06-21 17:00:37 +08:00
18 changed files with 100 additions and 67 deletions
@@ -18,8 +18,9 @@ abstract class BaseService : LifecycleService(), CoroutineScope by MainScope() {
scope: CoroutineScope = this, scope: CoroutineScope = this,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = Coroutine.async(scope, context, start) { block() } ) = Coroutine.async(scope, context, start, executeContext, block)
@CallSuper @CallSuper
override fun onCreate() { override fun onCreate() {
@@ -57,7 +58,7 @@ abstract class BaseService : LifecycleService(), CoroutineScope by MainScope() {
/** /**
* 检测通知权限 * 检测通知权限
*/ */
private fun checkNotificationPermission() { private fun checkNotificationPermission() {
PermissionsCompat.Builder() PermissionsCompat.Builder()
.addPermissions(Permissions.POST_NOTIFICATIONS) .addPermissions(Permissions.POST_NOTIFICATIONS)
.rationale(R.string.notification_permission_rationale) .rationale(R.string.notification_permission_rationale)
@@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import io.legado.app.App import io.legado.app.App
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -19,9 +20,11 @@ open class BaseViewModel(application: Application) : AndroidViewModel(applicatio
fun <T> execute( fun <T> execute(
scope: CoroutineScope = viewModelScope, scope: CoroutineScope = viewModelScope,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Coroutine<T> { ): Coroutine<T> {
return Coroutine.async(scope, context) { block() } return Coroutine.async(scope, context, start, executeContext, block)
} }
fun <R> submit( fun <R> submit(
@@ -183,7 +183,7 @@ data class BookSource(
fun getInvalidGroupNames(): String { fun getInvalidGroupNames(): String {
return bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.filter { return bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.filter {
"失效" in it "失效" in it || it == "校验超时"
}?.joinToString() ?: "" }?.joinToString() ?: ""
} }
@@ -25,6 +25,7 @@ class Coroutine<T>(
val scope: CoroutineScope, val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
val startOption: CoroutineStart = CoroutineStart.DEFAULT, val startOption: CoroutineStart = CoroutineStart.DEFAULT,
val executeContext: CoroutineContext = Dispatchers.Main,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) { ) {
@@ -36,9 +37,10 @@ class Coroutine<T>(
scope: CoroutineScope = DEFAULT, scope: CoroutineScope = DEFAULT,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Coroutine<T> { ): Coroutine<T> {
return Coroutine(scope, context, start, block) return Coroutine(scope, context, start, executeContext, block)
} }
} }
@@ -158,7 +160,7 @@ class Coroutine<T>(
context: CoroutineContext, context: CoroutineContext,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Job { ): Job {
return (scope.plus(Dispatchers.Main)).launch(start = startOption) { return (scope.plus(executeContext)).launch(start = startOption) {
try { try {
start?.let { dispatchVoidCallback(this, it) } start?.let { dispatchVoidCallback(this, it) }
ensureActive() ensureActive()
@@ -57,9 +57,8 @@ object LocalBook {
}.firstOrNull()?.let { }.firstOrNull()?.let {
getBookInputStream(it) getBookInputStream(it)
} }
} else if (webDavUrl != null) { } else if (webDavUrl != null && downloadRemoteBook(book)) {
// 下载远程链接 // 下载远程链接
downloadRemoteBook(book)
getBookInputStream(book) getBookInputStream(book)
} else { } else {
null null
@@ -90,12 +89,15 @@ object LocalBook {
book.isEpub -> { book.isEpub -> {
EpubFile.getChapterList(book) EpubFile.getChapterList(book)
} }
book.isUmd -> { book.isUmd -> {
UmdFile.getChapterList(book) UmdFile.getChapterList(book)
} }
book.isPdf -> { book.isPdf -> {
PdfFile.getChapterList(book) PdfFile.getChapterList(book)
} }
else -> { else -> {
TextFile.getChapterList(book) TextFile.getChapterList(book)
} }
@@ -117,12 +119,15 @@ object LocalBook {
book.isEpub -> { book.isEpub -> {
EpubFile.getContent(book, chapter) EpubFile.getContent(book, chapter)
} }
book.isUmd -> { book.isUmd -> {
UmdFile.getContent(book, chapter) UmdFile.getContent(book, chapter)
} }
book.isPdf -> { book.isPdf -> {
PdfFile.getContent(book, chapter) PdfFile.getContent(book, chapter)
} }
else -> { else -> {
TextFile.getContent(book, chapter) TextFile.getContent(book, chapter)
} }
@@ -218,7 +223,7 @@ object LocalBook {
} }
} }
/* 批量导入 支持自动导入压缩包的支持书籍 */ /* 批量导入 支持自动导入压缩包的支持书籍 */
fun importFiles(uri: Uri): List<Book> { fun importFiles(uri: Uri): List<Book> {
val books = mutableListOf<Book>() val books = mutableListOf<Book>()
val fileDoc = FileDoc.fromUri(uri, false) val fileDoc = FileDoc.fromUri(uri, false)
@@ -329,6 +334,7 @@ object LocalBook {
Base64.DEFAULT Base64.DEFAULT
) )
) )
else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL") else -> throw NoStackTraceException("在线导入书籍支持http/https/DataURL")
} }
return saveBookFile(inputStream, fileName) return saveBookFile(inputStream, fileName)
@@ -383,7 +389,7 @@ object LocalBook {
} }
//下载book对应的远程文件 并更新Book //下载book对应的远程文件 并更新Book
private fun downloadRemoteBook(localBook: Book) { private fun downloadRemoteBook(localBook: Book): Boolean {
val webDavUrl = localBook.getRemoteUrl() val webDavUrl = localBook.getRemoteUrl()
if (webDavUrl.isNullOrBlank()) throw NoStackTraceException("Book file is not webDav File") if (webDavUrl.isNullOrBlank()) throw NoStackTraceException("Book file is not webDav File")
try { try {
@@ -415,9 +421,11 @@ object LocalBook {
localBook.save() localBook.save()
} }
} }
return true
} catch (e: Exception) { } catch (e: Exception) {
e.printOnDebug() e.printOnDebug()
AppLog.put("自动下载webDav书籍失败", e) AppLog.put("自动下载webDav书籍失败", e)
return false
} }
} }
@@ -122,7 +122,11 @@ class CheckSourceService : BaseService() {
*校验书源 *校验书源
*/ */
private fun check(source: BookSource) { private fun check(source: BookSource) {
execute(context = searchCoroutine, start = CoroutineStart.LAZY) { execute(
context = searchCoroutine,
start = CoroutineStart.LAZY,
executeContext = IO
) {
Debug.startChecking(source) Debug.startChecking(source)
var searchWord = CheckSource.keyword var searchWord = CheckSource.keyword
source.ruleSearch?.checkKeyWord?.let { source.ruleSearch?.checkKeyWord?.let {
@@ -188,7 +192,6 @@ class CheckSourceService : BaseService() {
"" else "\n\n${source.bookSourceComment}" "" else "\n\n${source.bookSourceComment}"
Debug.updateFinalMessage(source.bookSourceUrl, "校验失败:${it.localizedMessage}") Debug.updateFinalMessage(source.bookSourceUrl, "校验失败:${it.localizedMessage}")
}.onSuccess(searchCoroutine) { }.onSuccess(searchCoroutine) {
source.removeGroup("校验超时")
Debug.updateFinalMessage(source.bookSourceUrl, "校验成功") Debug.updateFinalMessage(source.bookSourceUrl, "校验成功")
}.onFinally(IO) { }.onFinally(IO) {
source.respondTime = Debug.getRespondTime(source.bookSourceUrl) source.respondTime = Debug.getRespondTime(source.bookSourceUrl)
@@ -26,7 +26,7 @@ import io.legado.app.utils.printOnDebug
import io.legado.app.utils.servicePendingIntent import io.legado.app.utils.servicePendingIntent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
@@ -68,32 +68,21 @@ class HttpReadAloudService : BaseReadAloudService(),
super.onDestroy() super.onDestroy()
downloadTask?.cancel() downloadTask?.cancel()
exoPlayer.release() exoPlayer.release()
Coroutine.async {
removeCacheFile()
}
} }
override fun play() { override fun play() {
pageChanged = false pageChanged = false
exoPlayer.stop() exoPlayer.stop()
if (!requestFocus()) return
if (contentList.isEmpty()) { if (contentList.isEmpty()) {
AppLog.putDebug("朗读列表为空") AppLog.putDebug("朗读列表为空")
ReadBook.readAloud() ReadBook.readAloud()
} else { } else {
super.play() super.play()
kotlin.runCatching { downloadAndPlayAudios()
if (nowSpeak == 0) {
downloadAudio()
} else {
val fileName = md5SpeakFileName(contentList[nowSpeak])
val file = getSpeakFileAsMd5(fileName)
if (file.exists()) {
playAudio(file)
} else if (!downloadTaskActiveLock.isLocked) {
downloadAudio()
}
}
}.onFailure {
toastOnUi("朗读出错:${it.localizedMessage}")
AppLog.put("朗读出错:${it.localizedMessage}", it)
}
} }
} }
@@ -105,21 +94,21 @@ class HttpReadAloudService : BaseReadAloudService(),
readAloudNumber += contentList[nowSpeak].length + 1 readAloudNumber += contentList[nowSpeak].length + 1
if (nowSpeak < contentList.lastIndex) { if (nowSpeak < contentList.lastIndex) {
nowSpeak++ nowSpeak++
play()
} else { } else {
nextChapter() nextChapter()
} }
} }
private fun downloadAudio() { private fun downloadAndPlayAudios() {
exoPlayer.clearMediaItems()
downloadTask?.cancel() downloadTask?.cancel()
downloadTask = execute { downloadTask = execute {
downloadTaskActiveLock.withLock { downloadTaskActiveLock.withLock {
ensureActive() ensureActive()
removeCacheFile()
val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException("tts is null") val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException("tts is null")
contentList.forEachIndexed { index, content -> contentList.forEachIndexed { index, content ->
ensureActive() ensureActive()
if (index < nowSpeak) return@forEachIndexed
val fileName = md5SpeakFileName(content) val fileName = md5SpeakFileName(content)
val speakText = content.replace(AppPattern.notReadAloudRegex, "") val speakText = content.replace(AppPattern.notReadAloudRegex, "")
if (speakText.isEmpty()) { if (speakText.isEmpty()) {
@@ -141,13 +130,23 @@ class HttpReadAloudService : BaseReadAloudService(),
return@execute return@execute
} }
} }
if (index == nowSpeak) { val file = getSpeakFileAsMd5(fileName)
val file = getSpeakFileAsMd5(fileName) val mediaItem = MediaItem.fromUri(Uri.fromFile(file))
playAudio(file) launch(Main) {
if (exoPlayer.playbackState == Player.STATE_ENDED) {
exoPlayer.stop()
exoPlayer.clearMediaItems()
}
exoPlayer.addMediaItem(mediaItem)
if (!exoPlayer.isPlaying) {
exoPlayer.playWhenReady = !pause
exoPlayer.prepare()
}
} }
} }
} }
}.onError(IO) { }.onError {
toastOnUi("朗读出错:${it.localizedMessage}")
AppLog.put("朗读下载出错\n${it.localizedMessage}", it) AppLog.put("朗读下载出错\n${it.localizedMessage}", it)
} }
} }
@@ -193,6 +192,7 @@ class HttpReadAloudService : BaseReadAloudService(),
e.printOnDebug() e.printOnDebug()
throw e throw e
} }
is SocketTimeoutException, is ConnectException -> { is SocketTimeoutException, is ConnectException -> {
downloadErrorNo++ downloadErrorNo++
if (downloadErrorNo > 5) { if (downloadErrorNo > 5) {
@@ -202,6 +202,7 @@ class HttpReadAloudService : BaseReadAloudService(),
throw e throw e
} }
} }
else -> { else -> {
downloadErrorNo++ downloadErrorNo++
val msg = "tts下载错误\n${e.localizedMessage}" val msg = "tts下载错误\n${e.localizedMessage}"
@@ -223,22 +224,6 @@ class HttpReadAloudService : BaseReadAloudService(),
return null return null
} }
@Synchronized
private fun playAudio(file: File) {
if (requestFocus()) {
launch {
kotlin.runCatching {
val mediaItem = MediaItem.fromUri(Uri.fromFile(file))
exoPlayer.setMediaItem(mediaItem)
exoPlayer.playWhenReady = true
exoPlayer.prepare()
}.onFailure {
it.printOnDebug()
}
}
}
}
private fun md5SpeakFileName(content: String): String { private fun md5SpeakFileName(content: String): String {
return MD5Utils.md5Encode16(textChapter?.title ?: "") + "_" + return MD5Utils.md5Encode16(textChapter?.title ?: "") + "_" +
MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content") MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content")
@@ -338,7 +323,7 @@ class HttpReadAloudService : BaseReadAloudService(),
downloadTask?.cancel() downloadTask?.cancel()
exoPlayer.stop() exoPlayer.stop()
speechRate = AppConfig.speechRatePlay + 5 speechRate = AppConfig.speechRatePlay + 5
downloadAudio() downloadAndPlayAudios()
} }
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
@@ -347,15 +332,18 @@ class HttpReadAloudService : BaseReadAloudService(),
Player.STATE_IDLE -> { Player.STATE_IDLE -> {
// 空闲 // 空闲
} }
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
// 缓冲中 // 缓冲中
} }
Player.STATE_READY -> { Player.STATE_READY -> {
// 准备好 // 准备好
if (pause) return if (pause) return
exoPlayer.play() exoPlayer.play()
upPlayPos() upPlayPos()
} }
Player.STATE_ENDED -> { Player.STATE_ENDED -> {
// 结束 // 结束
playErrorNo = 0 playErrorNo = 0
@@ -364,6 +352,12 @@ class HttpReadAloudService : BaseReadAloudService(),
} }
} }
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return
upPlayPos()
playNext()
}
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error) super.onPlayerError(error)
AppLog.put("朗读错误\n${contentList[nowSpeak]}", error) AppLog.put("朗读错误\n${contentList[nowSpeak]}", error)
@@ -44,6 +44,7 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -204,7 +205,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
} }
} }
launch { launch {
appDb.bookSourceDao.flowEnabledGroups().conflate().collect { appDb.bookSourceDao.flowEnabledGroups().flowOn(IO).conflate().collect {
groups.clear() groups.clear()
groups.addAll(it) groups.addAll(it)
upGroupMenu() upGroupMenu()
@@ -47,8 +47,10 @@ import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -230,7 +232,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
} }
} }
launch { launch {
appDb.bookSourceDao.flowEnabledGroups().conflate().collect { appDb.bookSourceDao.flowEnabledGroups().flowOn(IO).conflate().collect {
groups.clear() groups.clear()
groups.addAll(it) groups.addAll(it)
upGroupMenu() upGroupMenu()
@@ -108,7 +108,8 @@ abstract class BaseReadBookActivity :
*/ */
fun upSystemUiVisibility( fun upSystemUiVisibility(
isInMultiWindow: Boolean, isInMultiWindow: Boolean,
toolBarHide: Boolean = true toolBarHide: Boolean = true,
useBgMeanColor: Boolean = false
) { ) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.run { window.insetsController?.run {
@@ -129,7 +130,10 @@ abstract class BaseReadBookActivity :
setLightStatusBar(ReadBookConfig.durConfig.curStatusIconDark()) setLightStatusBar(ReadBookConfig.durConfig.curStatusIconDark())
} else { } else {
val statusBarColor = val statusBarColor =
if (AppConfig.readBarStyleFollowPage && ReadBookConfig.durConfig.curBgType() == 0) { if (AppConfig.readBarStyleFollowPage
&& ReadBookConfig.durConfig.curBgType() == 0
|| useBgMeanColor
) {
ReadBookConfig.bgMeanColor ReadBookConfig.bgMeanColor
} else { } else {
ThemeStore.statusBarColor(this, AppConfig.isTransparentStatusBar) ThemeStore.statusBarColor(this, AppConfig.isTransparentStatusBar)
@@ -184,12 +188,14 @@ abstract class BaseReadBookActivity :
width = MATCH_PARENT width = MATCH_PARENT
gravity = Gravity.BOTTOM gravity = Gravity.BOTTOM
} }
Gravity.LEFT -> layoutParams = Gravity.LEFT -> layoutParams =
(layoutParams as FrameLayout.LayoutParams).apply { (layoutParams as FrameLayout.LayoutParams).apply {
height = MATCH_PARENT height = MATCH_PARENT
width = navigationBarHeight width = navigationBarHeight
gravity = Gravity.LEFT gravity = Gravity.LEFT
} }
Gravity.RIGHT -> layoutParams = Gravity.RIGHT -> layoutParams =
(layoutParams as FrameLayout.LayoutParams).apply { (layoutParams as FrameLayout.LayoutParams).apply {
height = MATCH_PARENT height = MATCH_PARENT
@@ -1044,7 +1044,7 @@ class ReadBookActivity : BaseReadBookActivity(),
* 更新状态栏,导航栏 * 更新状态栏,导航栏
*/ */
override fun upSystemUiVisibility() { override fun upSystemUiVisibility() {
upSystemUiVisibility(isInMultiWindow, !menuLayoutIsVisible) upSystemUiVisibility(isInMultiWindow, !menuLayoutIsVisible, bottomDialog > 0)
upNavigationBarColor() upNavigationBarColor()
} }
@@ -93,7 +93,7 @@ class SpeakEngineDialog(val callBack: CallBack) : BaseDialogFragment(R.layout.di
labelSys.visible() labelSys.visible()
cbName.text = "系统默认" cbName.text = "系统默认"
cbName.tag = "" cbName.tag = ""
cbName.isChecked = ttsEngine == null || ttsEngine!!.toIntOrNull() == null cbName.isChecked = ttsEngine == null || ttsEngine!!.isJsonObject()
&& GSON.fromJsonObject<SelectItem<String>>(ttsEngine) && GSON.fromJsonObject<SelectItem<String>>(ttsEngine)
.getOrNull()?.value.isNullOrEmpty() .getOrNull()?.value.isNullOrEmpty()
cbName.setOnClickListener { cbName.setOnClickListener {
@@ -31,6 +31,7 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
@@ -279,7 +280,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
adapter.setItems(it) adapter.setItems(it)
} }
launch { launch {
appDb.bookSourceDao.flowEnabledGroups().collect { appDb.bookSourceDao.flowEnabledGroups().flowOn(IO).collect {
groups = it groups = it
} }
} }
@@ -42,9 +42,11 @@ import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.* import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
/** /**
@@ -321,7 +323,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
}.catch { }.catch {
AppLog.put("书源界面更新书源出错", it) AppLog.put("书源界面更新书源出错", it)
}.conflate().collect { data -> }.flowOn(IO).conflate().collect { data ->
adapter.setItems(data, adapter.diffItemCallback) adapter.setItems(data, adapter.diffItemCallback)
itemTouchCallback.isCanDrag = sort == BookSourceSort.Default itemTouchCallback.isCanDrag = sort == BookSourceSort.Default
delay(500) delay(500)
@@ -336,10 +338,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
private fun initLiveDataGroup() { private fun initLiveDataGroup() {
launch { launch {
appDb.bookSourceDao.flowGroups().conflate().collect { appDb.bookSourceDao.flowGroups().flowOn(IO).conflate().collect {
groups.clear() groups.clear()
groups.addAll(it) groups.addAll(it)
upGroupMenu() upGroupMenu()
delay(500)
} }
} }
} }
@@ -25,6 +25,8 @@ import io.legado.app.utils.applyTint
import io.legado.app.utils.requestInputMethod import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.setLayout import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -55,7 +57,7 @@ class GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
private fun initData() { private fun initData() {
launch { launch {
appDb.bookSourceDao.flowGroups().collect { appDb.bookSourceDao.flowGroups().flowOn(IO).collect {
adapter.setItems(it) adapter.setItems(it)
} }
} }
@@ -20,6 +20,7 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CacheBookService import io.legado.app.service.CacheBookService
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min import kotlin.math.min
@@ -85,9 +86,11 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
upTocJob?.cancel() upTocJob?.cancel()
upTocJob = null upTocJob = null
} }
onUpTocBooks.size < threadCount -> { onUpTocBooks.size < threadCount -> {
updateToc() updateToc()
} }
else -> { else -> {
delay(500) delay(500)
} }
@@ -119,7 +122,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
} }
waitUpTocBooks.remove(bookUrl) waitUpTocBooks.remove(bookUrl)
upTocAdd(bookUrl) upTocAdd(bookUrl)
execute(context = upTocPool) { execute(context = upTocPool, executeContext = IO) {
kotlin.runCatching { kotlin.runCatching {
val oldBook = book.copy() val oldBook = book.copy()
WebBook.runPreUpdateJs(source, book) WebBook.runPreUpdateJs(source, book)
@@ -29,9 +29,11 @@ import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -107,7 +109,7 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
private fun initGroupData() { private fun initGroupData() {
launch { launch {
appDb.bookSourceDao.flowExploreGroups().conflate().collect { appDb.bookSourceDao.flowExploreGroups().flowOn(IO).conflate().collect {
groups.clear() groups.clear()
groups.addAll(it) groups.addAll(it)
upGroupsMenu() upGroupsMenu()
@@ -11,6 +11,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxLines="1" android:maxLines="1"
android:singleLine="true"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
@@ -25,12 +26,13 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:padding="8dp"
android:singleLine="true"
android:textColor="@color/secondaryText" android:textColor="@color/secondaryText"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toRightOf="@id/cb_source_name" app:layout_constraintLeft_toRightOf="@id/cb_source_name"
app:layout_constraintRight_toLeftOf="@id/tv_open" app:layout_constraintRight_toLeftOf="@id/tv_open"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintHorizontal_chainStyle="packed" />
<TextView <TextView
android:id="@+id/tv_open" android:id="@+id/tv_open"