mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-07 19:29:57 +02:00
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:
@@ -52,19 +52,28 @@ struct ContactsState: Equatable {
|
||||
final class ContactsModel: ObservableObject {
|
||||
@Published private(set) var state = ContactsState()
|
||||
|
||||
/// Set when the detail screen asks for a file picker; the platform picker
|
||||
/// modifier observes it, mirroring `SendModel`.
|
||||
@Published var pendingFilePick = false
|
||||
/// Device the picked files are destined for.
|
||||
@Published private(set) var sendTarget: String?
|
||||
|
||||
private let repository: CoreGateway
|
||||
private let messages: UiMessageController
|
||||
private let preferences: AppPreferencesRepository
|
||||
private let fileSystemService: FileSystemService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(
|
||||
repository: CoreGateway,
|
||||
messages: UiMessageController,
|
||||
preferences: AppPreferencesRepository
|
||||
preferences: AppPreferencesRepository,
|
||||
fileSystemService: FileSystemService
|
||||
) {
|
||||
self.repository = repository
|
||||
self.messages = messages
|
||||
self.preferences = preferences
|
||||
self.fileSystemService = fileSystemService
|
||||
state.grantLifetime = preferences.preferences.grantLifetime
|
||||
|
||||
repository.signals
|
||||
@@ -247,6 +256,48 @@ final class ContactsModel: ObservableObject {
|
||||
return ticket
|
||||
}
|
||||
|
||||
// MARK: - Sending to a device
|
||||
|
||||
/// Start choosing files to send to a remembered device.
|
||||
func chooseFilesToSend(to endpointId: String) {
|
||||
sendTarget = endpointId
|
||||
pendingFilePick = true
|
||||
}
|
||||
|
||||
func onFilePickFailed(_ reason: String) {
|
||||
sendTarget = nil
|
||||
messages.error(InvitationError.raw(reason))
|
||||
}
|
||||
|
||||
/// Send the picked selection straight to the chosen device.
|
||||
///
|
||||
/// Only the receiving user is prompted; this call returns once they have
|
||||
/// answered, so the button stays busy until then.
|
||||
func onFilesPicked(_ files: [PickedShareFile]) async {
|
||||
guard let endpointId = sendTarget else { return }
|
||||
sendTarget = nil
|
||||
guard !files.isEmpty else { return }
|
||||
|
||||
state.busyEndpoints.insert(endpointId)
|
||||
defer { state.busyEndpoints.remove(endpointId) }
|
||||
|
||||
let result = await fileSystemService.sharePickedFiles(
|
||||
repository: repository,
|
||||
files: files,
|
||||
transferName: files.count == 1 ? files[0].displayName : "",
|
||||
senderName: preferences.preferences.username,
|
||||
destination: .contact(endpointId: endpointId)
|
||||
)
|
||||
await fileSystemService.discardPickedFiles(files)
|
||||
switch result {
|
||||
case .success:
|
||||
messages.tryShow(UiMessage(text: .resource(L10n.Send.transferCreated), tone: .success))
|
||||
await refresh()
|
||||
case .failure(let error):
|
||||
messages.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Management
|
||||
|
||||
func setLabel(endpointId: String, label: String) async {
|
||||
|
||||
@@ -220,7 +220,19 @@ struct ContactDetailScreen: View {
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
|
||||
if !contact.canSend {
|
||||
if contact.canSend {
|
||||
Section {
|
||||
Button {
|
||||
model.chooseFilesToSend(to: endpointId)
|
||||
} label: {
|
||||
Label(
|
||||
String(localized: L10n.Contacts.sendTo),
|
||||
systemSymbol: .paperplane
|
||||
)
|
||||
}
|
||||
.disabled(model.state.busyEndpoints.contains(endpointId))
|
||||
}
|
||||
} else {
|
||||
Section {
|
||||
Label(
|
||||
String(localized: L10n.Contacts.unreachableBody),
|
||||
@@ -247,6 +259,7 @@ struct ContactDetailScreen: View {
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.navigationTitle(Text(contact?.displayName ?? ""))
|
||||
.contactSendPickers(model: model)
|
||||
.onAppear { label = contact?.localLabel ?? "" }
|
||||
.onDisappear { commitLabel() }
|
||||
.confirmationDialog(
|
||||
|
||||
@@ -353,7 +353,7 @@ final class SendModel: ObservableObject {
|
||||
files: current.selectedFiles,
|
||||
transferName: current.transferName.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
senderName: current.senderName.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
accessPolicy: current.accessPolicy
|
||||
destination: .invitation(accessPolicy: current.accessPolicy)
|
||||
)
|
||||
switch result {
|
||||
case .success(let share):
|
||||
|
||||
Reference in New Issue
Block a user