diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index b3458293f..9b984e6cf 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -21,6 +21,7 @@ object EventBus { const val NOTIFY_MAIN = "notifyMain" const val WEB_SERVICE = "webService" const val UP_DOWNLOAD = "upDownload" + const val UP_DOWNLOAD_STATE = "upDownloadState" const val SAVE_CONTENT = "saveContent" const val CHECK_SOURCE = "checkSource" const val CHECK_SOURCE_DONE = "checkSourceDone" diff --git a/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt b/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt index 040bb6b3d..a79af166a 100644 --- a/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt +++ b/app/src/main/java/io/legado/app/data/entities/HttpTTS.kt @@ -55,7 +55,8 @@ data class HttpTTS( loginUrl = doc.readString("$.loginUrl"), loginUi = if (loginUi is List<*>) GSON.toJson(loginUi) else loginUi?.toString(), header = doc.readString("$.header"), - loginCheckJs = doc.readString("$.loginCheckJs") + loginCheckJs = doc.readString("$.loginCheckJs"), + lastUpdateTime = doc.readLong("$.lastUpdateTime") ?: System.currentTimeMillis() ) } } diff --git a/app/src/main/java/io/legado/app/data/repository/ReadRecordRepository.kt b/app/src/main/java/io/legado/app/data/repository/ReadRecordRepository.kt index 379a57ef4..c176414f2 100644 --- a/app/src/main/java/io/legado/app/data/repository/ReadRecordRepository.kt +++ b/app/src/main/java/io/legado/app/data/repository/ReadRecordRepository.kt @@ -16,8 +16,8 @@ class ReadRecordRepository( ) { companion object { - // 2分钟内重新打开书本,视为同一次阅读 - private const val CONTINUE_THRESHOLD = 120 * 1000L + // 10秒内重新打开书本,视为同一次阅读 + private const val CONTINUE_THRESHOLD = 10 * 1000L // 会话总时长小于10秒则不记录 private const val MIN_READ_DURATION = 0 * 1000L } diff --git a/app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt b/app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt index 19561c75b..a6c5f47a0 100644 --- a/app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt +++ b/app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt @@ -1,3 +1,3 @@ package io.legado.app.help.glide.progress -typealias OnProgressListener = ((isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit)? \ No newline at end of file +typealias OnProgressListener = (isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt b/app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt index 8b37534fa..5011b0d2d 100644 --- a/app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt +++ b/app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt @@ -28,7 +28,7 @@ object ProgressManager { } fun addListener(url: String, listener: OnProgressListener) { - if (url.isNotEmpty() && listener != null) { + if (url.isNotEmpty()) { val url = getUrlNoOption(url) listenersMap[url] = listener listener.invoke(false, 1, 0, 0) @@ -42,7 +42,7 @@ object ProgressManager { } } - fun getProgressListener(url: String): OnProgressListener { + fun getProgressListener(url: String): OnProgressListener? { return if (url.isEmpty() || listenersMap.isEmpty()) { null } else { @@ -53,7 +53,7 @@ object ProgressManager { private fun getUrlNoOption(url: String): String { val urlMatcher = AnalyzeUrl.paramPattern.matcher(url) return if (urlMatcher.find()) { - url.substring(0, urlMatcher.start()) + url.take(urlMatcher.start()) } else { url } diff --git a/app/src/main/java/io/legado/app/model/CacheBook.kt b/app/src/main/java/io/legado/app/model/CacheBook.kt index 72ca1c641..28c24a2d6 100644 --- a/app/src/main/java/io/legado/app/model/CacheBook.kt +++ b/app/src/main/java/io/legado/app/model/CacheBook.kt @@ -11,25 +11,39 @@ import io.legado.app.data.entities.BookSource import io.legado.app.exception.ConcurrentException import io.legado.app.help.book.BookHelp import io.legado.app.help.book.isLocal +import io.legado.app.help.config.AppConfig import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.webBook.WebBook import io.legado.app.service.CacheBookService +import io.legado.app.utils.onEachParallel import io.legado.app.utils.postEvent import io.legado.app.utils.startService import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.isActive +import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withLock import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.CoroutineContext object CacheBook { val cacheBookMap = ConcurrentHashMap() - + private val workingState = MutableStateFlow(true) + private val mutex = Mutex() val totalCount: Int get() { var total = 0 @@ -117,6 +131,39 @@ object CacheBook { errorDownloadMap.clear() } + fun setWorkingState(value: Boolean) { + workingState.value = value + } + + suspend fun startProcessJob(context: CoroutineContext) = mutex.withLock { + setWorkingState(true) + flow { + while (currentCoroutineContext().isActive && cacheBookMap.isNotEmpty()) { + var emitted = false + + cacheBookMap.forEach { (_, model) -> + if (!model.isLoading()) { + emit(model) + emitted = true + } + workingState.first { it } + } + + if (!emitted) { + delay(1000) + } + } + }.onStart { + postEvent(EventBus.UP_DOWNLOAD_STATE, "") + }.onEachParallel(AppConfig.threadCount) { + coroutineScope { + it.download(this, context) + } + }.onCompletion { + postEvent(EventBus.UP_DOWNLOAD_STATE, "") + }.collect() + } + val downloadSummary: String get() { return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}" @@ -124,11 +171,12 @@ object CacheBook { val isRun: Boolean get() { - var isRun = false cacheBookMap.forEach { - isRun = isRun || it.value.isRun() + if (it.value.isRun()) { + return true + } } - return isRun + return false } private val waitCount: Int @@ -159,6 +207,7 @@ object CacheBook { private val tasks = CompositeCoroutine() private var isStopped = false private var waitingRetry = false + private var isLoading = false val waitCount get() = waitDownloadSet.size val onDownloadCount get() = onDownloadSet.size @@ -169,7 +218,7 @@ object CacheBook { @Synchronized fun isRun(): Boolean { - return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty() + return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty() || isLoading } @Synchronized @@ -177,11 +226,22 @@ object CacheBook { return isStopped || (!isRun() && !waitingRetry) } + @Synchronized + fun isLoading(): Boolean { + return isLoading + } + + @Synchronized + fun setLoading() { + isLoading = true + } + @Synchronized fun stop() { waitDownloadSet.clear() tasks.clear() isStopped = true + isLoading = false postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) } @@ -194,6 +254,7 @@ object CacheBook { } } cacheBookMap[book.bookUrl] = this + isLoading = false postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) } @@ -257,10 +318,9 @@ object CacheBook { */ @Synchronized fun download(scope: CoroutineScope, context: CoroutineContext) { - postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) val chapterIndex = waitDownloadSet.firstOrNull() if (chapterIndex == null) { - if (onDownloadSet.isEmpty()) { + if (!isLoading && onDownloadSet.isEmpty()) { cacheBookMap.remove(book.bookUrl) } return @@ -331,7 +391,6 @@ object CacheBook { } suspend fun downloadAwait(chapter: BookChapter): String { - postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) synchronized(this) { onDownloadSet.add(chapter.index) waitDownloadSet.remove(chapter.index) @@ -365,7 +424,6 @@ object CacheBook { if (onDownloadSet.contains(chapter.index)) { return } - postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) onDownloadSet.add(chapter.index) waitDownloadSet.remove(chapter.index) WebBook.getContent( diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index f08c8b25e..324b5f9b0 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -371,7 +371,7 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent { return } try { - readRecordRepository.saveOrMergeReadSession(sessionToSave) + readRecordRepository.saveReadSession(sessionToSave) } catch (e: Exception) { AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e) } finally { diff --git a/app/src/main/java/io/legado/app/model/ReadManga.kt b/app/src/main/java/io/legado/app/model/ReadManga.kt index 3ecb6d3bf..09ac21d67 100644 --- a/app/src/main/java/io/legado/app/model/ReadManga.kt +++ b/app/src/main/java/io/legado/app/model/ReadManga.kt @@ -217,7 +217,7 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{ return } try { - readRecordRepository.saveOrMergeReadSession(sessionToSave) + readRecordRepository.saveReadSession(sessionToSave) } catch (e: Exception) { AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e) } finally { diff --git a/app/src/main/java/io/legado/app/service/CacheBookService.kt b/app/src/main/java/io/legado/app/service/CacheBookService.kt index 650254376..e940d10a7 100644 --- a/app/src/main/java/io/legado/app/service/CacheBookService.kt +++ b/app/src/main/java/io/legado/app/service/CacheBookService.kt @@ -121,12 +121,14 @@ class CacheBookService : BaseService() { val chapterCount = appDb.bookChapterDao.getChapterCount(bookUrl) if (chapterCount == 0) { + cacheBook.setLoading() mutex.withLock { val name = book.name if (book.tocUrl.isEmpty()) { kotlin.runCatching { WebBook.getBookInfoAwait(cacheBook.bookSource, book) }.onFailure { + removeDownload(bookUrl) AppLog.put( "《$name》目录为空且加载详情页失败\n${it.localizedMessage}", it, @@ -141,6 +143,7 @@ class CacheBookService : BaseService() { book.totalChapterNum = 0 book.update() } + removeDownload(bookUrl) AppLog.put( "《$name》目录为空且加载目录失败\n${it.localizedMessage}", it, @@ -189,23 +192,8 @@ class CacheBookService : BaseService() { private fun download() { downloadJob?.cancel() downloadJob = lifecycleScope.launch(cachePool) { - while (isActive) { - if (!CacheBook.isRun) { - stopSelf() - return@launch - } - CacheBook.cacheBookMap.forEach { - val cacheBookModel = it.value - while (cacheBookModel.waitCount > 0) { - if (CacheBook.onDownloadCount < threadCount) { - cacheBookModel.download(this, cachePool) - } else { - delay(100) - } - } - } - delay(100) - } + CacheBook.startProcessJob(cachePool) + stopSelf() } } diff --git a/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt b/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt index 0ad391e61..1d5214eed 100644 --- a/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel import io.legado.app.utils.contains import io.legado.app.utils.inputStream +import io.legado.app.utils.jsonPath import io.legado.app.utils.readText abstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) { @@ -14,28 +15,33 @@ abstract class BaseAssociationViewModel(application: Application) : BaseViewMode val errorLive = MutableLiveData() fun importJson(uri: Uri) { - when { - uri.inputStream(context).getOrNull().contains("bookSourceUrl") -> - successLive.postValue(Pair("bookSource", uri.toString())) - else -> importJson(uri.readText(context)) + val map = uri.inputStream(context).getOrThrow().use { + jsonPath.parse(it).read>("$[0]") + } ?: uri.inputStream(context).getOrThrow().use { + jsonPath.parse(it).read("$") } - } - - private fun importJson(json: String) { - //暂时根据文件内容判断属于什么 when { - json.contains("sourceUrl") -> - successLive.postValue(Pair("rssSource", json)) - json.contains("pattern") -> - successLive.postValue(Pair("replaceRule", json)) - json.contains("themeName") -> - successLive.postValue(Pair("theme", json)) - json.contains("urlRule") && json.contains("showRule") -> - successLive.postValue(Pair("dictRule", json)) - json.contains("name") && json.contains("rule") -> - successLive.postValue(Pair("txtRule", json)) - json.contains("name") && json.contains("url") -> - successLive.postValue(Pair("httpTts", json)) + map.containsKey("bookSourceUrl") -> + successLive.postValue("bookSource" to uri.toString()) + + map.containsKey("sourceUrl") -> + successLive.postValue("rssSource" to uri.toString()) + + map.containsKey("pattern") -> + successLive.postValue("replaceRule" to uri.toString()) + + map.containsKey("themeName") -> + successLive.postValue("theme" to uri.toString()) + + map.containsKey("showRule") -> + successLive.postValue("dictRule" to uri.toString()) + + map.containsKey("name") && map.containsKey("rule") -> + successLive.postValue("txtRule" to uri.toString()) + + map.containsKey("name") && map.containsKey("url") -> + successLive.postValue("httpTts" to uri.toString()) + else -> errorLive.postValue("格式不对") } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt index 5dfd9fc74..ac0354d0e 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import io.legado.app.R import io.legado.app.base.BaseViewModel @@ -19,6 +20,9 @@ import io.legado.app.utils.fromJsonObject import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject +import io.legado.app.utils.isUri +import io.legado.app.utils.readText +import splitties.init.appCtx class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) { @@ -88,6 +92,9 @@ class ImportDictRuleViewModel(app: Application) : BaseViewModel(app) { text.isAbsUrl() -> { importSourceUrl(text) } + text.isUri() -> { + importSourceAwait(text.toUri().readText(appCtx)) + } else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt index 38a5971b4..47fe75d4a 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import io.legado.app.R import io.legado.app.base.BaseViewModel @@ -16,6 +17,9 @@ import io.legado.app.help.http.text import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject +import io.legado.app.utils.isUri +import io.legado.app.utils.readText +import splitties.init.appCtx class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) { @@ -85,6 +89,9 @@ class ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) { text.isAbsUrl() -> { importSourceUrl(text) } + text.isUri() -> { + importSourceAwait(text.toUri().readText(appCtx)) + } else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt index e8a149f5c..9f7380680 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppConst @@ -17,7 +18,10 @@ import io.legado.app.help.http.text import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject +import io.legado.app.utils.isUri +import io.legado.app.utils.readText import io.legado.app.utils.splitNotBlank +import splitties.init.appCtx class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) { var isAddGroup = false @@ -102,6 +106,10 @@ class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) { allRules.add(rule) } + text.isUri() -> { + importAwait(text.toUri().readText(appCtx)) + } + else -> throw NoStackTraceException("格式不对") } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt index e62b2fd14..0d10f7779 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import com.jayway.jsonpath.JsonPath import io.legado.app.R @@ -22,8 +23,11 @@ import io.legado.app.utils.fromJsonObject import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject +import io.legado.app.utils.isUri import io.legado.app.utils.jsonPath +import io.legado.app.utils.readText import io.legado.app.utils.splitNotBlank +import splitties.init.appCtx class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { var isAddGroup = false @@ -101,42 +105,7 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { fun importSource(text: String) { execute { - val mText = text.trim() - when { - mText.isJsonObject() -> kotlin.runCatching { - val json = JsonPath.parse(mText) - val urls = json.read>("$.sourceUrls") - if (!urls.isNullOrEmpty()) { - urls.forEach { - importSourceUrl(it) - } - } - }.onFailure { - GSON.fromJsonArray(mText).getOrThrow().let { - val source = it.firstOrNull() ?: return@let - if (source.sourceUrl.isEmpty()) { - throw NoStackTraceException("不是订阅源") - } - allSources.addAll(it) - } - } - - mText.isJsonArray() -> { - GSON.fromJsonArray(mText).getOrThrow().let { - val source = it.firstOrNull() ?: return@let - if (source.sourceUrl.isEmpty()) { - throw NoStackTraceException("不是订阅源") - } - allSources.addAll(it) - } - } - - mText.isAbsUrl() -> { - importSourceUrl(mText) - } - - else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) - } + importSourceAwait(text) }.onError { errorLiveData.postValue("ImportError:${it.localizedMessage}") AppLog.put("ImportError:${it.localizedMessage}", it) @@ -145,6 +114,53 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) { } } + private suspend fun importSourceAwait(text: String) { + val mText = text.trim() + when { + mText.isJsonObject() -> kotlin.runCatching { + val json = JsonPath.parse(mText) + val urls = json.read>("$.sourceUrls") + if (!urls.isNullOrEmpty()) { + urls.forEach { + importSourceUrl(it) + } + } + }.onFailure { + GSON.fromJsonArray(mText).getOrThrow().let { + val source = it.firstOrNull() ?: return@let + if (source.sourceUrl.isEmpty()) { + throw NoStackTraceException("不是订阅源") + + + } + allSources.addAll(it) + } + } + + mText.isJsonArray() -> { + GSON.fromJsonArray(mText).getOrThrow().let { + val source = it.firstOrNull() ?: return@let + if (source.sourceUrl.isEmpty()) { + throw NoStackTraceException("不是订阅源") + } + allSources.addAll(it) + } + } + + mText.isAbsUrl() -> { + importSourceUrl(mText) + } + + mText.isUri() -> { + importSourceAwait(mText.toUri().readText(appCtx)) + } + + else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) + + + } + } + private suspend fun importSourceUrl(url: String) { okHttpClient.newCallResponseBody { if (url.endsWith("#requestWithoutUA")) { diff --git a/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt index 06d802a52..e6bd927e3 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import io.legado.app.R import io.legado.app.base.BaseViewModel @@ -13,6 +14,7 @@ import io.legado.app.help.http.newCallResponseBody import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.text import io.legado.app.utils.* +import splitties.init.appCtx class ImportThemeViewModel(app: Application) : BaseViewModel(app) { @@ -81,6 +83,9 @@ class ImportThemeViewModel(app: Application) : BaseViewModel(app) { text.isAbsUrl() -> { importSourceUrl(text) } + text.isUri() -> { + importSourceAwait(text.toUri().readText(appCtx)) + } else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) } } diff --git a/app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt index c83cb8f4e..bfbe3f322 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.association import android.app.Application +import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import io.legado.app.R import io.legado.app.base.BaseViewModel @@ -19,6 +20,9 @@ import io.legado.app.utils.fromJsonObject import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonObject +import io.legado.app.utils.isUri +import io.legado.app.utils.readText +import splitties.init.appCtx class ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) { @@ -89,6 +93,9 @@ class ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) { text.isAbsUrl() -> { importSourceUrl(text) } + text.isUri() -> { + importSourceAwait(text.toUri().readText(appCtx)) + } else -> throw NoStackTraceException(context.getString(R.string.wrong_format)) } } diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt index 77297d67a..1ad322b53 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt @@ -299,6 +299,9 @@ class CacheActivity : VMBaseActivity() notifyItemChanged(it) } observeEvent(EventBus.UP_DOWNLOAD) { + notifyItemChanged(it) + } + observeEvent(EventBus.UP_DOWNLOAD_STATE) { if (!CacheBook.isRun) { menu?.findItem(R.id.menu_download)?.let { item -> item.setIcon(R.drawable.ic_play) @@ -312,7 +315,6 @@ class CacheActivity : VMBaseActivity() } menu?.applyTint(this) } - notifyItemChanged(it) } observeEvent>(EventBus.SAVE_CONTENT) { (book, chapter) -> viewModel.cacheChapters[book.bookUrl]?.add(chapter.url) diff --git a/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt index 530d55d74..8943a038b 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt @@ -48,20 +48,6 @@ abstract class BaseImportBookActivity : initSearchView() } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.action == MotionEvent.ACTION_DOWN) { - currentFocus?.let { - if (it.shouldHideSoftInput(ev)) { - it.post { - it.clearFocus() - it.hideSoftInput() - } - } - } - } - return super.dispatchTouchEvent(ev) - } - /** * 设置书籍保存位置 */ diff --git a/app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt index 50de29752..767dbf9b8 100644 --- a/app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt @@ -34,7 +34,8 @@ class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, t private val webDavServerUi = listOf( RowUi("url"), RowUi("username"), - RowUi("password", RowUi.Type.password) + RowUi("password", RowUi.Type.password), + RowUi("ipRange") ) override fun onStart() { diff --git a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt index e29586759..37d82b05a 100644 --- a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt @@ -118,20 +118,6 @@ class BookshelfManageActivity : upBookDataByGroupId() } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.action == MotionEvent.ACTION_DOWN) { - currentFocus?.let { - if (it.shouldHideSoftInput(ev)) { - it.post { - it.clearFocus() - it.hideSoftInput() - } - } - } - } - return super.dispatchTouchEvent(ev) - } - override fun observeLiveBus() { viewModel.batchChangeSourceState.observe(this) { if (it) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt index 1cb6c4b3b..5bb734157 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt @@ -57,7 +57,6 @@ class InfoConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_info } } binding.btnPaddingSetting.setOnClickListener { - dismissAllowingStateLoss() callBack?.showPaddingConfig() } upTvTipColor() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index 16551635c..42d4c8bf3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -101,12 +101,10 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_ } tvTextFont.setOnClickListener { - dismissAllowingStateLoss() callBack?.showFont() } tvPadding.setOnClickListener { - dismissAllowingStateLoss() callBack?.showInfoConfig() } tvTip.setOnClickListener { @@ -168,7 +166,6 @@ class ReadStyleDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_book_ } private fun showBgTextConfig(index: Int): Boolean { - dismissAllowingStateLoss() changeBgTextConfig(index) callBack?.showBgTextConfig() return true diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt index 38f9ae4eb..354902f59 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt @@ -63,7 +63,7 @@ class SpeakEngineDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_recycl private val callBack: CallBack? get() = parentFragment as? CallBack private val importDocResult = registerForActivityResult(HandleFileContract()) { it.uri?.let { uri -> - viewModel.importLocal(uri) + showDialogFragment(ImportHttpTtsDialog(uri.toString())) } } private val exportDirResult = registerForActivityResult(HandleFileContract()) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt index a43195b49..ae9ab7b90 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt @@ -1,17 +1,9 @@ package io.legado.app.ui.book.read.config import android.app.Application -import android.net.Uri import android.speech.tts.TextToSpeech import io.legado.app.base.BaseViewModel -import io.legado.app.data.appDb -import io.legado.app.data.entities.HttpTTS -import io.legado.app.exception.NoStackTraceException import io.legado.app.help.DefaultData -import io.legado.app.utils.isJsonArray -import io.legado.app.utils.isJsonObject -import io.legado.app.utils.readText -import io.legado.app.utils.toastOnUi class SpeakEngineViewModel(application: Application) : BaseViewModel(application) { @@ -28,34 +20,4 @@ class SpeakEngineViewModel(application: Application) : BaseViewModel(application } } - fun importLocal(uri: Uri) { - execute { - import(uri.readText(context)) - }.onSuccess { - context.toastOnUi("导入成功") - }.onError { - context.toastOnUi("导入失败\n${it.localizedMessage}") - } - } - - fun import(text: String) { - when { - text.isJsonArray() -> { - HttpTTS.fromJsonArray(text).getOrThrow().let { - appDb.httpTTSDao.insert(*it.toTypedArray()) - } - } - - text.isJsonObject() -> { - HttpTTS.fromJson(text).getOrThrow().let { - appDb.httpTTSDao.insert(it) - } - } - - else -> { - throw NoStackTraceException("格式不对") - } - } - } - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt index f2935db83..0d3f622da 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt @@ -16,7 +16,7 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) { private val slopSquare get() = readView.pageSlopSquare2 private var fadeProgress = 0f - private val flipThreshold = 0.2f + private val flipThreshold = 0.1f override fun setDirection(direction: PageDirection) { super.setDirection(direction) diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt index 2a8790843..91be47e49 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt @@ -95,20 +95,6 @@ class SearchContentActivity : return super.onCompatOptionsItemSelected(item) } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.action == MotionEvent.ACTION_DOWN) { - currentFocus?.let { - if (it.shouldHideSoftInput(ev)) { - it.post { - it.clearFocus() - it.hideSoftInput() - } - } - } - } - return super.dispatchTouchEvent(ev) - } - private fun initSearchResultList(list: List?, position: Int) { list ?: return viewModel.searchResultList.addAll(list) diff --git a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt index 9874defb5..edad58e3b 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt @@ -122,30 +122,37 @@ class BookSourceDebugActivity : VMBaseActivity - binding.textFx.onLongClick { - selector("选择发现", exploreKindTitles) { _, index -> - val explore = exploreKinds[index] - binding.textFx.text = "${explore.title}::${explore.url}" - searchView.setQuery(binding.textFx.text, true) + exploreKinds?.firstOrNull()?.let { + binding.textFx.text = "${it.title}::${it.url}" + if (it.title.startsWith("ERROR:")) { + adapter.addItem("获取发现出错\n${it.url}") + openOrCloseHelp(false) + searchView.clearFocus() + return@launch + } } + @Suppress("USELESS_ELVIS") + exploreKinds?.map { it.title ?: "" }?.let { exploreKindTitles -> + binding.textFx.onLongClick { + selector("选择发现", exploreKindTitles) { _, index -> + val explore = exploreKinds[index] + binding.textFx.text = "${explore.title}::${explore.url}" + searchView.setQuery(binding.textFx.text, true) + } + } + } + } catch (e: NullPointerException) { + adapter.addItem("获取发现出错 JSON 数据错误\n$e") + openOrCloseHelp(false) + searchView.clearFocus() } } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index e110f4293..631161d3d 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -632,6 +632,8 @@ class BookSourceEditActivity : val edit = view.editableText//获取EditText的文字 if (start < 0 || start >= edit.length) { edit.append(text) + } else if (start > end) { + edit.replace(end, start, text) } else { edit.replace(start, end, text)//光标所在位置插入文字 } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index ee0d52ece..9a18c9452 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -158,20 +158,6 @@ class BookSourceActivity : VMBaseActivity(), } } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.action == MotionEvent.ACTION_DOWN) { - currentFocus?.let { - if (it.shouldHideSoftInput(ev)) { - it.hideSoftInput() - } - } - } - return super.dispatchTouchEvent(ev) - } - override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.book_toc, menu) this.menu = menu diff --git a/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt b/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt index 6f023689b..c4afe452e 100644 --- a/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt +++ b/app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt @@ -5,6 +5,7 @@ import android.content.DialogInterface import android.os.Bundle import android.text.InputType import android.view.View +import androidx.core.view.setPadding import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import com.script.rhino.runScriptWithContext @@ -20,6 +21,7 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.ui.about.AppLogDialog import io.legado.app.utils.GSON import io.legado.app.utils.applyTint +import io.legado.app.utils.dpToPx import io.legado.app.utils.isAbsUrl import io.legado.app.utils.openUrl import io.legado.app.utils.printOnDebug @@ -47,47 +49,51 @@ class SourceLoginDialog : BaseBottomSheetDialogFragment(R.layout.dialog_login) { binding.toolBar.title = getString(R.string.login_source, source.getTag()) val loginInfo = source.getLoginInfoMap() val loginUi = source.loginUi() - loginUi?.forEachIndexed { index, rowUi -> - when (rowUi.type) { - RowUi.Type.text -> ItemSourceEditBinding.inflate( - layoutInflater, - binding.root, - false - ).let { - binding.flexbox.addView(it.root) - it.root.id = index + 1000 - it.textInputLayout.hint = rowUi.name - it.editText.setText(loginInfo?.get(rowUi.name)) - } + try { + loginUi?.forEachIndexed { index, rowUi -> + when (rowUi.type) { + RowUi.Type.text -> ItemSourceEditBinding.inflate( + layoutInflater, + binding.root, + false + ).let { + binding.flexbox.addView(it.root) + it.root.id = index + 1000 + it.textInputLayout.hint = rowUi.name + it.editText.setText(loginInfo?.get(rowUi.name)) + } - RowUi.Type.password -> ItemSourceEditBinding.inflate( - layoutInflater, - binding.root, - false - ).let { - binding.flexbox.addView(it.root) - it.root.id = index + 1000 - it.textInputLayout.hint = rowUi.name - it.editText.inputType = - InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT - it.editText.setText(loginInfo?.get(rowUi.name)) - } + RowUi.Type.password -> ItemSourceEditBinding.inflate( + layoutInflater, + binding.root, + false + ).let { + binding.flexbox.addView(it.root) + it.root.id = index + 1000 + it.textInputLayout.hint = rowUi.name + it.editText.inputType = + InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT + it.editText.setText(loginInfo?.get(rowUi.name)) + } - RowUi.Type.button -> ItemFilletTextBinding.inflate( - layoutInflater, - binding.root, - false - ).let { - binding.flexbox.addView(it.root) - rowUi.style().apply(it.root) - it.root.id = index + 1000 - it.textView.text = rowUi.name - - it.root.onClick { - handleButtonClick(source, rowUi, loginUi) + RowUi.Type.button -> ItemFilletTextBinding.inflate( + layoutInflater, + binding.root, + false + ).let { + binding.flexbox.addView(it.root) + rowUi.style().apply(it.root) + it.root.id = index + 1000 + it.textView.text = rowUi.name + it.textView.setPadding(16.dpToPx()) + it.root.onClick { + handleButtonClick(source, rowUi, loginUi) + } } } } + } catch (e: NullPointerException) { + AppLog.put("登录UI JSON 数据错误", e, true) } binding.toolBar.inflateMenu(R.menu.source_login) binding.toolBar.menu.applyTint(requireContext()) diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 332fb2e17..ee3e27ee3 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -147,20 +147,6 @@ open class MainActivity : VMBaseActivity(), toggleSystemBar(AppConfig.showStatusBar) } - override fun dispatchTouchEvent(ev: MotionEvent): Boolean { - if (ev.action == MotionEvent.ACTION_DOWN) { - currentFocus?.let { - if (it.shouldHideSoftInput(ev)) { - it.post { - it.clearFocus() - it.hideSoftInput() - } - } - } - } - return super.dispatchTouchEvent(ev) - } - override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) lifecycleScope.launch { diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index 16d833abd..17540fe17 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -213,28 +213,14 @@ class MainViewModel(application: Application) : BaseViewModel(application) { if (AppConfig.preDownloadNum == 0) return cacheBookJob?.cancel() cacheBookJob = viewModelScope.launch(upTocPool) { - while (isActive) { - if (CacheBookService.isRun || !CacheBook.isRun) { - cacheBookJob?.cancel() - cacheBookJob = null - return@launch + launch { + while (isActive && CacheBook.isRun) { + //有目录更新是不缓存,优先更新目录,现在更多网站限制并发 + CacheBook.setWorkingState(waitUpTocBooks.isEmpty() && onUpTocBooks.isEmpty()) + delay(1000) } - CacheBook.cacheBookMap.forEach { - val cacheBookModel = it.value - while (cacheBookModel.waitCount > 0) { - //有目录更新是不缓存,优先更新目录,现在更多网站限制并发 - if (waitUpTocBooks.isEmpty() - && onUpTocBooks.isEmpty() - && CacheBook.onDownloadCount < threadCount - ) { - cacheBookModel.download(this, upTocPool) - } else { - delay(100) - } - } - } - delay(100) } + CacheBook.startProcessJob(upTocPool) } } diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt index 95f0d69db..418893e3e 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt @@ -116,20 +116,6 @@ class ReplaceRuleActivity : VMBaseActivity= edit.length) { edit.append(text) + } else if (start > end) { + edit.replace(end, start, text) } else { edit.replace(start, end, text) } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index 8ad05afcb..1bd246ffa 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -396,6 +396,8 @@ class RssSourceEditActivity : val edit = view.editableText//获取EditText的文字 if (start < 0 || start >= edit.length) { edit.append(text) + } else if (start > end) { + edit.replace(end, start, text) } else { edit.replace(start, end, text)//光标所在位置插入文字 } diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt index 291aa68ef..3460fb96a 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt @@ -114,20 +114,6 @@ class RssSourceActivity : VMBaseActivity Flow.onEachParallelSafe( try { action(value) } catch (e: Throwable) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() } emit(value) } @@ -65,7 +66,7 @@ inline fun Flow.mapParallelSafe( try { emit(transform(value)) } catch (_: Throwable) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() } } }.buffer(0) @@ -79,7 +80,7 @@ inline fun Flow.transformParallelSafe( try { transform(value) } catch (e: Throwable) { - coroutineContext.ensureActive() + currentCoroutineContext().ensureActive() } } }.buffer(0) diff --git a/app/src/main/res/layout/dialog_webdav_server.xml b/app/src/main/res/layout/dialog_webdav_server.xml index 37fc8ce11..bd4f2b4ba 100644 --- a/app/src/main/res/layout/dialog_webdav_server.xml +++ b/app/src/main/res/layout/dialog_webdav_server.xml @@ -1,31 +1,31 @@ - + android:orientation="vertical"> + + - - - \ No newline at end of file + \ No newline at end of file