Auto-start transmission when scanning QR code (merge)

This commit is contained in:
Leon Böttger committed 2025-08-09 19:26:43 +02:00
1 parent a0c6770c51
commit 8e4f633686
12 files changed
+328 -43

No files matched your search

@@ -57,6 +57,7 @@ class NearbyConnection {
private var clientSeq: Int32 = 0
private(set) var pinCode: String?
private(set) var authKey: SymmetricKey?
init(connection: NWConnection, id: String) {
@@ -471,6 +472,7 @@ class NearbyConnection {
let authString = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 auth".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
let nextSecret = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 next".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32)
authKey = authString
pinCode = NearbyConnection.pinCodeFromAuthKey(authString)
let salt = Data([0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C,
@@ -5,14 +5,16 @@
// Created by Grishka on 08.04.2023.
//
import Foundation
import Network
import System
#if os(iOS)
import UIKit
#endif
import Foundation
import Network
import System
import SwiftECC
import CryptoKit
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate {
private let sleepManager = SleepManager.shared
@@ -25,6 +27,11 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
private var startedDeviceDiscovery = false
private var browsers: [NWBrowser] = []
private let serviceTypes = ["_FC9F5ED42C8A._tcp."]
private var qrCodePublicKey: ECPublicKey?
private var qrCodePrivateKey: ECPrivateKey?
private var qrCodeAdvertisingToken: Data?
private var qrCodeNameEncryptionKey: SymmetricKey?
private var qrCodeData: Data?
public let endpointID: [UInt8] = generateEndpointID()
public var mainAppDelegate: (any MainAppDelegate)?
@@ -109,7 +116,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
func getEndpointInfo() -> EndpointInfo {
#if os(macOS)
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
let endpointInfo = EndpointInfo(name: Host.current().localizedName ?? "Mac", deviceType: .computer)
#else
let endpointInfo = EndpointInfo(name: UIDevice.current.name, deviceType: .phone)
#endif
@@ -230,22 +237,56 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
}
guard let endpointID = endpointID(for: service) else { return }
var foundService = FoundServiceInfo(service: service)
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return }
guard let endpointInfoEncoded = txtRecord.dictionary["n"] else { return }
guard let endpointInfo = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return }
guard endpointInfo.count >= 19 else { return }
let deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7)
let deviceNameLength = Int(endpointInfo[17])
guard endpointInfo.count >= deviceNameLength + 17 else { return }
guard let deviceName = String(data: endpointInfo.subdata(in: 18 ..< (18 + deviceNameLength)), encoding: .utf8) else { return }
let deviceInfo = RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID)
guard let endpointInfoData = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return }
guard var endpointInfo = EndpointInfo(data: endpointInfoData) else { return }
var deviceInfo: RemoteDeviceInfo?
if let _ = endpointInfo.name {
deviceInfo = addFoundDevice(foundService: &foundService, endpointInfo: endpointInfo, endpointID: endpointID)
}
if let qrData = endpointInfo.qrCodeData, let qrCodeAdvertisingToken = qrCodeAdvertisingToken, let qrCodeNameEncryptionKey = qrCodeNameEncryptionKey {
log("[NearbyConnectionManager] Device has QR data: \(qrData.base64EncodedString()), advertising token is \(qrCodeAdvertisingToken.base64EncodedString())")
if qrData == qrCodeAdvertisingToken {
if let deviceInfo = deviceInfo {
for delegate in shareExtensionDelegates {
delegate.startTransferWithQrCode(device: deviceInfo)
}
}
}
else if qrData.count > 28 {
do {
let box = try AES.GCM.SealedBox(combined: qrData)
let decryptedName = try AES.GCM.open(box, using: qrCodeNameEncryptionKey, authenticating: qrCodeAdvertisingToken)
guard let name = String.init(data: decryptedName, encoding: .utf8) else { return }
endpointInfo.name = name
let deviceInfo = addFoundDevice(foundService: &foundService, endpointInfo: endpointInfo, endpointID: endpointID)
for delegate in shareExtensionDelegates {
delegate.startTransferWithQrCode(device: deviceInfo)
}
} catch {
log("[NearbyConnectionManager] Error decrypting QR code data of an invisible device: \(error)")
}
}
}
}
private func addFoundDevice(foundService: inout FoundServiceInfo, endpointInfo: EndpointInfo, endpointID: String) -> RemoteDeviceInfo {
let deviceInfo = RemoteDeviceInfo(info: endpointInfo, id: endpointID)
foundService.device = deviceInfo
foundServices[endpointID] = foundService
for delegate in shareExtensionDelegates {
delegate.addDevice(device: deviceInfo)
}
return deviceInfo
}
@@ -256,6 +297,35 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
delegate.removeDevice(id: endpointID)
}
}
public func generateQrCodeKey() -> String{
let domain = Domain.instance(curve: .EC256r1)
let (pubKey, privKey) = domain.makeKeyPair()
qrCodePublicKey = pubKey
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))
let ikm = SymmetricKey(data: keyData)
qrCodeAdvertisingToken = NearbyConnection.hkdf(inputKeyMaterial: ikm, salt: Data(), info: "advertisingContext".data(using: .utf8)!, outputByteCount: 16).data()
qrCodeNameEncryptionKey = NearbyConnection.hkdf(inputKeyMaterial: ikm, salt: Data(), info: "encryptionKey".data(using: .utf8)!, outputByteCount: 16)
qrCodeData = keyData
return keyData.urlSafeBase64EncodedString()
}
public func clearQrCodeKey(){
qrCodePublicKey = nil
qrCodePrivateKey = nil
qrCodeAdvertisingToken = nil
qrCodeNameEncryptionKey = nil
qrCodeData = nil
}
public func startOutgoingTransfer(deviceID: String, delegate: ShareExtensionDelegate, urls: [URL], textToSend: String?) {
@@ -265,6 +335,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
let nwconn = NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp))
let conn = OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls, textToSend: textToSend)
conn.delegate = self
conn.qrCodePrivateKey = qrCodePrivateKey
let transfer = OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate)
outgoingTransfers[deviceID] = transfer
conn.start()
@@ -26,6 +26,7 @@ class OutboundNearbyConnection: NearbyConnection {
private var totalBytesSent: Int64 = 0
private var textPayloadID: Int64 = 0
public var qrCodePrivateKey: ECPrivateKey?
public var delegate: OutboundNearbyConnectionDelegate?
enum State {
@@ -174,7 +175,7 @@ class OutboundNearbyConnection: NearbyConnection {
frame.v1.type = .connectionRequest
frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame()
frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)!
frame.v1.connectionRequest.endpointName = endpointInfo.name
frame.v1.connectionRequest.endpointName = endpointInfo.name ?? "QuickDrop"
frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize()
frame.v1.connectionRequest.mediums = [.wifiLan]
try sendFrameAsync(frame.serializedData())
@@ -277,6 +278,14 @@ class OutboundNearbyConnection: NearbyConnection {
pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame()
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
if let qrKey = qrCodePrivateKey, let authKey = authKey {
let signature = qrKey.sign(msg: authKey.data())
var serializedSignature = Data(signature.r)
serializedSignature.append(Data(signature.s))
pairedEncryption.v1.pairedKeyEncryption.qrCodeHandshakeData = serializedSignature
}
try sendTransferSetupFrame(pairedEncryption)
currentState = .sentPairedKeyEncryption
+58 -12
View File
@@ -7,6 +7,8 @@
import Foundation
import Network
import CryptoKit
import SwiftECC
protocol InboundNearbyConnectionDelegate {
@@ -36,6 +38,7 @@ public protocol MainAppDelegate {
public protocol ShareExtensionDelegate: AnyObject {
func addDevice(device: RemoteDeviceInfo)
func removeDevice(id: String)
func startTransferWithQrCode(device: RemoteDeviceInfo)
func connectionWasEstablished(pinCode: String)
func connectionFailed(with error: Error)
func transferAccepted()
@@ -45,19 +48,23 @@ public protocol ShareExtensionDelegate: AnyObject {
public struct RemoteDeviceInfo: Identifiable, Equatable {
public let name: String
public let name: String?
public let type: DeviceType
public let qrCodeData:Data?
public var id: String?
init(name: String, type: DeviceType, id: String? = nil) {
self.name = name
self.type = type
self.id = id
self.qrCodeData = nil
}
init(info: EndpointInfo) {
name = info.name
type = info.deviceType
init(info: EndpointInfo, id: String? = nil) {
self.name = info.name
self.type = info.deviceType
self.qrCodeData = info.qrCodeData
self.id = id
}
@@ -116,24 +123,63 @@ public struct FileMetadata {
struct EndpointInfo {
let name: String
var name: String?
let deviceType: RemoteDeviceInfo.DeviceType
let qrCodeData: Data?
init(name: String, deviceType: RemoteDeviceInfo.DeviceType) {
self.name = name
self.deviceType = deviceType
self.qrCodeData = nil
}
init?(data: Data) {
guard data.count > 17 else { return nil }
let deviceNameLength = Int(data[17])
guard data.count >= deviceNameLength + 18 else { return nil }
guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil }
let rawDeviceType = Int(data[0] & 7) >> 1
name = deviceName
deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)
let hasName = (data[0] & 0x10) == 0
let deviceNameLength: Int
let deviceName: String?
if hasName {
deviceNameLength = Int(data[17])
guard data.count >= deviceNameLength + 18 else { return nil }
guard let newDeviceName = String(data: data[18..<(18 + deviceNameLength)], encoding: .utf8) else { return nil }
deviceName = newDeviceName
}
else {
deviceNameLength = 0
deviceName = nil
}
let rawDeviceType: Int = Int(data[0] & 7) >> 1
self.name = deviceName
self.deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)
var offset = 1 + 16
if hasName {
offset = offset + 1 + deviceNameLength
}
var qrCodeData: Data? = nil
// read TLV records, if any
while data.count - offset > 2 {
let type = data[offset]
let length = Int(data[offset+1])
offset = offset + 2
if data.count - offset >= length{
// QR code data
if type == 1 {
qrCodeData = data.subdata(in: offset..<offset + length)
}
offset = offset + length
}
}
self.qrCodeData = qrCodeData
}
@@ -146,7 +192,7 @@ struct EndpointInfo {
endpointInfo.append(UInt8.random(in: 0 ... 255))
}
// Device name in UTF-8 prefixed with 1-byte length
var nameChars = [UInt8](name.utf8)
var nameChars = [UInt8]((name ?? "").utf8)
if nameChars.count > 255 {
nameChars = [UInt8](nameChars[0 ..< 255])
}
+19 -2
View File
@@ -6,6 +6,7 @@
//
import Foundation
import CryptoKit
extension Data {
func urlSafeBase64EncodedString() -> String {
@@ -20,7 +21,7 @@ extension Data {
})
}
static func randomData(length: Int) -> Data {
var data = Data(count: length)
data.withUnsafeMutableBytes {
@@ -29,7 +30,16 @@ extension Data {
return data
}
func suffixOfAtMost(numBytes: Int) -> Data{
if count <= numBytes {
return self
}
return subdata(in: count-numBytes..<count)
}
static func dataFromUrlSafeBase64(_ str: String) -> Data? {
var regularB64 = String(str.map {
if $0 == "_" {
@@ -53,6 +63,13 @@ extension Data {
}
extension SymmetricKey{
func data() -> Data{
return withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})
}
}
extension String {
var dataFromHex: Data {
var data = Data(capacity: count / 2)
+32
View File
@@ -95,6 +95,9 @@
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB9925852E45EF0900357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925842E45EF0900357055 /* QRCode */; };
EB9925872E45F12F00357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925862E45F12F00357055 /* QRCode */; };
EB9925892E45F13500357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925882E45F13500357055 /* QRCode */; };
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; };
@@ -243,6 +246,7 @@
files = (
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */,
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */,
EB9925852E45EF0900357055 /* QRCode in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -250,6 +254,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EB9925872E45F12F00357055 /* QRCode in Frameworks */,
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */,
EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */,
EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */,
@@ -262,6 +267,7 @@
files = (
EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */,
EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */,
EB9925892E45F13500357055 /* QRCode in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -509,6 +515,7 @@
EB2F9BFD2DA05F48004010A2 /* BezelNotification */,
EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */,
EB5C37DB2E377AD60052EFD7 /* SwiftECC */,
EB9925862E45F12F00357055 /* QRCode */,
);
productName = NearDrop;
productReference = 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */;
@@ -533,6 +540,7 @@
packageProductDependencies = (
EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */,
EB5C37EF2E377C3F0052EFD7 /* SwiftECC */,
EB9925882E45F13500357055 /* QRCode */,
);
productName = QuickDropiOS;
productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */;
@@ -589,6 +597,7 @@
69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */,
69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */,
EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */,
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */,
);
productRefGroup = 69DA9A0F29E0BF5100A442DA /* Products */;
projectDirPath = "";
@@ -1183,6 +1192,14 @@
minimumVersion = 2.1.0;
};
};
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dagronf/qrcode.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 26.10.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -1221,6 +1238,21 @@
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
EB9925842E45EF0900357055 /* QRCode */ = {
isa = XCSwiftPackageProductDependency;
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
productName = QRCode;
};
EB9925862E45F12F00357055 /* QRCode */ = {
isa = XCSwiftPackageProductDependency;
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
productName = QRCode;
};
EB9925882E45F13500357055 /* QRCode */ = {
isa = XCSwiftPackageProductDependency;
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
productName = QRCode;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 69DA9A0629E0BF5100A442DA /* Project object */;
@@ -1,5 +1,5 @@
{
"originHash" : "c998f2b1c34ecbb24a88c5711f17c1a351f48f3505ccd4cab4552e5760bd39a3",
"originHash" : "10744462d42c114b38e0002c6a341b37abcba576ba3203c8d951905fbbdf1f58",
"pins" : [
{
"identity" : "asn1",
@@ -28,6 +28,15 @@
"version" : "1.21.0"
}
},
{
"identity" : "qrcode",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dagronf/qrcode.git",
"state" : {
"revision" : "177d4091f7fcce1efc22de2a5fcdb671bd103101",
"version" : "26.10.0"
}
},
{
"identity" : "swift-cross-kit-types",
"kind" : "remoteSourceControl",
@@ -55,6 +64,15 @@
"version" : "1.30.0"
}
},
{
"identity" : "swift-qrcode-generator",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dagronf/swift-qrcode-generator",
"state" : {
"revision" : "2b1980b825f08a81ccc762b0c4d17fcde9d5e953",
"version" : "2.0.2"
}
},
{
"identity" : "swiftecc",
"kind" : "remoteSourceControl",
@@ -63,6 +81,15 @@
"revision" : "2b672501e3a9361316898aa2d5b0e684c992e01e",
"version" : "3.9.0"
}
},
{
"identity" : "swiftimagereadwrite",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dagronf/SwiftImageReadWrite",
"state" : {
"revision" : "42ace2412279f18bc264bc306e96b51c36e12a33",
"version" : "1.9.2"
}
}
],
"version" : 3
+4 -3
View File
@@ -355,16 +355,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
let mainMessage: String
let name = device.name ?? "Android"
switch transfer.type {
case .file:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [device.name, fileStr])
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [name, fileStr])
case .text:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingText" : "DeviceSendingText").localized(), arguments: [device.name, fileStr])
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingText" : "DeviceSendingText").localized(), arguments: [name, fileStr])
case .url:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingUrl" : "DeviceSendingUrl").localized(), arguments: [device.name, fileStr])
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingUrl" : "DeviceSendingUrl").localized(), arguments: [name, fileStr])
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "QRBackground.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: 66 KiB

+33 -4
View File
@@ -8,8 +8,10 @@
import Cocoa
import Foundation
import SwiftUI
import QRCode
class ShareViewController: NSViewController, ShareExtensionDelegate {
private var urls: [URL] = []
private var textToSend: String? = nil
private var foundDevices: [RemoteDeviceInfo] = []
@@ -154,7 +156,27 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
private func openQrCodeView() {
if qrCodeSheetView == nil {
let contentView = SmallSheetView(type: .sendToDeviceQrCode) {
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) {
self.closeQrCodeView()
}
@@ -279,6 +301,12 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
}
func startTransferWithQrCode(device: RemoteDeviceInfo) {
closeQrCodeView()
selectDevice(device: device)
}
func connectionWasEstablished(pinCode: String) {
connectionEstablished = true
@@ -447,9 +475,10 @@ extension ShareViewController: NSCollectionViewDataSource {
func getDeviceName(device: RemoteDeviceInfo) -> String {
if device.name.count <= 1 {
return "Android"
if let name = device.name, name.count > 1 {
return name
}
return device.name
return "Android"
}
}
+36 -6
View File
@@ -6,6 +6,7 @@
//
import SwiftUI
import QRCode
let smallSheetViewSize = CGSize(width: 530.0, height: 270.0)
@@ -13,6 +14,7 @@ struct SmallSheetView: View {
@State private var qrCode: String = ""
let type: SheetViewType
var dynamicQrCode: Image? = nil
let closeView: () -> Void
@Environment(\.colorScheme) var colorScheme
@@ -85,10 +87,18 @@ struct SmallSheetView: View {
HStack {
Spacer()
getImage()
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(height: 160)
ZStack {
if type == .sendToDeviceQrCode && dynamicQrCode != nil {
Image(.qrBackground)
.resizable()
.aspectRatio(1, contentMode: .fit)
}
getImage()
.resizable()
.aspectRatio(1, contentMode: .fit)
}
.frame(height: 160)
Spacer()
@@ -138,7 +148,7 @@ struct SmallSheetView: View {
func getImage() -> Image {
switch type {
case .sendToDeviceQrCode:
return connectWithApp ? Image(.qrApp) : Image(.QR)
return connectWithApp ? Image(.qrApp) : (dynamicQrCode ?? Image(.QR))
case .downloadAndroidApp:
return Image(.qrApp)
case .downloadCableConnectionApp:
@@ -156,5 +166,25 @@ enum SheetViewType {
#Preview {
SmallSheetView(type: .downloadCableConnectionApp, closeView: {})
Preview()
}
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))
}
}