diff --git a/NearbyShare/Connection/NearbyConnectionManager.swift b/NearbyShare/Connection/NearbyConnectionManager.swift index 16043f1..3b9bf41 100644 --- a/NearbyShare/Connection/NearbyConnectionManager.swift +++ b/NearbyShare/Connection/NearbyConnectionManager.swift @@ -16,6 +16,7 @@ import System import SwiftECC import CryptoKit import SwiftUI +import QRCode public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject { @@ -397,13 +398,17 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb } - public func generateQrCodeKey() -> String{ + public func generateQrCodeKey() -> Image? { + + log("[NearbyConnectionManager] Generating new QR code key") + let domain = Domain.instance(curve: .EC256r1) let (pubKey, privKey) = domain.makeKeyPair() qrCodePrivateKey = privKey var keyData = Data() keyData.append(contentsOf: [0, 0, 2]) let keyBytes = Data(pubKey.w.x.asSignedBytes()) + // Sometimes, for some keys, there will be a leading zero byte. Strip that, Android really hates it (it breaks the endpoint info) keyData.append(keyBytes.suffixOfAtMost(numBytes: 32)) @@ -411,7 +416,26 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb qrCodeAdvertisingToken = HKDF.deriveKey(ikm: ikm, salt: Data(), info: "advertisingContext".data(using: .utf8)!, outputLength: 16).data() qrCodeNameEncryptionKey = HKDF.deriveKey(ikm: ikm, salt: Data(), info: "encryptionKey".data(using: .utf8)!, outputLength: 16) - return keyData.urlSafeBase64EncodedString() + + do { + let qrKey = keyData.urlSafeBase64EncodedString() + let qrCodeImage = try QRCode.build + .text("https://quickshare.google/qrcode#key=\(qrKey)") + .backgroundColor(CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 0)) + .quietZonePixelCount(3) + .onPixels.shape(.circle()) + .eye.shape(.squircle()) + .errorCorrection(.low) + .generate + .image(dimension: 1000) + + return Image(decorative: qrCodeImage, scale: 1.0, orientation: .up) + } + catch { + log("Error generating QR code: \(error)") + } + + return nil } diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 7cf0c83..b047ac3 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -121,6 +121,8 @@ EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; }; EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54132D78E9E400989564 /* LicenseWindow.swift */; }; + EB821C992E76FE5300036963 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; }; + EB821C9A2E76FE5300036963 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; }; EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; }; EB886E532E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; }; EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; }; @@ -987,6 +989,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EB821C992E76FE5300036963 /* SmallSheetView.swift in Sources */, EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */, EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */, EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */, @@ -1054,6 +1057,7 @@ EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */, EB1325D52E701D8E00FD749C /* Settings.swift in Sources */, EB950B562E520436002D68FA /* DataModel.swift in Sources */, + EB821C9A2E76FE5300036963 /* SmallSheetView.swift in Sources */, EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */, EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */, EB16B6192E58B4C8007D9792 /* TrustStore.swift in Sources */, @@ -1128,8 +1132,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; - SHARED_BUILD = 73; - SHARED_VERSION = 2.4; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) EXTENSION"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1168,8 +1170,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; - SHARED_BUILD = 73; - SHARED_VERSION = 2.4; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXTENSION; "SWIFT_ACTIVE_COMPILATION_CONDITIONS[arch=*]" = ""; diff --git a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 06ad0c9..a6f5dc7 100644 --- a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -43,7 +43,7 @@ "location" : "https://github.com/leonboe1/LUI.git", "state" : { "branch" : "main", - "revision" : "2e656d5e3634e47f95f81af86a2a56080f05af88" + "revision" : "a0d723963afaec816df7a99ae7195674f8d4ce98" } }, { diff --git a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDropiOS.xcscheme b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDropiOS.xcscheme index dacdd71..6d9a4e8 100644 --- a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDropiOS.xcscheme +++ b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDropiOS.xcscheme @@ -32,8 +32,8 @@ UISceneConfigurations - UIDesignRequiresCompatibility - UIFileSharingEnabled diff --git a/QuickDropiOS/SendModel.swift b/QuickDropiOS/SendModel.swift index 31fb586..b4d6cd9 100644 --- a/QuickDropiOS/SendModel.swift +++ b/QuickDropiOS/SendModel.swift @@ -12,6 +12,8 @@ import LUI class SendModel: ObservableObject, OutboundAppDelegate { + @Published var showQrCodeView: Bool = false + @Published var foundDevices: [RemoteDeviceInfo] = [] @Published var selectedDevice: RemoteDeviceInfo? @@ -94,11 +96,20 @@ class SendModel: ObservableObject, OutboundAppDelegate { func startTransferWithQrCode(device: RemoteDeviceInfo) { - selectDevice(device: device) + showQrCodeView = false + + #if EXTENSION + if let attachments = NearbyConnectionManager.shared.attachments { + self.selectDevice(device:device, with: attachments) + } + #endif } - func selectDevice(device: RemoteDeviceInfo) { + func selectDevice(device: RemoteDeviceInfo, with attachments: AttachmentDetails) { + + self.urls = attachments.urls + self.textToSend = attachments.textToSend // already selected, cancel transfer if device == selectedDevice { @@ -108,6 +119,7 @@ class SendModel: ObservableObject, OutboundAppDelegate { NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!) } else { + progressValue = 0 progressState = "Connecting".localized() selectedDevice = device diff --git a/ShareExtension/Info.plist b/ShareExtension/Info.plist index 853aa65..2018939 100644 --- a/ShareExtension/Info.plist +++ b/ShareExtension/Info.plist @@ -14,5 +14,7 @@ NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).ShareViewController + UIDesignRequiresCompatibility + diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index 305dc79..aaf9ca0 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -8,7 +8,6 @@ import Cocoa import Foundation import SwiftUI -import QRCode class ShareViewController: NSViewController, OutboundAppDelegate { @@ -133,26 +132,7 @@ class ShareViewController: NSViewController, OutboundAppDelegate { private func openQrCodeView() { if qrCodeSheetView == nil { - var image: Image? = nil - - do { - let qrKey = NearbyConnectionManager.shared.generateQrCodeKey() - let qrCodeImage = try QRCode.build - .text("https://quickshare.google/qrcode#key=\(qrKey)") - .backgroundColor(CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 0)) - .quietZonePixelCount(3) - .onPixels.shape(.circle()) - .eye.shape(.squircle()) - .errorCorrection(.low) - .generate.image(dimension: 1000) - - image = Image(nsImage: NSImage(cgImage: qrCodeImage, size: qrCodeImage.size)) - } - catch { - log("Error generating QR code: \(error)") - } - - let contentView = SmallSheetView(type: .sendToDeviceQrCode, dynamicQrCode: image) { + let contentView = SmallSheetView(type: .sendToDeviceQrCode, dynamicQrCode: NearbyConnectionManager.shared.generateQrCodeKey()) { self.closeQrCodeView() } diff --git a/ShareExtension/View/SmallSheetView.swift b/ShareExtension/View/SmallSheetView.swift index e3b65f4..64ac3d1 100644 --- a/ShareExtension/View/SmallSheetView.swift +++ b/ShareExtension/View/SmallSheetView.swift @@ -8,7 +8,9 @@ import SwiftUI import QRCode +#if os(macOS) let smallSheetViewSize = CGSize(width: 530.0, height: 270.0) +#endif struct SmallSheetView: View { @State private var qrCode: String = "" @@ -26,6 +28,7 @@ struct SmallSheetView: View { HStack { + #if os(macOS) HStack(spacing: 5) { let imageSize: CGFloat = type == .sendToDeviceQrCode ? 25 : 20 @@ -40,21 +43,31 @@ struct SmallSheetView: View { } } .padding(.leading, 18) + #endif if type == .sendToDeviceQrCode { + + #if os(macOS) Spacer() + #endif HStack { + Text("ConnectWithQuickDropApp") .padding(5) .padding(.horizontal, 4) .background(Capsule().fill(colorScheme == .light ? .white : .white.opacity(0.15)).opacity(connectWithApp ? 1 : 0)) - Text("ConnectWithoutApp") - .padding(5) - .padding(.horizontal, 4) - .background(Capsule().fill(colorScheme == .light ? .white : .white.opacity(0.15)).opacity(connectWithApp ? 0 : 1)) + ZStack { + Text("ConnectWithoutApp") + Text("ConnectWithQuickDropApp") + .opacity(0) // keep size for label the same + } + .padding(5) + .padding(.horizontal, 4) + .background(Capsule().fill(colorScheme == .light ? .white : .white.opacity(0.15)).opacity(connectWithApp ? 0 : 1)) } + .multilineTextAlignment(.center) .padding(.horizontal, 1) .padding(4) .background(Capsule().fill(Color.gray.opacity(colorScheme == .light ? 0.2 : 0.15))) @@ -66,6 +79,9 @@ struct SmallSheetView: View { .animation(.smooth, value: connectWithApp) } + + #if os(macOS) + Spacer() Button(action: { @@ -76,6 +92,7 @@ struct SmallSheetView: View { .foregroundColor(.gray) }) .buttonStyle(PlainButtonStyle()) + #endif } .padding(.trailing, 15) .padding(.leading, 10) @@ -84,55 +101,80 @@ struct SmallSheetView: View { Spacer() + #if os(macOS) HStack { Spacer() - - ZStack { - if type == .sendToDeviceQrCode && dynamicQrCode != nil { - Image(.qrBackground) - .resizable() - .aspectRatio(1, contentMode: .fit) - } - - getImage() - .resizable() - .aspectRatio(1, contentMode: .fit) - } - .frame(height: 160) - + imageView Spacer() - - VStack { - Text(getDescription().localized()) - .padding(.top, 5) - .padding(.horizontal) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(.center) - - if type == .downloadCableConnectionApp { - Button { - if let url = URL(string: "https://apps.apple.com/de/app/idroid-phone-file-manager/id6746444380") { - NSWorkspace.shared.open(url) - } - } label: { - Text("DownloadCableConnectionApp") - } - .keyboardShortcut(.defaultAction) - .padding() - } - } - + descriptionView Spacer() } .padding(.horizontal) .padding(.bottom) + #else + imageView + .frame(maxWidth: 300) + .padding(.horizontal, 30) + Spacer() + descriptionView + #endif Spacer() } + #if os(macOS) .frame(width: smallSheetViewSize.width, height: smallSheetViewSize.height) + #endif } + + var imageView: some View { + ZStack { + if type == .sendToDeviceQrCode && dynamicQrCode != nil { + Image(.qrBackground) + .resizable() + .aspectRatio(1, contentMode: .fit) + } + + getImage() + .resizable() + .aspectRatio(1, contentMode: .fit) + } + #if os(macOS) + .frame(height: 160) + #endif + } + + + var descriptionView: some View { + VStack { + Text(getDescription().localized()) + .padding(.top, 5) + .padding(.horizontal) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.center) + + #if !(EXTENSION && os(iOS)) + if type == .downloadCableConnectionApp { + Button { + if let url = URL(string: "https://apps.apple.com/de/app/idroid-phone-file-manager/id6746444380") { + #if os(iOS) + UIApplication.shared.open(url) + #else + NSWorkspace.shared.open(url) + #endif + } + } label: { + Text("DownloadCableConnectionApp") + } + .keyboardShortcut(.defaultAction) + .padding() + } + #endif + } + } + + func getDescription() -> String { switch type { @@ -172,19 +214,6 @@ enum SheetViewType { private struct Preview: View { var body: some View { - SmallSheetView(type: .sendToDeviceQrCode, dynamicQrCode: getImage(), closeView: {}) - } - - func getImage() -> Image { - let qrCodeImage = try! QRCode.build - .text("https://quickshare.google/qrcode#key=AAA") - .backgroundColor(CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 0)) - .quietZonePixelCount(2) - .onPixels.shape(.circle()) - .eye.shape(.squircle()) - .errorCorrection(.low) - .generate.image(dimension: 1000) - - return Image(nsImage: NSImage(cgImage: qrCodeImage, size: qrCodeImage.size)) + SmallSheetView(type: .sendToDeviceQrCode, dynamicQrCode: NearbyConnectionManager.shared.generateQrCodeKey(), closeView: {}) } } diff --git a/iOSShareExtension/Info.plist b/iOSShareExtension/Info.plist index 1ebc4cf..853aa65 100644 --- a/iOSShareExtension/Info.plist +++ b/iOSShareExtension/Info.plist @@ -9,10 +9,10 @@ NSExtensionActivationRule SUBQUERY(extensionItems, $extensionItem, TRUEPREDICATE).@count > 0 - NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).ShareViewController NSExtensionPointIdentifier com.apple.share-services + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController