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

修复上传文件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()) 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)
@@ -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(
@@ -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()
onRetry()
delay(1000L)
}
} }
} }
@@ -397,14 +397,14 @@ class BookshelfViewModel(
val uiState: StateFlow<BookshelfUiState> = combine( val uiState: StateFlow<BookshelfUiState> = combine(
dataStateFlow, dataStateFlow,
interactionStateFlow interactionStateFlow,
) { data, interaction -> 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
@@ -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)
} }