mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Add device name lookup for unnamed devices
This commit is contained in:
1 parent
84d2150893
commit
e6d1c7b1a3
9 files changed
+397
-24
No files matched your search
@@ -36,6 +36,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
private var startedDeviceDiscovery = false
|
||||
private var startedAdvertising = false { didSet { informAboutStatus() }}
|
||||
private var browsers: [NWBrowser] = []
|
||||
private let deviceNameResolver = NearbyDeviceNameResolver()
|
||||
private let serviceTypes = ["_FC9F5ED42C8A._tcp."]
|
||||
private var qrCodePrivateKey: ECPrivateKey?
|
||||
private var qrCodeAdvertisingToken: Data?
|
||||
@@ -315,11 +316,11 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
var txtRecord: [String: Data] = [
|
||||
txtKeyEndpointInfo: deviceInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
]
|
||||
#if os(macOS)
|
||||
txtRecord[txtQuickDropCapabilities] = Data(notificationSyncCapabilityToken.utf8)
|
||||
if let keyFingerprint = notificationSyncKeyFingerprintHex() {
|
||||
txtRecord[txtQuickDropDeviceKeyFingerprint] = Data(keyFingerprint.utf8)
|
||||
}
|
||||
#if os(macOS)
|
||||
txtRecord[txtQuickDropCapabilities] = Data(notificationSyncCapabilityToken.utf8)
|
||||
#endif
|
||||
service.setTXTRecord(NetService.data(fromTXTRecord: txtRecord))
|
||||
service.publish()
|
||||
@@ -473,7 +474,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
|
||||
browsers.removeAll()
|
||||
startedDeviceDiscovery = false
|
||||
|
||||
deviceNameResolver.cancelAll()
|
||||
|
||||
for delegate in outboundAppDelegates {
|
||||
|
||||
@@ -552,14 +553,40 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return }
|
||||
guard let endpointInfoEncoded = txtRecord.dictionary[txtKeyEndpointInfo] else { return }
|
||||
guard let endpointInfoData = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return }
|
||||
var endpointInfo = EndpointInfo(data: endpointInfoData)
|
||||
let parsedEndpointInfo = EndpointInfo(data: endpointInfoData)
|
||||
var endpointInfo = parsedEndpointInfo
|
||||
?? EndpointInfo(name: unnamedAndroidFallbackDeviceName, deviceType: .unknown)
|
||||
|
||||
let cleanedName = endpointInfo.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if cleanedName?.isEmpty != false {
|
||||
let isQuickDropPeer = txtRecord.dictionary[txtQuickDropDeviceKeyFingerprint] != nil
|
||||
let needsDeviceNameLookup = deviceNameResolver.needsLookup(for: parsedEndpointInfo)
|
||||
if needsDeviceNameLookup {
|
||||
endpointInfo.name = unnamedAndroidFallbackDeviceName
|
||||
}
|
||||
let deviceInfo = addFoundDevice(foundService: &foundService, endpointInfo: endpointInfo, endpointID: endpointID)
|
||||
let deviceInfo = addFoundDevice(
|
||||
foundService: &foundService,
|
||||
endpointInfo: endpointInfo,
|
||||
endpointID: endpointID,
|
||||
isQuickDropPeer: isQuickDropPeer
|
||||
)
|
||||
|
||||
if needsDeviceNameLookup {
|
||||
deviceNameResolver.resolveName(for: service, endpointID: endpointID) { [weak self] deviceName in
|
||||
guard let self else { return }
|
||||
guard self.foundServices[endpointID]?.device?.name == self.unnamedAndroidFallbackDeviceName else { return }
|
||||
|
||||
var resolvedEndpointInfo = endpointInfo
|
||||
resolvedEndpointInfo.name = deviceName
|
||||
var resolvedFoundService = FoundServiceInfo(service: service)
|
||||
_ = self.addFoundDevice(
|
||||
foundService: &resolvedFoundService,
|
||||
endpointInfo: resolvedEndpointInfo,
|
||||
endpointID: endpointID,
|
||||
isQuickDropPeer: isQuickDropPeer
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
deviceNameResolver.cancelLookup(for: endpointID)
|
||||
}
|
||||
|
||||
if let qrData = endpointInfo.qrCodeData, let qrCodeAdvertisingToken = qrCodeAdvertisingToken, let qrCodeNameEncryptionKey = qrCodeNameEncryptionKey {
|
||||
|
||||
@@ -576,7 +603,12 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
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)
|
||||
let deviceInfo = addFoundDevice(
|
||||
foundService: &foundService,
|
||||
endpointInfo: endpointInfo,
|
||||
endpointID: endpointID,
|
||||
isQuickDropPeer: isQuickDropPeer
|
||||
)
|
||||
for delegate in outboundAppDelegates {
|
||||
delegate.startTransferWithQrCode(device: deviceInfo)
|
||||
}
|
||||
@@ -588,9 +620,18 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
}
|
||||
|
||||
|
||||
private func addFoundDevice(foundService: inout FoundServiceInfo, endpointInfo: EndpointInfo, endpointID: String) -> RemoteDeviceInfo {
|
||||
private func addFoundDevice(
|
||||
foundService: inout FoundServiceInfo,
|
||||
endpointInfo: EndpointInfo,
|
||||
endpointID: String,
|
||||
isQuickDropPeer: Bool = false
|
||||
) -> RemoteDeviceInfo {
|
||||
let hadPreviousDevice = foundServices[endpointID]?.device != nil
|
||||
let deviceInfo = RemoteDeviceInfo(info: endpointInfo, id: endpointID)
|
||||
let deviceInfo = RemoteDeviceInfo(
|
||||
info: endpointInfo,
|
||||
id: endpointID,
|
||||
isQuickDropPeer: isQuickDropPeer
|
||||
)
|
||||
foundService.device = deviceInfo
|
||||
foundServices[endpointID] = foundService
|
||||
|
||||
@@ -610,6 +651,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
|
||||
private func removeFoundDevice(service: NWBrowser.Result) {
|
||||
guard let endpointID = endpointID(for: service) else { return }
|
||||
deviceNameResolver.cancelLookup(for: endpointID)
|
||||
guard let _ = foundServices.removeValue(forKey: endpointID) else { return }
|
||||
for delegate in outboundAppDelegates {
|
||||
delegate.removeDevice(id: endpointID)
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
//
|
||||
// NearbyDeviceNameResolver.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 25.04.2026.
|
||||
//
|
||||
|
||||
import Darwin
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
final class NearbyDeviceNameResolver {
|
||||
private var hostnameResolvers: [UUID: NearbyNetworkScannerHostnameResolver] = [:]
|
||||
private var lookupTokens: [String: UUID] = [:]
|
||||
|
||||
func needsLookup(for endpointInfo: EndpointInfo?) -> Bool {
|
||||
let cleanedName = endpointInfo?.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return cleanedName?.isEmpty != false
|
||||
}
|
||||
|
||||
|
||||
func resolveName(for service: NWBrowser.Result, endpointID: String, completion: @escaping (String) -> Void) {
|
||||
let lookupToken = UUID()
|
||||
lookupTokens[endpointID] = lookupToken
|
||||
|
||||
resolveAdvertisedHostname(for: service) { [weak self] hostname in
|
||||
guard let self else { return }
|
||||
guard self.lookupTokens[endpointID] == lookupToken else { return }
|
||||
self.lookupTokens.removeValue(forKey: endpointID)
|
||||
guard let hostname else { return }
|
||||
completion(hostname)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cancelLookup(for endpointID: String) {
|
||||
lookupTokens.removeValue(forKey: endpointID)
|
||||
}
|
||||
|
||||
|
||||
func cancelAll() {
|
||||
lookupTokens.removeAll()
|
||||
hostnameResolvers.values.forEach { $0.cancel() }
|
||||
hostnameResolvers.removeAll()
|
||||
}
|
||||
|
||||
|
||||
private func resolveAdvertisedHostname(for service: NWBrowser.Result, completion: @escaping (String?) -> Void) {
|
||||
let resolverID = UUID()
|
||||
guard let resolver = NearbyNetworkScannerHostnameResolver(result: service, resolveTimeout: 0.5, completion: { [weak self] hostname in
|
||||
self?.hostnameResolvers.removeValue(forKey: resolverID)
|
||||
completion(hostname)
|
||||
}) else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
hostnameResolvers[resolverID] = resolver
|
||||
resolver.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final class NearbyNetworkScannerHostnameResolver: NSObject, NetServiceDelegate {
|
||||
private static let hostnameLookupQueue = DispatchQueue(
|
||||
label: "com.leonboettger.quickdrop.networkScannerHostname",
|
||||
qos: .utility
|
||||
)
|
||||
|
||||
private let service: NetService
|
||||
private let resolveTimeout: TimeInterval
|
||||
private var timeoutWorkItem: DispatchWorkItem?
|
||||
private var completion: ((String?) -> Void)?
|
||||
private var finished = false
|
||||
|
||||
init?(
|
||||
result: NWBrowser.Result,
|
||||
resolveTimeout: TimeInterval,
|
||||
completion: @escaping (String?) -> Void
|
||||
) {
|
||||
guard case let NWEndpoint.service(name: name, type: type, domain: domain, interface: _) = result.endpoint else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.service = NetService(
|
||||
domain: Self.normalizedServiceDomain(domain),
|
||||
type: Self.normalizedServiceType(type),
|
||||
name: name
|
||||
)
|
||||
self.resolveTimeout = resolveTimeout
|
||||
self.completion = completion
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
func start() {
|
||||
service.delegate = self
|
||||
service.resolve(withTimeout: resolveTimeout)
|
||||
|
||||
let timeoutWorkItem = DispatchWorkItem { [weak self] in
|
||||
self?.finish(hostname: nil)
|
||||
}
|
||||
self.timeoutWorkItem = timeoutWorkItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + resolveTimeout + 1.0, execute: timeoutWorkItem)
|
||||
}
|
||||
|
||||
|
||||
func cancel() {
|
||||
finish(hostname: nil, notify: false)
|
||||
}
|
||||
|
||||
|
||||
func netServiceDidResolveAddress(_ sender: NetService) {
|
||||
let numericAddresses = sender.addresses?.compactMap(Self.numericHostString) ?? []
|
||||
guard let ipv4Address = numericAddresses.first(where: NearbyNetworkScannerHostLookup.isIPv4Address) else {
|
||||
finish(hostname: nil)
|
||||
return
|
||||
}
|
||||
|
||||
Self.hostnameLookupQueue.async { [weak self] in
|
||||
let hostname = NearbyNetworkScannerHostLookup.hostname(forIPv4: ipv4Address)
|
||||
DispatchQueue.main.async {
|
||||
self?.finish(hostname: hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
|
||||
finish(hostname: nil)
|
||||
}
|
||||
|
||||
|
||||
private func finish(hostname: String?, notify: Bool = true) {
|
||||
guard !finished else { return }
|
||||
finished = true
|
||||
timeoutWorkItem?.cancel()
|
||||
service.stop()
|
||||
service.delegate = nil
|
||||
|
||||
let completion = completion
|
||||
self.completion = nil
|
||||
|
||||
if notify {
|
||||
completion?(hostname)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static func normalizedServiceDomain(_ domain: String) -> String {
|
||||
let trimmedDomain = domain.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedDomain.isEmpty else { return "local." }
|
||||
return trimmedDomain.hasSuffix(".") ? trimmedDomain : "\(trimmedDomain)."
|
||||
}
|
||||
|
||||
|
||||
private static func normalizedServiceType(_ type: String) -> String {
|
||||
let trimmedType = type.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmedType.hasSuffix(".") ? trimmedType : "\(trimmedType)."
|
||||
}
|
||||
|
||||
|
||||
private static func numericHostString(from addressData: Data) -> String? {
|
||||
addressData.withUnsafeBytes { rawBuffer in
|
||||
guard let baseAddress = rawBuffer.baseAddress else { return nil }
|
||||
|
||||
var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||
let sockaddrPointer = baseAddress.assumingMemoryBound(to: sockaddr.self)
|
||||
let result = getnameinfo(
|
||||
sockaddrPointer,
|
||||
socklen_t(addressData.count),
|
||||
&hostBuffer,
|
||||
socklen_t(hostBuffer.count),
|
||||
nil,
|
||||
0,
|
||||
NI_NUMERICHOST
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
return String(cString: hostBuffer)
|
||||
.split(separator: "%")
|
||||
.first
|
||||
.map(String.init)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum NearbyNetworkScannerHostLookup {
|
||||
static func hostname(forIPv4 address: String) -> String? {
|
||||
reverseDNSName(forIPv4: address).flatMap(prettifyDisplayName)
|
||||
}
|
||||
|
||||
|
||||
static func isIPv4Address(_ address: String) -> Bool {
|
||||
!address.contains(":")
|
||||
}
|
||||
|
||||
|
||||
private static func reverseDNSName(forIPv4 address: String) -> String? {
|
||||
var socketAddress = sockaddr_in()
|
||||
socketAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
|
||||
socketAddress.sin_family = sa_family_t(AF_INET)
|
||||
|
||||
guard inet_pton(AF_INET, address, &socketAddress.sin_addr) == 1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||
let result = withUnsafePointer(to: &socketAddress) { pointer in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPointer in
|
||||
getnameinfo(
|
||||
sockaddrPointer,
|
||||
socklen_t(MemoryLayout<sockaddr_in>.size),
|
||||
&hostBuffer,
|
||||
socklen_t(hostBuffer.count),
|
||||
nil,
|
||||
0,
|
||||
NI_NAMEREQD
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
return String(cString: hostBuffer)
|
||||
}
|
||||
|
||||
|
||||
private static func prettifyDisplayName(_ value: String?) -> String? {
|
||||
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let withoutZone = value.split(separator: "%").first.map(String.init) ?? value
|
||||
let withoutDomain = withoutZone.split(separator: ".").first.map(String.init) ?? withoutZone
|
||||
let prettified = withoutDomain
|
||||
.replacingOccurrences(of: "-", with: " ")
|
||||
.replacingOccurrences(of: "_", with: " ")
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
guard !prettified.isEmpty else { return nil }
|
||||
return prettified.capitalized
|
||||
}
|
||||
}
|
||||
@@ -62,20 +62,52 @@ public struct RemoteDeviceInfo: Codable, Identifiable, Equatable {
|
||||
public let name: String?
|
||||
public let type: DeviceType
|
||||
public let qrCodeData: Data?
|
||||
public let isQuickDropPeer: Bool
|
||||
public var id: String?
|
||||
|
||||
init(name: String?, type: DeviceType?, id: String? = nil) {
|
||||
init(name: String?, type: DeviceType?, id: String? = nil, isQuickDropPeer: Bool = false) {
|
||||
self.name = name
|
||||
self.type = type ?? .phone
|
||||
self.id = id
|
||||
self.qrCodeData = nil
|
||||
self.isQuickDropPeer = isQuickDropPeer
|
||||
}
|
||||
|
||||
init(info: EndpointInfo, id: String? = nil) {
|
||||
init(info: EndpointInfo, id: String? = nil, isQuickDropPeer: Bool = false) {
|
||||
self.name = info.name
|
||||
self.type = info.deviceType
|
||||
self.qrCodeData = info.qrCodeData
|
||||
self.id = id
|
||||
self.isQuickDropPeer = isQuickDropPeer
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case type
|
||||
case qrCodeData
|
||||
case isQuickDropPeer
|
||||
case isVerifiedQuickDropPeer
|
||||
case id
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
name = try container.decodeIfPresent(String.self, forKey: .name)
|
||||
type = try container.decode(DeviceType.self, forKey: .type)
|
||||
qrCodeData = try container.decodeIfPresent(Data.self, forKey: .qrCodeData)
|
||||
isQuickDropPeer = try container.decodeIfPresent(Bool.self, forKey: .isQuickDropPeer)
|
||||
?? container.decodeIfPresent(Bool.self, forKey: .isVerifiedQuickDropPeer)
|
||||
?? false
|
||||
id = try container.decodeIfPresent(String.self, forKey: .id)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(name, forKey: .name)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encodeIfPresent(qrCodeData, forKey: .qrCodeData)
|
||||
try container.encode(isQuickDropPeer, forKey: .isQuickDropPeer)
|
||||
try container.encodeIfPresent(id, forKey: .id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
EB3B9F162EF7150400D6704E /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB3B9F172EF7150400D6704E /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
EB3B9F182EF7150400D6704E /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB3B9F192EF7150400D6704F /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */; };
|
||||
EB3B9F192EF7150400D6704E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
|
||||
EB3B9F1A2EF7150400D6704E /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
|
||||
EB3B9F1B2EF7150400D6704E /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; };
|
||||
@@ -147,6 +148,7 @@
|
||||
EB3B9F6C2EF7151900D6704E /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB3B9F6D2EF7151900D6704E /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
EB3B9F6E2EF7151900D6704E /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB3B9F6F2EF7151900D6704F /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */; };
|
||||
EB3B9F6F2EF7151900D6704E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
|
||||
EB3B9F702EF7151900D6704E /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E92E7025D900FD749C /* ApIsolationTester.swift */; };
|
||||
EB3B9F712EF7151900D6704E /* ActiveNetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E72E7025D900FD749C /* ActiveNetworkInterface.swift */; };
|
||||
@@ -199,11 +201,13 @@
|
||||
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB5C37CF2E377A7F0052EFD8 /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */; };
|
||||
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB5C37D42E377A7F0052EFD8 /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */; };
|
||||
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */; };
|
||||
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D72E377ACF0052EFD7 /* SwiftECC */; };
|
||||
EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */; };
|
||||
@@ -213,6 +217,7 @@
|
||||
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
|
||||
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB5C37F42E377CE70052EFD8 /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */; };
|
||||
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
|
||||
EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
|
||||
EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; };
|
||||
@@ -285,6 +290,7 @@
|
||||
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 */; };
|
||||
EB950B522E520436002D68FB /* NearbyDeviceNameResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.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 */; };
|
||||
EB950B562E520436002D68FA /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
@@ -495,6 +501,7 @@
|
||||
69DA9A1329E0BF5200A442DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
69DA9A1829E0BF5200A442DA /* QuickDrop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickDrop.entitlements; sourceTree = "<group>"; };
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnectionManager.swift; sourceTree = "<group>"; };
|
||||
EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyDeviceNameResolver.swift; sourceTree = "<group>"; };
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboundNearbyConnection.swift; sourceTree = "<group>"; };
|
||||
69DA9A2829E18CB500A442DA /* ukey.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ukey.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2929E18CB500A442DA /* wire_format.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = wire_format.pb.swift; sourceTree = "<group>"; };
|
||||
@@ -788,6 +795,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
|
||||
EB3B9F192EF7150400D67050 /* NearbyDeviceNameResolver.swift */,
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
|
||||
A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */,
|
||||
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */,
|
||||
@@ -1315,6 +1323,7 @@
|
||||
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D42F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EB5C37D42E377A7F0052EFD8 /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */,
|
||||
EB1325EB2E7025D900FD749C /* ApIsolationTester.swift in Sources */,
|
||||
EB1325EC2E7025D900FD749C /* ActiveNetworkInterface.swift in Sources */,
|
||||
@@ -1354,6 +1363,7 @@
|
||||
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D52F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EB5C37CF2E377A7F0052EFD8 /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EBE5FF422EDB6F6F0019B380 /* Configuration.swift in Sources */,
|
||||
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */,
|
||||
EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */,
|
||||
@@ -1411,6 +1421,7 @@
|
||||
EB3B9F172EF7150400D6704E /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D62F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB3B9F182EF7150400D6704E /* NearbyConnectionManager.swift in Sources */,
|
||||
EB3B9F192EF7150400D6704F /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EB3B9F192EF7150400D6704E /* Configuration.swift in Sources */,
|
||||
EB3B9F1A2EF7150400D6704E /* AttachmentDetails.swift in Sources */,
|
||||
EB3B9F1B2EF7150400D6704E /* HKDF.swift in Sources */,
|
||||
@@ -1476,6 +1487,7 @@
|
||||
EB3B9F6D2EF7151900D6704E /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D72F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB3B9F6E2EF7151900D6704E /* NearbyConnectionManager.swift in Sources */,
|
||||
EB3B9F6F2EF7151900D6704F /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EB3B9F6F2EF7151900D6704E /* Helpers.swift in Sources */,
|
||||
EB3B9F702EF7151900D6704E /* ApIsolationTester.swift in Sources */,
|
||||
EB3B9F712EF7151900D6704E /* ActiveNetworkInterface.swift in Sources */,
|
||||
@@ -1496,6 +1508,7 @@
|
||||
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EB886E672E4F2F800052B7D9 /* License.swift in Sources */,
|
||||
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EB5C37F42E377CE70052EFD8 /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */,
|
||||
EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */,
|
||||
EB886E532E4E19390052B7D9 /* HKDF.swift in Sources */,
|
||||
@@ -1551,6 +1564,7 @@
|
||||
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */,
|
||||
EBA1BB1F2EF1F6030068E46E /* ChangeDeviceNameModifier.swift in Sources */,
|
||||
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */,
|
||||
EB950B522E520436002D68FB /* NearbyDeviceNameResolver.swift in Sources */,
|
||||
EB950B532E520436002D68FA /* ukey.pb.swift in Sources */,
|
||||
EBBC93B22EC8B76600F541A1 /* LocalNetworkPermissionView.swift in Sources */,
|
||||
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */,
|
||||
|
||||
@@ -289,9 +289,21 @@ struct DeviceButtonLabel: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(name)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color.primary)
|
||||
HStack(spacing: 5) {
|
||||
Text(name)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color.primary)
|
||||
|
||||
if device.isQuickDropPeer {
|
||||
Image(.menuBarIcon)
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 14, height: 14)
|
||||
.foregroundColor(Color.primary)
|
||||
.opacity(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
Text(isSelected ? progress ?? "..." : "Available".localized())
|
||||
.font(.system(size: 12))
|
||||
|
||||
@@ -95,6 +95,9 @@ class ShareViewController: NSViewController, OutboundAppDelegate {
|
||||
|
||||
progressDeviceIconWrap!.wantsLayer = true
|
||||
progressDeviceIconWrap!.layer!.masksToBounds = false
|
||||
progressDeviceSecondaryIcon?.image = NSImage(named: "MenuBarIcon")
|
||||
progressDeviceSecondaryIcon?.image?.isTemplate = true
|
||||
progressDeviceSecondaryIcon?.contentTintColor = .white
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +253,7 @@ class ShareViewController: NSViewController, OutboundAppDelegate {
|
||||
progressView?.animator().isHidden = false
|
||||
progressDeviceName?.stringValue = getDeviceName(device: device)
|
||||
progressDeviceIcon?.image = imageForDeviceType(type: device.type)
|
||||
progressDeviceSecondaryIcon?.isHidden = !device.isQuickDropPeer
|
||||
progressProgressBar?.startAnimation(nil)
|
||||
progressState?.stringValue = "Preparing".localized()
|
||||
chosenDevice = device
|
||||
@@ -383,8 +387,11 @@ extension ShareViewController: NSCollectionViewDataSource {
|
||||
|
||||
let device = foundDevices[indexPath[1]]
|
||||
|
||||
collectionViewItem.textField?.stringValue = getDeviceName(device: device)
|
||||
collectionViewItem.imageView?.image = imageForDeviceType(type: device.type)
|
||||
collectionViewItem.configure(
|
||||
deviceName: getDeviceName(device: device),
|
||||
deviceImage: imageForDeviceType(type: device.type),
|
||||
showsQuickDropPeerMarker: device.isQuickDropPeer && device.type == .phone
|
||||
)
|
||||
collectionViewItem.clickHandler = {
|
||||
self.selectDevice(device: device)
|
||||
}
|
||||
|
||||
@@ -177,17 +177,17 @@ Gw
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="rb3-78-PXe"/>
|
||||
</imageView>
|
||||
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BEO-kU-a2Q">
|
||||
<rect key="frame" x="24" y="-8" width="32" height="32"/>
|
||||
<rect key="frame" x="16.800000000000001" y="16.800000000000001" width="14.4" height="14.4"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="32" id="05I-Ef-84Z"/>
|
||||
<constraint firstAttribute="width" constant="32" id="V0g-Fz-HWH"/>
|
||||
<constraint firstAttribute="height" constant="14.4" id="05I-Ef-84Z"/>
|
||||
<constraint firstAttribute="width" constant="14.4" id="V0g-Fz-HWH"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="k1c-Xl-fTQ"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="k1c-Xl-fTQ"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="BEO-kU-a2Q" secondAttribute="trailing" constant="-8" id="59U-Zi-ESa"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BEO-kU-a2Q" secondAttribute="bottom" constant="-8" id="8ty-pq-miR"/>
|
||||
<constraint firstItem="BEO-kU-a2Q" firstAttribute="centerX" secondItem="84U-lB-SRB" secondAttribute="centerX" id="59U-Zi-ESa"/>
|
||||
<constraint firstItem="BEO-kU-a2Q" firstAttribute="centerY" secondItem="84U-lB-SRB" secondAttribute="centerY" id="8ty-pq-miR"/>
|
||||
<constraint firstItem="84U-lB-SRB" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="top" id="MB9-UI-2mb"/>
|
||||
<constraint firstItem="84U-lB-SRB" firstAttribute="leading" secondItem="jBg-Ih-Gac" secondAttribute="leading" id="b23-hJ-aWT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="84U-lB-SRB" secondAttribute="trailing" id="eKl-3V-K49"/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import Cocoa
|
||||
|
||||
class DeviceListCell: NSCollectionViewItem {
|
||||
public var clickHandler: (() -> Void)?
|
||||
@IBOutlet private var peerMarkerView: NSImageView?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -17,10 +18,20 @@ class DeviceListCell: NSCollectionViewItem {
|
||||
btn.setButtonType(.momentaryPushIn)
|
||||
btn.action = #selector(onClick)
|
||||
btn.target = self
|
||||
|
||||
peerMarkerView?.image = NSImage(named: "MenuBarIcon")
|
||||
peerMarkerView?.image?.isTemplate = true
|
||||
peerMarkerView?.contentTintColor = .white
|
||||
}
|
||||
|
||||
@IBAction func onClick(_: Any?) {
|
||||
guard let handler = clickHandler else { return }
|
||||
handler()
|
||||
}
|
||||
|
||||
func configure(deviceName: String, deviceImage: NSImage, showsQuickDropPeerMarker: Bool) {
|
||||
textField?.stringValue = deviceName
|
||||
imageView?.image = deviceImage
|
||||
peerMarkerView?.isHidden = !showsQuickDropPeerMarker
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,20 @@
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="QGU-rR-rJQ"/>
|
||||
</imageView>
|
||||
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="89K-DQ-f8g">
|
||||
<rect key="frame" x="95.799999999999997" y="58.799999999999997" width="14.4" height="14.4"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="14.4" id="Lfh-Ha-Njl"/>
|
||||
<constraint firstAttribute="height" constant="14.4" id="Wm1-YW-85X"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="0fp-y5-fj9"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="89K-DQ-f8g" firstAttribute="centerX" secondItem="O1J-am-CoK" secondAttribute="centerX" id="Bja-VK-UQf"/>
|
||||
<constraint firstItem="O1J-am-CoK" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="R76-2L-pXb"/>
|
||||
<constraint firstAttribute="bottom" secondItem="QPM-sE-maP" secondAttribute="bottom" id="Ryn-nY-yZp"/>
|
||||
<constraint firstItem="89K-DQ-f8g" firstAttribute="centerY" secondItem="O1J-am-CoK" secondAttribute="centerY" id="Yeh-mY-trP"/>
|
||||
<constraint firstItem="O1J-am-CoK" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="ZFA-S9-fpT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="QPM-sE-maP" secondAttribute="trailing" id="eOl-Va-HXq"/>
|
||||
<constraint firstItem="QPM-sE-maP" firstAttribute="top" secondItem="O1J-am-CoK" secondAttribute="bottom" constant="10" id="leg-nD-mHB"/>
|
||||
@@ -53,6 +63,7 @@
|
||||
<collectionViewItem id="1Lb-iJ-Nar" customClass="DeviceListCell" customModule="QuickDropShareExtension" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="imageView" destination="O1J-am-CoK" id="zQn-mV-bRy"/>
|
||||
<outlet property="peerMarkerView" destination="89K-DQ-f8g" id="iWY-ru-o1h"/>
|
||||
<outlet property="textField" destination="QPM-sE-maP" id="s0j-Qq-7iA"/>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="gZn-20-o77"/>
|
||||
</connections>
|
||||
|
||||
Reference in new issue
Block a user