Просмотр исходного кода

修复加载更多提示无限循环的问题
修复上传文件JSON解析NPE的问题
修复书架数据双重过滤的问题
修复备份恢复重复插入崩溃的问题
修复RSS加载状态缺失问题

HapeLee 6 месяцев назад
Родитель
Сommit
5154a59e0d

+ 68 - 18
app/src/main/java/io/legado/app/help/storage/Restore.kt

@@ -130,13 +130,22 @@ object Restore {
             appDb.bookDao.insert(*newBooks.toTypedArray())
             appDb.bookDao.insert(*newBooks.toTypedArray())
         }
         }
         fileToListT<Bookmark>(path, "bookmark.json")?.let {
         fileToListT<Bookmark>(path, "bookmark.json")?.let {
-            appDb.bookmarkDao.insert(*it.toTypedArray())
+            try {
+                appDb.bookmarkDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<BookGroup>(path, "bookGroup.json")?.let {
         fileToListT<BookGroup>(path, "bookGroup.json")?.let {
-            appDb.bookGroupDao.insert(*it.toTypedArray())
+            try {
+                appDb.bookGroupDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<BookSource>(path, "bookSource.json")?.let {
         fileToListT<BookSource>(path, "bookSource.json")?.let {
-            appDb.bookSourceDao.insert(*it.toTypedArray())
+            try {
+                appDb.bookSourceDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         } ?: run {
         } ?: run {
             val bookSourceFile = File(path, "bookSource.json")
             val bookSourceFile = File(path, "bookSource.json")
             if (bookSourceFile.exists()) {
             if (bookSourceFile.exists()) {
@@ -145,54 +154,92 @@ object Restore {
             }
             }
         }
         }
         fileToListT<RssSource>(path, "rssSources.json")?.let {
         fileToListT<RssSource>(path, "rssSources.json")?.let {
-            appDb.rssSourceDao.insert(*it.toTypedArray())
+            try {
+                appDb.rssSourceDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<RssStar>(path, "rssStar.json")?.let {
         fileToListT<RssStar>(path, "rssStar.json")?.let {
-            appDb.rssStarDao.insert(*it.toTypedArray())
+            try {
+                appDb.rssStarDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
         fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
-            appDb.replaceRuleDao.insert(*it.toTypedArray())
+            try {
+                appDb.replaceRuleDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<SearchKeyword>(path, "searchHistory.json")?.let {
         fileToListT<SearchKeyword>(path, "searchHistory.json")?.let {
-            appDb.searchKeywordDao.insert(*it.toTypedArray())
+            try {
+                appDb.searchKeywordDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<RuleSub>(path, "sourceSub.json")?.let {
         fileToListT<RuleSub>(path, "sourceSub.json")?.let {
-            appDb.ruleSubDao.insert(*it.toTypedArray())
+            try {
+                appDb.ruleSubDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<TxtTocRule>(path, "txtTocRule.json")?.let {
         fileToListT<TxtTocRule>(path, "txtTocRule.json")?.let {
-            appDb.txtTocRuleDao.insert(*it.toTypedArray())
+            try {
+                appDb.txtTocRuleDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<HttpTTS>(path, "httpTTS.json")?.let {
         fileToListT<HttpTTS>(path, "httpTTS.json")?.let {
-            appDb.httpTTSDao.insert(*it.toTypedArray())
+            try {
+                appDb.httpTTSDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<DictRule>(path, "dictRule.json")?.let {
         fileToListT<DictRule>(path, "dictRule.json")?.let {
-            appDb.dictRuleDao.insert(*it.toTypedArray())
+            try {
+                appDb.dictRuleDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<KeyboardAssist>(path, "keyboardAssists.json")?.let {
         fileToListT<KeyboardAssist>(path, "keyboardAssists.json")?.let {
-            appDb.keyboardAssistsDao.insert(*it.toTypedArray())
+            try {
+                appDb.keyboardAssistsDao.insert(*it.toTypedArray())
+            } catch (_: SQLiteConstraintException) {
+            }
         }
         }
         fileToListT<ReadRecord>(path, "readRecord.json")?.let {
         fileToListT<ReadRecord>(path, "readRecord.json")?.let {
             it.forEach { readRecord ->
             it.forEach { readRecord ->
-                //判断是不是本机记录
                 if (readRecord.deviceId != androidId) {
                 if (readRecord.deviceId != androidId) {
-                    appDb.readRecordDao.insert(readRecord)
+                    try {
+                        appDb.readRecordDao.insert(readRecord)
+                    } catch (_: SQLiteConstraintException) {
+                    }
                 } else {
                 } else {
                     val time = appDb.readRecordDao
                     val time = appDb.readRecordDao
                         .getReadTime(readRecord.deviceId, readRecord.bookName, readRecord.bookAuthor)
                         .getReadTime(readRecord.deviceId, readRecord.bookName, readRecord.bookAuthor)
                     if (time == null || time < readRecord.readTime) {
                     if (time == null || time < readRecord.readTime) {
-                        appDb.readRecordDao.insert(readRecord)
+                        try {
+                            appDb.readRecordDao.insert(readRecord)
+                        } catch (_: SQLiteConstraintException) {
+                        }
                     }
                     }
                 }
                 }
             }
             }
         }
         }
         fileToListT<ReadRecordDetail>(path, "readRecordDetail.json")?.let {
         fileToListT<ReadRecordDetail>(path, "readRecordDetail.json")?.let {
             it.forEach { detail ->
             it.forEach { detail ->
-                appDb.readRecordDao.insertDetail(detail)
+                try {
+                    appDb.readRecordDao.insertDetail(detail)
+                } catch (_: SQLiteConstraintException) {
+                }
             }
             }
         }
         }
         fileToListT<ReadRecordSession>(path, "readRecordSession.json")?.let {
         fileToListT<ReadRecordSession>(path, "readRecordSession.json")?.let {
             it.forEach { session ->
             it.forEach { session ->
-                appDb.readRecordDao.insertSession(session)
+                try {
+                    appDb.readRecordDao.insertSession(session)
+                } catch (_: SQLiteConstraintException) {
+                }
             }
             }
         }
         }
         File(path, "servers.json").takeIf {
         File(path, "servers.json").takeIf {
@@ -203,7 +250,10 @@ object Restore {
                 json = aes.decryptStr(json)
                 json = aes.decryptStr(json)
             }
             }
             GSON.fromJsonArray<Server>(json).getOrNull()?.let {
             GSON.fromJsonArray<Server>(json).getOrNull()?.let {
-                appDb.serverDao.insert(*it.toTypedArray())
+                try {
+                    appDb.serverDao.insert(*it.toTypedArray())
+                } catch (_: SQLiteConstraintException) {
+                }
             }
             }
         }?.onFailure {
         }?.onFailure {
             AppLog.put("恢复服务器配置出错\n${it.localizedMessage}", it)
             AppLog.put("恢复服务器配置出错\n${it.localizedMessage}", it)

+ 3 - 1
app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt

@@ -15,6 +15,7 @@ import io.legado.app.constant.AppConst.UA_NAME
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppPattern
 import io.legado.app.data.entities.BaseSource
 import io.legado.app.data.entities.BaseSource
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
+import io.legado.app.exception.NoStackTraceException
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.help.CacheManager
 import io.legado.app.help.CacheManager
 import io.legado.app.help.ConcurrentRateLimiter
 import io.legado.app.help.ConcurrentRateLimiter
@@ -668,9 +669,10 @@ class AnalyzeUrl(
      * 上传文件
      * 上传文件
      */
      */
     suspend fun upload(fileName: String, file: Any, contentType: String): StrResponse {
     suspend fun upload(fileName: String, file: Any, contentType: String): StrResponse {
+        val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body).getOrNull()
+            ?: return getErrStrResponse(NoStackTraceException("请求体不是合法的JSON格式"))
         return getProxyClient(proxy).newCallStrResponse(retry) {
         return getProxyClient(proxy).newCallStrResponse(retry) {
             url(urlNoQuery)
             url(urlNoQuery)
-            val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body).getOrNull()!!
             bodyMap.forEach { entry ->
             bodyMap.forEach { entry ->
                 if (entry.value.toString() == "fileRequest") {
                 if (entry.value.toString() == "fileRequest") {
                     bodyMap[entry.key] = mapOf(
                     bodyMap[entry.key] = mapOf(

+ 1 - 5
app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt

@@ -86,7 +86,6 @@ import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.main.bookCoverSharedElementKey
 import io.legado.app.ui.main.bookCoverSharedElementKey
-import kotlinx.coroutines.delay
 import org.koin.androidx.compose.koinViewModel
 import org.koin.androidx.compose.koinViewModel
 import org.koin.compose.koinInject
 import org.koin.compose.koinInject
 
 
@@ -563,10 +562,7 @@ fun LoadMoreFooter(
 
 
     LaunchedEffect(isLoading, errorMsg, isEnd) {
     LaunchedEffect(isLoading, errorMsg, isEnd) {
         if (!isLoading && errorMsg == null && !isEnd) {
         if (!isLoading && errorMsg == null && !isEnd) {
-            while (true) {
-                onRetry()
-                delay(1000L)
-            }
+            onRetry()
         }
         }
     }
     }
 
 

+ 3 - 3
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -397,14 +397,14 @@ class BookshelfViewModel(
 
 
     val uiState: StateFlow<BookshelfUiState> = combine(
     val uiState: StateFlow<BookshelfUiState> = combine(
         dataStateFlow,
         dataStateFlow,
-        interactionStateFlow
-    ) { data, interaction ->
+        interactionStateFlow,
+        visibleBooksFlow
+    ) { data, interaction, filteredBooks ->
         val books = data.books
         val books = data.books
         val groups = data.groups
         val groups = data.groups
         val allGroups = data.allGroups
         val allGroups = data.allGroups
         val previews = data.previews
         val previews = data.previews
         val internal = data.internal
         val internal = data.internal
-        val filteredBooks = filterBooks(books, internal.searchKey, internal.isSearchMode)
         val selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
         val selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
             .coerceAtLeast(0)
             .coerceAtLeast(0)
         val currentGroupName = allGroups.firstOrNull { it.groupId == internal.groupId }?.groupName
         val currentGroupName = allGroups.firstOrNull { it.groupId == internal.groupId }?.groupName

+ 6 - 1
app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt

@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.flowOn
@@ -35,6 +36,7 @@ class RssViewModel(
     val effects = _effects.asSharedFlow()
     val effects = _effects.asSharedFlow()
 
 
     init {
     init {
+        _uiState.update { it.copy(isLoading = true) }
         initGroupData()
         initGroupData()
         initRssData()
         initRssData()
     }
     }
@@ -62,7 +64,10 @@ class RssViewModel(
             }
             }
             .flowOn(IO)
             .flowOn(IO)
             .onEach { sources ->
             .onEach { sources ->
-                _uiState.update { state -> state.copy(items = sources.toImmutableList()) }
+                _uiState.update { state -> state.copy(items = sources.toImmutableList(), isLoading = false) }
+            }
+            .catch {
+                _uiState.update { it.copy(isLoading = false) }
             }
             }
             .launchIn(viewModelScope)
             .launchIn(viewModelScope)
     }
     }