mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-24 12:51:22 +08:00
fix(import): 修复本地 TXT 文件选择与导入
- 书架和文件管理统一使用 TXT 文件选择器,支持大小写扩展名及多选,排除 JSON 书源 - 保留所选文件访问权限至异步导入完成,等待选择器关闭后展示导入确认 - 补充双入口文件选择回归、导入校验和 TXT 测试样本
This commit is contained in:
@@ -26,9 +26,9 @@ private final class FileManagementModel: ObservableObject {
|
||||
await reload(library)
|
||||
}
|
||||
|
||||
func importFiles(_ urls: [URL], library: BookLibraryModel) async {
|
||||
func importFiles(_ files: [LocalBookImportFile], library: BookLibraryModel) async {
|
||||
await run(library) {
|
||||
await library.importLocalFiles(urls)
|
||||
await library.importPickedLocalFiles(files)
|
||||
errorMessage = library.errorMessage
|
||||
library.clearError()
|
||||
}
|
||||
@@ -136,6 +136,7 @@ struct FileManagementView: View {
|
||||
}
|
||||
}
|
||||
} label: { Image(systemName: "ellipsis.circle") }
|
||||
.accessibilityLabel("更多")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,11 +163,10 @@ struct FileManagementView: View {
|
||||
|
||||
private var importAndRenameView: some View {
|
||||
fileList
|
||||
.fileImporter(isPresented: $importsFile, allowedContentTypes: [.text, .plainText, .data], allowsMultipleSelection: true) { result in
|
||||
switch result {
|
||||
case .success(let urls): Task { await model.importFiles(urls, library: library) }
|
||||
case .failure(let error): model.errorMessage = error.localizedDescription
|
||||
}
|
||||
.readingTXTFileImporter(isPresented: $importsFile) { files in
|
||||
Task { await model.importFiles(files, library: library) }
|
||||
} onError: { error in
|
||||
model.errorMessage = error.localizedDescription
|
||||
}
|
||||
.alert("重命名文件", isPresented: Binding(get: { renameURL != nil }, set: { if !$0 { renameURL = nil } }), presenting: renameURL) { url in
|
||||
TextField("文件名", text: $newName)
|
||||
|
||||
@@ -4,6 +4,20 @@ import SwiftUI
|
||||
import LegadoCore
|
||||
#endif
|
||||
|
||||
final class LocalBookImportFile: @unchecked Sendable {
|
||||
let url: URL
|
||||
private let hasSecurityScope: Bool
|
||||
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
hasSecurityScope = url.startAccessingSecurityScopedResource()
|
||||
}
|
||||
|
||||
deinit {
|
||||
if hasSecurityScope { url.stopAccessingSecurityScopedResource() }
|
||||
}
|
||||
}
|
||||
|
||||
enum AppBookChapterRepository {
|
||||
static func live() -> any BookChapterRepository {
|
||||
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
@@ -318,10 +332,15 @@ final class BookLibraryModel: ObservableObject {
|
||||
}
|
||||
|
||||
func importLocalFiles(_ urls: [URL]) async {
|
||||
await importPickedLocalFiles(urls.map { LocalBookImportFile(url: $0) })
|
||||
}
|
||||
|
||||
func importPickedLocalFiles(_ files: [LocalBookImportFile]) async {
|
||||
// Keep the picker grants alive through the review and asynchronous copy.
|
||||
defer { withExtendedLifetime(files) {} }
|
||||
var failures: [String] = []
|
||||
for url in urls {
|
||||
let granted = url.startAccessingSecurityScopedResource()
|
||||
defer { if granted { url.stopAccessingSecurityScopedResource() } }
|
||||
for file in files {
|
||||
let url = file.url
|
||||
do {
|
||||
let stored = try await localFiles.importFile(from: url)
|
||||
do {
|
||||
|
||||
Reference in New Issue
Block a user