mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
feat(apple): add context menus to Send and Receive rows
Send rows get a context menu that acts inline without navigating: Share opens the share panel (QR + delivery) over the list via a dedicated sheet host, Stop sharing (active shares) and Delete transfer run in place, the latter through a new id-based SendModel.deleteTransfer and a list-level confirmation alert. Receive rows get a Delete action mirroring swipe-to-delete (handy on macOS).
This commit is contained in:
@@ -79,6 +79,15 @@ struct ReceiveScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
if transfer.status.isTerminalReceiveHistory {
|
||||
Button(role: .destructive) {
|
||||
model.requestDeleteHistoryItem(transfer.transferId)
|
||||
} label: {
|
||||
Label(String(localized: L10n.Button.deleteTransfer), systemSymbol: .trash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: L10n.Receive.historyTitle))
|
||||
|
||||
@@ -228,6 +228,31 @@ final class SendModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes a transfer by id, independent of the detail selection — used by the
|
||||
/// list context menu so it can act inline without navigating into the detail.
|
||||
func deleteTransfer(id: UInt64) {
|
||||
if state.isDeleting { return }
|
||||
state.isDeleting = true
|
||||
Task {
|
||||
let result = await repository.delete(transferId: id)
|
||||
switch result {
|
||||
case .success:
|
||||
filePreviewRepository.remove(transferId: id)
|
||||
if state.selectedTransferId == id {
|
||||
state.selectedTransferId = nil
|
||||
state.detailPanel = nil
|
||||
state.receiverHistory = []
|
||||
}
|
||||
state.isDeleting = false
|
||||
_ = await repository.refresh()
|
||||
messages.tryShow(UiMessage(text: .resource(L10n.Transfer.deleted), tone: .success))
|
||||
case .failure(let error):
|
||||
state.isDeleting = false
|
||||
messages.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels/refuses a single receiver by responding to its request negatively.
|
||||
/// Uses the core's `respondReceiverRequest` (no backend change); applies to
|
||||
/// receivers that are still pending or accepted.
|
||||
|
||||
@@ -7,6 +7,11 @@ struct SendScreen: View {
|
||||
@ObservedObject var model: SendModel
|
||||
let windowClass: WindowClass
|
||||
|
||||
/// Transfer whose share panel is presented inline from the list context menu.
|
||||
@State private var shareTarget: Transfer?
|
||||
/// Transfer pending an inline (list-level) delete confirmation.
|
||||
@State private var deleteTarget: Transfer?
|
||||
|
||||
private var outgoing: [Transfer] {
|
||||
model.coreState.transfers.filter { $0.direction == .send }
|
||||
}
|
||||
@@ -41,6 +46,18 @@ struct SendScreen: View {
|
||||
detailView(for: transfer)
|
||||
}
|
||||
}
|
||||
// Attached inside the NavigationStack (a different sheet host than the
|
||||
// composer drawer on the outer body, so the two don't clash). Opens the
|
||||
// share panel over the list without navigating into the transfer detail.
|
||||
.adaptiveDrawer(
|
||||
isPresented: Binding(get: { shareTarget != nil }, set: { if !$0 { shareTarget = nil } }),
|
||||
windowClass: windowClass,
|
||||
onDismiss: { shareTarget = nil }
|
||||
) {
|
||||
if let shareTarget {
|
||||
TransferSharePanel(model: model, transfer: shareTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
.adaptiveDrawer(
|
||||
isPresented: Binding(get: { model.state.isComposerOpen }, set: { _ in }),
|
||||
@@ -49,8 +66,24 @@ struct SendScreen: View {
|
||||
) {
|
||||
TransferComposer(model: model, windowClass: windowClass)
|
||||
}
|
||||
.alert(
|
||||
Text(String(localized: L10n.Transfer.deleteTitle)),
|
||||
isPresented: Binding(get: { deleteTarget != nil }, set: { if !$0 { deleteTarget = nil } })
|
||||
) {
|
||||
Button(String(localized: L10n.Button.cancel), role: .cancel) { deleteTarget = nil }
|
||||
Button(String(localized: L10n.Button.deleteTransfer), role: .destructive) {
|
||||
if let target = deleteTarget { model.deleteTransfer(id: target.transferId) }
|
||||
deleteTarget = nil
|
||||
}
|
||||
} message: {
|
||||
if let target = deleteTarget {
|
||||
Text(L10n.Transfer.deleteDescription(
|
||||
transferName: target.transferName ?? String(localized: L10n.Send.newTransferTitle)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The pushed transfer details view, with its detail-panel sheet and delete
|
||||
/// alert attached here so they present from the detail's own context (presenting
|
||||
/// modals from the parent stack while a detail is pushed is unreliable on macOS).
|
||||
@@ -91,6 +124,28 @@ struct SendScreen: View {
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contextMenu {
|
||||
if transfer.ticket != nil {
|
||||
Button {
|
||||
shareTarget = transfer
|
||||
} label: {
|
||||
Label(String(localized: L10n.Transfer.shareTitle), systemSymbol: .squareAndArrowUp)
|
||||
}
|
||||
}
|
||||
if transfer.status == .sharing {
|
||||
Button(role: .destructive) {
|
||||
model.stopSharing(transferId: transfer.transferId)
|
||||
} label: {
|
||||
Label(String(localized: L10n.Send.stopSharing), systemSymbol: .stopCircle)
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
Button(role: .destructive) {
|
||||
deleteTarget = transfer
|
||||
} label: {
|
||||
Label(String(localized: L10n.Button.deleteTransfer), systemSymbol: .trash)
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(String(localized: L10n.Send.transfersTitle))
|
||||
|
||||
Reference in New Issue
Block a user