Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user