Add save file option for profile sharing

Add "Save File" and "Save Content JSON" options to profile share menu,
allowing users to save profiles directly to a chosen location using
fileExporter instead of the system share sheet.
This commit is contained in:
世界
2026-01-01 20:25:28 +08:00
parent 55e01a89b5
commit 313cb5d213
11 changed files with 1674 additions and 26 deletions
@@ -98,6 +98,28 @@ public struct ProfileCard: View {
}
}
#endif
.fileExporter(
isPresented: $viewModel.showProfileExporter,
document: viewModel.profileExportDocument,
contentType: .profile,
defaultFilename: viewModel.profileExportDocument?.filename
) { result in
viewModel.profileExportDocument = nil
if case let .failure(error) = result {
viewModel.alert = AlertState(error: error)
}
}
.fileExporter(
isPresented: $viewModel.showJSONExporter,
document: viewModel.profileJSONExportDocument,
contentType: .json,
defaultFilename: viewModel.profileJSONExportDocument?.filename
) { result in
viewModel.profileJSONExportDocument = nil
if case let .failure(error) = result {
viewModel.alert = AlertState(error: error)
}
}
#endif
.alert($viewModel.alert)
}
@@ -244,12 +266,30 @@ public struct ProfileCard: View {
}
#else
Menu {
Button {
exportProfile(profile, type: .file)
} label: {
Label("Save File", systemImage: "square.and.arrow.down")
}
Button {
viewModel.shareItemType = .file
} label: {
Label("Share File", systemImage: "doc")
}
Button {
exportProfile(profile, type: .json)
} label: {
Label("Save Content JSON", systemImage: "square.and.arrow.down")
}
Button {
viewModel.shareItemType = .json
} label: {
Label("Share Content JSON File", systemImage: "curlybraces")
}
if profile.type == .remote {
Button {
viewModel.showQRCode = true
@@ -257,12 +297,6 @@ public struct ProfileCard: View {
Label("Share URL as QR Code", systemImage: "qrcode")
}
}
Button {
viewModel.shareItemType = .json
} label: {
Label("Share Content JSON File", systemImage: "curlybraces")
}
} label: {
Image(systemName: "square.and.arrow.up")
.font(.system(size: 16))
@@ -312,6 +346,21 @@ public struct ProfileCard: View {
}
}
private func exportProfile(_ profile: ProfilePreview, type: ExportItemType) {
do {
switch type {
case .file:
viewModel.profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
viewModel.showProfileExporter = true
case .json:
viewModel.profileJSONExportDocument = ProfileJSONExportDocument(jsonContent: try profile.origin.read(), name: profile.name)
viewModel.showJSONExporter = true
}
} catch {
viewModel.alert = AlertState(error: error)
}
}
#if os(iOS)
private func presentShareController(_ item: URL) {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
@@ -403,6 +452,11 @@ extension ProfileCard {
case json
}
enum ExportItemType {
case file
case json
}
@MainActor
class ViewModel: ObservableObject {
@Published var showNewProfile = false
@@ -412,6 +466,10 @@ extension ProfileCard {
@Published var alert: AlertState?
@Published var profileToEdit: Profile?
@Published var shareItemType: ShareItemType?
@Published var profileExportDocument: ProfileExportDocument?
@Published var showProfileExporter = false
@Published var profileJSONExportDocument: ProfileJSONExportDocument?
@Published var showJSONExporter = false
#if os(macOS)
var shareButtonView: NSView?
#endif