fix: 修复书源导入、阅读外观和书签状态

- 修复书源文件选择、访问权限与导入预览流程,支持查看分组内的书源
- 修复背景图片、字体选择与主题切换,限制背景图片解码尺寸
- 根据已保存书签恢复当前位置状态,避免重复添加
- 补充模型、阅读器和文件选择回归测试及测试样本
- 更新应用版本为 0.1.10(build 16)

Fixes #1
This commit is contained in:
Aminark
2026-10-20 23:30:42 +08:00
parent 0bf332441d
commit 0915a144a7
17 changed files with 903 additions and 101 deletions
+36 -8
View File
@@ -4,6 +4,36 @@ import LegadoCore
import Foundation
import SwiftUI
final class SourceImportFile: @unchecked Sendable {
let url: URL
private let hasSecurityScope: Bool
init(url: URL) {
self.url = url
hasSecurityScope = url.startAccessingSecurityScopedResource()
}
deinit {
if hasSecurityScope { url.stopAccessingSecurityScopedResource() }
}
func read() throws -> Data {
var coordinationError: NSError?
var result: Result<Data, Error>?
NSFileCoordinator().coordinate(readingItemAt: url, options: [], error: &coordinationError) { readableURL in
result = Result {
guard try readableURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory != true else {
throw BookSourceImportError.invalidDocument("请选择书源 JSON 文件,而不是文件夹。")
}
return try Data(contentsOf: readableURL)
}
}
if let coordinationError { throw coordinationError }
guard let result else { throw CocoaError(.fileReadUnknown) }
return try result.get()
}
}
@MainActor
final class SourceLibraryModel: ObservableObject {
@Published private(set) var sources: [BookSource] = []
@@ -168,16 +198,18 @@ final class SourceLibraryModel: ObservableObject {
}
func previewFiles(_ urls: [URL]) async {
await previewPickedFiles(urls.map { SourceImportFile(url: $0) })
}
func previewPickedFiles(_ files: [SourceImportFile]) async {
isWorking = true
defer { isWorking = false }
do {
var incoming: [String: BookSource] = [:]
var invalidItems: [PendingImport.InvalidItem] = []
for url in urls {
let granted = url.startAccessingSecurityScopedResource()
defer { if granted { url.stopAccessingSecurityScopedResource() } }
for file in files {
let data = try await Task.detached(priority: .userInitiated) { [importer] in
try importer.normalizedData(Data(contentsOf: url))
try importer.normalizedData(file.read())
}.value
var budget = 100
let batch = try await resolveImport(String(decoding: data, as: UTF8.self), depth: 0, visited: [], budget: &budget)
@@ -186,10 +218,6 @@ final class SourceLibraryModel: ObservableObject {
incoming[source.id] = source
}
}
// UIDocumentPicker is still dismissing when its completion runs.
// Presenting the review sheet in that same frame is ignored by
// SwiftUI, so stage it after the picker has left the hierarchy.
try? await Task.sleep(nanoseconds: 300_000_000)
stage(Array(incoming.values), invalidItems: invalidItems)
} catch {
notice = .init(kind: .error, title: "书源导入失败", detail: error.localizedDescription)