Browse Source

同步上游更新

HapeLee 11 months ago
parent
commit
15a25eface
39 changed files with 297 additions and 336 deletions
  1. 1 0
      app/src/main/java/io/legado/app/constant/EventBus.kt
  2. 2 1
      app/src/main/java/io/legado/app/data/entities/HttpTTS.kt
  3. 2 2
      app/src/main/java/io/legado/app/data/repository/ReadRecordRepository.kt
  4. 1 1
      app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt
  5. 3 3
      app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt
  6. 67 9
      app/src/main/java/io/legado/app/model/CacheBook.kt
  7. 1 1
      app/src/main/java/io/legado/app/model/ReadBook.kt
  8. 1 1
      app/src/main/java/io/legado/app/model/ReadManga.kt
  9. 5 17
      app/src/main/java/io/legado/app/service/CacheBookService.kt
  10. 26 20
      app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt
  11. 7 0
      app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt
  12. 7 0
      app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt
  13. 8 0
      app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt
  14. 48 32
      app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt
  15. 5 0
      app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt
  16. 7 0
      app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt
  17. 3 1
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  18. 0 14
      app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt
  19. 2 1
      app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt
  20. 0 14
      app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt
  21. 0 1
      app/src/main/java/io/legado/app/ui/book/read/config/InfoConfigDialog.kt
  22. 0 3
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  23. 1 1
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
  24. 0 38
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
  25. 1 1
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt
  26. 0 14
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  27. 26 19
      app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt
  28. 2 0
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  29. 0 14
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  30. 0 11
      app/src/main/java/io/legado/app/ui/book/toc/TocActivity.kt
  31. 42 36
      app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt
  32. 0 14
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  33. 6 20
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  34. 0 14
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
  35. 2 0
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt
  36. 2 0
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  37. 0 14
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  38. 4 3
      app/src/main/java/io/legado/app/utils/FlowExtensions.kt
  39. 15 16
      app/src/main/res/layout/dialog_webdav_server.xml

+ 1 - 0
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"

+ 2 - 1
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()
                 )
             }
         }

+ 2 - 2
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
     }

+ 1 - 1
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)?
+typealias OnProgressListener = (isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit

+ 3 - 3
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
         }

+ 67 - 9
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<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(

+ 1 - 1
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 {

+ 1 - 1
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 {

+ 5 - 17
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()
         }
     }
 

+ 26 - 20
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<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("格式不对")
         }
     }

+ 7 - 0
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))
         }
     }

+ 7 - 0
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))
         }
     }

+ 8 - 0
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("格式不对")
         }
     }

+ 48 - 32
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,47 +105,59 @@ 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)
+            importSourceAwait(text)
+        }.onError {
+            errorLiveData.postValue("ImportError:${it.localizedMessage}")
+            AppLog.put("ImportError:${it.localizedMessage}", it)
+        }.onSuccess {
+            comparisonSource()
+        }
+    }
+
+    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("不是订阅源")
+
 
-                mText.isJsonArray() -> {
-                    GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
-                        val source = it.firstOrNull() ?: return@let
-                        if (source.sourceUrl.isEmpty()) {
-                            throw NoStackTraceException("不是订阅源")
-                        }
-                        allSources.addAll(it)
                     }
+                    allSources.addAll(it)
                 }
+            }
 
-                mText.isAbsUrl() -> {
-                    importSourceUrl(mText)
+            mText.isJsonArray() -> {
+                GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {
+                    val source = it.firstOrNull() ?: return@let
+                    if (source.sourceUrl.isEmpty()) {
+                        throw NoStackTraceException("不是订阅源")
+                    }
+                    allSources.addAll(it)
                 }
+            }
 
-                else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
+            mText.isAbsUrl() -> {
+                importSourceUrl(mText)
             }
-        }.onError {
-            errorLiveData.postValue("ImportError:${it.localizedMessage}")
-            AppLog.put("ImportError:${it.localizedMessage}", it)
-        }.onSuccess {
-            comparisonSource()
+
+            mText.isUri() -> {
+                importSourceAwait(mText.toUri().readText(appCtx))
+            }
+
+            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
+
+
         }
     }
 

+ 5 - 0
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))
         }
     }

+ 7 - 0
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))
         }
     }

+ 3 - 1
app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt

@@ -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)

+ 0 - 14
app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt

@@ -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)
-    }
-
     /**
      * 设置书籍保存位置
      */

+ 2 - 1
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() {

+ 0 - 14
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) {

+ 0 - 1
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()

+ 0 - 3
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

+ 1 - 1
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()) {

+ 0 - 38
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("格式不对")
-            }
-        }
-    }
-
 }

+ 1 - 1
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)

+ 0 - 14
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<SearchResult>?, position: Int) {
         list ?: return
         viewModel.searchResultList.addAll(list)

+ 26 - 19
app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt

@@ -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()
             }
         }
     }

+ 2 - 0
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)//光标所在位置插入文字
             }

+ 0 - 14
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt

@@ -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)

+ 0 - 11
app/src/main/java/io/legado/app/ui/book/toc/TocActivity.kt

@@ -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

+ 42 - 36
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))
-                }
-
-                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))
-                }
+        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.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
+                    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))
+                    }
 
-                    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())

+ 0 - 14
app/src/main/java/io/legado/app/ui/main/MainActivity.kt

@@ -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 {

+ 6 - 20
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)
         }
     }
 

+ 0 - 14
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt

@@ -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)

+ 2 - 0
app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt

@@ -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)
             }

+ 2 - 0
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)//光标所在位置插入文字
             }

+ 0 - 14
app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt

@@ -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)

+ 4 - 3
app/src/main/java/io/legado/app/utils/FlowExtensions.kt

@@ -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)

+ 15 - 16
app/src/main/res/layout/dialog_webdav_server.xml

@@ -1,31 +1,31 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:padding="16dp">
+    android:orientation="vertical">
+
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/tool_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="3dp"
+        android:elevation="5dp"
+        android:theme="?attr/actionBarStyle"
+        app:title="@string/server_config"
+        app:displayHomeAsUp="false"
+        app:fitStatusBar="false" />
 
     <LinearLayout
         android:id="@+id/vw_bg"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:padding="16dp"
         android:layout_gravity="center"
-        android:background="@drawable/shape_card_view"
         android:orientation="vertical"
         tools:ignore="UselessParent">
 
-        <androidx.appcompat.widget.Toolbar
-            android:id="@+id/tool_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="3dp"
-            android:elevation="5dp"
-            android:theme="?attr/actionBarStyle"
-            app:title="@string/server_config"
-            app:displayHomeAsUp="false"
-            app:fitStatusBar="false" />
-
         <io.legado.app.ui.widget.text.TextInputLayout
             android:id="@+id/til_name"
             android:layout_width="match_parent"
@@ -73,7 +73,6 @@
                 android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
                 android:clipToPadding="false"
-                android:padding="3dp"
                 app:dividerDrawable="@drawable/shape_space_divider"
                 app:flexDirection="row"
                 app:flexWrap="wrap"
@@ -82,4 +81,4 @@
         </androidx.core.widget.NestedScrollView>
 
     </LinearLayout>
-</FrameLayout>
+</LinearLayout>