feat(apple): send files to a remembered device

Adds the Send files action to the device detail, routing the picked
selection through sendToContact.

Rather than a second picker path, sharePickedFiles now takes a
ShareDestination, so the macOS security-scoped access handling covers both
routes. A contact destination carries no access policy, matching the core's
rule that an offer share is never public.
This commit is contained in:
2026-08-06 18:21:44 +02:00
parent dc23e87c56
commit 268fbf161d
11 changed files with 218 additions and 17 deletions

View File

@@ -70,6 +70,33 @@ struct SendPickers: ViewModifier {
}
}
/// File picker for "send to this device", reusing the share picker's selection
/// handling so security-scoped bookmarks are captured the same way.
struct ContactSendPickers: ViewModifier {
@ObservedObject var model: ContactsModel
func body(content: Content) -> some View {
content
.fileImporter(
isPresented: $model.pendingFilePick,
allowedContentTypes: [.item],
allowsMultipleSelection: true
) { result in
switch result {
case .success(let urls):
let files = urls.compactMap { PickerSupport.pickedFile(from: $0, isDirectory: false) }
if files.isEmpty {
model.onFilePickFailed("The selected document could not be opened")
} else {
Task { await model.onFilesPicked(files) }
}
case .failure(let error):
if !error.isUserCancellation { model.onFilePickFailed(error.technicalDetail) }
}
}
}
}
enum PickerSupport {
static func receiveFolder(from url: URL) -> ReceiveFolder {
#if os(iOS)
@@ -129,4 +156,8 @@ extension View {
func sendPickers(model: SendModel) -> some View {
modifier(SendPickers(model: model))
}
func contactSendPickers(model: ContactsModel) -> some View {
modifier(ContactSendPickers(model: model))
}
}