同步上游更新
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package io.legado.app.help.glide.progress
|
||||
|
||||
typealias OnProgressListener = ((isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit)?
|
||||
typealias OnProgressListener = (isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<String, CacheBookModel>()
|
||||
|
||||
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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String>()
|
||||
|
||||
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<Map<String, *>>("$[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("格式不对")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("格式不对")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<String>>("$.sourceUrls")
|
||||
if (!urls.isNullOrEmpty()) {
|
||||
urls.forEach {
|
||||
importSourceUrl(it)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
||||
val source = it.firstOrNull() ?: return@let
|
||||
if (source.sourceUrl.isEmpty()) {
|
||||
throw NoStackTraceException("不是订阅源")
|
||||
}
|
||||
allSources.addAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
mText.isJsonArray() -> {
|
||||
GSON.fromJsonArray<RssSource>(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<List<String>>("$.sourceUrls")
|
||||
if (!urls.isNullOrEmpty()) {
|
||||
urls.forEach {
|
||||
importSourceUrl(it)
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
|
||||
val source = it.firstOrNull() ?: return@let
|
||||
if (source.sourceUrl.isEmpty()) {
|
||||
throw NoStackTraceException("不是订阅源")
|
||||
|
||||
|
||||
}
|
||||
allSources.addAll(it)
|
||||
}
|
||||
}
|
||||
|
||||
mText.isJsonArray() -> {
|
||||
GSON.fromJsonArray<RssSource>(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")) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,9 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
|
||||
notifyItemChanged(it)
|
||||
}
|
||||
observeEvent<String>(EventBus.UP_DOWNLOAD) {
|
||||
notifyItemChanged(it)
|
||||
}
|
||||
observeEvent<String>(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<ActivityCacheBookBinding, CacheViewModel>()
|
||||
}
|
||||
menu?.applyTint(this)
|
||||
}
|
||||
notifyItemChanged(it)
|
||||
}
|
||||
observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->
|
||||
viewModel.cacheChapters[book.bookUrl]?.add(chapter.url)
|
||||
|
||||
@@ -48,20 +48,6 @@ abstract class BaseImportBookActivity<VM : ViewModel> :
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置书籍保存位置
|
||||
*/
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -57,7 +57,6 @@ class InfoConfigDialog : BaseBottomSheetDialogFragment(R.layout.dialog_read_info
|
||||
}
|
||||
}
|
||||
binding.btnPaddingSetting.setOnClickListener {
|
||||
dismissAllowingStateLoss()
|
||||
callBack?.showPaddingConfig()
|
||||
}
|
||||
upTvTipColor()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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("格式不对")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<SearchResult>?, position: Int) {
|
||||
list ?: return
|
||||
viewModel.searchResultList.addAll(list)
|
||||
|
||||
@@ -122,30 +122,37 @@ class BookSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, BookS
|
||||
}
|
||||
initExploreKinds()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initExploreKinds() {
|
||||
lifecycleScope.launch {
|
||||
val exploreKinds = viewModel.bookSource?.exploreKinds()?.filter {
|
||||
!it.url.isNullOrBlank()
|
||||
}
|
||||
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
|
||||
try {
|
||||
val exploreKinds = viewModel.bookSource?.exploreKinds()?.filter {
|
||||
!it.url.isNullOrBlank()
|
||||
}
|
||||
}
|
||||
@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)
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)//光标所在位置插入文字
|
||||
}
|
||||
|
||||
@@ -158,20 +158,6 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
||||
}
|
||||
}
|
||||
|
||||
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 onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.book_source, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
|
||||
@@ -198,17 +198,6 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -147,20 +147,6 @@ open class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
|
||||
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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,6 @@ class ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRu
|
||||
observeGroupData()
|
||||
}
|
||||
|
||||
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 onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.replace_rule, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
|
||||
@@ -203,6 +203,8 @@ class ReplaceEditActivity :
|
||||
val edit = view.editableText
|
||||
if (start < 0 || start >= edit.length) {
|
||||
edit.append(text)
|
||||
} else if (start > end) {
|
||||
edit.replace(end, start, text)
|
||||
} else {
|
||||
edit.replace(start, end, text)
|
||||
}
|
||||
|
||||
@@ -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)//光标所在位置插入文字
|
||||
}
|
||||
|
||||
@@ -114,20 +114,6 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
|
||||
initSelectActionBar()
|
||||
}
|
||||
|
||||
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 onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.rss_source, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import io.legado.app.data.appDb
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
@@ -43,7 +44,7 @@ inline fun <T> Flow<T>.onEachParallelSafe(
|
||||
try {
|
||||
action(value)
|
||||
} catch (e: Throwable) {
|
||||
coroutineContext.ensureActive()
|
||||
currentCoroutineContext().ensureActive()
|
||||
}
|
||||
emit(value)
|
||||
}
|
||||
@@ -65,7 +66,7 @@ inline fun <T, R> Flow<T>.mapParallelSafe(
|
||||
try {
|
||||
emit(transform(value))
|
||||
} catch (_: Throwable) {
|
||||
coroutineContext.ensureActive()
|
||||
currentCoroutineContext().ensureActive()
|
||||
}
|
||||
}
|
||||
}.buffer(0)
|
||||
@@ -79,7 +80,7 @@ inline fun <T, R> Flow<T>.transformParallelSafe(
|
||||
try {
|
||||
transform(value)
|
||||
} catch (e: Throwable) {
|
||||
coroutineContext.ensureActive()
|
||||
currentCoroutineContext().ensureActive()
|
||||
}
|
||||
}
|
||||
}.buffer(0)
|
||||
|
||||
Reference in New Issue
Block a user