fix(import): 修复本地 TXT 文件选择与导入

- 书架和文件管理统一使用 TXT 文件选择器,支持大小写扩展名及多选,排除 JSON 书源
- 保留所选文件访问权限至异步导入完成,等待选择器关闭后展示导入确认
- 补充双入口文件选择回归、导入校验和 TXT 测试样本
This commit is contained in:
Aminark
2026-10-22 10:51:09 +08:00
parent 7daa6b5635
commit bf72ebc894
9 changed files with 252 additions and 37 deletions
+17 -24
View File
@@ -146,7 +146,7 @@ private struct BookshelfView: View {
@EnvironmentObject private var library: BookLibraryModel
@EnvironmentObject private var sources: SourceLibraryModel
@State private var showsLocalImporter = false
@State private var pendingLocalURLs: [URL] = []
@State private var pendingLocalFiles: [LocalBookImportFile] = []
@State private var selectedGroupID = BookGroup.idAll
@State private var showsGroupManager = false
@AppStorage("bookshelfLayout") private var bookshelfLayout = "grid3"
@@ -222,26 +222,17 @@ private struct BookshelfView: View {
}
.background(ReadingTheme.canvas)
.navigationBarHidden(true)
.fileImporter(
isPresented: $showsLocalImporter,
allowedContentTypes: [.text, .plainText, .data],
allowsMultipleSelection: true
) { result in
switch result {
case .success(let urls):
pendingLocalURLs = urls.filter { $0.pathExtension.lowercased() == "txt" }
if pendingLocalURLs.isEmpty, !urls.isEmpty {
library.reportError(LocalBookError.unsupportedFormat)
}
case .failure(let error): library.reportError(error)
}
.readingTXTFileImporter(isPresented: $showsLocalImporter) { files in
pendingLocalFiles = files
} onError: { error in
library.reportError(error)
}
.sheet(isPresented: Binding(
get: { !pendingLocalURLs.isEmpty },
set: { if !$0 { pendingLocalURLs = [] } }
get: { !pendingLocalFiles.isEmpty },
set: { if !$0 { pendingLocalFiles = [] } }
)) {
LocalBookImportReviewView(urls: pendingLocalURLs) {
pendingLocalURLs = []
LocalBookImportReviewView(files: pendingLocalFiles) {
pendingLocalFiles = []
}
.environmentObject(library)
}
@@ -646,15 +637,17 @@ private struct BookGroupSelectView: View {
private struct LocalBookImportReviewView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var library: BookLibraryModel
let urls: [URL]
let files: [LocalBookImportFile]
let onFinish: () -> Void
@State private var selectedPaths: Set<String>
@State private var isImporting = false
init(urls: [URL], onFinish: @escaping () -> Void) {
self.urls = urls
private var urls: [URL] { files.map(\.url) }
init(files: [LocalBookImportFile], onFinish: @escaping () -> Void) {
self.files = files
self.onFinish = onFinish
_selectedPaths = State(initialValue: Set(urls.map(\.path)))
_selectedPaths = State(initialValue: Set(files.map { $0.url.path }))
}
var body: some View {
@@ -690,9 +683,9 @@ private struct LocalBookImportReviewView: View {
ToolbarItem(placement: .confirmationAction) {
Button("导入") {
isImporting = true
let selected = urls.filter { selectedPaths.contains($0.path) }
let selected = files.filter { selectedPaths.contains($0.url.path) }
Task {
await library.importLocalFiles(selected)
await library.importPickedLocalFiles(selected)
finish()
}
}