修复加载更多提示无限循环的问题

修复上传文件JSON解析NPE的问题
修复书架数据双重过滤的问题
修复备份恢复重复插入崩溃的问题
修复RSS加载状态缺失问题
This commit is contained in:
HapeLee
2026-05-11 03:28:12 +08:00
parent 27c7ccc10d
commit 5154a59e0d
5 changed files with 81 additions and 28 deletions
@@ -130,13 +130,22 @@ object Restore {
appDb.bookDao.insert(*newBooks.toTypedArray())
}
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 {
appDb.bookGroupDao.insert(*it.toTypedArray())
try {
appDb.bookGroupDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
fileToListT<BookSource>(path, "bookSource.json")?.let {
appDb.bookSourceDao.insert(*it.toTypedArray())
try {
appDb.bookSourceDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
} ?: run {
val bookSourceFile = File(path, "bookSource.json")
if (bookSourceFile.exists()) {
@@ -145,54 +154,92 @@ object Restore {
}
}
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 {
appDb.rssStarDao.insert(*it.toTypedArray())
try {
appDb.rssStarDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
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 {
appDb.searchKeywordDao.insert(*it.toTypedArray())
try {
appDb.searchKeywordDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
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 {
appDb.txtTocRuleDao.insert(*it.toTypedArray())
try {
appDb.txtTocRuleDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
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 {
appDb.dictRuleDao.insert(*it.toTypedArray())
try {
appDb.dictRuleDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
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 {
it.forEach { readRecord ->
//判断是不是本机记录
if (readRecord.deviceId != androidId) {
appDb.readRecordDao.insert(readRecord)
try {
appDb.readRecordDao.insert(readRecord)
} catch (_: SQLiteConstraintException) {
}
} else {
val time = appDb.readRecordDao
.getReadTime(readRecord.deviceId, readRecord.bookName, readRecord.bookAuthor)
if (time == null || time < readRecord.readTime) {
appDb.readRecordDao.insert(readRecord)
try {
appDb.readRecordDao.insert(readRecord)
} catch (_: SQLiteConstraintException) {
}
}
}
}
}
fileToListT<ReadRecordDetail>(path, "readRecordDetail.json")?.let {
it.forEach { detail ->
appDb.readRecordDao.insertDetail(detail)
try {
appDb.readRecordDao.insertDetail(detail)
} catch (_: SQLiteConstraintException) {
}
}
}
fileToListT<ReadRecordSession>(path, "readRecordSession.json")?.let {
it.forEach { session ->
appDb.readRecordDao.insertSession(session)
try {
appDb.readRecordDao.insertSession(session)
} catch (_: SQLiteConstraintException) {
}
}
}
File(path, "servers.json").takeIf {
@@ -203,7 +250,10 @@ object Restore {
json = aes.decryptStr(json)
}
GSON.fromJsonArray<Server>(json).getOrNull()?.let {
appDb.serverDao.insert(*it.toTypedArray())
try {
appDb.serverDao.insert(*it.toTypedArray())
} catch (_: SQLiteConstraintException) {
}
}
}?.onFailure {
AppLog.put("恢复服务器配置出错\n${it.localizedMessage}", it)
@@ -15,6 +15,7 @@ import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.Book
import io.legado.app.exception.NoStackTraceException
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.CacheManager
import io.legado.app.help.ConcurrentRateLimiter
@@ -668,9 +669,10 @@ class AnalyzeUrl(
* 上传文件
*/
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) {
url(urlNoQuery)
val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body).getOrNull()!!
bodyMap.forEach { entry ->
if (entry.value.toString() == "fileRequest") {
bodyMap[entry.key] = mapOf(
@@ -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.GlassTopAppBarDefaults
import io.legado.app.ui.main.bookCoverSharedElementKey
import kotlinx.coroutines.delay
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
@@ -563,10 +562,7 @@ fun LoadMoreFooter(
LaunchedEffect(isLoading, errorMsg, isEnd) {
if (!isLoading && errorMsg == null && !isEnd) {
while (true) {
onRetry()
delay(1000L)
}
onRetry()
}
}
@@ -397,14 +397,14 @@ class BookshelfViewModel(
val uiState: StateFlow<BookshelfUiState> = combine(
dataStateFlow,
interactionStateFlow
) { data, interaction ->
interactionStateFlow,
visibleBooksFlow
) { data, interaction, filteredBooks ->
val books = data.books
val groups = data.groups
val allGroups = data.allGroups
val previews = data.previews
val internal = data.internal
val filteredBooks = filterBooks(books, internal.searchKey, internal.isSearchMode)
val selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
.coerceAtLeast(0)
val currentGroupName = allGroups.firstOrNull { it.groupId == internal.groupId }?.groupName
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -35,6 +36,7 @@ class RssViewModel(
val effects = _effects.asSharedFlow()
init {
_uiState.update { it.copy(isLoading = true) }
initGroupData()
initRssData()
}
@@ -62,7 +64,10 @@ class RssViewModel(
}
.flowOn(IO)
.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)
}