Fix main-thread blocking I/O
This commit is contained in:
@@ -78,6 +78,20 @@ public struct ProfileCard: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.sheet(
|
||||
isPresented: $viewModel.showQRSShare,
|
||||
onDismiss: {
|
||||
viewModel.qrsShareData = nil
|
||||
viewModel.qrsShareProfileName = nil
|
||||
},
|
||||
content: {
|
||||
if let data = viewModel.qrsShareData, let name = viewModel.qrsShareProfileName {
|
||||
QRSSheet(profileName: name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
)
|
||||
#else
|
||||
.sheet(isPresented: $viewModel.showNewProfile, onDismiss: {
|
||||
environments.profileUpdate.send()
|
||||
@@ -98,11 +112,20 @@ public struct ProfileCard: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
.sheet(isPresented: $viewModel.showQRSShare) {
|
||||
if let profile = selectedProfile, let data = try? profile.origin.toContent().encode() {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
.sheet(
|
||||
isPresented: $viewModel.showQRSShare,
|
||||
onDismiss: {
|
||||
viewModel.qrsShareData = nil
|
||||
viewModel.qrsShareProfileName = nil
|
||||
},
|
||||
content: {
|
||||
if let data = viewModel.qrsShareData, let name = viewModel.qrsShareProfileName {
|
||||
QRSSheet(profileName: name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.fileExporter(
|
||||
isPresented: $viewModel.showExporter,
|
||||
document: viewModel.exportDocument,
|
||||
@@ -253,12 +276,10 @@ public struct ProfileCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let data = try? profile.origin.toContent().encode() {
|
||||
FormNavigationLink {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "qrcode")
|
||||
}
|
||||
Button {
|
||||
prepareQRSShare(profile)
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "qrcode")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
@@ -301,7 +322,7 @@ public struct ProfileCard: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
viewModel.showQRSShare = true
|
||||
prepareQRSShare(profile)
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "qrcode")
|
||||
}
|
||||
@@ -333,42 +354,52 @@ public struct ProfileCard: View {
|
||||
|
||||
#if !os(tvOS)
|
||||
private func shareProfile(_ profile: ProfilePreview, type: ShareItemType) {
|
||||
do {
|
||||
let url: URL
|
||||
switch type {
|
||||
case .file:
|
||||
url = try profile.origin.toContent().generateShareFile()
|
||||
case .json:
|
||||
url = try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
Task {
|
||||
do {
|
||||
let url: URL
|
||||
switch type {
|
||||
case .file:
|
||||
url = try await profile.origin.generateShareFileAsync()
|
||||
case .json:
|
||||
url = try await profile.origin.generateJSONShareFileAsync(name: "\(profile.name).json")
|
||||
}
|
||||
#if os(iOS)
|
||||
presentShareController(url)
|
||||
#elseif os(macOS)
|
||||
let anchorView = viewModel.shareButtonView ?? NSApp.keyWindow?.contentView ?? NSView()
|
||||
NSSharingServicePicker(items: [url]).show(
|
||||
relativeTo: .zero,
|
||||
of: anchorView,
|
||||
preferredEdge: .minY
|
||||
)
|
||||
#endif
|
||||
} catch {
|
||||
viewModel.alert = AlertState(error: error)
|
||||
}
|
||||
#if os(iOS)
|
||||
presentShareController(url)
|
||||
#elseif os(macOS)
|
||||
let anchorView = viewModel.shareButtonView ?? NSApp.keyWindow?.contentView ?? NSView()
|
||||
NSSharingServicePicker(items: [url]).show(
|
||||
relativeTo: .zero,
|
||||
of: anchorView,
|
||||
preferredEdge: .minY
|
||||
)
|
||||
#endif
|
||||
} catch {
|
||||
viewModel.alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func exportProfile(_ profile: ProfilePreview, type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
let doc = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
viewModel.exportDocument = ProfileAnyExportDocument(profile: doc)
|
||||
case .json:
|
||||
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
|
||||
viewModel.exportDocument = ProfileAnyExportDocument(json: doc)
|
||||
Task {
|
||||
do {
|
||||
let document: ProfileAnyExportDocument
|
||||
switch type {
|
||||
case .file:
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
document = ProfileAnyExportDocument(data: data, filename: "\(profile.name).bpf", contentType: .data)
|
||||
case .json:
|
||||
let content = try await profile.origin.readAsync()
|
||||
document = ProfileAnyExportDocument(data: Data(content.utf8), filename: "\(profile.name).json", contentType: .json)
|
||||
}
|
||||
await MainActor.run {
|
||||
viewModel.exportDocument = document
|
||||
viewModel.showExporter = true
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
viewModel.alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
viewModel.showExporter = true
|
||||
} catch {
|
||||
viewModel.alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +422,20 @@ public struct ProfileCard: View {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private func prepareQRSShare(_ profile: ProfilePreview) {
|
||||
viewModel.qrsShareProfileName = profile.name
|
||||
viewModel.qrsShareData = nil
|
||||
viewModel.showQRSShare = true
|
||||
Task {
|
||||
do {
|
||||
viewModel.qrsShareData = try await profile.origin.encodedContentDataAsync()
|
||||
} catch {
|
||||
viewModel.alert = AlertState(error: error)
|
||||
viewModel.showQRSShare = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func profileInfo(for profile: ProfilePreview) -> some View {
|
||||
HStack(spacing: 8) {
|
||||
@@ -474,6 +519,8 @@ extension ProfileCard {
|
||||
@Published var showProfilePicker = false
|
||||
@Published var showQRCode = false
|
||||
@Published var showQRSShare = false
|
||||
@Published var qrsShareData: Data?
|
||||
@Published var qrsShareProfileName: String?
|
||||
@Published var isUpdating = false
|
||||
@Published var alert: AlertState?
|
||||
@Published var profileToEdit: Profile?
|
||||
|
||||
@@ -529,6 +529,7 @@ private struct ProfilePickerRow: View {
|
||||
@State private var isUpdating = false
|
||||
@State private var showQRCode = false
|
||||
@State private var showQRSShare = false
|
||||
@State private var qrsShareData: Data?
|
||||
#if os(macOS)
|
||||
@State private var shareItemType: ShareItemType?
|
||||
@State private var exportItemType: ExportItemType?
|
||||
@@ -561,6 +562,19 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.sheet(
|
||||
isPresented: $showQRSShare,
|
||||
onDismiss: {
|
||||
qrsShareData = nil
|
||||
},
|
||||
content: {
|
||||
if let data = qrsShareData {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var tvOSNormalBody: some View {
|
||||
@@ -618,12 +632,10 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let data = try? profile.origin.toContent().encode() {
|
||||
FormNavigationLink {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "barcode")
|
||||
}
|
||||
Button {
|
||||
prepareQRSShare()
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "barcode")
|
||||
}
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
@@ -710,11 +722,19 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showQRSShare) {
|
||||
if let data = try? profile.origin.toContent().encode() {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
.sheet(
|
||||
isPresented: $showQRSShare,
|
||||
onDismiss: {
|
||||
qrsShareData = nil
|
||||
},
|
||||
content: {
|
||||
if let data = qrsShareData {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.fileExporter(
|
||||
isPresented: $showExporter,
|
||||
document: exportDocument,
|
||||
@@ -749,11 +769,19 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showQRSShare) {
|
||||
if let data = try? profile.origin.toContent().encode() {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
.sheet(
|
||||
isPresented: $showQRSShare,
|
||||
onDismiss: {
|
||||
qrsShareData = nil
|
||||
},
|
||||
content: {
|
||||
if let data = qrsShareData {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.fileExporter(
|
||||
isPresented: $showExporter,
|
||||
document: exportDocument,
|
||||
@@ -881,6 +909,24 @@ private struct ProfilePickerRow: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
private func prepareQRSShare() {
|
||||
qrsShareData = nil
|
||||
showQRSShare = true
|
||||
Task {
|
||||
do {
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
await MainActor.run {
|
||||
qrsShareData = data
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
showQRSShare = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
@ViewBuilder
|
||||
private var shareMenu: some View {
|
||||
@@ -919,7 +965,7 @@ private struct ProfilePickerRow: View {
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share File", systemImage: "doc")
|
||||
} itemURL: {
|
||||
try profile.origin.toContent().generateShareFile()
|
||||
try await profile.origin.generateShareFileAsync()
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -931,7 +977,7 @@ private struct ProfilePickerRow: View {
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
try await profile.origin.generateJSONShareFileAsync(name: "\(profile.name).json")
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -944,7 +990,7 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
showQRSShare = true
|
||||
prepareQRSShare()
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "barcode")
|
||||
}
|
||||
@@ -954,18 +1000,26 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
|
||||
private func exportProfile(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
let doc = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
exportDocument = ProfileAnyExportDocument(profile: doc)
|
||||
case .json:
|
||||
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
|
||||
exportDocument = ProfileAnyExportDocument(json: doc)
|
||||
Task {
|
||||
do {
|
||||
let document: ProfileAnyExportDocument
|
||||
switch type {
|
||||
case .file:
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
document = ProfileAnyExportDocument(data: data, filename: "\(profile.name).bpf", contentType: .data)
|
||||
case .json:
|
||||
let content = try await profile.origin.readAsync()
|
||||
document = ProfileAnyExportDocument(data: Data(content.utf8), filename: "\(profile.name).json", contentType: .json)
|
||||
}
|
||||
await MainActor.run {
|
||||
exportDocument = document
|
||||
showExporter = true
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
showExporter = true
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -996,38 +1050,52 @@ private struct ProfilePickerRow: View {
|
||||
|
||||
#if os(macOS)
|
||||
private func shareProfile(type: ShareItemType) {
|
||||
do {
|
||||
let url: URL
|
||||
switch type {
|
||||
case .file:
|
||||
url = try profile.origin.toContent().generateShareFile()
|
||||
case .json:
|
||||
url = try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
Task {
|
||||
do {
|
||||
let url: URL
|
||||
switch type {
|
||||
case .file:
|
||||
url = try await profile.origin.generateShareFileAsync()
|
||||
case .json:
|
||||
url = try await profile.origin.generateJSONShareFileAsync(name: "\(profile.name).json")
|
||||
}
|
||||
await MainActor.run {
|
||||
let anchorView = menuAnchorView ?? NSApp.keyWindow?.contentView ?? NSView()
|
||||
NSSharingServicePicker(items: [url]).show(
|
||||
relativeTo: .zero,
|
||||
of: anchorView,
|
||||
preferredEdge: .minY
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
let anchorView = menuAnchorView ?? NSApp.keyWindow?.contentView ?? NSView()
|
||||
NSSharingServicePicker(items: [url]).show(
|
||||
relativeTo: .zero,
|
||||
of: anchorView,
|
||||
preferredEdge: .minY
|
||||
)
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func exportProfileMacOS(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
let doc = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
exportDocument = ProfileAnyExportDocument(profile: doc)
|
||||
case .json:
|
||||
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
|
||||
exportDocument = ProfileAnyExportDocument(json: doc)
|
||||
Task {
|
||||
do {
|
||||
let document: ProfileAnyExportDocument
|
||||
switch type {
|
||||
case .file:
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
document = ProfileAnyExportDocument(data: data, filename: "\(profile.name).bpf", contentType: .data)
|
||||
case .json:
|
||||
let content = try await profile.origin.readAsync()
|
||||
document = ProfileAnyExportDocument(data: Data(content.utf8), filename: "\(profile.name).json", contentType: .json)
|
||||
}
|
||||
await MainActor.run {
|
||||
exportDocument = document
|
||||
showExporter = true
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
showExporter = true
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1135,6 +1203,7 @@ private struct ProfilePickerRow: View {
|
||||
@State private var isUpdating = false
|
||||
@State private var showQRCode = false
|
||||
@State private var showQRSShare = false
|
||||
@State private var qrsShareData: Data?
|
||||
@State private var exportDocument: ProfileAnyExportDocument?
|
||||
@State private var showExporter = false
|
||||
|
||||
@@ -1194,11 +1263,19 @@ private struct ProfilePickerRow: View {
|
||||
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showQRSShare) {
|
||||
if let data = try? profile.origin.toContent().encode() {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
.sheet(
|
||||
isPresented: $showQRSShare,
|
||||
onDismiss: {
|
||||
qrsShareData = nil
|
||||
},
|
||||
content: {
|
||||
if let data = qrsShareData {
|
||||
QRSSheet(profileName: profile.name, profileData: data)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.fileExporter(
|
||||
isPresented: $showExporter,
|
||||
document: exportDocument,
|
||||
@@ -1250,6 +1327,24 @@ private struct ProfilePickerRow: View {
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private func prepareQRSShare() {
|
||||
qrsShareData = nil
|
||||
showQRSShare = true
|
||||
Task {
|
||||
do {
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
await MainActor.run {
|
||||
qrsShareData = data
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
showQRSShare = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var shareMenu: some View {
|
||||
Menu {
|
||||
@@ -1262,7 +1357,7 @@ private struct ProfilePickerRow: View {
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share File", systemImage: "doc")
|
||||
} itemURL: {
|
||||
try profile.origin.toContent().generateShareFile()
|
||||
try await profile.origin.generateShareFileAsync()
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -1274,7 +1369,7 @@ private struct ProfilePickerRow: View {
|
||||
ShareButtonCompat($alert) {
|
||||
Label("Share Content JSON File", systemImage: "curlybraces")
|
||||
} itemURL: {
|
||||
try profile.origin.read().generateShareFile(name: "\(profile.name).json")
|
||||
try await profile.origin.generateJSONShareFileAsync(name: "\(profile.name).json")
|
||||
}
|
||||
|
||||
if profile.type == .remote {
|
||||
@@ -1286,7 +1381,7 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
showQRSShare = true
|
||||
prepareQRSShare()
|
||||
} label: {
|
||||
Label("Share as QRS Code", systemImage: "barcode")
|
||||
}
|
||||
@@ -1296,18 +1391,26 @@ private struct ProfilePickerRow: View {
|
||||
}
|
||||
|
||||
private func exportProfile(type: ExportItemType) {
|
||||
do {
|
||||
switch type {
|
||||
case .file:
|
||||
let doc = try ProfileExportDocument(content: profile.origin.toContent())
|
||||
exportDocument = ProfileAnyExportDocument(profile: doc)
|
||||
case .json:
|
||||
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
|
||||
exportDocument = ProfileAnyExportDocument(json: doc)
|
||||
Task {
|
||||
do {
|
||||
let document: ProfileAnyExportDocument
|
||||
switch type {
|
||||
case .file:
|
||||
let data = try await profile.origin.encodedContentDataAsync()
|
||||
document = ProfileAnyExportDocument(data: data, filename: "\(profile.name).bpf", contentType: .data)
|
||||
case .json:
|
||||
let content = try await profile.origin.readAsync()
|
||||
document = ProfileAnyExportDocument(data: Data(content.utf8), filename: "\(profile.name).json", contentType: .json)
|
||||
}
|
||||
await MainActor.run {
|
||||
exportDocument = document
|
||||
showExporter = true
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
showExporter = true
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user