mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Auto-start transmission when scanning QR code (merge)
This commit is contained in:
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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user