Add iOS Share Extension

This commit is contained in:
Leon Böttger committed 2025-08-17 19:03:47 +02:00
1 parent 13ce7522a7
commit 406247d968
13 files changed
+602 -147

No files matched your search

@@ -198,7 +198,7 @@ class InboundNearbyConnection: NearbyConnection {
} else if let url = URL(string: urlStr) {
#if os(macOS)
NSWorkspace.shared.open(url)
#elseif os(iOS)
#elseif os(iOS) && !EXTENSION
UIApplication.shared.open(url, options: [:], completionHandler: nil)
#endif
}
@@ -18,7 +18,9 @@ import CryptoKit
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate {
#if !EXTENSION
private let sleepManager = SleepManager.shared
#endif
private var tcpListener: NWListener
private var mdnsServices: [NetService] = []
private var incomingConnections: [String: InboundNearbyConnection] = [:]
+4 -5
View File
@@ -126,15 +126,14 @@ public class SaveFilesManager {
#else
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
if let documentsUrl = documentsUrl, let sharedUrl = URL(string: "shareddocuments://\(documentsUrl.path)") {
if UIApplication.shared.canOpenURL(sharedUrl) {
UIApplication.shared.open(sharedUrl, options: [:])
}
#if !EXTENSION
if let documentsUrl = documentsUrl, let sharedUrl = URL(string: "shareddocuments://\(documentsUrl.path)"), UIApplication.shared.canOpenURL(sharedUrl) {
UIApplication.shared.open(sharedUrl, options: [:])
}
else {
showAlert(title: "CouldNotOpenDocumentsFolder", message: "CouldNotOpenDocumentsFolderDescription")
}
#endif
#endif
File renamed without changes.
+280 -9
View File
@@ -97,6 +97,50 @@
EB886E672E4F2F800052B7D9 /* License.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E662E4F2F7C0052B7D9 /* License.swift */; };
EB886E682E4F2F800052B7D9 /* License.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E662E4F2F7C0052B7D9 /* License.swift */; };
EB886E6A2E4F37B90052B7D9 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EB950B372E52027F002D68FA /* iOSShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F902E50CA23008B8C5C /* SendModel.swift */; };
EB950B3F2E52038A002D68FA /* FilePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8F2E50CA23008B8C5C /* FilePicker.swift */; };
EB950B402E52038A002D68FA /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
EB950B412E52038A002D68FA /* PieProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F992E50CAA6008B8C5C /* PieProgressView.swift */; };
EB950B432E52038A002D68FA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8E2E50CA23008B8C5C /* ContentView.swift */; };
EB950B442E520414002D68FA /* License.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E662E4F2F7C0052B7D9 /* License.swift */; };
EB950B452E520436002D68FA /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB950B462E520436002D68FA /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
EB950B472E520436002D68FA /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB950B482E520436002D68FA /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EB950B492E520436002D68FA /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
EB950B4A2E520436002D68FA /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
EB950B4B2E520436002D68FA /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
EB950B4C2E520436002D68FA /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
EB950B4F2E520436002D68FA /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
EB950B532E520436002D68FA /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
EB950B542E520436002D68FA /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; };
EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; };
EB950B562E520436002D68FA /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB950B572E520591002D68FA /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EB950B582E5205B4002D68FA /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC27F82D80C9F70079C478 /* Zip.swift */; };
EB950B592E520649002D68FA /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB950B5A2E52065A002D68FA /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */; };
EB950B5B2E52065A002D68FA /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */; };
EB950B5C2E52065A002D68FA /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB950B5D2E52065A002D68FA /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; };
EB950B5F2E520671002D68FA /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B5E2E520671002D68FA /* SwiftProtobuf */; };
EB950B612E520674002D68FA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B602E520674002D68FA /* SwiftECC */; };
EB950B632E52067A002D68FA /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B622E52067A002D68FA /* QRCode */; };
EB950B652E52067D002D68FA /* LUI in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B642E52067D002D68FA /* LUI */; };
EB950B672E520680002D68FA /* RNDeviceName in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B662E520680002D68FA /* RNDeviceName */; };
EB950B6F2E5206E2002D68FA /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B6B2E5206E2002D68FA /* ShareViewController.swift */; };
EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; };
EB950B712E52073F002D68FA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB950B742E520957002D68FA /* Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B732E520954002D68FA /* Attachments.swift */; };
EB950B752E52111C002D68FA /* Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B732E520954002D68FA /* Attachments.swift */; };
EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
EB950B782E521712002D68FA /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
EB950B792E521717002D68FA /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; };
EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; };
EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
@@ -151,6 +195,13 @@
remoteGlobalIDString = 699DEBA02AB0573200115D22;
remoteInfo = ShareExtension;
};
EB950B352E52027F002D68FA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
proxyType = 1;
remoteGlobalIDString = EB950B2C2E52027F002D68FA;
remoteInfo = iOSShareExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -195,6 +246,17 @@
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
};
EB950B382E52027F002D68FA /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
EB950B372E52027F002D68FA /* iOSShareExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@@ -239,6 +301,12 @@
EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = "<group>"; };
EB886E512E4E19390052B7D9 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; };
EB886E662E4F2F7C0052B7D9 /* License.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = License.swift; sourceTree = "<group>"; };
EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = iOSShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
EB950B682E5206E2002D68FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EB950B6B2E5206E2002D68FA /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
EB950B722E5207AF002D68FA /* iOSShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iOSShareExtension.entitlements; sourceTree = "<group>"; };
EB950B732E520954002D68FA /* Attachments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachments.swift; sourceTree = "<group>"; };
EB950B762E521712002D68FA /* AttachmentDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentDetails.swift; sourceTree = "<group>"; };
EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallSheetView.swift; sourceTree = "<group>"; };
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = "<group>"; };
EB979F8D2E50CA23008B8C5C /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
@@ -302,6 +370,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
EB950B2A2E52027F002D68FA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EB950B672E520680002D68FA /* RNDeviceName in Frameworks */,
EB950B612E520674002D68FA /* SwiftECC in Frameworks */,
EB950B652E52067D002D68FA /* LUI in Frameworks */,
EB950B5F2E520671002D68FA /* SwiftProtobuf in Frameworks */,
EB950B632E52067A002D68FA /* QRCode in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -318,7 +398,8 @@
isa = PBXGroup;
children = (
699DEBA52AB0573200115D22 /* ShareViewController.swift */,
EB5C371F2E3669500052EFD7 /* Util */,
EB950B732E520954002D68FA /* Attachments.swift */,
EB950B762E521712002D68FA /* AttachmentDetails.swift */,
EB5C371E2E36693D0052EFD7 /* View */,
699DEBAA2AB0573200115D22 /* Info.plist */,
699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */,
@@ -333,6 +414,7 @@
699DEBA22AB0573200115D22 /* ShareExtension */,
69DCF4822AB70D0600CBE2CC /* NearbyShare */,
EB979F932E50CA23008B8C5C /* QuickDropiOS */,
EB950B6C2E5206E2002D68FA /* iOSShareExtension */,
698DFAEC29E353220064F247 /* Frameworks */,
69DA9A0F29E0BF5100A442DA /* Products */,
EBC05C8B2E33B59E00EECC18 /* Resources */,
@@ -345,6 +427,7 @@
69DA9A0E29E0BF5100A442DA /* QuickDrop.app */,
699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */,
EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */,
EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -393,6 +476,7 @@
children = (
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */,
EB2B41E32DC63AC80070FC39 /* SleepManager.swift */,
EBCC27F82D80C9F70079C478 /* Zip.swift */,
EB886E512E4E19390052B7D9 /* HKDF.swift */,
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */,
EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */,
@@ -423,14 +507,6 @@
path = View;
sourceTree = "<group>";
};
EB5C371F2E3669500052EFD7 /* Util */ = {
isa = PBXGroup;
children = (
EBCC27F82D80C9F70079C478 /* Zip.swift */,
);
path = Util;
sourceTree = "<group>";
};
EB5C37DD2E377B430052EFD7 /* Model */ = {
isa = PBXGroup;
children = (
@@ -440,6 +516,16 @@
path = Model;
sourceTree = "<group>";
};
EB950B6C2E5206E2002D68FA /* iOSShareExtension */ = {
isa = PBXGroup;
children = (
EB950B722E5207AF002D68FA /* iOSShareExtension.entitlements */,
EB950B682E5206E2002D68FA /* Info.plist */,
EB950B6B2E5206E2002D68FA /* ShareViewController.swift */,
);
path = iOSShareExtension;
sourceTree = "<group>";
};
EB979F932E50CA23008B8C5C /* QuickDropiOS */ = {
isa = PBXGroup;
children = (
@@ -577,10 +663,12 @@
EB5C37DE2E377C2E0052EFD7 /* Sources */,
EB5C37DF2E377C2E0052EFD7 /* Frameworks */,
EB5C37E02E377C2E0052EFD7 /* Resources */,
EB950B382E52027F002D68FA /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
EB950B362E52027F002D68FA /* PBXTargetDependency */,
);
name = QuickDropiOS;
packageProductDependencies = (
@@ -594,6 +682,30 @@
productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */;
productType = "com.apple.product-type.application";
};
EB950B2C2E52027F002D68FA /* iOSShareExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = EB950B3C2E52027F002D68FA /* Build configuration list for PBXNativeTarget "iOSShareExtension" */;
buildPhases = (
EB950B292E52027F002D68FA /* Sources */,
EB950B2A2E52027F002D68FA /* Frameworks */,
EB950B2B2E52027F002D68FA /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = iOSShareExtension;
packageProductDependencies = (
EB950B5E2E520671002D68FA /* SwiftProtobuf */,
EB950B602E520674002D68FA /* SwiftECC */,
EB950B622E52067A002D68FA /* QRCode */,
EB950B642E52067D002D68FA /* LUI */,
EB950B662E520680002D68FA /* RNDeviceName */,
);
productName = iOSShareExtension;
productReference = EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -614,6 +726,9 @@
EB5C37E12E377C2E0052EFD7 = {
CreatedOnToolsVersion = 16.4;
};
EB950B2C2E52027F002D68FA = {
CreatedOnToolsVersion = 16.4;
};
};
};
buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "QuickDrop" */;
@@ -656,6 +771,7 @@
69DA9A0D29E0BF5100A442DA /* QuickDrop */,
699DEBA02AB0573200115D22 /* QuickDropShareExtension */,
EB5C37E12E377C2E0052EFD7 /* QuickDropiOS */,
EB950B2C2E52027F002D68FA /* iOSShareExtension */,
);
};
/* End PBXProject section */
@@ -671,6 +787,7 @@
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */,
EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */,
EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
EB950B5A2E52065A002D68FA /* timeIsNow.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -698,6 +815,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
EB950B2B2E52027F002D68FA /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB950B5B2E52065A002D68FA /* timeIsNow.mp3 in Resources */,
EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */,
EB950B5C2E52065A002D68FA /* justMaybe.mp3 in Resources */,
EB950B712E52073F002D68FA /* Assets.xcassets in Resources */,
EB950B5D2E52065A002D68FA /* License.txt in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -721,6 +850,7 @@
EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */,
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB950B742E520957002D68FA /* Attachments.swift in Sources */,
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */,
@@ -729,6 +859,7 @@
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */,
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */,
EB950B782E521712002D68FA /* AttachmentDetails.swift in Sources */,
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */,
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
@@ -823,10 +954,47 @@
EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */,
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */,
EB950B792E521717002D68FA /* AttachmentDetails.swift in Sources */,
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
EB950B292E52027F002D68FA /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB950B452E520436002D68FA /* Helpers.swift in Sources */,
EB950B572E520591002D68FA /* ErrorAlertHandler.swift in Sources */,
EB950B462E520436002D68FA /* OutboundNearbyConnection.swift in Sources */,
EB950B472E520436002D68FA /* sharing_enums.pb.swift in Sources */,
EB950B482E520436002D68FA /* Localized.swift in Sources */,
EB950B492E520436002D68FA /* device_to_device_messages.pb.swift in Sources */,
EB950B4A2E520436002D68FA /* securegcm.pb.swift in Sources */,
EB950B4B2E520436002D68FA /* offline_wire_formats.pb.swift in Sources */,
EB950B4C2E520436002D68FA /* InboundNearbyConnection.swift in Sources */,
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */,
EB950B4F2E520436002D68FA /* Logger.swift in Sources */,
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */,
EB950B582E5205B4002D68FA /* Zip.swift in Sources */,
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */,
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */,
EB950B532E520436002D68FA /* ukey.pb.swift in Sources */,
EB950B542E520436002D68FA /* HKDF.swift in Sources */,
EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */,
EB950B562E520436002D68FA /* DataModel.swift in Sources */,
EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */,
EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */,
EB950B3F2E52038A002D68FA /* FilePicker.swift in Sources */,
EB950B402E52038A002D68FA /* Configuration.swift in Sources */,
EB950B412E52038A002D68FA /* PieProgressView.swift in Sources */,
EB950B592E520649002D68FA /* AudioManager.swift in Sources */,
EB950B752E52111C002D68FA /* Attachments.swift in Sources */,
EB950B6F2E5206E2002D68FA /* ShareViewController.swift in Sources */,
EB950B432E52038A002D68FA /* ContentView.swift in Sources */,
EB950B442E520414002D68FA /* License.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -835,6 +1003,11 @@
target = 699DEBA02AB0573200115D22 /* QuickDropShareExtension */;
targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */;
};
EB950B362E52027F002D68FA /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = EB950B2C2E52027F002D68FA /* iOSShareExtension */;
targetProxy = EB950B352E52027F002D68FA /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -878,6 +1051,7 @@
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) EXTENSION";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
@@ -911,6 +1085,8 @@
PROVISIONING_PROFILE_SPECIFIER = "";
REGISTER_APP_GROUPS = YES;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXTENSION;
"SWIFT_ACTIVE_COMPILATION_CONDITIONS[arch=*]" = "";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
@@ -1210,6 +1386,67 @@
};
name = Release;
};
EB950B392E52027F002D68FA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = iOSShareExtension/iOSShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iOSShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = iOSShareExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.iOSShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) EXTENSION";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
EB950B3A2E52027F002D68FA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = iOSShareExtension/iOSShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = iOSShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = iOSShareExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.iOSShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXTENSION;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1249,6 +1486,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
EB950B3C2E52027F002D68FA /* Build configuration list for PBXNativeTarget "iOSShareExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
EB950B392E52027F002D68FA /* Debug */,
EB950B3A2E52027F002D68FA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@@ -1343,6 +1589,31 @@
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
productName = LUI;
};
EB950B5E2E520671002D68FA /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
EB950B602E520674002D68FA /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
EB950B622E52067A002D68FA /* QRCode */ = {
isa = XCSwiftPackageProductDependency;
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
productName = QRCode;
};
EB950B642E52067D002D68FA /* LUI */ = {
isa = XCSwiftPackageProductDependency;
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
productName = LUI;
};
EB950B662E520680002D68FA /* RNDeviceName */ = {
isa = XCSwiftPackageProductDependency;
package = EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */;
productName = RNDeviceName;
};
EB97A0122E51D6B2008B8C5C /* RNDeviceName */ = {
isa = XCSwiftPackageProductDependency;
package = EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */;
-4
View File
@@ -89,10 +89,6 @@ class ErrorAlertHandler {
#if os(macOS)
let primaryButtonTitle = "InformDeveloper".localized()
let primaryButtonAction: () -> Void = {
log("Sending logging string")
sendLoggingString()
}
let secondaryButtonTitle = "CloseAlert".localized()
self.isAlertShown = true
+95 -64
View File
@@ -10,12 +10,11 @@ import LUI
struct ContentView: View {
@StateObject var sendModel = SendModel()
@StateObject var receiveModel = ReceiveModel()
var attachments: AttachmentDetails? = nil
var body: some View {
AppRootView(isPlus: .constant(true), phoneView: {
DeviceListView(sendModel: sendModel, receiveModel: receiveModel)
DeviceListView(attachments: attachments)
}, settingsView: {
EmptyView()
})
@@ -24,8 +23,13 @@ struct ContentView: View {
struct DeviceListView: View {
@ObservedObject var sendModel: SendModel
@ObservedObject var receiveModel: ReceiveModel
var attachments: AttachmentDetails? = nil
@StateObject var sendModel = SendModel()
#if !EXTENSION
@StateObject var receiveModel = ReceiveModel()
#endif
var body: some View {
@@ -67,53 +71,25 @@ struct DeviceListView: View {
let isSelected = sendModel.selectedDevice == device
SendPickerButton {
HStack(spacing: 12) {
let name = device.name ?? "Android"
ZStack {
Circle()
.fill(
LinearGradient(
colors: [Color(red: 0.623, green: 0.659, blue: 0.855),
Color(red: 0.474, green: 0.525, blue: 0.796)],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: 40, height: 40)
Image(systemName: device.icon)
.foregroundColor(.white)
.fontWeight(.bold)
ZStack {
if let attachments = attachments {
LUIButton {
sendModel.urls = attachments.urls
sendModel.textToSend = attachments.textToSend
sendModel.selectDevice(device: device)
} label: {
DeviceButton(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
}
VStack(alignment: .leading, spacing: 5) {
Text(name)
.fontWeight(.medium)
.foregroundColor(Color.primary)
Text(isSelected ? sendModel.progressState ?? "..." : "Available".localized())
.font(.system(size: 12))
.foregroundColor(Color.primary.opacity(0.6))
}
Spacer()
if isSelected, let progress = sendModel.progressValue {
PieProgressView(progress: progress, size: 20)
}
else {
Image(systemName: "chevron.right")
.foregroundColor(Color.primary.opacity(0.6))
}
else {
SendPickerButton {
DeviceButton(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
} onResult: { urls, text in
sendModel.urls = urls ?? []
sendModel.textToSend = text
sendModel.selectDevice(device: device)
}
}
.padding(.vertical, 13)
} onResult: { urls, text in
sendModel.urls = urls ?? []
sendModel.textToSend = text
sendModel.selectDevice(device: device)
}
.animation(.easeInOut, value: sendModel.progressValue)
.animation(.easeInOut, value: isSelected)
@@ -135,21 +111,76 @@ struct DeviceListView: View {
}
}
extension DeviceListView {
static var preview: DeviceListView {
let model = SendModel()
model.foundDevices = [
RemoteDeviceInfo(name: "MacBook Pro", type: .computer, id: "macbook-pro-id"),
RemoteDeviceInfo(name: "iPhone 14", type: .phone, id: "iphone-14-id"),
RemoteDeviceInfo(name: "Samsung Galaxy S21", type: .phone, id: "samsung-galaxy-s21-id")
]
return DeviceListView(sendModel: model, receiveModel: ReceiveModel())
struct DeviceButton: View {
let device: RemoteDeviceInfo
let isSelected: Bool
let progress: String?
let progressValue: Double?
var body: some View {
HStack(spacing: 12) {
let name = device.name ?? "Android"
ZStack {
Circle()
.fill(
LinearGradient(
colors: [Color(red: 0.623, green: 0.659, blue: 0.855),
Color(red: 0.474, green: 0.525, blue: 0.796)],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: 40, height: 40)
Image(systemName: device.icon)
.foregroundColor(.white)
.fontWeight(.bold)
}
VStack(alignment: .leading, spacing: 5) {
Text(name)
.fontWeight(.medium)
.foregroundColor(Color.primary)
Text(isSelected ? progress ?? "..." : "Available".localized())
.font(.system(size: 12))
.foregroundColor(Color.primary.opacity(0.6))
}
Spacer()
if isSelected, let progress = progressValue {
PieProgressView(progress: progress, size: 20)
}
else {
Image(systemName: "chevron.right")
.foregroundColor(Color.primary.opacity(0.6))
}
}
.padding(.vertical, 13)
}
}
#Preview {
NavigationView {
DeviceListView.preview
}
}
//extension DeviceListView {
// static var preview: DeviceListView {
// let model = SendModel()
// model.foundDevices = [
// RemoteDeviceInfo(name: "MacBook Pro", type: .computer, id: "macbook-pro-id"),
// RemoteDeviceInfo(name: "iPhone 14", type: .phone, id: "iphone-14-id"),
// RemoteDeviceInfo(name: "Samsung Galaxy S21", type: .phone, id: "samsung-galaxy-s21-id")
// ]
//
// return DeviceListView(sendModel: model, receiveModel: ReceiveModel())
// }
//}
//
//#Preview {
// NavigationView {
// DeviceListView.preview
// }
//}
+22
View File
@@ -0,0 +1,22 @@
//
// AttachmentDetails.swift
// QuickDrop
//
// Created by Leon Böttger on 17.08.25.
//
import Foundation
import SwiftUI
struct AttachmentDetails {
var urls: [URL]
var textToSend: String?
var shortDescription: String
#if os(macOS)
var previewImage: NSImage?
#else
var previewImage: Image?
#endif
var closeView: (() -> Void)? = nil
}
+112
View File
@@ -0,0 +1,112 @@
//
// Attachments.swift
// QuickDrop
//
// Created by Leon Böttger on 17.08.25.
//
import Foundation
import UniformTypeIdentifiers
import SwiftUI
#if os(macOS)
import Cocoa
#endif
func loadAttachments(with extensionContext: NSExtensionContext?, loadedItems: @escaping (AttachmentDetails) -> Void) {
guard let extensionContext = extensionContext else { fatalError() }
log("Loading attachments...")
var result = AttachmentDetails(urls: [], textToSend: nil, shortDescription: "", previewImage: nil)
let item = extensionContext.inputItems[0] as! NSExtensionItem
if let attachments = item.attachments {
if let text = item.attributedContentText?.string, attachments.isEmpty {
result.textToSend = text
#if os(macOS)
result.previewImage = NSImage(named: NSImage.multipleDocumentsName)
#else
result.previewImage = Image(systemName: "text.document")
#endif
let maxLength = 50
let cleanedText = text.replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: "")
if cleanedText.count > maxLength {
let index = cleanedText.index(cleanedText.startIndex, offsetBy: maxLength)
result.shortDescription = String(cleanedText[..<index]) + "..."
} else {
result.shortDescription = cleanedText
}
loadedItems(result)
} else {
for attachment in attachments {
attachment.loadItem(forTypeIdentifier: attachment.registeredTypeIdentifiers.first!, options: nil) { data, _ in
if let urlData = data as? Data,
let url = URL(dataRepresentation: urlData, relativeTo: nil, isAbsolute: false) {
log("Found URL from Data: \(url)")
result.urls += [url]
}
else if let url = data as? NSURL {
log("Found NSURL: \(url)")
result.urls += [(url as URL)]
}
else {
log("Attachment is not a URL, ignoring.")
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
extensionContext.cancelRequest(withError: cancelError)
}
if result.urls.count == attachments.count {
if result.urls.count == 1 {
if result.urls[0].isFileURL {
result.shortDescription = result.urls[0].lastPathComponent
#if os(macOS)
result.previewImage = NSWorkspace.shared.icon(forFile: result.urls[0].path)
#else
result.previewImage = Image(systemName: "document")
#endif
}
else if result.urls[0].scheme == "http" || result.urls[0].scheme == "https" {
result.shortDescription = result.urls[0].absoluteString
#if os(macOS)
result.previewImage = NSImage(named: NSImage.networkName)
#else
result.previewImage = Image(systemName: "network")
#endif
}
}
else {
result.shortDescription = String.localizedStringWithFormat("NFiles".localized(), result.urls.count)
#if os(macOS)
result.previewImage = NSImage(named: NSImage.multipleDocumentsName)
#else
result.previewImage = Image(systemName: "document")
#endif
}
DispatchQueue.main.async {
loadedItems(result)
}
}
}
}
}
}
else {
log("No attachments found in extension context. Exiting.")
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
extensionContext.cancelRequest(withError: cancelError)
}
}
+9 -64
View File
@@ -49,41 +49,15 @@ class ShareViewController: NSViewController, OutboundAppDelegate {
override func loadView() {
super.loadView()
let item = extensionContext!.inputItems[0] as! NSExtensionItem
if let attachments = item.attachments {
if let text = item.attributedContentText?.string, attachments.isEmpty {
textToSend = text
DispatchQueue.main.async {
self.setUpIcon()
}
} else {
for attachment in attachments {
if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { data, _ in
if let urlData = data as? Data,
let url = URL(dataRepresentation: urlData, relativeTo: nil, isAbsolute: false)
{
self.urls.append(url)
} else if let url = data as? NSURL {
self.urls.append(url as URL)
}
if self.urls.count == attachments.count {
DispatchQueue.main.async {
self.setUpIcon()
}
}
}
}
}
}
} else {
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
extensionContext!.cancelRequest(withError: cancelError)
return
}
loadAttachments(with: extensionContext, loadedItems: { result in
log("Loaded attachments: \(result)")
self.urls = result.urls
self.textToSend = result.textToSend
self.filesLabel?.stringValue = result.shortDescription
self.filesIcon?.image = result.previewImage
})
contentWrap!.addSubview(listViewWrapper!)
contentWrap!.addSubview(loadingOverlay!)
@@ -209,35 +183,6 @@ class ShareViewController: NSViewController, OutboundAppDelegate {
}
private func setUpIcon() {
if let textToSend = textToSend {
let maxLength = 50
let cleanedText = textToSend.replacingOccurrences(of: "\n", with: " ").replacingOccurrences(of: "\r", with: "")
if cleanedText.count > maxLength {
let index = cleanedText.index(cleanedText.startIndex, offsetBy: maxLength)
filesLabel!.stringValue = String(cleanedText[..<index]) + "..."
} else {
filesLabel!.stringValue = cleanedText
}
filesIcon!.image = NSImage(named: NSImage.multipleDocumentsName)
}
else if urls.count == 1 {
if urls[0].isFileURL {
filesLabel!.stringValue = urls[0].lastPathComponent
filesIcon!.image = NSWorkspace.shared.icon(forFile: urls[0].path)
} else if urls[0].scheme == "http" || urls[0].scheme == "https" {
filesLabel!.stringValue = urls[0].absoluteString
filesIcon!.image = NSImage(named: NSImage.networkName)
}
} else {
filesLabel!.stringValue = String.localizedStringWithFormat("NFiles".localized(), urls.count)
filesIcon!.image = NSImage(named: NSImage.multipleDocumentsName)
}
}
func addDevice(device: RemoteDeviceInfo) {
if foundDevices.isEmpty {
loadingOverlay?.animator().isHidden = true
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>SUBQUERY(extensionItems, $extensionItem, TRUEPREDICATE).@count &gt; 0</string>
</dict>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,47 @@
//
// ShareViewController.swift
// iOSShareExtension
//
// Created by Leon Böttger on 17.08.25.
//
import UIKit
import SwiftUI
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadAttachments(with: extensionContext, loadedItems: { [self] result in
log("ShareViewController: Loaded attachments: \(result)")
var attachmentDetails = result
attachmentDetails.closeView = { [weak self] in
// Close the extension view
self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
// Replace with your app’s SwiftUI entry view
let contentView = ContentView(attachments: attachmentDetails)
// Embed SwiftUI inside the extension’s controller
let hostingController = UIHostingController(rootView: contentView)
addChild(hostingController)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(hostingController.view)
// Pin to edges
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
hostingController.didMove(toParent: self)
})
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.multicast</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.leonboettger.neardrop</string>
</array>
</dict>
</plist>