From 1563bf80d67e8e7b3e2f8869ad2109da1e3b5647 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:40:07 +0200 Subject: [PATCH] feat(apple): type-safe SF Symbols via SFSafeSymbols Replaces every stringly-typed SF Symbol name with a compile-time-checked SFSymbol case, mirroring the L10n accessor approach. A mistyped or OS-unavailable symbol is now a build error instead of a silently blank glyph at runtime. Adds the SFSafeSymbols SPM package (project.yml) and migrates all call sites: Image(systemName:)/Label(systemImage:) -> systemSymbol, and the five symbol-carrying view properties (AppDestination.systemSymbol, SettingsRow.icon, AboutPoint.symbol, MethodRow.icon, PolicyOption.icon) flipped from String to SFSymbol end to end. --- apple/VniDrop/App/RootView.swift | 5 +-- .../Features/Approvals/ApprovalModal.swift | 3 +- .../Receive/ReceiveInvitationActions.swift | 11 ++++--- .../Features/Receive/ReceiveScreen.swift | 13 ++++---- apple/VniDrop/Features/Send/SendScreen.swift | 11 ++++--- .../Features/Send/TransferComposer.swift | 17 +++++----- .../Features/Send/TransferDetailsView.swift | 7 ++-- .../Features/Settings/SettingsScreen.swift | 17 +++++----- .../Features/Settings/SettingsSections.swift | 33 ++++++++++--------- apple/VniDrop/UI/Feedback/SnackbarHost.swift | 3 +- .../UI/Navigation/AppDestination.swift | 9 ++--- apple/project.yml | 4 +++ 12 files changed, 74 insertions(+), 59 deletions(-) diff --git a/apple/VniDrop/App/RootView.swift b/apple/VniDrop/App/RootView.swift index eea5b8f..7abbc96 100644 --- a/apple/VniDrop/App/RootView.swift +++ b/apple/VniDrop/App/RootView.swift @@ -1,3 +1,4 @@ +import SFSafeSymbols import SwiftUI /// App root, ported from `App.kt`. Owns the object graph and feature models, wires @@ -111,7 +112,7 @@ struct RootView: View { #if os(macOS) NavigationSplitView { List(AppDestination.allCases, selection: sidebarBinding) { destination in - Label(String(localized: destination.labelKey), systemImage: destination.systemImage) + Label(String(localized: destination.labelKey), systemSymbol: destination.systemSymbol) .tag(destination) } .navigationSplitViewColumnWidth(min: 180, ideal: 200, max: 260) @@ -123,7 +124,7 @@ struct RootView: View { ForEach(AppDestination.allCases) { destination in screen(for: destination, windowClass: windowClass) .tabItem { - Label(String(localized: destination.labelKey), systemImage: destination.systemImage) + Label(String(localized: destination.labelKey), systemSymbol: destination.systemSymbol) } .tag(destination) } diff --git a/apple/VniDrop/Features/Approvals/ApprovalModal.swift b/apple/VniDrop/Features/Approvals/ApprovalModal.swift index c731ef8..53a6e01 100644 --- a/apple/VniDrop/Features/Approvals/ApprovalModal.swift +++ b/apple/VniDrop/Features/Approvals/ApprovalModal.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Non-dismissable receiver-approval modal, presented as a native sheet that can't /// be swiped away. The endpoint id is the trusted identity; display names are @@ -40,7 +41,7 @@ private struct ApprovalSheet: View { let busy = state.respondingIds.contains(request.id) let receiver = request.receiverName ?? request.receiverDeviceName ?? String(localized: L10n.Approval.nearbyDevice) VStack(spacing: 16) { - Image(systemName: "checkmark.shield.fill") + Image(systemSymbol: .checkmarkShieldFill) .font(.system(size: 44)) .foregroundStyle(.tint) .padding(.top, 12) diff --git a/apple/VniDrop/Features/Receive/ReceiveInvitationActions.swift b/apple/VniDrop/Features/Receive/ReceiveInvitationActions.swift index f0b7a21..65f1646 100644 --- a/apple/VniDrop/Features/Receive/ReceiveInvitationActions.swift +++ b/apple/VniDrop/Features/Receive/ReceiveInvitationActions.swift @@ -1,3 +1,4 @@ +import SFSafeSymbols import SwiftUI enum ReceiveMethodAvailability { case available, unavailable, hidden } @@ -27,19 +28,19 @@ struct ReceiveMethodPanel: View { Text(String(localized: L10n.Receive.chooseMethodBody)).foregroundStyle(colors.foregroundLighter) MethodRow( - icon: "doc", titleKey: L10n.Receive.methodFile, descKey: L10n.Receive.methodFileDescription, + icon: .doc, titleKey: L10n.Receive.methodFile, descKey: L10n.Receive.methodFileDescription, availability: actions.fileAvailability ) { actions.pickInvitation { model.onInvitationResult(.invitationFile, $0) } } if actions.qrAvailability != .hidden { MethodRow( - icon: "qrcode.viewfinder", titleKey: L10n.Receive.methodScan, descKey: L10n.Receive.methodScanDescription, + icon: .qrcodeViewfinder, titleKey: L10n.Receive.methodScan, descKey: L10n.Receive.methodScanDescription, availability: actions.qrAvailability ) { actions.scanQrCode { model.onInvitationResult(.qrCode, $0) } } } if actions.nfcAvailability != .hidden { MethodRow( - icon: "wave.3.right", + icon: .wave3Right, titleOverride: model.state.isWaitingForNfc ? String(localized: L10n.Receive.nfcWaiting) : nil, titleKey: L10n.Receive.methodNfc, descKey: L10n.Receive.methodNfcDescription, availability: model.state.isWaitingForNfc ? .unavailable : actions.nfcAvailability @@ -56,7 +57,7 @@ struct ReceiveMethodPanel: View { private struct MethodRow: View { @Environment(\.vniColors) private var colors - let icon: String + let icon: SFSymbol var titleOverride: String? = nil let titleKey: String.LocalizationValue let descKey: String.LocalizationValue @@ -67,7 +68,7 @@ private struct MethodRow: View { let enabled = availability == .available Button(action: onTap) { HStack(spacing: 14) { - Image(systemName: icon).font(.system(size: 22)) + Image(systemSymbol: icon).font(.system(size: 22)) .foregroundStyle(enabled ? colors.brandLink : colors.foregroundLighter) .frame(width: 24) VStack(alignment: .leading, spacing: 3) { diff --git a/apple/VniDrop/Features/Receive/ReceiveScreen.swift b/apple/VniDrop/Features/Receive/ReceiveScreen.swift index d82dc24..1f2c705 100644 --- a/apple/VniDrop/Features/Receive/ReceiveScreen.swift +++ b/apple/VniDrop/Features/Receive/ReceiveScreen.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Receive screen, rebuilt on native SwiftUI. A grouped `List` of received /// transfers with swipe-to-delete, and the acquisition flow as a native sheet. @@ -26,13 +27,13 @@ struct ReceiveScreen: View { .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: model.openAcquisition) { - Label(String(localized: L10n.Button.receiveFiles), systemImage: "plus") + Label(String(localized: L10n.Button.receiveFiles), systemSymbol: .plus) } } if !deletable.isEmpty { ToolbarItem(placement: .primaryAction) { Button(role: .destructive, action: model.requestClearHistory) { - Label(String(localized: L10n.Receive.clearHistory), systemImage: "trash") + Label(String(localized: L10n.Receive.clearHistory), systemSymbol: .trash) } } } @@ -74,7 +75,7 @@ struct ReceiveScreen: View { Button(role: .destructive) { model.requestDeleteHistoryItem(transfer.transferId) } label: { - Label(String(localized: L10n.Button.deleteTransfer), systemImage: "trash") + Label(String(localized: L10n.Button.deleteTransfer), systemSymbol: .trash) } } } @@ -89,12 +90,12 @@ struct ReceiveScreen: View { private var emptyState: some View { ContentUnavailableView { - Label(String(localized: L10n.Receive.emptyTitle), systemImage: "tray.and.arrow.down") + Label(String(localized: L10n.Receive.emptyTitle), systemSymbol: .trayAndArrowDown) } description: { Text(String(localized: L10n.Receive.emptyBody)) } actions: { Button(action: model.openAcquisition) { - Label(String(localized: L10n.Button.receiveFiles), systemImage: "plus") + Label(String(localized: L10n.Button.receiveFiles), systemSymbol: .plus) } .buttonStyle(.borderedProminent) .controlSize(.large) @@ -129,7 +130,7 @@ private struct ReceiveTransferRow: View { var body: some View { HStack(spacing: 12) { - Image(systemName: "doc") + Image(systemSymbol: .doc) .foregroundStyle(.secondary) .frame(width: 40, height: 40) .background(.quaternary, in: RoundedRectangle(cornerRadius: 9)) diff --git a/apple/VniDrop/Features/Send/SendScreen.swift b/apple/VniDrop/Features/Send/SendScreen.swift index 93ddc21..66e0c4d 100644 --- a/apple/VniDrop/Features/Send/SendScreen.swift +++ b/apple/VniDrop/Features/Send/SendScreen.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Send screen, rebuilt on native SwiftUI. A grouped `List` of outgoing transfers, /// with the composer and detail panels as native sheets and delete as an alert. @@ -31,7 +32,7 @@ struct SendScreen: View { .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: model.openComposer) { - Label(String(localized: L10n.Button.createNewTransfer), systemImage: "plus") + Label(String(localized: L10n.Button.createNewTransfer), systemSymbol: .plus) } } } @@ -101,12 +102,12 @@ struct SendScreen: View { private var emptyState: some View { ContentUnavailableView { - Label(String(localized: L10n.Send.emptyTitle), systemImage: "paperplane") + Label(String(localized: L10n.Send.emptyTitle), systemSymbol: .paperplane) } description: { Text(String(localized: L10n.Send.emptyBody)) } actions: { Button(action: model.openComposer) { - Label(String(localized: L10n.Button.createNewTransfer), systemImage: "plus") + Label(String(localized: L10n.Button.createNewTransfer), systemSymbol: .plus) } .buttonStyle(.borderedProminent) .controlSize(.large) @@ -166,7 +167,7 @@ private struct TransferListItem: View { .padding(.top, 2) } } - Image(systemName: "chevron.forward") + Image(systemSymbol: .chevronForward) .font(.footnote.weight(.semibold)).foregroundStyle(.tertiary) } .contentShape(Rectangle()) @@ -181,7 +182,7 @@ struct FileArtwork: View { image.resizable().aspectRatio(contentMode: .fill) .clipShape(RoundedRectangle(cornerRadius: 8)) } else { - Image(systemName: "doc") + Image(systemSymbol: .doc) .font(.system(size: 18)) .foregroundStyle(.secondary) } diff --git a/apple/VniDrop/Features/Send/TransferComposer.swift b/apple/VniDrop/Features/Send/TransferComposer.swift index 4dbd74f..1cbfff7 100644 --- a/apple/VniDrop/Features/Send/TransferComposer.swift +++ b/apple/VniDrop/Features/Send/TransferComposer.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Transfer composer drawer, ported from `feature/send/TransferComposer.kt`. /// Two steps: choose files/folder, then review + name + access policy + share. @@ -27,7 +28,7 @@ struct TransferComposer: View { Text(String(localized: L10n.Send.chooseFileBody)) .font(.subheadline).foregroundStyle(.secondary) VStack(spacing: 14) { - Image(systemName: "doc").font(.system(size: 30)).foregroundStyle(.tint) + Image(systemSymbol: .doc).font(.system(size: 30)).foregroundStyle(.tint) PrimaryButton(title: String(localized: L10n.Button.chooseFiles), action: model.selectFile).fixedSize() QuietButton(title: String(localized: L10n.Button.chooseFolder), action: model.selectFolder) } @@ -57,17 +58,17 @@ struct TransferComposer: View { value: Binding(get: { state.senderName }, set: { model.setSenderName($0) })) Text(String(localized: L10n.Send.accessTitle)).font(.headline) PolicyOption( - icon: "checkmark.shield", titleKey: L10n.Send.accessApproval, descKey: L10n.Send.accessApprovalDescription, + icon: .checkmarkShield, titleKey: L10n.Send.accessApproval, descKey: L10n.Send.accessApprovalDescription, selected: state.accessPolicy == .requireApproval, onTap: { model.setAccessPolicy(.requireApproval) } ) PolicyOption( - icon: "globe", titleKey: L10n.Send.accessAnyone, descKey: L10n.Send.accessAnyoneDescription, + icon: .globe, titleKey: L10n.Send.accessAnyone, descKey: L10n.Send.accessAnyoneDescription, selected: state.accessPolicy == .anyoneWithTransfer, onTap: { model.setAccessPolicy(.anyoneWithTransfer) } ) if state.accessPolicy == .anyoneWithTransfer { - Label(String(localized: L10n.Send.accessAnyoneWarning), systemImage: "exclamationmark.triangle.fill") + Label(String(localized: L10n.Send.accessAnyoneWarning), systemSymbol: .exclamationmarkTriangleFill) .font(.caption).foregroundStyle(.orange) } actions @@ -116,7 +117,7 @@ private struct SelectedFileCard: View { Spacer() if canRemove { Button(role: .destructive, action: onRemove) { - Image(systemName: "trash") + Image(systemSymbol: .trash) } .buttonStyle(.borderless) .tint(.red) @@ -135,7 +136,7 @@ private struct SelectedFileCard: View { } private struct PolicyOption: View { - let icon: String + let icon: SFSymbol let titleKey: String.LocalizationValue let descKey: String.LocalizationValue let selected: Bool @@ -144,7 +145,7 @@ private struct PolicyOption: View { var body: some View { Button(action: onTap) { HStack(spacing: 12) { - Image(systemName: icon) + Image(systemSymbol: icon) .font(.system(size: 20)) .foregroundStyle(selected ? AnyShapeStyle(.tint) : AnyShapeStyle(.secondary)) .frame(width: 22) @@ -153,7 +154,7 @@ private struct PolicyOption: View { Text(String(localized: descKey)).font(.caption).foregroundStyle(.secondary) } Spacer() - Image(systemName: selected ? "checkmark.circle.fill" : "circle") + Image(systemSymbol: selected ? .checkmarkCircleFill : .circle) .foregroundStyle(selected ? AnyShapeStyle(.tint) : AnyShapeStyle(.tertiary)) } .padding(14) diff --git a/apple/VniDrop/Features/Send/TransferDetailsView.swift b/apple/VniDrop/Features/Send/TransferDetailsView.swift index 643d0f6..90d0077 100644 --- a/apple/VniDrop/Features/Send/TransferDetailsView.swift +++ b/apple/VniDrop/Features/Send/TransferDetailsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols import CoreImage.CIFilterBuiltins /// Transfer details + drawer panels, ported from `feature/send/TransferDetails.kt`. @@ -56,7 +57,7 @@ struct TransferDetailsView: View { Button(role: .destructive) { showStopConfirmation = true } label: { - Label(String(localized: L10n.Send.stopSharing), systemImage: "stop.circle") + Label(String(localized: L10n.Send.stopSharing), systemSymbol: .stopCircle) } } } @@ -69,7 +70,7 @@ struct TransferDetailsView: View { .toolbar { ToolbarItem(placement: .primaryAction) { Button(role: .destructive, action: model.requestDeleteTransfer) { - Image(systemName: "trash") + Image(systemSymbol: .trash) } } } @@ -115,7 +116,7 @@ private struct DetailDestination: View { .font(.footnote) .foregroundStyle(.secondary) } - Image(systemName: "chevron.forward") + Image(systemSymbol: .chevronForward) .font(.footnote.weight(.semibold)).foregroundStyle(.tertiary) } .contentShape(Rectangle()) diff --git a/apple/VniDrop/Features/Settings/SettingsScreen.swift b/apple/VniDrop/Features/Settings/SettingsScreen.swift index 099a7a0..ae0723f 100644 --- a/apple/VniDrop/Features/Settings/SettingsScreen.swift +++ b/apple/VniDrop/Features/Settings/SettingsScreen.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Settings screen, rebuilt on a native `Form` with `NavigationStack` push /// navigation. The model stays the source of truth via a derived path binding. @@ -25,21 +26,21 @@ struct SettingsScreen: View { Form { Section { NavigationLink(value: SettingsSection.preferences) { - SettingsRow(icon: "person.crop.circle", title: String(localized: L10n.Preferences.title), value: model.state.username) + SettingsRow(icon: .personCropCircle, title: String(localized: L10n.Preferences.title), value: model.state.username) } NavigationLink(value: SettingsSection.appearance) { - SettingsRow(icon: "sun.max", title: String(localized: L10n.Appearance.title), value: themeModeLabel(model.state.themeMode)) + SettingsRow(icon: .sunMax, title: String(localized: L10n.Appearance.title), value: themeModeLabel(model.state.themeMode)) } } Section { NavigationLink(value: SettingsSection.notifications) { - SettingsRow(icon: "bell", title: String(localized: L10n.Notifications.title), value: nil) + SettingsRow(icon: .bell, title: String(localized: L10n.Notifications.title), value: nil) } NavigationLink(value: SettingsSection.storage) { - SettingsRow(icon: "internaldrive", title: String(localized: L10n.Storage.title), value: nil) + SettingsRow(icon: .internaldrive, title: String(localized: L10n.Storage.title), value: nil) } NavigationLink(value: SettingsSection.about) { - SettingsRow(icon: "info.circle", title: String(localized: L10n.About.title), value: nil) + SettingsRow(icon: .infoCircle, title: String(localized: L10n.About.title), value: nil) } } } @@ -69,7 +70,7 @@ struct SettingsScreen: View { Button { showBugReport = true } label: { - Label(String(localized: L10n.About.bugReport), systemImage: "ladybug") + Label(String(localized: L10n.About.bugReport), systemSymbol: .ladybug) } } } @@ -110,13 +111,13 @@ private struct SettingsSectionContent: View { } struct SettingsRow: View { - let icon: String + let icon: SFSymbol let title: String let value: String? var body: some View { HStack(spacing: 12) { - Image(systemName: icon) + Image(systemSymbol: icon) .foregroundStyle(.tint) .frame(width: 26) Text(title).foregroundStyle(.primary) diff --git a/apple/VniDrop/Features/Settings/SettingsSections.swift b/apple/VniDrop/Features/Settings/SettingsSections.swift index 1cbc173..665bcb9 100644 --- a/apple/VniDrop/Features/Settings/SettingsSections.swift +++ b/apple/VniDrop/Features/Settings/SettingsSections.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Settings section detail views, rebuilt as native `Form` content. Each view is /// placed inside a parent `Form`, so it returns `Section`s / rows directly. @@ -126,24 +127,24 @@ struct AboutSettings: View { } Section(String(localized: L10n.About.isTitle)) { - AboutPoint(L10n.About.isDirect, "paperplane") - AboutPoint(L10n.About.isNoAccount, "person.crop.circle.badge.xmark") - AboutPoint(L10n.About.isInControl, "checkmark.shield") - AboutPoint(L10n.About.isEncrypted, "lock") - AboutPoint(L10n.About.isOpen, "chevron.left.forwardslash.chevron.right") + AboutPoint(L10n.About.isDirect, .paperplane) + AboutPoint(L10n.About.isNoAccount, .personCropCircleBadgeXmark) + AboutPoint(L10n.About.isInControl, .checkmarkShield) + AboutPoint(L10n.About.isEncrypted, .lock) + AboutPoint(L10n.About.isOpen, .chevronLeftForwardslashChevronRight) } Section(String(localized: L10n.About.isntTitle)) { - AboutPoint(L10n.About.isntCloud, "icloud.slash") - AboutPoint(L10n.About.isntSync, "arrow.triangle.2.circlepath") - AboutPoint(L10n.About.isntPublic, "megaphone") + AboutPoint(L10n.About.isntCloud, .icloudSlash) + AboutPoint(L10n.About.isntSync, .arrowTriangle2Circlepath) + AboutPoint(L10n.About.isntPublic, .megaphone) } Section(String(localized: L10n.About.privacyTitle)) { - AboutPoint(L10n.About.privacyCapability, "qrcode") - AboutPoint(L10n.About.privacyDeny, "hand.raised") - AboutPoint(L10n.About.privacyRelay, "antenna.radiowaves.left.and.right") - AboutPoint(L10n.About.privacyLocal, "internaldrive") + AboutPoint(L10n.About.privacyCapability, .qrcode) + AboutPoint(L10n.About.privacyDeny, .handRaised) + AboutPoint(L10n.About.privacyRelay, .antennaRadiowavesLeftAndRight) + AboutPoint(L10n.About.privacyLocal, .internaldrive) } Section(String(localized: L10n.About.title)) { @@ -154,7 +155,7 @@ struct AboutSettings: View { } LabeledContent(String(localized: L10n.About.licenseLabel), value: "Apache 2.0") Link(destination: Self.privacyPolicyURL) { - Label(String(localized: L10n.About.privacyPolicyLabel), systemImage: "hand.raised") + Label(String(localized: L10n.About.privacyPolicyLabel), systemSymbol: .handRaised) } } @@ -205,9 +206,9 @@ struct BugReportSheet: View { /// A bullet-style informational row with an SF Symbol and wrapping localized text. private struct AboutPoint: View { let key: String.LocalizationValue - let symbol: String + let symbol: SFSymbol - init(_ key: String.LocalizationValue, _ symbol: String) { + init(_ key: String.LocalizationValue, _ symbol: SFSymbol) { self.key = key self.symbol = symbol } @@ -218,7 +219,7 @@ private struct AboutPoint: View { .font(.subheadline) .fixedSize(horizontal: false, vertical: true) } icon: { - Image(systemName: symbol).foregroundStyle(.tint) + Image(systemSymbol: symbol).foregroundStyle(.tint) } } } diff --git a/apple/VniDrop/UI/Feedback/SnackbarHost.swift b/apple/VniDrop/UI/Feedback/SnackbarHost.swift index c49ec51..b60a414 100644 --- a/apple/VniDrop/UI/Feedback/SnackbarHost.swift +++ b/apple/VniDrop/UI/Feedback/SnackbarHost.swift @@ -1,4 +1,5 @@ import SwiftUI +import SFSafeSymbols /// Bottom toast host driven by `UiMessageController`, ported from /// `ui/feedback/VniDropSnackbarHost.kt`. Tone drives the accent color; errors get @@ -52,7 +53,7 @@ struct SnackbarHost: View { .buttonStyle(.borderless) } Button(action: dismiss) { - Image(systemName: "xmark") + Image(systemSymbol: .xmark) .font(.footnote.weight(.semibold)) .foregroundStyle(.secondary) .frame(width: 36, height: 36) diff --git a/apple/VniDrop/UI/Navigation/AppDestination.swift b/apple/VniDrop/UI/Navigation/AppDestination.swift index 991ec47..de8804d 100644 --- a/apple/VniDrop/UI/Navigation/AppDestination.swift +++ b/apple/VniDrop/UI/Navigation/AppDestination.swift @@ -1,4 +1,5 @@ import Foundation +import SFSafeSymbols /// Top-level destinations, ported from `ui/navigation/AppDestination.kt`. enum AppDestination: String, CaseIterable, Identifiable { @@ -17,11 +18,11 @@ enum AppDestination: String, CaseIterable, Identifiable { } /// SF Symbol approximating the Compose line icon. - var systemImage: String { + var systemSymbol: SFSymbol { switch self { - case .send: return "paperplane" - case .receive: return "tray.and.arrow.down" - case .settings: return "gearshape" + case .send: return .paperplane + case .receive: return .trayAndArrowDown + case .settings: return .gearshape } } } diff --git a/apple/project.yml b/apple/project.yml index ab0d4c5..ff0b7fb 100644 --- a/apple/project.yml +++ b/apple/project.yml @@ -12,6 +12,9 @@ options: packages: VnidropCore: path: VnidropCore + SFSafeSymbols: + url: https://github.com/SFSafeSymbols/SFSafeSymbols + from: "5.3.0" targets: VniDrop: @@ -48,6 +51,7 @@ targets: CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements dependencies: - package: VnidropCore + - package: SFSafeSymbols - sdk: SystemConfiguration.framework - sdk: Security.framework - sdk: libresolv.tbd