mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Add AirDrop detection
This commit is contained in:
1 parent
9de9ebb4ba
commit
d98be9c23c
15 files changed
+9183
-6496
No files matched your search
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
DerivedData/
|
DerivedData/
|
||||||
.DerivedData/
|
.DerivedData/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
xcuserdata/
|
xcuserdata/
|
||||||
|
/research/
|
||||||
@@ -28,6 +28,15 @@
|
|||||||
A1F5B3DC2F3A1A0100A1B2C7 /* PairingToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D82F3A1A0100A1B2C3 /* PairingToken.swift */; };
|
A1F5B3DC2F3A1A0100A1B2C7 /* PairingToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D82F3A1A0100A1B2C3 /* PairingToken.swift */; };
|
||||||
A1F5B3DD2F3A1A0100A1B2C8 /* PairingQrSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */; };
|
A1F5B3DD2F3A1A0100A1B2C8 /* PairingQrSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */; };
|
||||||
A1F5B3DE2F3A1A0100A1B2C9 /* PairingQrSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */; };
|
A1F5B3DE2F3A1A0100A1B2C9 /* PairingQrSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */; };
|
||||||
|
A3D000000000000000000202 /* BluetoothPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D000000000000000000002 /* BluetoothPermissionManager.swift */; };
|
||||||
|
A3D000000000000000000203 /* BluetoothPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D000000000000000000002 /* BluetoothPermissionManager.swift */; };
|
||||||
|
A6D000000000000000000101 /* AndroidAirDropModeDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D000000000000000000001 /* AndroidAirDropModeDetector.swift */; };
|
||||||
|
A6D000000000000000000102 /* AndroidAirDropModeDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D000000000000000000001 /* AndroidAirDropModeDetector.swift */; };
|
||||||
|
A6D000000000000000000201 /* AndroidAirDropModeDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D000000000000000000002 /* AndroidAirDropModeDetectorTests.swift */; };
|
||||||
|
A7D000000000000000000101 /* AndroidAirDropModeIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */; };
|
||||||
|
A7D000000000000000000102 /* AndroidAirDropModeIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */; };
|
||||||
|
A7D000000000000000000103 /* AndroidAirDropModeIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */; };
|
||||||
|
A7D000000000000000000104 /* AndroidAirDropModeIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */; };
|
||||||
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||||
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||||
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||||
@@ -382,6 +391,13 @@
|
|||||||
remoteGlobalIDString = 699DEBA02AB0573200115D22;
|
remoteGlobalIDString = 699DEBA02AB0573200115D22;
|
||||||
remoteInfo = ShareExtension;
|
remoteInfo = ShareExtension;
|
||||||
};
|
};
|
||||||
|
E14627D44A6E1CA296A9A53D /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 69DA9A0D29E0BF5100A442DA;
|
||||||
|
remoteInfo = QuickDrop;
|
||||||
|
};
|
||||||
EB3B9F8C2EF7153000D6704E /* PBXContainerItemProxy */ = {
|
EB3B9F8C2EF7153000D6704E /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
||||||
@@ -495,6 +511,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
156115A05E122702111639A0 /* QuickDropTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QuickDropTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
691F53B92ABB70840089FD92 /* DeviceListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListCell.swift; sourceTree = "<group>"; };
|
691F53B92ABB70840089FD92 /* DeviceListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListCell.swift; sourceTree = "<group>"; };
|
||||||
691F53BA2ABB70840089FD92 /* DeviceListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceListCell.xib; sourceTree = "<group>"; };
|
691F53BA2ABB70840089FD92 /* DeviceListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceListCell.xib; sourceTree = "<group>"; };
|
||||||
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundNearbyConnection.swift; sourceTree = "<group>"; };
|
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundNearbyConnection.swift; sourceTree = "<group>"; };
|
||||||
@@ -523,6 +540,10 @@
|
|||||||
A1C0DE152F4C000100ABC001 /* ClipboardSyncNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardSyncNotificationPresenter.swift; sourceTree = "<group>"; };
|
A1C0DE152F4C000100ABC001 /* ClipboardSyncNotificationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardSyncNotificationPresenter.swift; sourceTree = "<group>"; };
|
||||||
A1F5B3D82F3A1A0100A1B2C3 /* PairingToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingToken.swift; sourceTree = "<group>"; };
|
A1F5B3D82F3A1A0100A1B2C3 /* PairingToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingToken.swift; sourceTree = "<group>"; };
|
||||||
A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingQrSheetManager.swift; sourceTree = "<group>"; };
|
A1F5B3D92F3A1A0100A1B2C4 /* PairingQrSheetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingQrSheetManager.swift; sourceTree = "<group>"; };
|
||||||
|
A3D000000000000000000002 /* BluetoothPermissionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothPermissionManager.swift; sourceTree = "<group>"; };
|
||||||
|
A6D000000000000000000001 /* AndroidAirDropModeDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidAirDropModeDetector.swift; sourceTree = "<group>"; };
|
||||||
|
A6D000000000000000000002 /* AndroidAirDropModeDetectorTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AndroidAirDropModeDetectorTests.swift; sourceTree = "<group>"; };
|
||||||
|
A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AndroidAirDropModeIssueView.swift; sourceTree = "<group>"; };
|
||||||
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; };
|
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; };
|
||||||
EB0A22E22FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyBonjourServiceResolver.swift; sourceTree = "<group>"; };
|
EB0A22E22FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyBonjourServiceResolver.swift; sourceTree = "<group>"; };
|
||||||
EB1325D22E701D8900FD749C /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
EB1325D22E701D8900FD749C /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||||
@@ -595,6 +616,13 @@
|
|||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
F5F5F63DAC90375051BA9F4E /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
699DEB9E2AB0573200115D22 /* Frameworks */ = {
|
699DEB9E2AB0573200115D22 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -713,6 +741,7 @@
|
|||||||
EBC05C8B2E33B59E00EECC18 /* Resources */,
|
EBC05C8B2E33B59E00EECC18 /* Resources */,
|
||||||
EB3B9F492EF7150400D6704E /* QuickDrop copy-Info.plist */,
|
EB3B9F492EF7150400D6704E /* QuickDrop copy-Info.plist */,
|
||||||
EB3B9F8A2EF7151900D6704E /* QuickDropShareExtension copy-Info.plist */,
|
EB3B9F8A2EF7151900D6704E /* QuickDropShareExtension copy-Info.plist */,
|
||||||
|
9E6A957675D279417AA88926 /* QuickDropTests */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -725,6 +754,7 @@
|
|||||||
EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */,
|
EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */,
|
||||||
EB3B9F482EF7150400D6704E /* QuickDropGitHub.app */,
|
EB3B9F482EF7150400D6704E /* QuickDropGitHub.app */,
|
||||||
EB3B9F892EF7151900D6704E /* QuickDropShareExtensionGitHub.appex */,
|
EB3B9F892EF7151900D6704E /* QuickDropShareExtensionGitHub.appex */,
|
||||||
|
156115A05E122702111639A0 /* QuickDropTests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -770,6 +800,14 @@
|
|||||||
path = NearbyShare;
|
path = NearbyShare;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9E6A957675D279417AA88926 /* QuickDropTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A6D000000000000000000002 /* AndroidAirDropModeDetectorTests.swift */,
|
||||||
|
);
|
||||||
|
path = QuickDropTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A1F5B3DA2F3A1A0100A1B2C5 /* Pairing */ = {
|
A1F5B3DA2F3A1A0100A1B2C5 /* Pairing */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -944,6 +982,7 @@
|
|||||||
EBC05C6B2E33B57100EECC18 /* Util */ = {
|
EBC05C6B2E33B57100EECC18 /* Util */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A3D000000000000000000002 /* BluetoothPermissionManager.swift */,
|
||||||
A1C0DE102F4C000100ABC001 /* ClipboardSyncCoordinator.swift */,
|
A1C0DE102F4C000100ABC001 /* ClipboardSyncCoordinator.swift */,
|
||||||
A1C0DE152F4C000100ABC001 /* ClipboardSyncNotificationPresenter.swift */,
|
A1C0DE152F4C000100ABC001 /* ClipboardSyncNotificationPresenter.swift */,
|
||||||
EB1325F72E70287C00FD749C /* SendToExtension.swift */,
|
EB1325F72E70287C00FD749C /* SendToExtension.swift */,
|
||||||
@@ -952,6 +991,7 @@
|
|||||||
EB6FE7D12EFEFB2700FF2BCF /* AllowedWorkMonitor.swift */,
|
EB6FE7D12EFEFB2700FF2BCF /* AllowedWorkMonitor.swift */,
|
||||||
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */,
|
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */,
|
||||||
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */,
|
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */,
|
||||||
|
A6D000000000000000000001 /* AndroidAirDropModeDetector.swift */,
|
||||||
EB1325EA2E7025D900FD749C /* SelfTest */,
|
EB1325EA2E7025D900FD749C /* SelfTest */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
@@ -964,6 +1004,7 @@
|
|||||||
EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */,
|
EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */,
|
||||||
EBC05C7F2E33B58300EECC18 /* IssueView.swift */,
|
EBC05C7F2E33B58300EECC18 /* IssueView.swift */,
|
||||||
EBC05C802E33B58300EECC18 /* NetworkFilterIssueView.swift */,
|
EBC05C802E33B58300EECC18 /* NetworkFilterIssueView.swift */,
|
||||||
|
A7D000000000000000000001 /* AndroidAirDropModeIssueView.swift */,
|
||||||
);
|
);
|
||||||
path = IssueViews;
|
path = IssueViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -981,6 +1022,24 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
32A98F34E7D10C21DC2EEA19 /* QuickDropTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = B473F6C65C8455203EA5FD18 /* Build configuration list for PBXNativeTarget "QuickDropTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
BECDC1522AF83179791FA522 /* Sources */,
|
||||||
|
F5F5F63DAC90375051BA9F4E /* Frameworks */,
|
||||||
|
36CE9ED35EE7975FAB802535 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
C4F698F843B0C498BFC216AB /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = QuickDropTests;
|
||||||
|
productName = QuickDropTests;
|
||||||
|
productReference = 156115A05E122702111639A0 /* QuickDropTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
699DEBA02AB0573200115D22 /* QuickDropShareExtension */ = {
|
699DEBA02AB0573200115D22 /* QuickDropShareExtension */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "QuickDropShareExtension" */;
|
buildConfigurationList = 699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "QuickDropShareExtension" */;
|
||||||
@@ -1137,6 +1196,9 @@
|
|||||||
LastSwiftUpdateCheck = 1640;
|
LastSwiftUpdateCheck = 1640;
|
||||||
LastUpgradeCheck = 2600;
|
LastUpgradeCheck = 2600;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
32A98F34E7D10C21DC2EEA19 = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
};
|
||||||
699DEBA02AB0573200115D22 = {
|
699DEBA02AB0573200115D22 = {
|
||||||
CreatedOnToolsVersion = 14.3.1;
|
CreatedOnToolsVersion = 14.3.1;
|
||||||
};
|
};
|
||||||
@@ -1196,11 +1258,19 @@
|
|||||||
EB950B2C2E52027F002D68FA /* iOSShareExtension */,
|
EB950B2C2E52027F002D68FA /* iOSShareExtension */,
|
||||||
EB3B9EF02EF7150400D6704E /* QuickDropGitHub */,
|
EB3B9EF02EF7150400D6704E /* QuickDropGitHub */,
|
||||||
EB3B9F4A2EF7151900D6704E /* QuickDropShareExtensionGitHub */,
|
EB3B9F4A2EF7151900D6704E /* QuickDropShareExtensionGitHub */,
|
||||||
|
32A98F34E7D10C21DC2EEA19 /* QuickDropTests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
36CE9ED35EE7975FAB802535 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
699DEB9F2AB0573200115D22 /* Resources */ = {
|
699DEB9F2AB0573200115D22 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -1300,6 +1370,14 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
BECDC1522AF83179791FA522 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
A6D000000000000000000201 /* AndroidAirDropModeDetectorTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
699DEB9D2AB0573200115D22 /* Sources */ = {
|
699DEB9D2AB0573200115D22 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -1312,6 +1390,7 @@
|
|||||||
EBE5FF432EDB71810019B380 /* Configuration.swift in Sources */,
|
EBE5FF432EDB71810019B380 /* Configuration.swift in Sources */,
|
||||||
EB6FE7D42EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
EB6FE7D42EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
||||||
EBC05C872E33B58300EECC18 /* IssueView.swift in Sources */,
|
EBC05C872E33B58300EECC18 /* IssueView.swift in Sources */,
|
||||||
|
A7D000000000000000000103 /* AndroidAirDropModeIssueView.swift in Sources */,
|
||||||
EBC05C882E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
|
EBC05C882E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
|
||||||
EBC05C892E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
|
EBC05C892E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
|
||||||
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */,
|
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */,
|
||||||
@@ -1363,6 +1442,8 @@
|
|||||||
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||||
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
|
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
|
||||||
EB1325D42E701D8E00FD749C /* Settings.swift in Sources */,
|
EB1325D42E701D8E00FD749C /* Settings.swift in Sources */,
|
||||||
|
A3D000000000000000000202 /* BluetoothPermissionManager.swift in Sources */,
|
||||||
|
A6D000000000000000000101 /* AndroidAirDropModeDetector.swift in Sources */,
|
||||||
EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */,
|
EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */,
|
||||||
EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */,
|
EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */,
|
||||||
EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */,
|
EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */,
|
||||||
@@ -1398,6 +1479,7 @@
|
|||||||
EB6FE7D62EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
EB6FE7D62EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
||||||
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
|
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
|
||||||
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
|
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
|
||||||
|
A7D000000000000000000101 /* AndroidAirDropModeIssueView.swift in Sources */,
|
||||||
EB1325FA2E70288300FD749C /* SendToExtension.swift in Sources */,
|
EB1325FA2E70288300FD749C /* SendToExtension.swift in Sources */,
|
||||||
EB0A22E72FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift in Sources */,
|
EB0A22E72FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift in Sources */,
|
||||||
EB1325F42E7025D900FD749C /* ApIsolationTester.swift in Sources */,
|
EB1325F42E7025D900FD749C /* ApIsolationTester.swift in Sources */,
|
||||||
@@ -1424,6 +1506,8 @@
|
|||||||
EB3B9F082EF7150400D6704E /* TrustStore.swift in Sources */,
|
EB3B9F082EF7150400D6704E /* TrustStore.swift in Sources */,
|
||||||
EB3B9F092EF7150400D6704E /* ErrorAlertHandler.swift in Sources */,
|
EB3B9F092EF7150400D6704E /* ErrorAlertHandler.swift in Sources */,
|
||||||
EB3B9F0A2EF7150400D6704E /* Settings.swift in Sources */,
|
EB3B9F0A2EF7150400D6704E /* Settings.swift in Sources */,
|
||||||
|
A3D000000000000000000203 /* BluetoothPermissionManager.swift in Sources */,
|
||||||
|
A6D000000000000000000102 /* AndroidAirDropModeDetector.swift in Sources */,
|
||||||
EB3B9F0B2EF7150400D6704E /* Zip.swift in Sources */,
|
EB3B9F0B2EF7150400D6704E /* Zip.swift in Sources */,
|
||||||
EB3B9F0C2EF7150400D6704E /* ukey.pb.swift in Sources */,
|
EB3B9F0C2EF7150400D6704E /* ukey.pb.swift in Sources */,
|
||||||
EB3B9F0D2EF7150400D6704E /* securemessage.pb.swift in Sources */,
|
EB3B9F0D2EF7150400D6704E /* securemessage.pb.swift in Sources */,
|
||||||
@@ -1459,6 +1543,7 @@
|
|||||||
EB6FE7D52EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
EB6FE7D52EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
||||||
EB3B9F272EF7150400D6704E /* ApIsolationIssueView.swift in Sources */,
|
EB3B9F272EF7150400D6704E /* ApIsolationIssueView.swift in Sources */,
|
||||||
EB3B9F282EF7150400D6704E /* FirewallIssueView.swift in Sources */,
|
EB3B9F282EF7150400D6704E /* FirewallIssueView.swift in Sources */,
|
||||||
|
A7D000000000000000000102 /* AndroidAirDropModeIssueView.swift in Sources */,
|
||||||
EB3B9F292EF7150400D6704E /* SendToExtension.swift in Sources */,
|
EB3B9F292EF7150400D6704E /* SendToExtension.swift in Sources */,
|
||||||
EB0A22E82FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift in Sources */,
|
EB0A22E82FBCF59300B78FF5 /* NearbyBonjourServiceResolver.swift in Sources */,
|
||||||
EB3B9F2A2EF7150400D6704E /* ApIsolationTester.swift in Sources */,
|
EB3B9F2A2EF7150400D6704E /* ApIsolationTester.swift in Sources */,
|
||||||
@@ -1483,6 +1568,7 @@
|
|||||||
EB3B9F522EF7151900D6704E /* Configuration.swift in Sources */,
|
EB3B9F522EF7151900D6704E /* Configuration.swift in Sources */,
|
||||||
EB6FE7D22EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
EB6FE7D22EFEFB2700FF2BCF /* AllowedWorkMonitor.swift in Sources */,
|
||||||
EB3B9F532EF7151900D6704E /* IssueView.swift in Sources */,
|
EB3B9F532EF7151900D6704E /* IssueView.swift in Sources */,
|
||||||
|
A7D000000000000000000104 /* AndroidAirDropModeIssueView.swift in Sources */,
|
||||||
EB3B9F542EF7151900D6704E /* FirewallIssueView.swift in Sources */,
|
EB3B9F542EF7151900D6704E /* FirewallIssueView.swift in Sources */,
|
||||||
EB3B9F552EF7151900D6704E /* ApIsolationIssueView.swift in Sources */,
|
EB3B9F552EF7151900D6704E /* ApIsolationIssueView.swift in Sources */,
|
||||||
EB3B9F562EF7151900D6704E /* ShareViewController.swift in Sources */,
|
EB3B9F562EF7151900D6704E /* ShareViewController.swift in Sources */,
|
||||||
@@ -1622,6 +1708,12 @@
|
|||||||
target = 699DEBA02AB0573200115D22 /* QuickDropShareExtension */;
|
target = 699DEBA02AB0573200115D22 /* QuickDropShareExtension */;
|
||||||
targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */;
|
targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
C4F698F843B0C498BFC216AB /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = QuickDrop;
|
||||||
|
target = 69DA9A0D29E0BF5100A442DA /* QuickDrop */;
|
||||||
|
targetProxy = E14627D44A6E1CA296A9A53D /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
EB3B9F8D2EF7153000D6704E /* PBXTargetDependency */ = {
|
EB3B9F8D2EF7153000D6704E /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = EB3B9F4A2EF7151900D6704E /* QuickDropShareExtensionGitHub */;
|
target = EB3B9F4A2EF7151900D6704E /* QuickDropShareExtensionGitHub */;
|
||||||
@@ -1647,6 +1739,49 @@
|
|||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
9A4E1D6A03B4FDA5B2074DF0 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 92SDAWLN76;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.tests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QuickDrop.app/Contents/MacOS/QuickDrop";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
A8C87F63E411A0C639408AC5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = 92SDAWLN76;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@loader_path/../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.tests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QuickDrop.app/Contents/MacOS/QuickDrop";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
699DEBAF2AB0573200115D22 /* Debug */ = {
|
699DEBAF2AB0573200115D22 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -1904,7 +2039,7 @@
|
|||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
@@ -1919,6 +2054,8 @@
|
|||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_LSUIElement = YES;
|
INFOPLIST_KEY_LSUIElement = YES;
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
|
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
||||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
@@ -1969,7 +2106,7 @@
|
|||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
@@ -1984,6 +2121,8 @@
|
|||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_LSUIElement = YES;
|
INFOPLIST_KEY_LSUIElement = YES;
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
|
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
||||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
@@ -2033,7 +2172,7 @@
|
|||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
@@ -2048,6 +2187,8 @@
|
|||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_LSUIElement = YES;
|
INFOPLIST_KEY_LSUIElement = YES;
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
|
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
||||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
@@ -2099,7 +2240,7 @@
|
|||||||
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO;
|
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||||
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
ENABLE_RESOURCE_ACCESS_CALENDARS = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
ENABLE_RESOURCE_ACCESS_CAMERA = NO;
|
||||||
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
ENABLE_RESOURCE_ACCESS_CONTACTS = NO;
|
||||||
@@ -2114,6 +2255,8 @@
|
|||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||||
INFOPLIST_KEY_LSUIElement = YES;
|
INFOPLIST_KEY_LSUIElement = YES;
|
||||||
|
INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
|
INFOPLIST_KEY_NSBluetoothPeripheralUsageDescription = "QuickDrop uses Bluetooth for enhanced nearby device detection.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025-2026 Leon Böttger";
|
||||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
@@ -2431,6 +2574,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
B473F6C65C8455203EA5FD18 /* Build configuration list for PBXNativeTarget "QuickDropTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
9A4E1D6A03B4FDA5B2074DF0 /* Release */,
|
||||||
|
A8C87F63E411A0C639408AC5 /* Debug */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
EB3B9F452EF7150400D6704E /* Build configuration list for PBXNativeTarget "QuickDropGitHub" */ = {
|
EB3B9F452EF7150400D6704E /* Build configuration list for PBXNativeTarget "QuickDropGitHub" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2650"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "32A98F34E7D10C21DC2EEA19"
|
||||||
|
BuildableName = "QuickDropTests.xctest"
|
||||||
|
BlueprintName = "QuickDropTests"
|
||||||
|
ReferencedContainer = "container:QuickDrop.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
queueDebuggingEnableBacktraceRecording = "Yes">
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import AudioToolbox
|
import AudioToolbox
|
||||||
import BezelNotification
|
import BezelNotification
|
||||||
|
import Combine
|
||||||
import Cocoa
|
import Cocoa
|
||||||
import Network
|
import Network
|
||||||
import StoreKit
|
import StoreKit
|
||||||
@@ -27,15 +28,22 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
private var errorAlertHandler = ErrorAlertHandler.shared
|
private var errorAlertHandler = ErrorAlertHandler.shared
|
||||||
|
|
||||||
private var receiveModel: ReceiveModel?
|
private var receiveModel: ReceiveModel?
|
||||||
|
private var bluetoothAuthorizationObserver: AnyCancellable?
|
||||||
private let welcomeNavigationState = WelcomeScreenNavigationState()
|
private let welcomeNavigationState = WelcomeScreenNavigationState()
|
||||||
|
|
||||||
var showsFirewallAlert = false
|
var showsFirewallAlert = false
|
||||||
var visibleItem: NSMenuItem? = nil
|
var visibleItem: NSMenuItem? = nil
|
||||||
|
private var didShowAndroidAirDropModeAlert = false
|
||||||
|
|
||||||
|
|
||||||
// MARK: NSApplicationDelegate functions
|
// MARK: NSApplicationDelegate functions
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_: Notification) {
|
func applicationDidFinishLaunching(_: Notification) {
|
||||||
|
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
|
||||||
|
LUIInit(configuration: configuration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().delegate = self
|
||||||
|
|
||||||
// app did not lauch before
|
// app did not lauch before
|
||||||
@@ -120,6 +128,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
NearbyConnectionManager.shared.changedDeviceNameCallback = {
|
NearbyConnectionManager.shared.changedDeviceNameCallback = {
|
||||||
self.visibleItem?.title = self.getDefaultVisibleLabel()
|
self.visibleItem?.title = self.getDefaultVisibleLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupAndroidAirDropModeDetection()
|
||||||
|
requestBluetoothPermissionForExistingUserIfNeeded()
|
||||||
|
|
||||||
|
|
||||||
// MARK: Logging
|
// MARK: Logging
|
||||||
@@ -183,6 +194,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
func applicationWillTerminate(_: Notification) {
|
func applicationWillTerminate(_: Notification) {
|
||||||
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
||||||
ClipboardSyncCoordinator.shared.stop()
|
ClipboardSyncCoordinator.shared.stop()
|
||||||
|
AndroidAirDropModeDetector.shared.stop()
|
||||||
NearbyConnectionManager.shared.stopDeviceDiscovery()
|
NearbyConnectionManager.shared.stopDeviceDiscovery()
|
||||||
NearbyConnectionManager.shared.becomeInvisible()
|
NearbyConnectionManager.shared.becomeInvisible()
|
||||||
}
|
}
|
||||||
@@ -419,6 +431,58 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupAndroidAirDropModeDetection() {
|
||||||
|
log("[AppDelegate] Setting up Android AirDrop mode detection. authorization=\(BluetoothPermissionManager.shared.authorization.rawValue)")
|
||||||
|
|
||||||
|
bluetoothAuthorizationObserver = BluetoothPermissionManager.shared.$authorization
|
||||||
|
.removeDuplicates()
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] authorization in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
log("[AppDelegate] Bluetooth authorization changed for Android AirDrop mode detection: \(authorization.rawValue)")
|
||||||
|
|
||||||
|
if authorization == .allowedAlways {
|
||||||
|
self.startAndroidAirDropModeDetection()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
AndroidAirDropModeDetector.shared.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothPermissionManager.shared.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestBluetoothPermissionForExistingUserIfNeeded() {
|
||||||
|
let bluetoothPermission = BluetoothPermissionManager.shared
|
||||||
|
bluetoothPermission.refresh()
|
||||||
|
|
||||||
|
guard Settings.sharedInstance.appLaunchedBefore, bluetoothPermission.canRequestPermission else { return }
|
||||||
|
|
||||||
|
log("[AppDelegate] Requesting Bluetooth permission for existing user.")
|
||||||
|
|
||||||
|
bluetoothPermission.requestPermission { allowed in
|
||||||
|
log("[AppDelegate] Existing user Bluetooth permission request completed. allowed=\(allowed)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startAndroidAirDropModeDetection() {
|
||||||
|
guard !didShowAndroidAirDropModeAlert else { return }
|
||||||
|
|
||||||
|
AndroidAirDropModeDetector.shared.start { [weak self] _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.showAndroidAirDropModeAlertIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showAndroidAirDropModeAlertIfNeeded() {
|
||||||
|
guard !didShowAndroidAirDropModeAlert else { return }
|
||||||
|
|
||||||
|
didShowAndroidAirDropModeAlert = true
|
||||||
|
ErrorAlertHandler.shared.openAlert(type: .AndroidAirDropMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private func getDefaultVisibleLabel() -> String {
|
private func getDefaultVisibleLabel() -> String {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "Smartphone.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
@@ -27,6 +27,472 @@
|
|||||||
},
|
},
|
||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
},
|
},
|
||||||
|
"NSBluetoothAlwaysUsageDescription" : {
|
||||||
|
"comment" : "Privacy - Bluetooth Always Usage Description",
|
||||||
|
"localizations" : {
|
||||||
|
"ar" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "يستخدم QuickDrop تقنية Bluetooth لتحسين اكتشاف الأجهزة القريبة."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilitza Bluetooth per millorar la detecció de dispositius propers."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cs" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop používá Bluetooth pro vylepšenou detekci zařízení v okolí."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"da" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop bruger Bluetooth til forbedret registrering af enheder i nærheden."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop verwendet Bluetooth für eine verbesserte Erkennung von Geräten in der Nähe."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"el" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Το QuickDrop χρησιμοποιεί Bluetooth για βελτιωμένο εντοπισμό κοντινών συσκευών."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-AU" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-CA" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa Bluetooth para mejorar la detección de dispositivos cercanos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es-419" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa Bluetooth para mejorar la detección de dispositivos cercanos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop käyttää Bluetoothia läheisten laitteiden parempaan havaitsemiseen."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilise le Bluetooth pour améliorer la détection des appareils à proximité."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr-CA" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilise le Bluetooth pour améliorer la détection des appareils à proximité."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"he" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop משתמש ב-Bluetooth לזיהוי משופר של מכשירים בקרבת מקום."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop आस-पास के डिवाइस की बेहतर पहचान के लिए Bluetooth का उपयोग करता है।"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop koristi Bluetooth za poboljšano otkrivanje uređaja u blizini."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "A QuickDrop Bluetoothot használ a közeli eszközök fejlettebb felismeréséhez."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop menggunakan Bluetooth untuk deteksi perangkat terdekat yang lebih baik."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa il Bluetooth per un rilevamento migliorato dei dispositivi nelle vicinanze."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDropは、近くのデバイスをより正確に検出するためにBluetoothを使用します。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop은 주변 기기를 더 잘 감지하기 위해 Bluetooth를 사용합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop menggunakan Bluetooth untuk pengesanan peranti berdekatan yang dipertingkatkan."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop bruker Bluetooth for forbedret oppdagelse av enheter i nærheten."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop gebruikt Bluetooth voor verbeterde detectie van apparaten in de buurt."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop używa Bluetooth do ulepszonego wykrywania pobliskich urządzeń."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "O QuickDrop usa Bluetooth para melhorar a detecção de dispositivos próximos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-PT" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "O QuickDrop usa Bluetooth para melhorar a deteção de dispositivos próximos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ro" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop folosește Bluetooth pentru detectarea îmbunătățită a dispozitivelor din apropiere."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop использует Bluetooth для улучшенного обнаружения устройств поблизости."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sv" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop använder Bluetooth för förbättrad upptäckt av enheter i närheten."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"th" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop ใช้ Bluetooth เพื่อการตรวจจับอุปกรณ์ใกล้เคียงที่ดียิ่งขึ้น"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop, yakındaki cihazları daha iyi algılamak için Bluetooth kullanır."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop використовує Bluetooth для покращеного виявлення пристроїв поблизу."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop sử dụng Bluetooth để phát hiện thiết bị lân cận tốt hơn."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop 使用蓝牙来增强附近设备检测。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop 使用藍牙來增強附近裝置偵測。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NSBluetoothPeripheralUsageDescription" : {
|
||||||
|
"comment" : "Privacy - Bluetooth Peripheral Usage Description",
|
||||||
|
"localizations" : {
|
||||||
|
"ar" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "يستخدم QuickDrop تقنية Bluetooth لتحسين اكتشاف الأجهزة القريبة."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilitza Bluetooth per millorar la detecció de dispositius propers."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cs" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop používá Bluetooth pro vylepšenou detekci zařízení v okolí."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"da" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop bruger Bluetooth til forbedret registrering af enheder i nærheden."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop verwendet Bluetooth für eine verbesserte Erkennung von Geräten in der Nähe."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"el" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Το QuickDrop χρησιμοποιεί Bluetooth για βελτιωμένο εντοπισμό κοντινών συσκευών."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-AU" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-CA" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop uses Bluetooth for enhanced nearby device detection."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa Bluetooth para mejorar la detección de dispositivos cercanos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es-419" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa Bluetooth para mejorar la detección de dispositivos cercanos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop käyttää Bluetoothia läheisten laitteiden parempaan havaitsemiseen."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilise le Bluetooth pour améliorer la détection des appareils à proximité."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr-CA" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop utilise le Bluetooth pour améliorer la détection des appareils à proximité."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"he" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop משתמש ב-Bluetooth לזיהוי משופר של מכשירים בקרבת מקום."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop आस-पास के डिवाइस की बेहतर पहचान के लिए Bluetooth का उपयोग करता है।"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop koristi Bluetooth za poboljšano otkrivanje uređaja u blizini."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "A QuickDrop Bluetoothot használ a közeli eszközök fejlettebb felismeréséhez."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop menggunakan Bluetooth untuk deteksi perangkat terdekat yang lebih baik."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop usa il Bluetooth per un rilevamento migliorato dei dispositivi nelle vicinanze."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDropは、近くのデバイスをより正確に検出するためにBluetoothを使用します。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop은 주변 기기를 더 잘 감지하기 위해 Bluetooth를 사용합니다."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop menggunakan Bluetooth untuk pengesanan peranti berdekatan yang dipertingkatkan."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop bruker Bluetooth for forbedret oppdagelse av enheter i nærheten."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop gebruikt Bluetooth voor verbeterde detectie van apparaten in de buurt."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop używa Bluetooth do ulepszonego wykrywania pobliskich urządzeń."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "O QuickDrop usa Bluetooth para melhorar a detecção de dispositivos próximos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-PT" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "O QuickDrop usa Bluetooth para melhorar a deteção de dispositivos próximos."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ro" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop folosește Bluetooth pentru detectarea îmbunătățită a dispozitivelor din apropiere."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ru" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop использует Bluetooth для улучшенного обнаружения устройств поблизости."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sv" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop använder Bluetooth för förbättrad upptäckt av enheter i närheten."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"th" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop ใช้ Bluetooth เพื่อการตรวจจับอุปกรณ์ใกล้เคียงที่ดียิ่งขึ้น"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop, yakındaki cihazları daha iyi algılamak için Bluetooth kullanır."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop використовує Bluetooth для покращеного виявлення пристроїв поблизу."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vi" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop sử dụng Bluetooth để phát hiện thiết bị lân cận tốt hơn."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop 使用蓝牙来增强附近设备检测。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "QuickDrop 使用藍牙來增強附近裝置偵測。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"NSHumanReadableCopyright" : {
|
"NSHumanReadableCopyright" : {
|
||||||
"comment" : "Copyright (human-readable)",
|
"comment" : "Copyright (human-readable)",
|
||||||
"extractionState" : "extracted_with_value",
|
"extractionState" : "extracted_with_value",
|
||||||
@@ -742,4 +1208,4 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
}
|
}
|
||||||
+7876
-6478
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,229 @@
|
|||||||
|
//
|
||||||
|
// AndroidAirDropModeDetector.swift
|
||||||
|
// QuickDrop
|
||||||
|
//
|
||||||
|
// Created by Leon Böttger on 10.06.26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreBluetooth
|
||||||
|
import Foundation
|
||||||
|
import LUI
|
||||||
|
|
||||||
|
struct AndroidAirDropAdvertisement: Equatable {
|
||||||
|
let rssi: Int
|
||||||
|
let manufacturerDataHex: String?
|
||||||
|
let serviceUUIDs: Set<String>
|
||||||
|
|
||||||
|
init(rssi: Int, manufacturerDataHex: String?, serviceUUIDs: Set<String>) {
|
||||||
|
self.rssi = rssi
|
||||||
|
self.manufacturerDataHex = manufacturerDataHex?.lowercased()
|
||||||
|
self.serviceUUIDs = Set(serviceUUIDs.map { $0.uppercased() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AndroidAirDropModeObservation: Equatable {
|
||||||
|
let airDropModePacketCount: Int
|
||||||
|
let strongestAirDropModeRSSI: Int?
|
||||||
|
let minimumRSSI: Int
|
||||||
|
let requiredPacketsPerSignal: Int
|
||||||
|
|
||||||
|
var isDetected: Bool {
|
||||||
|
airDropModePacketCount >= requiredPacketsPerSignal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AndroidAirDropModeClassifier {
|
||||||
|
static let defaultMinimumRSSI = -70
|
||||||
|
static let defaultRequiredPacketsPerSignal = 2
|
||||||
|
|
||||||
|
static let airDropModeManufacturerPrefix = "4c000512000000000000000001"
|
||||||
|
static let airDropModeServiceUUID = "FCF1"
|
||||||
|
|
||||||
|
let minimumRSSI: Int
|
||||||
|
let requiredPacketsPerSignal: Int
|
||||||
|
|
||||||
|
private(set) var airDropModePacketCount = 0
|
||||||
|
private(set) var strongestAirDropModeRSSI: Int?
|
||||||
|
|
||||||
|
init(
|
||||||
|
minimumRSSI: Int = Self.defaultMinimumRSSI,
|
||||||
|
requiredPacketsPerSignal: Int = Self.defaultRequiredPacketsPerSignal
|
||||||
|
) {
|
||||||
|
self.minimumRSSI = minimumRSSI
|
||||||
|
self.requiredPacketsPerSignal = requiredPacketsPerSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
var observation: AndroidAirDropModeObservation {
|
||||||
|
AndroidAirDropModeObservation(
|
||||||
|
airDropModePacketCount: airDropModePacketCount,
|
||||||
|
strongestAirDropModeRSSI: strongestAirDropModeRSSI,
|
||||||
|
minimumRSSI: minimumRSSI,
|
||||||
|
requiredPacketsPerSignal: requiredPacketsPerSignal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func reset() {
|
||||||
|
airDropModePacketCount = 0
|
||||||
|
strongestAirDropModeRSSI = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
mutating func record(_ advertisement: AndroidAirDropAdvertisement) -> AndroidAirDropModeObservation {
|
||||||
|
guard advertisement.rssi >= minimumRSSI else {
|
||||||
|
return observation
|
||||||
|
}
|
||||||
|
|
||||||
|
if Self.isAirDropModeAdvertisement(advertisement) {
|
||||||
|
airDropModePacketCount += 1
|
||||||
|
strongestAirDropModeRSSI = Self.strongerRSSI(strongestAirDropModeRSSI, advertisement.rssi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return observation
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isAirDropModeAdvertisement(_ advertisement: AndroidAirDropAdvertisement) -> Bool {
|
||||||
|
advertisement.manufacturerDataHex?.hasPrefix(airDropModeManufacturerPrefix) == true &&
|
||||||
|
advertisement.serviceUUIDs.contains(airDropModeServiceUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func strongerRSSI(_ current: Int?, _ candidate: Int) -> Int {
|
||||||
|
guard let current else { return candidate }
|
||||||
|
return max(current, candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class AndroidAirDropModeDetector: NSObject, CBCentralManagerDelegate {
|
||||||
|
static let shared = AndroidAirDropModeDetector()
|
||||||
|
static let scanServiceUUIDs = [CBUUID(string: AndroidAirDropModeClassifier.airDropModeServiceUUID)]
|
||||||
|
static let scanOptions: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
||||||
|
|
||||||
|
private var centralManager: CBCentralManager?
|
||||||
|
private var classifier = AndroidAirDropModeClassifier()
|
||||||
|
private var onDetected: ((AndroidAirDropModeObservation) -> Void)?
|
||||||
|
private var isScanning = false
|
||||||
|
private var hasDeliveredDetection = false
|
||||||
|
|
||||||
|
func start(onDetected: @escaping (AndroidAirDropModeObservation) -> Void) {
|
||||||
|
guard BluetoothPermissionManager.shared.isAllowed else {
|
||||||
|
log("[AndroidAirDropModeDetector] Bluetooth permission is not granted; skipping scan. authorization=\(BluetoothPermissionManager.shared.authorization.rawValue)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onDetected = onDetected
|
||||||
|
|
||||||
|
guard !hasDeliveredDetection else { return }
|
||||||
|
guard !isScanning else { return }
|
||||||
|
|
||||||
|
log("[AndroidAirDropModeDetector] Starting detector.")
|
||||||
|
|
||||||
|
classifier.reset()
|
||||||
|
|
||||||
|
if centralManager == nil {
|
||||||
|
log("[AndroidAirDropModeDetector] Creating central manager.")
|
||||||
|
centralManager = CBCentralManager(delegate: self, queue: .main)
|
||||||
|
}
|
||||||
|
else if centralManager?.state == .poweredOn {
|
||||||
|
beginScanning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
log("[AndroidAirDropModeDetector] Stopping detector.")
|
||||||
|
centralManager?.stopScan()
|
||||||
|
classifier.reset()
|
||||||
|
onDetected = nil
|
||||||
|
hasDeliveredDetection = false
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||||
|
log("[AndroidAirDropModeDetector] Central state changed: \(central.state.rawValue)")
|
||||||
|
|
||||||
|
guard central.state == .poweredOn else {
|
||||||
|
if isScanning {
|
||||||
|
central.stopScan()
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if onDetected != nil {
|
||||||
|
beginScanning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func centralManager(
|
||||||
|
_ central: CBCentralManager,
|
||||||
|
didDiscover peripheral: CBPeripheral,
|
||||||
|
advertisementData: [String: Any],
|
||||||
|
rssi RSSI: NSNumber
|
||||||
|
) {
|
||||||
|
guard !hasDeliveredDetection else { return }
|
||||||
|
|
||||||
|
let advertisement = Self.advertisement(from: advertisementData, rssi: RSSI.intValue)
|
||||||
|
let observation = classifier.record(advertisement)
|
||||||
|
|
||||||
|
guard observation.isDetected else { return }
|
||||||
|
|
||||||
|
hasDeliveredDetection = true
|
||||||
|
isScanning = false
|
||||||
|
central.stopScan()
|
||||||
|
|
||||||
|
let callback = onDetected
|
||||||
|
onDetected = nil
|
||||||
|
|
||||||
|
log(
|
||||||
|
"[AndroidAirDropModeDetector] Detected Android AirDrop-compatible sharing mode. " +
|
||||||
|
"airDropModePackets=\(observation.airDropModePacketCount), " +
|
||||||
|
"minimumRSSI=\(observation.minimumRSSI)"
|
||||||
|
)
|
||||||
|
|
||||||
|
callback?(observation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func beginScanning() {
|
||||||
|
guard !isScanning, let centralManager, centralManager.state == .poweredOn else { return }
|
||||||
|
|
||||||
|
log("[AndroidAirDropModeDetector] BLE scan started for service \(AndroidAirDropModeClassifier.airDropModeServiceUUID).")
|
||||||
|
|
||||||
|
// Duplicate delivery is intentional: the classifier waits for repeated
|
||||||
|
// matching packets so one transient advertisement does not trigger the alert.
|
||||||
|
centralManager.scanForPeripherals(
|
||||||
|
withServices: Self.scanServiceUUIDs,
|
||||||
|
options: Self.scanOptions
|
||||||
|
)
|
||||||
|
isScanning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func advertisement(
|
||||||
|
from advertisementData: [String: Any],
|
||||||
|
rssi: Int
|
||||||
|
) -> AndroidAirDropAdvertisement {
|
||||||
|
var serviceUUIDs = Set<String>()
|
||||||
|
|
||||||
|
[
|
||||||
|
CBAdvertisementDataServiceUUIDsKey,
|
||||||
|
CBAdvertisementDataOverflowServiceUUIDsKey,
|
||||||
|
CBAdvertisementDataSolicitedServiceUUIDsKey
|
||||||
|
].forEach { key in
|
||||||
|
guard let uuids = advertisementData[key] as? [CBUUID] else { return }
|
||||||
|
uuids.forEach { serviceUUIDs.insert($0.uuidString) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
|
||||||
|
serviceData.keys.forEach { serviceUUIDs.insert($0.uuidString) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return AndroidAirDropAdvertisement(
|
||||||
|
rssi: rssi,
|
||||||
|
manufacturerDataHex: (advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data)?.androidAirDropModeHexString,
|
||||||
|
serviceUUIDs: serviceUUIDs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Data {
|
||||||
|
var androidAirDropModeHexString: String {
|
||||||
|
map { String(format: "%02x", $0) }.joined()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// BluetoothPermissionManager.swift
|
||||||
|
// QuickDrop
|
||||||
|
//
|
||||||
|
// Created by Leon Böttger on 31.05.26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import CoreBluetooth
|
||||||
|
import Foundation
|
||||||
|
import LUI
|
||||||
|
|
||||||
|
#if os(iOS) && !EXTENSION
|
||||||
|
import UIKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
import AppKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
final class BluetoothPermissionManager: NSObject, ObservableObject, CBCentralManagerDelegate {
|
||||||
|
|
||||||
|
static let shared = BluetoothPermissionManager()
|
||||||
|
|
||||||
|
@Published private(set) var authorization: CBManagerAuthorization = CBManager.authorization
|
||||||
|
|
||||||
|
private var centralManager: CBCentralManager?
|
||||||
|
private var completion: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
var isAllowed: Bool {
|
||||||
|
authorization == .allowedAlways
|
||||||
|
}
|
||||||
|
|
||||||
|
var canRequestPermission: Bool {
|
||||||
|
authorization == .notDetermined
|
||||||
|
}
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func refresh() {
|
||||||
|
let currentAuthorization = CBManager.authorization
|
||||||
|
guard authorization != currentAuthorization else { return }
|
||||||
|
|
||||||
|
authorization = currentAuthorization
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestPermission(completion: ((Bool) -> Void)? = nil) {
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
guard authorization == .notDetermined else {
|
||||||
|
completion?(isAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.completion = completion
|
||||||
|
centralManager = CBCentralManager(delegate: self, queue: .main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
guard authorization != .notDetermined else { return }
|
||||||
|
|
||||||
|
let completion = completion
|
||||||
|
self.completion = nil
|
||||||
|
completion?(isAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openSystemBluetoothSettings() {
|
||||||
|
#if os(iOS) && !EXTENSION
|
||||||
|
openAppSettings()
|
||||||
|
#elseif os(macOS)
|
||||||
|
let urls = [
|
||||||
|
URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Bluetooth"),
|
||||||
|
URL(string: "x-apple.systempreferences:com.apple.BluetoothSettings")
|
||||||
|
].compactMap { $0 }
|
||||||
|
|
||||||
|
if let url = urls.first {
|
||||||
|
NSWorkspace.shared.open(url)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ class ErrorAlertHandler {
|
|||||||
private var firewallAlertWindow: NSWindow?
|
private var firewallAlertWindow: NSWindow?
|
||||||
private var apIsolationAlertWindow: NSWindow?
|
private var apIsolationAlertWindow: NSWindow?
|
||||||
private var networkFilterAlertWindow: NSWindow?
|
private var networkFilterAlertWindow: NSWindow?
|
||||||
|
private var androidAirDropModeAlertWindow: NSWindow?
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func showErrorAlert(for deviceName: String, error: Error) {
|
func showErrorAlert(for deviceName: String, error: Error) {
|
||||||
@@ -124,6 +125,11 @@ class ErrorAlertHandler {
|
|||||||
AudioManager.playErrorSound()
|
AudioManager.playErrorSound()
|
||||||
|
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
|
if let existingWindow = alertWindow(for: type), existingWindow.isVisible {
|
||||||
|
bringAlertWindowToFront(existingWindow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Create an NSWindow to host the SwiftUI view
|
// Create an NSWindow to host the SwiftUI view
|
||||||
let alertWindow = NSWindow(
|
let alertWindow = NSWindow(
|
||||||
contentRect: NSRect(x: 0, y: 0, width: issueViewWidth, height: issueViewHeight),
|
contentRect: NSRect(x: 0, y: 0, width: issueViewWidth, height: issueViewHeight),
|
||||||
@@ -142,6 +148,9 @@ class ErrorAlertHandler {
|
|||||||
case .Firewall:
|
case .Firewall:
|
||||||
firewallAlertWindow = alertWindow
|
firewallAlertWindow = alertWindow
|
||||||
alertWindow.contentView = NSHostingView(rootView: FirewallIssueView())
|
alertWindow.contentView = NSHostingView(rootView: FirewallIssueView())
|
||||||
|
case .AndroidAirDropMode:
|
||||||
|
androidAirDropModeAlertWindow = alertWindow
|
||||||
|
alertWindow.contentView = NSHostingView(rootView: AndroidAirDropModeIssueView())
|
||||||
}
|
}
|
||||||
|
|
||||||
alertWindow.title = "QuickDrop"
|
alertWindow.title = "QuickDrop"
|
||||||
@@ -149,9 +158,8 @@ class ErrorAlertHandler {
|
|||||||
alertWindow.isReleasedWhenClosed = false
|
alertWindow.isReleasedWhenClosed = false
|
||||||
alertWindow.setFrameAutosaveName(type.rawValue)
|
alertWindow.setFrameAutosaveName(type.rawValue)
|
||||||
|
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
|
||||||
alertWindow.makeKeyAndOrderFront(nil)
|
|
||||||
alertWindow.level = .normal
|
alertWindow.level = .normal
|
||||||
|
bringAlertWindowToFront(alertWindow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +171,26 @@ class ErrorAlertHandler {
|
|||||||
self.apIsolationAlertWindow = nil
|
self.apIsolationAlertWindow = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func alertWindow(for type: AlertType) -> NSWindow? {
|
||||||
|
switch type {
|
||||||
|
case .ApIsolation:
|
||||||
|
return apIsolationAlertWindow
|
||||||
|
case .NetworkFilter:
|
||||||
|
return networkFilterAlertWindow
|
||||||
|
case .Firewall:
|
||||||
|
return firewallAlertWindow
|
||||||
|
case .AndroidAirDropMode:
|
||||||
|
return androidAirDropModeAlertWindow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bringAlertWindowToFront(_ window: NSWindow) {
|
||||||
|
_ = NSApp.setActivationPolicy(.regular)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
window.orderFrontRegardless()
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,4 +199,5 @@ enum AlertType: String {
|
|||||||
case ApIsolation
|
case ApIsolation
|
||||||
case NetworkFilter
|
case NetworkFilter
|
||||||
case Firewall
|
case Firewall
|
||||||
|
case AndroidAirDropMode
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,8 @@ struct IntroductionView: View {
|
|||||||
static let height: CGFloat = 540
|
static let height: CGFloat = 540
|
||||||
|
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
@ObservedObject private var bluetooth = BluetoothPermissionManager.shared
|
||||||
@State private var currentPage: IntroductionPage = .splash
|
@State private var currentPage: IntroductionPage = .splash
|
||||||
|
|
||||||
@State private var pollingPage: IntroductionPage? = nil
|
@State private var pollingPage: IntroductionPage? = nil
|
||||||
@@ -120,6 +122,10 @@ struct IntroductionView: View {
|
|||||||
.onChange(of: currentPage) { newValue in
|
.onChange(of: currentPage) { newValue in
|
||||||
pageDidChange(to: newValue)
|
pageDidChange(to: newValue)
|
||||||
}
|
}
|
||||||
|
.onChange(of: scenePhase) { newValue in
|
||||||
|
guard newValue == .active, currentPage == .bluetoothAccess else { return }
|
||||||
|
refreshBluetoothPermissionAndAdvanceIfAllowed()
|
||||||
|
}
|
||||||
.task(id: pollingPage) {
|
.task(id: pollingPage) {
|
||||||
guard let page = pollingPage else { return }
|
guard let page = pollingPage else { return }
|
||||||
|
|
||||||
@@ -181,6 +187,11 @@ struct IntroductionView: View {
|
|||||||
|
|
||||||
|
|
||||||
private func continueTapped() {
|
private func continueTapped() {
|
||||||
|
if currentPage == .bluetoothAccess {
|
||||||
|
continueBluetoothPermission()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If already allowed, advance immediately
|
// If already allowed, advance immediately
|
||||||
if currentPage.canContinue() {
|
if currentPage.canContinue() {
|
||||||
advance()
|
advance()
|
||||||
@@ -197,13 +208,57 @@ struct IntroductionView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func continueBluetoothPermission() {
|
||||||
|
bluetooth.refresh()
|
||||||
|
|
||||||
|
if bluetooth.isAllowed {
|
||||||
|
advance()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pollingPage = .bluetoothAccess
|
||||||
|
|
||||||
|
guard bluetooth.canRequestPermission else {
|
||||||
|
bluetooth.openSystemBluetoothSettings()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bluetooth.requestPermission { allowed in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard currentPage == .bluetoothAccess else {
|
||||||
|
stopPolling()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
stopPolling()
|
||||||
|
advance()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pollingPage = .bluetoothAccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func refreshBluetoothPermissionAndAdvanceIfAllowed() {
|
||||||
|
bluetooth.refresh()
|
||||||
|
|
||||||
|
guard currentPage == .bluetoothAccess, bluetooth.isAllowed else { return }
|
||||||
|
|
||||||
|
stopPolling()
|
||||||
|
advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private func advance() {
|
private func advance() {
|
||||||
|
|
||||||
if currentPage == .localNetworkAccess {
|
if currentPage == .localNetworkAccess {
|
||||||
let _ = DeviceToDeviceHeuristicScanner.shared.hasLocalNetworkAccess(completion: {_ in })
|
let _ = DeviceToDeviceHeuristicScanner.shared.hasLocalNetworkAccess(completion: {_ in })
|
||||||
triggerLocalNetworkPermission()
|
triggerLocalNetworkPermission()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let nextPage = currentPage.presentNextPage() {
|
if let nextPage = currentPage.presentNextPage() {
|
||||||
currentPage = nextPage
|
currentPage = nextPage
|
||||||
} else {
|
} else {
|
||||||
@@ -223,6 +278,7 @@ enum IntroductionPage: CaseIterable {
|
|||||||
case splash
|
case splash
|
||||||
case noWifi
|
case noWifi
|
||||||
case localNetworkAccess
|
case localNetworkAccess
|
||||||
|
case bluetoothAccess
|
||||||
case enableShareExtension
|
case enableShareExtension
|
||||||
case finished
|
case finished
|
||||||
|
|
||||||
@@ -234,6 +290,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
return "introduction_no_wifi"
|
return "introduction_no_wifi"
|
||||||
case .localNetworkAccess:
|
case .localNetworkAccess:
|
||||||
return "introduction_local_network_access"
|
return "introduction_local_network_access"
|
||||||
|
case .bluetoothAccess:
|
||||||
|
return "BluetoothPermissionRequiredTitle"
|
||||||
case .enableShareExtension:
|
case .enableShareExtension:
|
||||||
return "introduction_enable_share_extension"
|
return "introduction_enable_share_extension"
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -250,6 +308,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
return "introduction_no_wifi_description"
|
return "introduction_no_wifi_description"
|
||||||
case .localNetworkAccess:
|
case .localNetworkAccess:
|
||||||
return "introduction_local_network_access_description".localized(with: "introduction_continue".localized())
|
return "introduction_local_network_access_description".localized(with: "introduction_continue".localized())
|
||||||
|
case .bluetoothAccess:
|
||||||
|
return "BluetoothPermissionRequiredSubtitle"
|
||||||
case .enableShareExtension:
|
case .enableShareExtension:
|
||||||
if #available(macOS 13.0, *) {
|
if #available(macOS 13.0, *) {
|
||||||
return "introduction_enable_share_extension_description".localized(with: "EnableQuickDropExtension".localized())
|
return "introduction_enable_share_extension_description".localized(with: "EnableQuickDropExtension".localized())
|
||||||
@@ -267,6 +327,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .noWifi:
|
case .noWifi:
|
||||||
return .init(action: presentNextPage, warningTitle: "introduction_no_wifi_skip_title", warningMessage: "introduction_no_wifi_skip_message")
|
return .init(action: presentNextPage, warningTitle: "introduction_no_wifi_skip_title", warningMessage: "introduction_no_wifi_skip_message")
|
||||||
|
case .bluetoothAccess:
|
||||||
|
return .init(action: { .enableShareExtension }, warningTitle: "BluetoothPermissionSkipAlertTitle", warningMessage: "BluetoothPermissionSkipAlertMessage")
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -290,6 +352,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
case .noWifi:
|
case .noWifi:
|
||||||
return firstPermissionPane()
|
return firstPermissionPane()
|
||||||
case .localNetworkAccess:
|
case .localNetworkAccess:
|
||||||
|
return .bluetoothAccess
|
||||||
|
case .bluetoothAccess:
|
||||||
return .enableShareExtension
|
return .enableShareExtension
|
||||||
case .enableShareExtension:
|
case .enableShareExtension:
|
||||||
return .finished
|
return .finished
|
||||||
@@ -304,7 +368,7 @@ enum IntroductionPage: CaseIterable {
|
|||||||
// Local network access is requested first on macOS 15+
|
// Local network access is requested first on macOS 15+
|
||||||
return .localNetworkAccess
|
return .localNetworkAccess
|
||||||
}
|
}
|
||||||
return .enableShareExtension
|
return .bluetoothAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -312,6 +376,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .noWifi:
|
case .noWifi:
|
||||||
return NearbyConnectionManager.shared.isConnectedToLocalNetwork
|
return NearbyConnectionManager.shared.isConnectedToLocalNetwork
|
||||||
|
case .bluetoothAccess:
|
||||||
|
return BluetoothPermissionManager.shared.isAllowed
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -332,6 +398,8 @@ enum IntroductionPage: CaseIterable {
|
|||||||
imageView(for: "wifi.slash", with: height)
|
imageView(for: "wifi.slash", with: height)
|
||||||
case .localNetworkAccess:
|
case .localNetworkAccess:
|
||||||
imageView(for: "network", with: height)
|
imageView(for: "network", with: height)
|
||||||
|
case .bluetoothAccess:
|
||||||
|
imageView(for: "antenna.radiowaves.left.and.right", with: height)
|
||||||
case .enableShareExtension:
|
case .enableShareExtension:
|
||||||
imageView(for: "square.and.arrow.up", with: height)
|
imageView(for: "square.and.arrow.up", with: height)
|
||||||
case .finished:
|
case .finished:
|
||||||
@@ -350,8 +418,7 @@ enum IntroductionPage: CaseIterable {
|
|||||||
.frame(height: height * 0.5)
|
.frame(height: height * 0.5)
|
||||||
.padding(.top, height * 0.13)
|
.padding(.top, height * 0.13)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SkipAction: Identifiable {
|
struct SkipAction: Identifiable {
|
||||||
let action: () -> IntroductionPage?
|
let action: () -> IntroductionPage?
|
||||||
let warningTitle: String
|
let warningTitle: String
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// AndroidAirDropModeIssueView.swift
|
||||||
|
// QuickDrop
|
||||||
|
//
|
||||||
|
// Created by Leon Böttger on 10.06.26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AndroidAirDropModeIssueView: View {
|
||||||
|
var body: some View {
|
||||||
|
IssueView(
|
||||||
|
image: .smartphone,
|
||||||
|
header: "AndroidAirDropModeHeader".localized(),
|
||||||
|
description: "AndroidAirDropModeDescription".localized()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
AndroidAirDropModeIssueView()
|
||||||
|
.frame(width: issueViewWidth, height: issueViewHeight)
|
||||||
|
}
|
||||||
@@ -41,9 +41,9 @@ struct TutorialView: View {
|
|||||||
|
|
||||||
if tab == .receive {
|
if tab == .receive {
|
||||||
Button {
|
Button {
|
||||||
showSamsungOneUI85Alert()
|
showAndroidAirDropModeAlert()
|
||||||
} label: {
|
} label: {
|
||||||
Text("SamsungOneUI85HelpLink".localized())
|
Text("AndroidAirDropModeHelpLink".localized())
|
||||||
.underline()
|
.underline()
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -68,11 +68,11 @@ struct TutorialView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showSamsungOneUI85Alert() {
|
private func showAndroidAirDropModeAlert() {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.alertStyle = .informational
|
alert.alertStyle = .informational
|
||||||
alert.messageText = "SamsungOneUI85AlertTitle".localized()
|
alert.messageText = "AndroidAirDropModeAlertTitle".localized()
|
||||||
alert.informativeText = "SamsungOneUI85AlertMessage".localized()
|
alert.informativeText = "AndroidAirDropModeAlertMessage".localized()
|
||||||
alert.addButton(withTitle: "CloseAlert".localized())
|
alert.addButton(withTitle: "CloseAlert".localized())
|
||||||
|
|
||||||
if let window = NSApp.mainWindow ?? NSApp.keyWindow {
|
if let window = NSApp.mainWindow ?? NSApp.keyWindow {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import Foundation
|
||||||
|
import CoreBluetooth
|
||||||
|
import Testing
|
||||||
|
@testable import QuickDrop
|
||||||
|
|
||||||
|
@Suite(.serialized)
|
||||||
|
struct AndroidAirDropModeDetectorTests {
|
||||||
|
@Test
|
||||||
|
func detectsWhenAirDropModeSignalsPassThreshold() {
|
||||||
|
var classifier = AndroidAirDropModeClassifier(minimumRSSI: -70, requiredPacketsPerSignal: 2)
|
||||||
|
|
||||||
|
let firstAirDropMode = classifier.record(.airDropMode(rssi: -58))
|
||||||
|
#expect(!firstAirDropMode.isDetected)
|
||||||
|
|
||||||
|
let detected = classifier.record(.airDropMode(rssi: -56))
|
||||||
|
#expect(detected.isDetected)
|
||||||
|
#expect(detected.airDropModePacketCount == 2)
|
||||||
|
#expect(detected.strongestAirDropModeRSSI == -56)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func matchesObservedMoseyAirDropSuffixVariants() {
|
||||||
|
#expect(
|
||||||
|
AndroidAirDropModeClassifier.isAirDropModeAdvertisement(
|
||||||
|
.airDropMode(manufacturerDataSuffix: "28b429412f624af300", rssi: -58)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
#expect(
|
||||||
|
AndroidAirDropModeClassifier.isAirDropModeAdvertisement(
|
||||||
|
.airDropMode(manufacturerDataSuffix: "03b429412f624af300", rssi: -58)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func ignoresSignalsBelowRSSIThreshold() {
|
||||||
|
var classifier = AndroidAirDropModeClassifier(minimumRSSI: -70, requiredPacketsPerSignal: 2)
|
||||||
|
|
||||||
|
classifier.record(.airDropMode(rssi: -72))
|
||||||
|
let observation = classifier.record(.airDropMode(rssi: -85))
|
||||||
|
|
||||||
|
#expect(!observation.isDetected)
|
||||||
|
#expect(observation.airDropModePacketCount == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func ignoresNearbyAppleAndBackgroundFCF1Advertisements() {
|
||||||
|
var classifier = AndroidAirDropModeClassifier(minimumRSSI: -70, requiredPacketsPerSignal: 1)
|
||||||
|
|
||||||
|
classifier.record(.advertisement(rssi: -50, manufacturerDataHex: "4c001006031dc4e5be78", serviceUUIDs: []))
|
||||||
|
classifier.record(.advertisement(rssi: -55, manufacturerDataHex: nil, serviceUUIDs: ["FCF1"]))
|
||||||
|
classifier.record(.advertisement(rssi: -56, manufacturerDataHex: "4c00051200000000000000000128b429412f624af300", serviceUUIDs: []))
|
||||||
|
let observation = classifier.record(
|
||||||
|
.advertisement(
|
||||||
|
rssi: -56,
|
||||||
|
manufacturerDataHex: "4c00051200000000000000000028b429412f624af300",
|
||||||
|
serviceUUIDs: ["FCF1"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
#expect(!observation.isDetected)
|
||||||
|
#expect(observation.airDropModePacketCount == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func scansOnlyForAirDropModeServiceAndAllowsDuplicatePackets() {
|
||||||
|
#expect(AndroidAirDropModeDetector.scanServiceUUIDs.map(\.uuidString) == [AndroidAirDropModeClassifier.airDropModeServiceUUID])
|
||||||
|
#expect(AndroidAirDropModeDetector.scanOptions[CBCentralManagerScanOptionAllowDuplicatesKey] as? Bool == true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AndroidAirDropAdvertisement {
|
||||||
|
static func airDropMode(rssi: Int) -> Self {
|
||||||
|
airDropMode(manufacturerDataSuffix: "28b429412f624af300", rssi: rssi)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func airDropMode(manufacturerDataSuffix: String, rssi: Int) -> Self {
|
||||||
|
advertisement(
|
||||||
|
rssi: rssi,
|
||||||
|
manufacturerDataHex: "4c000512000000000000000001\(manufacturerDataSuffix)",
|
||||||
|
serviceUUIDs: ["FCF1"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func advertisement(
|
||||||
|
rssi: Int,
|
||||||
|
manufacturerDataHex: String?,
|
||||||
|
serviceUUIDs: Set<String>
|
||||||
|
) -> Self {
|
||||||
|
AndroidAirDropAdvertisement(
|
||||||
|
rssi: rssi,
|
||||||
|
manufacturerDataHex: manufacturerDataHex,
|
||||||
|
serviceUUIDs: serviceUUIDs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user