45 Commits
Author SHA1 Message Date
Leon Böttger 21f0f865b6 Bump version 2025-11-08 09:50:14 +01:00
Leon Böttger 026ebc33db Do not show review on GitHub release 2025-11-08 09:49:22 +01:00
Leon Böttger d01e1d7c34 Refactoring 2025-11-06 20:57:46 +01:00
Leon Böttger 14390035a3 Refactoring 2025-11-06 19:17:23 +01:00
Leon Böttger a3d295cd38 UI improvements 2025-11-06 19:03:16 +01:00
Leon Böttger 0ca2a13d66 Improve Toast View UI 2025-11-05 15:50:12 +01:00
Leon Böttger 05321adb6d Fix: progress bar not shown immediately when receiving many files 2025-11-05 15:09:07 +01:00
Leon Böttger 31b93a34a2 Fix incoming transmission capsule shape 2025-11-05 11:16:39 +01:00
Leon Böttger 261f91bc94 Add menu bar menu icons 2025-11-05 10:18:52 +01:00
Leon Böttger 646da767a7 Show average process for multiple incoming connnections 2025-10-19 14:39:25 +02:00
Leon Böttger 9d1e698d9b Refactoring 2025-10-19 14:20:44 +02:00
Leon Böttger a69a75cb07 Refactoring 2025-10-19 14:01:02 +02:00
Leon Böttger 1ac988a4fc Add progress notification when receiving files 2025-10-06 19:42:06 +02:00
Leon Böttger 673d4418f8 Better localizations 2025-10-06 14:09:47 +02:00
Leon Böttger 779144d26a Fix: iOS app was unable to send URLs 2025-10-06 14:09:10 +02:00
Leon Böttger 0fa585652d Update error handling instructions 2025-10-05 14:16:53 +02:00
Leon Böttger 1b1206d0b8 Comments 2025-10-05 14:01:37 +02:00
Leon Böttger 86afa8a206 Fix: Connection not cleaned up after unexpected close be peer 2025-10-05 13:07:10 +02:00
Leon Böttger dda001cb1a Fix: Connection not cleaned up after FIN was sent 2025-10-05 12:17:34 +02:00
Leon Böttger 4ae1663779 Update Strings 2025-09-23 12:38:07 +02:00
Leon Böttger 66acbf25a3 Fix logging 2025-09-23 12:02:43 +02:00
Leon Böttger 93537094e4 iOS: UI improvements 2025-09-21 18:53:53 +02:00
Leon Böttger b6b11c00c0 Add support for iPhone 17 Series 2025-09-21 14:49:04 +02:00
Leon Böttger 92671d8983 Clean up screenshots 2025-09-14 21:21:23 +02:00
Leon Böttger bfc23fde15 Add QR Code View to iOS 2025-09-14 21:02:39 +02:00
Leon Böttger a39a5a324a Fix App Icon for iOS 2025-09-13 16:15:02 +02:00
Leon Böttger fd7dfd959b Add Liquid Glass Icon 2025-09-13 15:13:18 +02:00
Leon Böttger bd4b181a49 Refactoring 2025-09-12 11:48:24 +02:00
Leon Böttger f066a11608 Fix: iOS app advertised to scan even though not connected to Wi-Fi 2025-09-12 11:37:48 +02:00
Leon Böttger 5429126c1e Fix: Only one file would be saved for multiple incoming files with same name 2025-09-12 11:20:28 +02:00
Leon Böttger 4bf8a9052d Disable Liquid Glass for now 2025-09-11 15:48:15 +02:00
Leon Böttger f786af9d12 Improve logging 2025-09-11 15:47:12 +02:00
Leon Böttger 6def7dbc9f Refactoring 2025-09-09 14:35:25 +02:00
Leon Böttger fcc0ce0818 If selecting custom folder, automatically enable "openFinderAfterReceiving" 2025-09-09 14:11:54 +02:00
Leon Böttger fd6978cc90 Add alert when selecting invalid folder for storing files 2025-09-09 13:58:36 +02:00
Leon Böttger 12ef87c4da Simplify Menu Bar Items 2025-09-09 12:49:56 +02:00
Leon Böttger 518fabcd33 Add translations for InfoPlist 2025-09-09 12:33:56 +02:00
Leon Böttger eb6bb8fc4d Add more languages 2025-09-09 12:10:47 +02:00
Leon Böttger d443412697 Fix iOS 2025-09-09 11:40:27 +02:00
Leon Böttger 110b0a4ae7 Add alert if share extension disabled 2025-09-09 11:35:44 +02:00
Leon Böttger 2b272c81c1 Refactoring 2025-09-09 11:14:29 +02:00
Leon Böttger 11afc232ab Refactoring UserDefaults 2025-09-09 11:08:13 +02:00
Leon Böttger 943414db58 Fix iOS compilation 2025-09-05 19:31:29 +02:00
Leon Böttger b135229d30 Fix: Mac showed no connection when connected using Ethernet 2025-09-05 19:25:56 +02:00
Leon Böttger 6126d9428d Merge pull request #3 from leonboe1/ios
Add QuickDrop for iOS
2025-09-05 18:56:26 +02:00
117 changed files with 19954 additions and 1192 deletions

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -392,6 +392,13 @@ class InboundNearbyConnection: NearbyConnection {
let pkeFrame = frame.v1.pairedKeyEncryption let pkeFrame = frame.v1.pairedKeyEncryption
if frame.v1.certificateInfo.publicCertificate.isEmpty {
log("[InboundNearbyConnection \(self.id)] Detected legacy peer.")
}
else {
log("[InboundNearbyConnection \(self.id)] Detected QuickDrop peer.")
}
// Check if we can accept the connection automatically // Check if we can accept the connection automatically
if !pkeFrame.secretIDHash.isEmpty, if !pkeFrame.secretIDHash.isEmpty,
let trustedCertData = TrustStore.shared.findTrustedKey(for: pkeFrame.secretIDHash), let trustedCertData = TrustStore.shared.findTrustedKey(for: pkeFrame.secretIDHash),
@@ -461,9 +468,11 @@ class InboundNearbyConnection: NearbyConnection {
} }
private func makeFileDestinationURL(_ initialDest: URL) -> URL { private func makeFileDestinationURL(_ initialDest: URL, usedDestinations: Set<URL>) -> URL {
var dest = initialDest var dest = initialDest
if FileManager.default.fileExists(atPath: dest.path) { let fm = FileManager.default
if fm.fileExists(atPath: dest.path) || usedDestinations.contains(dest) {
var counter = 1 var counter = 1
var path: String var path: String
let ext = dest.pathExtension let ext = dest.pathExtension
@@ -474,8 +483,8 @@ class InboundNearbyConnection: NearbyConnection {
path += ".\(ext)" path += ".\(ext)"
} }
counter += 1 counter += 1
} while FileManager.default.fileExists(atPath: path) dest = URL(fileURLWithPath: path)
dest = URL(fileURLWithPath: path) } while fm.fileExists(atPath: dest.path) || usedDestinations.contains(dest)
} }
return dest return dest
} }
@@ -487,12 +496,18 @@ class InboundNearbyConnection: NearbyConnection {
if frame.v1.introduction.fileMetadata.count > 0 && frame.v1.introduction.textMetadata.isEmpty { if frame.v1.introduction.fileMetadata.count > 0 && frame.v1.introduction.textMetadata.isEmpty {
let saveDirectory = SaveFilesManager.shared.getSaveDirectory() let saveDirectory = SaveFilesManager.shared.getSaveDirectory()
var usedDestinations = Set<URL>()
for file in frame.v1.introduction.fileMetadata { for file in frame.v1.introduction.fileMetadata {
let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent(file.name)) let initialDest = saveDirectory.appendingPathComponent(file.name)
let info = InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), let dest = makeFileDestinationURL(initialDest, usedDestinations: usedDestinations)
payloadID: file.payloadID, usedDestinations.insert(dest)
destinationURL: dest)
let info = InternalFileInfo(
meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType),
payloadID: file.payloadID,
destinationURL: dest
)
filesToBeReceived[file.payloadID] = info filesToBeReceived[file.payloadID] = info
bytesToBeTransferred += file.size bytesToBeTransferred += file.size
} }
@@ -524,10 +539,16 @@ class InboundNearbyConnection: NearbyConnection {
func checkIfCanProceed(metadata: TransferMetadata) { func checkIfCanProceed(metadata: TransferMetadata) {
let acceptAutomatically = isAuthenticated || UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue) let acceptAutomatically = isAuthenticated || Settings.shared.automaticallyAcceptFiles
DispatchQueue.main.async { DispatchQueue.main.async {
self.delegate?.obtainUserConsent(transfer: metadata, device: self.remoteDeviceInfo!, connection: self, acceptAutomatically: acceptAutomatically) if acceptAutomatically {
self.delegate?.obtainedUserConsentAutomatically(transfer: metadata, device: self.remoteDeviceInfo!, connection: self)
self.submitUserConsent(accepted: true, trustDevice: false)
}
else {
self.delegate?.obtainUserConsent(transfer: metadata, device: self.remoteDeviceInfo!, connection: self)
}
} }
} }
} }
@@ -542,14 +563,14 @@ class InboundNearbyConnection: NearbyConnection {
} }
func submitUserConsent(accepted: Bool, trustDevice: Bool, storeInTemp: Bool = false) { func submitUserConsent(accepted: Bool, trustDevice: Bool) {
if trustDevice, let peerCertificate = peerCertificate, let remoteDeviceInfo = remoteDeviceInfo { if trustDevice, let peerCertificate = peerCertificate, let remoteDeviceInfo = remoteDeviceInfo {
TrustStore.shared.addTrusted(certificate: peerCertificate, device: remoteDeviceInfo) TrustStore.shared.addTrusted(certificate: peerCertificate, device: remoteDeviceInfo)
} }
DispatchQueue.global(qos: .utility).async { DispatchQueue.global(qos: .utility).async {
if accepted { if accepted {
self.acceptTransfer(storeInTemp: storeInTemp) self.acceptTransfer()
} else { } else {
self.rejectTransfer() self.rejectTransfer()
} }
@@ -557,16 +578,28 @@ class InboundNearbyConnection: NearbyConnection {
} }
private func acceptTransfer(storeInTemp: Bool) { private func acceptTransfer() {
if currentState == .disconnected { if currentState == .disconnected {
log("[InboundNearbyConnection \(self.id)] Detected timeout, not accepting transfer") log("[InboundNearbyConnection \(self.id)] Detected timeout, not accepting transfer")
return return
} }
let storeInTemp = isFileTransferRestricted()
if storeInTemp {
log("[InboundNearbyConnection \(self.id)] File transfer restrictions detected, storing files in temporary directory.")
delegate?.showPlusScreen()
}
do { do {
if storeInTemp { if storeInTemp {
try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true)
} }
if filesToBeReceived.count >= 1 {
// Show progress bar for file transfers immediately
NearbyConnectionManager.shared.updatedTransferProgress(connection: self, progress: 0)
}
for (id, file) in filesToBeReceived { for (id, file) in filesToBeReceived {
let targetURL = storeInTemp ? SaveFilesManager.shared.tempDirectory.appendingPathComponent(file.destinationURL.lastPathComponent) : file.destinationURL let targetURL = storeInTemp ? SaveFilesManager.shared.tempDirectory.appendingPathComponent(file.destinationURL.lastPathComponent) : file.destinationURL
@@ -638,12 +671,7 @@ class InboundNearbyConnection: NearbyConnection {
#endif #endif
guard file.created else { continue } guard file.created else { continue }
do { try? FileManager.default.removeItem(at: file.destinationURL)
try FileManager.default.removeItem(at: file.destinationURL)
}
catch {
// if it fails, we don't care. Could be because file was not created yet
}
} }
} }
} }
@@ -164,6 +164,8 @@ class NearbyConnection {
private func receiveFrameAsync() { private func receiveFrameAsync() {
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in
if self.connectionClosed { if self.connectionClosed {
log("[NearbyConnection \(self.id)] Received FIN from peer, connection closed.")
self.connection.cancel()
return return
} }
if isComplete { if isComplete {
@@ -171,6 +173,7 @@ class NearbyConnection {
self.connectionClosedByPeer { self.connectionClosedByPeer {
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect() self.disconnect()
self.connection.cancel()
} }
return return
} }
@@ -201,6 +204,8 @@ class NearbyConnection {
private func receiveFrameAsync(length: UInt32) { private func receiveFrameAsync(length: UInt32) {
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { [self] content, _, isComplete, _ in connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { [self] content, _, isComplete, _ in
if self.connectionClosed { if self.connectionClosed {
log("[NearbyConnection \(self.id)] Received FIN from peer, connection closed.")
connection.cancel()
return return
} }
if isComplete { if isComplete {
@@ -216,6 +221,7 @@ class NearbyConnection {
self.connectionClosedByPeer { self.connectionClosedByPeer {
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect() self.disconnect()
self.connection.cancel()
} }
return return
@@ -515,6 +521,19 @@ class NearbyConnection {
} }
func cancel() {
cancelled = true
if encryptionDone {
var cancel = Sharing_Nearby_Frame()
cancel.version = .v1
cancel.v1 = Sharing_Nearby_V1Frame()
cancel.v1.type = .cancel
try? sendTransferSetupFrame(cancel)
}
try? sendDisconnectionAndDisconnect()
}
func disconnect() { func disconnect() {
log("[NearbyConnection \(self.id)] Disconnecting from connection.") log("[NearbyConnection \(self.id)] Disconnecting from connection.")
@@ -7,7 +7,7 @@
#if os(iOS) #if os(iOS)
import UIKit import UIKit
import RNDeviceName import DeviceKit
#endif #endif
import Foundation import Foundation
@@ -16,6 +16,7 @@ import System
import SwiftECC import SwiftECC
import CryptoKit import CryptoKit
import SwiftUI import SwiftUI
import QRCode
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject { public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject {
@@ -26,7 +27,8 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
private var mdnsServices: [NetService] = [] private var mdnsServices: [NetService] = []
private var incomingConnections: [String: InboundNearbyConnection] = [:] private var incomingConnections: [String: InboundNearbyConnection] = [:]
private var foundServices: [String: FoundServiceInfo] = [:] private var foundServices: [String: FoundServiceInfo] = [:]
private var shareExtensionDelegates: [OutboundAppDelegate] = [] private var outboundAppDelegates: [OutboundAppDelegate] = []
private var inboundAppDelegates: [InboundAppDelegate] = []
private var outgoingTransfers: [String: OutgoingTransferInfo] = [:] private var outgoingTransfers: [String: OutgoingTransferInfo] = [:]
private var startedDeviceDiscovery = false private var startedDeviceDiscovery = false
private var startedAdvertising = false private var startedAdvertising = false
@@ -40,16 +42,16 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
public let deviceInfo: EndpointInfo public let deviceInfo: EndpointInfo
public let endpointID: [UInt8] = generateEndpointID() public let endpointID: [UInt8] = generateEndpointID()
public var mainAppDelegate: (any InboundAppDelegate)?
public static let shared = NearbyConnectionManager() public static let shared = NearbyConnectionManager()
@Published var attachments: AttachmentDetails? = nil @Published var attachments: AttachmentDetails? = nil
@Published var hasLocalNetworkAccess = true @Published var hasLocalNetworkPermission = true
@Published var isConnectedViaWiFi = true @Published var isConnectedToLocalNetwork = true
public var connectionUpdateCallback: (Bool) -> Void = { _ in } { public var connectionUpdateCallback: (Bool) -> Void = { _ in } {
didSet { didSet {
connectionUpdateCallback(isConnectedViaWiFi) connectionUpdateCallback(isConnectedToLocalNetwork)
} }
} }
@@ -58,7 +60,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
#if os(macOS) #if os(macOS)
self.deviceInfo = EndpointInfo(name: Host.current().localizedName ?? "Mac", deviceType: .computer) self.deviceInfo = EndpointInfo(name: Host.current().localizedName ?? "Mac", deviceType: .computer)
#else #else
let marketingName = UIDevice.current.marketingName.withoutBracketedContent let marketingName = Device.current.description.withoutBracketedContent
let isiPad = UIDevice.current.model == "iPad" let isiPad = UIDevice.current.model == "iPad"
self.deviceInfo = EndpointInfo(name: marketingName, deviceType: isiPad ? .tablet : .phone) self.deviceInfo = EndpointInfo(name: marketingName, deviceType: isiPad ? .tablet : .phone)
@@ -69,18 +71,23 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
super.init() super.init()
hasConnectionMonitor.pathUpdateHandler = { path in hasConnectionMonitor.pathUpdateHandler = { path in
if path.usesInterfaceType(.wifi) {
log("[NearbyConnectionManager] Connected via WiFi.") #if os(macOS)
DispatchQueue.main.async { // On Mac, the local network is available as long as the path is satisfied, since Macs do not have cellular modems
self.isConnectedViaWiFi = true let isConnected = path.status == .satisfied
self.connectionUpdateCallback(true) #else
} // On iPhone and iPad, the local network is only available using Wi-Fi or Ethernet
} else { let isConnected = path.usesInterfaceType(.wifi) || path.usesInterfaceType(.wiredEthernet)
log("[NearbyConnectionManager] WiFi connection lost.") #endif
DispatchQueue.main.async {
self.isConnectedViaWiFi = false DispatchQueue.main.async {
self.connectionUpdateCallback(false) if isConnected {
log("[NearbyConnectionManager] Connected to local network.")
} else {
log("[NearbyConnectionManager] Local network lost.")
} }
self.isConnectedToLocalNetwork = isConnected
self.connectionUpdateCallback(isConnected)
} }
} }
@@ -117,7 +124,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
public func becomeInvisible() { public func becomeInvisible() {
if !startedAdvertising { if !startedAdvertising {
print("[NearbyConnectionManager] Already invisible, ignoring becomeInvisible()") log("[NearbyConnectionManager] Already invisible, ignoring becomeInvisible()")
return return
} }
@@ -130,12 +137,10 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
private static func generateEndpointID() -> [UInt8] { private static func generateEndpointID() -> [UInt8] {
let userDefaultsKey = UserDefaultsKeys.endpointID.rawValue
// Try to retrieve from UserDefaults // Try to retrieve from UserDefaults
if let savedString = UserDefaults.standard.string(forKey: userDefaultsKey), if let savedString = Settings.shared.endpointID,
let savedData = savedString.data(using: .utf8) let savedData = savedString.data(using: .utf8) {
{
return [UInt8](savedData) return [UInt8](savedData)
} }
@@ -148,7 +153,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
// Save to UserDefaults as String // Save to UserDefaults as String
let idString = String(bytes: id, encoding: .utf8) ?? "" let idString = String(bytes: id, encoding: .utf8) ?? ""
UserDefaults.standard.set(idString, forKey: userDefaultsKey) Settings.shared.endpointID = idString
return id return id
} }
@@ -186,8 +191,17 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
} }
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection _: InboundNearbyConnection, acceptAutomatically: Bool) { func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
mainAppDelegate?.obtainUserConsent(transfer: transfer, device: device, acceptAutomatically: acceptAutomatically) inboundAppDelegates.forEach { delegate in
delegate.obtainUserConsent(transfer: transfer, device: device)
}
}
func obtainedUserConsentAutomatically(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
inboundAppDelegates.forEach { delegate in
delegate.obtainedUserConsentAutomatically(transfer: transfer, device: device)
}
} }
@@ -195,21 +209,32 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
incomingConnections.removeValue(forKey: connection.id) incomingConnections.removeValue(forKey: connection.id)
if !connection.wasUserRejected { if !connection.wasUserRejected {
mainAppDelegate?.connectionWasTerminated(from: connection.remoteDeviceInfo ?? RemoteDeviceInfo(name: "??", type: .unknown), wasPlainTextTransfer: connection.isPlainTextTransfer, error: error) inboundAppDelegates.forEach { delegate in
delegate.connectionWasTerminated(connectionID: connection.id, from: connection.remoteDeviceInfo ?? RemoteDeviceInfo(name: "UnknownDevice".localized(), type: .unknown), wasPlainTextTransfer: connection.isPlainTextTransfer, error: error)
}
}
}
func showPlusScreen() {
inboundAppDelegates.forEach { delegate in
delegate.showPlusScreen()
} }
} }
func updatedTransferProgress(connection: InboundNearbyConnection, progress: Double) { func updatedTransferProgress(connection: InboundNearbyConnection, progress: Double) {
mainAppDelegate?.transferProgress(progress: progress) inboundAppDelegates.forEach { delegate in
delegate.transferProgress(connectionID: connection.id, progress: progress)
}
} }
public func submitUserConsent(transferID: String, accept: Bool, trustDevice: Bool, storeInTemp: Bool = false) { public func submitUserConsent(transferID: String, accept: Bool, trustDevice: Bool) {
guard let conn = incomingConnections[transferID] else { return } guard let conn = incomingConnections[transferID] else { return }
log("[NearbyConnectionManager] Submitting user consent for transfer \(transferID), accepted: \(accept), store in temp: \(storeInTemp)") log("[NearbyConnectionManager] Submitting user consent for transfer \(transferID), accepted: \(accept)")
conn.submitUserConsent(accepted: accept, trustDevice: trustDevice, storeInTemp: storeInTemp) conn.submitUserConsent(accepted: accept, trustDevice: trustDevice)
} }
@@ -245,13 +270,13 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
log("[NearbyConnectionManager] Browser failed: \(error)") log("[NearbyConnectionManager] Browser failed: \(error)")
case .ready: case .ready:
log("[NearbyConnectionManager] Browser ready") log("[NearbyConnectionManager] Browser ready")
self.hasLocalNetworkAccess = true self.hasLocalNetworkPermission = true
case .waiting(let error): case .waiting(let error):
log("[NearbyConnectionManager] Browser waiting: \(error)") log("[NearbyConnectionManager] Browser waiting: \(error)")
if error == NWError.dns(DNSServiceErrorType(kDNSServiceErr_PolicyDenied)) { if error == NWError.dns(DNSServiceErrorType(kDNSServiceErr_PolicyDenied)) {
log("[NearbyConnectionManager] Local network access not granted.") log("[NearbyConnectionManager] Local network access not granted.")
self.hasLocalNetworkAccess = false self.hasLocalNetworkPermission = false
} }
default: default:
break break
@@ -279,7 +304,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
startedDeviceDiscovery = false startedDeviceDiscovery = false
for delegate in shareExtensionDelegates { for delegate in outboundAppDelegates {
foundServices.values.forEach { service in foundServices.values.forEach { service in
guard let device = service.device, let id = device.id else { return } guard let device = service.device, let id = device.id else { return }
@@ -290,8 +315,8 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
} }
public func addShareExtensionDelegate(_ delegate: OutboundAppDelegate) { public func addOutboundAppDelegate(_ delegate: OutboundAppDelegate) {
shareExtensionDelegates.append(delegate) outboundAppDelegates.append(delegate)
for service in foundServices.values { for service in foundServices.values {
guard let device = service.device else { continue } guard let device = service.device else { continue }
delegate.addDevice(device: device) delegate.addDevice(device: device)
@@ -299,14 +324,28 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
} }
public func removeShareExtensionDelegate(_ delegate: OutboundAppDelegate) { public func addInboundAppDelegate(_ delegate: InboundAppDelegate) {
shareExtensionDelegates.removeAll(where: { $0 === delegate }) inboundAppDelegates.append(delegate)
} }
public func cancelOutgoingTransfer(id: String) { public func removeOutboundAppDelegate(_ delegate: OutboundAppDelegate) {
guard let transfer = outgoingTransfers[id] else { return } outboundAppDelegates.removeAll(where: { $0 === delegate })
transfer.connection.cancel() }
public func removeInboundAppDelegate(_ delegate: InboundAppDelegate) {
inboundAppDelegates.removeAll(where: { $0 === delegate })
}
public func cancelTransfer(id: String) {
if let transfer = incomingConnections[id] {
transfer.cancel()
}
if let transfer = outgoingTransfers[id] {
transfer.connection.cancel()
}
} }
@@ -349,7 +388,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
if qrData == qrCodeAdvertisingToken { if qrData == qrCodeAdvertisingToken {
if let deviceInfo = deviceInfo { if let deviceInfo = deviceInfo {
for delegate in shareExtensionDelegates { for delegate in outboundAppDelegates {
delegate.startTransferWithQrCode(device: deviceInfo) delegate.startTransferWithQrCode(device: deviceInfo)
} }
} }
@@ -361,7 +400,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
guard let name = String.init(data: decryptedName, encoding: .utf8) else { return } guard let name = String.init(data: decryptedName, encoding: .utf8) else { return }
endpointInfo.name = name endpointInfo.name = name
let deviceInfo = addFoundDevice(foundService: &foundService, endpointInfo: endpointInfo, endpointID: endpointID) let deviceInfo = addFoundDevice(foundService: &foundService, endpointInfo: endpointInfo, endpointID: endpointID)
for delegate in shareExtensionDelegates { for delegate in outboundAppDelegates {
delegate.startTransferWithQrCode(device: deviceInfo) delegate.startTransferWithQrCode(device: deviceInfo)
} }
} catch { } catch {
@@ -377,7 +416,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
foundService.device = deviceInfo foundService.device = deviceInfo
foundServices[endpointID] = foundService foundServices[endpointID] = foundService
for delegate in shareExtensionDelegates { for delegate in outboundAppDelegates {
delegate.addDevice(device: deviceInfo) delegate.addDevice(device: deviceInfo)
} }
@@ -388,19 +427,23 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
private func removeFoundDevice(service: NWBrowser.Result) { private func removeFoundDevice(service: NWBrowser.Result) {
guard let endpointID = endpointID(for: service) else { return } guard let endpointID = endpointID(for: service) else { return }
guard let _ = foundServices.removeValue(forKey: endpointID) else { return } guard let _ = foundServices.removeValue(forKey: endpointID) else { return }
for delegate in shareExtensionDelegates { for delegate in outboundAppDelegates {
delegate.removeDevice(id: endpointID) delegate.removeDevice(id: endpointID)
} }
} }
public func generateQrCodeKey() -> String{ public func generateQrCodeKey() -> Image? {
log("[NearbyConnectionManager] Generating new QR code key")
let domain = Domain.instance(curve: .EC256r1) let domain = Domain.instance(curve: .EC256r1)
let (pubKey, privKey) = domain.makeKeyPair() let (pubKey, privKey) = domain.makeKeyPair()
qrCodePrivateKey = privKey qrCodePrivateKey = privKey
var keyData = Data() var keyData = Data()
keyData.append(contentsOf: [0, 0, 2]) keyData.append(contentsOf: [0, 0, 2])
let keyBytes = Data(pubKey.w.x.asSignedBytes()) 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) // 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)) keyData.append(keyBytes.suffixOfAtMost(numBytes: 32))
@@ -408,7 +451,26 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
qrCodeAdvertisingToken = HKDF.deriveKey(ikm: ikm, salt: Data(), info: "advertisingContext".data(using: .utf8)!, outputLength: 16).data() 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) 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
} }
@@ -431,7 +493,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
conn.start() conn.start()
} catch { } catch {
log("[NearbyConnectionManager] Error zipping URLs: \(error)") log("[NearbyConnectionManager] Error zipping URLs: \(error)")
shareExtensionDelegates.forEach { delegate in outboundAppDelegates.forEach { delegate in
delegate.connectionFailed(error: error) delegate.connectionFailed(error: error)
} }
} }
@@ -68,19 +68,6 @@ class OutboundNearbyConnection: NearbyConnection {
} }
} }
} }
public func cancel() {
cancelled = true
if encryptionDone {
var cancel = Sharing_Nearby_Frame()
cancel.version = .v1
cancel.v1 = Sharing_Nearby_V1Frame()
cancel.v1.type = .cancel
try? sendTransferSetupFrame(cancel)
}
try? sendDisconnectionAndDisconnect()
}
override func connectionReady() { override func connectionReady() {
+38 -4
View File
@@ -12,9 +12,11 @@ import SwiftECC
protocol InboundNearbyConnectionDelegate { protocol InboundNearbyConnectionDelegate {
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection, acceptAutomatically: Bool) func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection)
func obtainedUserConsentAutomatically(transfer: TransferMetadata, device: RemoteDeviceInfo, connection: InboundNearbyConnection)
func updatedTransferProgress(connection: InboundNearbyConnection, progress: Double) func updatedTransferProgress(connection: InboundNearbyConnection, progress: Double)
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?)
func showPlusScreen()
} }
@@ -28,9 +30,11 @@ protocol OutboundNearbyConnectionDelegate {
public protocol InboundAppDelegate: AnyObject { public protocol InboundAppDelegate: AnyObject {
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, acceptAutomatically: Bool) func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo)
func connectionWasTerminated(from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: Error?) func obtainedUserConsentAutomatically(transfer: TransferMetadata, device: RemoteDeviceInfo)
func transferProgress(progress: Double) func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: Error?)
func transferProgress(connectionID: String, progress: Double)
func showPlusScreen()
} }
@@ -133,6 +137,36 @@ public struct TransferMetadata {
case text case text
case url case url
} }
private func getSummary() -> String {
if let textTitle = textDescription {
return textTitle
} else if files.count == 1 {
return files[0].name
} else {
return String.localizedStringWithFormat("NFiles".localized(), files.count)
}
}
public func getDescription(deviceName: String, alreadyAccepted: Bool) -> String {
let fileStr = getSummary()
switch type {
case .file:
return String(format: (alreadyAccepted ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [deviceName, fileStr])
case .text:
return String(format: (alreadyAccepted ? "DeviceCurrentlySendingText" : "DeviceSendingText").localized(), arguments: [deviceName, fileStr])
case .url:
return String(format: (alreadyAccepted ? "DeviceCurrentlySendingUrl" : "DeviceSendingUrl").localized(), arguments: [deviceName, fileStr])
}
}
public func getPinCodeMessage() -> String {
return String(format: "PinCode".localized(), arguments: [pinCode ?? "?"])
}
} }
+3 -20
View File
@@ -110,23 +110,11 @@ extension String {
} }
public enum UserDefaultsKeys: String, CaseIterable { public func fullVersion() -> Bool {
case isEligibleForIap = "isEligibleForIap"
case appLaunchedBefore = "ShowedWelcomeScreen"
case plusVersion = "isPlusVersion"
case transmissionCount = "reviewRequestCountKey"
case automaticallyAcceptFiles = "automaticallyAcceptFiles"
case saveFolderBookmark = "saveFolderBookmark"
case openFinderAfterReceiving = "openFinderAfterReceiving"
case endpointID = "endpointID"
}
public func isPlusVersion() -> Bool {
#if os(macOS) #if os(macOS)
// Enable full functionality if app distributed directly // Enable full functionality if app distributed directly
return DistributionDetector.isDirectDistributionEnabled || UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue) return Settings.shared.gotPlus || DistributionDetector.isDirectDistributionEnabled
#else #else
// iOS // iOS
return true return true
@@ -135,10 +123,5 @@ public func isPlusVersion() -> Bool {
public func isFileTransferRestricted() -> Bool { public func isFileTransferRestricted() -> Bool {
(!isPlusVersion() && incomingTransmissionCount() > 1) (!fullVersion() && Settings.shared.incomingTransmissionCount > 1)
}
public func incomingTransmissionCount() -> Int {
return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue)
} }
+3 -3
View File
@@ -59,7 +59,7 @@ public class SaveFilesManager {
public func movePendingFilesToTarget() { public func movePendingFilesToTarget() {
if isPlusVersion() { if fullVersion() {
do { do {
let fileManager = FileManager.default let fileManager = FileManager.default
let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil) let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil)
@@ -119,7 +119,7 @@ public class SaveFilesManager {
if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() { if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() {
#if os(macOS) #if os(macOS)
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) { if Settings.shared.openFinderAfterReceiving {
log("[SaveFilesManager] Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.") log("[SaveFilesManager] Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.")
NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun) NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun)
} }
@@ -171,7 +171,7 @@ public class SaveFilesManager {
return securityScopeUrl return securityScopeUrl
} }
if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) { if let bookmarkData = Settings.shared.saveFolderBookmark {
var isStale = false var isStale = false
do { do {
+149 -109
View File
@@ -17,6 +17,20 @@
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; }; EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; }; EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; }; EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB1325D32E701D8E00FD749C /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325D22E701D8900FD749C /* Settings.swift */; };
EB1325D42E701D8E00FD749C /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325D22E701D8900FD749C /* Settings.swift */; };
EB1325D52E701D8E00FD749C /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325D22E701D8900FD749C /* Settings.swift */; };
EB1325D62E701D8E00FD749C /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325D22E701D8900FD749C /* Settings.swift */; };
EB1325EB2E7025D900FD749C /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E92E7025D900FD749C /* ApIsolationTester.swift */; };
EB1325EC2E7025D900FD749C /* ActiveNetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E72E7025D900FD749C /* ActiveNetworkInterface.swift */; };
EB1325ED2E7025D900FD749C /* ApIsolation6Tester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E82E7025D900FD749C /* ApIsolation6Tester.swift */; };
EB1325F42E7025D900FD749C /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E92E7025D900FD749C /* ApIsolationTester.swift */; };
EB1325F52E7025D900FD749C /* ActiveNetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E72E7025D900FD749C /* ActiveNetworkInterface.swift */; };
EB1325F62E7025D900FD749C /* ApIsolation6Tester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E82E7025D900FD749C /* ApIsolation6Tester.swift */; };
EB1325F82E70288300FD749C /* SendToExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325F72E70287C00FD749C /* SendToExtension.swift */; };
EB1325FA2E70288300FD749C /* SendToExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325F72E70287C00FD749C /* SendToExtension.swift */; };
EB1325FD2E7034B000FD749C /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB1325FC2E7034B000FD749C /* InfoPlist.xcstrings */; };
EB1325FE2E7034B000FD749C /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB1325FC2E7034B000FD749C /* InfoPlist.xcstrings */; };
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; }; EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; }; EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; }; EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
@@ -97,16 +111,19 @@
EB704A822E4A7BB3008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; }; EB704A822E4A7BB3008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; }; EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB704A842E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; }; EB704A842E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB721F242E75AFA900F2099F /* QuickDropIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = EB721F232E75AFA900F2099F /* QuickDropIcon.icon */; };
EB721F252E75AFA900F2099F /* QuickDropIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = EB721F232E75AFA900F2099F /* QuickDropIcon.icon */; };
EB7654782E525ACC006B4AA3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EB7654772E525ACC006B4AA3 /* MainMenu.xib */; }; EB7654782E525ACC006B4AA3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EB7654772E525ACC006B4AA3 /* MainMenu.xib */; };
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; }; EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
EB7654C02E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB7654C02E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB7654C12E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB7654C12E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB7654C42E534E89006B4AA3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EB7654C32E534E89006B4AA3 /* InfoPlist.strings */; };
EB7654C62E534E89006B4AA3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EB7654C32E534E89006B4AA3 /* InfoPlist.strings */; };
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; }; EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; };
EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54132D78E9E400989564 /* LicenseWindow.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 */; };
EB869DB52E92AA3A009AAAD4 /* QuickDropToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */; };
EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; }; EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; };
EB886E532E4E19390052B7D9 /* 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 */; }; EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E512E4E19390052B7D9 /* HKDF.swift */; };
@@ -152,7 +169,6 @@
EB950B612E520674002D68FA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B602E520674002D68FA /* SwiftECC */; }; EB950B612E520674002D68FA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B602E520674002D68FA /* SwiftECC */; };
EB950B632E52067A002D68FA /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B622E52067A002D68FA /* QRCode */; }; EB950B632E52067A002D68FA /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B622E52067A002D68FA /* QRCode */; };
EB950B652E52067D002D68FA /* LUI in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B642E52067D002D68FA /* LUI */; }; EB950B652E52067D002D68FA /* LUI in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B642E52067D002D68FA /* LUI */; };
EB950B672E520680002D68FA /* RNDeviceName in Frameworks */ = {isa = PBXBuildFile; productRef = EB950B662E520680002D68FA /* RNDeviceName */; };
EB950B6F2E5206E2002D68FA /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B6B2E5206E2002D68FA /* ShareViewController.swift */; }; EB950B6F2E5206E2002D68FA /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B6B2E5206E2002D68FA /* ShareViewController.swift */; };
EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; }; EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; };
EB950B742E520957002D68FA /* Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B732E520954002D68FA /* Attachments.swift */; }; EB950B742E520957002D68FA /* Attachments.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B732E520954002D68FA /* Attachments.swift */; };
@@ -173,7 +189,6 @@
EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F992E50CAA6008B8C5C /* PieProgressView.swift */; }; EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F992E50CAA6008B8C5C /* PieProgressView.swift */; };
EB97A00F2E511124008B8C5C /* ReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A00E2E511120008B8C5C /* ReceiveModel.swift */; }; EB97A00F2E511124008B8C5C /* ReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A00E2E511120008B8C5C /* ReceiveModel.swift */; };
EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A00E2E511120008B8C5C /* ReceiveModel.swift */; }; EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A00E2E511120008B8C5C /* ReceiveModel.swift */; };
EB97A0132E51D6B2008B8C5C /* RNDeviceName in Frameworks */ = {isa = PBXBuildFile; productRef = EB97A0122E51D6B2008B8C5C /* RNDeviceName */; };
EB97A0142E51DD00008B8C5C /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; EB97A0142E51DD00008B8C5C /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; };
EB97A0152E51DD00008B8C5C /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */; }; EB97A0152E51DD00008B8C5C /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */; };
EB97A01A2E51EAA2008B8C5C /* UIAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A0192E51EA9C008B8C5C /* UIAlert.swift */; }; EB97A01A2E51EAA2008B8C5C /* UIAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB97A0192E51EA9C008B8C5C /* UIAlert.swift */; };
@@ -183,16 +198,13 @@
EB9925852E45EF0900357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925842E45EF0900357055 /* QRCode */; }; EB9925852E45EF0900357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925842E45EF0900357055 /* QRCode */; };
EB9925872E45F12F00357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925862E45F12F00357055 /* QRCode */; }; EB9925872E45F12F00357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925862E45F12F00357055 /* QRCode */; };
EB9925892E45F13500357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925882E45F13500357055 /* QRCode */; }; EB9925892E45F13500357055 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = EB9925882E45F13500357055 /* QRCode */; };
EBADEB8F2E8029750021B5E4 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = EBADEB8E2E8029750021B5E4 /* DeviceKit */; };
EBADEB912E802A830021B5E4 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = EBADEB902E802A830021B5E4 /* DeviceKit */; };
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; }; EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C5C2E3394EF00EECC18 /* 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 */; }; EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; };
EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6E2E33B57D00EECC18 /* Helper.swift */; }; EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6E2E33B57D00EECC18 /* Helper.swift */; };
EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6F2E33B57D00EECC18 /* PlusView.swift */; }; EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6F2E33B57D00EECC18 /* PlusView.swift */; };
EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C742E33B58100EECC18 /* ActiveNetworkInterface.swift */; };
EBC05C792E33B58100EECC18 /* ApIsolation6Tester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C752E33B58100EECC18 /* ApIsolation6Tester.swift */; };
EBC05C7A2E33B58100EECC18 /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C762E33B58100EECC18 /* ApIsolationTester.swift */; };
EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C742E33B58100EECC18 /* ActiveNetworkInterface.swift */; };
EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C762E33B58100EECC18 /* ApIsolationTester.swift */; };
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */; }; EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */; };
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */; }; EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */; };
EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7F2E33B58300EECC18 /* IssueView.swift */; }; EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7F2E33B58300EECC18 /* IssueView.swift */; };
@@ -303,6 +315,12 @@
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; }; 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; }; 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; }; EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; };
EB1325D22E701D8900FD749C /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
EB1325E72E7025D900FD749C /* ActiveNetworkInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveNetworkInterface.swift; sourceTree = "<group>"; };
EB1325E82E7025D900FD749C /* ApIsolation6Tester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolation6Tester.swift; sourceTree = "<group>"; };
EB1325E92E7025D900FD749C /* ApIsolationTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationTester.swift; sourceTree = "<group>"; };
EB1325F72E70287C00FD749C /* SendToExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToExtension.swift; sourceTree = "<group>"; };
EB1325FC2E7034B000FD749C /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
EB16B6112E58B4B9007D9792 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; }; EB16B6112E58B4B9007D9792 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; };
EB16B6162E58B4C8007D9792 /* TrustStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustStore.swift; sourceTree = "<group>"; }; EB16B6162E58B4C8007D9792 /* TrustStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustStore.swift; sourceTree = "<group>"; };
EB16B7762E59BA9F007D9792 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; }; EB16B7762E59BA9F007D9792 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
@@ -319,26 +337,12 @@
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; }; EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
EB704A812E4A7BAE008E608F /* DistributionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionDetector.swift; sourceTree = "<group>"; }; EB704A812E4A7BAE008E608F /* DistributionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionDetector.swift; sourceTree = "<group>"; };
EB721F232E75AFA900F2099F /* QuickDropIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = QuickDropIcon.icon; sourceTree = "<group>"; };
EB7654772E525ACC006B4AA3 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; }; EB7654772E525ACC006B4AA3 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
EB7654C22E534E89006B4AA3 /* InfoPlist.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654C82E534F2C006B4AA3 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654C92E534F2D006B4AA3 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
EB7654CA2E534F31006B4AA3 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654CB2E534F32006B4AA3 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654CC2E534F33006B4AA3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654CD2E534F33006B4AA3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654CE2E534F34006B4AA3 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654CF2E534F35006B4AA3 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654D02E534F35006B4AA3 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
EB7654D12E534F41006B4AA3 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
EB7654D22E534F42006B4AA3 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654D32E534F43006B4AA3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654D42E534F43006B4AA3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654D52E534F44006B4AA3 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7654D62E534F45006B4AA3 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = "<group>"; }; EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = "<group>"; };
EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = "<group>"; }; EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = "<group>"; };
EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDropToastView.swift; sourceTree = "<group>"; };
EB886E512E4E19390052B7D9 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; }; EB886E512E4E19390052B7D9 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; };
EB886E662E4F2F7C0052B7D9 /* License.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = License.swift; sourceTree = "<group>"; }; EB886E662E4F2F7C0052B7D9 /* License.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = License.swift; sourceTree = "<group>"; };
EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = iOSShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = iOSShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -364,9 +368,6 @@
EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = "<group>"; }; EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = "<group>"; };
EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; }; EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; };
EBC05C6F2E33B57D00EECC18 /* PlusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusView.swift; sourceTree = "<group>"; }; EBC05C6F2E33B57D00EECC18 /* PlusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusView.swift; sourceTree = "<group>"; };
EBC05C742E33B58100EECC18 /* ActiveNetworkInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveNetworkInterface.swift; sourceTree = "<group>"; };
EBC05C752E33B58100EECC18 /* ApIsolation6Tester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolation6Tester.swift; sourceTree = "<group>"; };
EBC05C762E33B58100EECC18 /* ApIsolationTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationTester.swift; sourceTree = "<group>"; };
EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationIssueView.swift; sourceTree = "<group>"; }; EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationIssueView.swift; sourceTree = "<group>"; };
EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallIssueView.swift; sourceTree = "<group>"; }; EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallIssueView.swift; sourceTree = "<group>"; };
EBC05C7F2E33B58300EECC18 /* IssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueView.swift; sourceTree = "<group>"; }; EBC05C7F2E33B58300EECC18 /* IssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueView.swift; sourceTree = "<group>"; };
@@ -402,8 +403,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB97A0132E51D6B2008B8C5C /* RNDeviceName in Frameworks */,
EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */, EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */,
EBADEB8F2E8029750021B5E4 /* DeviceKit in Frameworks */,
EB886E582E4E66420052B7D9 /* LUI in Frameworks */, EB886E582E4E66420052B7D9 /* LUI in Frameworks */,
EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */, EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */,
EB9925892E45F13500357055 /* QRCode in Frameworks */, EB9925892E45F13500357055 /* QRCode in Frameworks */,
@@ -414,8 +415,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB950B672E520680002D68FA /* RNDeviceName in Frameworks */,
EB950B612E520674002D68FA /* SwiftECC in Frameworks */, EB950B612E520674002D68FA /* SwiftECC in Frameworks */,
EBADEB912E802A830021B5E4 /* DeviceKit in Frameworks */,
EB950B652E52067D002D68FA /* LUI in Frameworks */, EB950B652E52067D002D68FA /* LUI in Frameworks */,
EB950B5F2E520671002D68FA /* SwiftProtobuf in Frameworks */, EB950B5F2E520671002D68FA /* SwiftProtobuf in Frameworks */,
EB950B632E52067A002D68FA /* QRCode in Frameworks */, EB950B632E52067A002D68FA /* QRCode in Frameworks */,
@@ -438,7 +439,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
699DEBA52AB0573200115D22 /* ShareViewController.swift */, 699DEBA52AB0573200115D22 /* ShareViewController.swift */,
EB950B732E520954002D68FA /* Attachments.swift */, EB644B872EA50827006C2E6C /* Helpers */,
EB950B762E521712002D68FA /* AttachmentDetails.swift */, EB950B762E521712002D68FA /* AttachmentDetails.swift */,
EB5C371E2E36693D0052EFD7 /* View */, EB5C371E2E36693D0052EFD7 /* View */,
699DEBAA2AB0573200115D22 /* Info.plist */, 699DEBAA2AB0573200115D22 /* Info.plist */,
@@ -450,6 +451,7 @@
69DA9A0529E0BF5100A442DA = { 69DA9A0529E0BF5100A442DA = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EB721F232E75AFA900F2099F /* QuickDropIcon.icon */,
69DA9A1029E0BF5100A442DA /* QuickDrop */, 69DA9A1029E0BF5100A442DA /* QuickDrop */,
699DEBA22AB0573200115D22 /* ShareExtension */, 699DEBA22AB0573200115D22 /* ShareExtension */,
69DCF4822AB70D0600CBE2CC /* NearbyShare */, 69DCF4822AB70D0600CBE2CC /* NearbyShare */,
@@ -476,8 +478,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
69DA9A1129E0BF5100A442DA /* AppDelegate.swift */, 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */,
EB1325D22E701D8900FD749C /* Settings.swift */,
EB644B852EA507CF006C2E6C /* ViewModel */,
EBC05C6A2E33B55100EECC18 /* View */, EBC05C6A2E33B55100EECC18 /* View */,
EBC05C772E33B58100EECC18 /* SelfTest */,
EBC05C6B2E33B57100EECC18 /* Util */, EBC05C6B2E33B57100EECC18 /* Util */,
EBC05C702E33B57D00EECC18 /* Plus */, EBC05C702E33B57D00EECC18 /* Plus */,
69DA9A1329E0BF5200A442DA /* Assets.xcassets */, 69DA9A1329E0BF5200A442DA /* Assets.xcassets */,
@@ -511,6 +514,16 @@
path = NearbyShare; path = NearbyShare;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EB1325EA2E7025D900FD749C /* SelfTest */ = {
isa = PBXGroup;
children = (
EB1325E72E7025D900FD749C /* ActiveNetworkInterface.swift */,
EB1325E82E7025D900FD749C /* ApIsolation6Tester.swift */,
EB1325E92E7025D900FD749C /* ApIsolationTester.swift */,
);
path = SelfTest;
sourceTree = "<group>";
};
EB5C371C2E3669010052EFD7 /* Util */ = { EB5C371C2E3669010052EFD7 /* Util */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -559,6 +572,34 @@
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EB644B852EA507CF006C2E6C /* ViewModel */ = {
isa = PBXGroup;
children = (
EB979F902E50CA23008B8C5C /* SendModel.swift */,
EB97A00E2E511120008B8C5C /* ReceiveModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
EB644B862EA50807006C2E6C /* View */ = {
isa = PBXGroup;
children = (
EB979F8E2E50CA23008B8C5C /* ContentView.swift */,
EB979F8F2E50CA23008B8C5C /* FilePicker.swift */,
EB97A0192E51EA9C008B8C5C /* UIAlert.swift */,
EB979F992E50CAA6008B8C5C /* PieProgressView.swift */,
);
path = View;
sourceTree = "<group>";
};
EB644B872EA50827006C2E6C /* Helpers */ = {
isa = PBXGroup;
children = (
EB950B732E520954002D68FA /* Attachments.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
EB950B6C2E5206E2002D68FA /* iOSShareExtension */ = { EB950B6C2E5206E2002D68FA /* iOSShareExtension */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -573,14 +614,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EB97A0162E51DF4F008B8C5C /* Info.plist */, EB97A0162E51DF4F008B8C5C /* Info.plist */,
EB7654C32E534E89006B4AA3 /* InfoPlist.strings */, EB1325FC2E7034B000FD749C /* InfoPlist.xcstrings */,
EB979F8D2E50CA23008B8C5C /* Configuration.swift */, EB979F8D2E50CA23008B8C5C /* Configuration.swift */,
EB979F8E2E50CA23008B8C5C /* ContentView.swift */, EB644B862EA50807006C2E6C /* View */,
EB979F8F2E50CA23008B8C5C /* FilePicker.swift */,
EB979F902E50CA23008B8C5C /* SendModel.swift */,
EB97A00E2E511120008B8C5C /* ReceiveModel.swift */,
EB97A0192E51EA9C008B8C5C /* UIAlert.swift */,
EB979F992E50CAA6008B8C5C /* PieProgressView.swift */,
EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */, EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */,
EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */, EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */,
); );
@@ -594,6 +630,7 @@
EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */, EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */,
EB7F54112D78E95C00989564 /* TutorialView.swift */, EB7F54112D78E95C00989564 /* TutorialView.swift */,
EB7F54132D78E9E400989564 /* LicenseWindow.swift */, EB7F54132D78E9E400989564 /* LicenseWindow.swift */,
EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */,
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */, EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */,
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */, EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */,
EBC05C812E33B58300EECC18 /* IssueViews */, EBC05C812E33B58300EECC18 /* IssueViews */,
@@ -604,10 +641,12 @@
EBC05C6B2E33B57100EECC18 /* Util */ = { EBC05C6B2E33B57100EECC18 /* Util */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EB1325F72E70287C00FD749C /* SendToExtension.swift */,
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */, EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */,
EB886E662E4F2F7C0052B7D9 /* License.swift */, EB886E662E4F2F7C0052B7D9 /* License.swift */,
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */, EB36435F2D73A29F009CDFF8 /* AudioManager.swift */,
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */, EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */,
EB1325EA2E7025D900FD749C /* SelfTest */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -623,16 +662,6 @@
path = Plus; path = Plus;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EBC05C772E33B58100EECC18 /* SelfTest */ = {
isa = PBXGroup;
children = (
EBC05C742E33B58100EECC18 /* ActiveNetworkInterface.swift */,
EBC05C752E33B58100EECC18 /* ApIsolation6Tester.swift */,
EBC05C762E33B58100EECC18 /* ApIsolationTester.swift */,
);
path = SelfTest;
sourceTree = "<group>";
};
EBC05C812E33B58300EECC18 /* IssueViews */ = { EBC05C812E33B58300EECC18 /* IssueViews */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -722,7 +751,7 @@
EB5C37EF2E377C3F0052EFD7 /* SwiftECC */, EB5C37EF2E377C3F0052EFD7 /* SwiftECC */,
EB9925882E45F13500357055 /* QRCode */, EB9925882E45F13500357055 /* QRCode */,
EB886E572E4E66420052B7D9 /* LUI */, EB886E572E4E66420052B7D9 /* LUI */,
EB97A0122E51D6B2008B8C5C /* RNDeviceName */, EBADEB8E2E8029750021B5E4 /* DeviceKit */,
); );
productName = QuickDropiOS; productName = QuickDropiOS;
productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */; productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */;
@@ -746,7 +775,7 @@
EB950B602E520674002D68FA /* SwiftECC */, EB950B602E520674002D68FA /* SwiftECC */,
EB950B622E52067A002D68FA /* QRCode */, EB950B622E52067A002D68FA /* QRCode */,
EB950B642E52067D002D68FA /* LUI */, EB950B642E52067D002D68FA /* LUI */,
EB950B662E520680002D68FA /* RNDeviceName */, EBADEB902E802A830021B5E4 /* DeviceKit */,
); );
productName = iOSShareExtension; productName = iOSShareExtension;
productReference = EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */; productReference = EB950B2D2E52027F002D68FA /* iOSShareExtension.appex */;
@@ -760,7 +789,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640; LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640; LastUpgradeCheck = 2600;
TargetAttributes = { TargetAttributes = {
699DEBA02AB0573200115D22 = { 699DEBA02AB0573200115D22 = {
CreatedOnToolsVersion = 14.3.1; CreatedOnToolsVersion = 14.3.1;
@@ -808,7 +837,7 @@
EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */, EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */,
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */, EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */,
EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */, EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */,
EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */, EBADEB8B2E8029420021B5E4 /* XCRemoteSwiftPackageReference "DeviceKit" */,
); );
productRefGroup = 69DA9A0F29E0BF5100A442DA /* Products */; productRefGroup = 69DA9A0F29E0BF5100A442DA /* Products */;
projectDirPath = ""; projectDirPath = "";
@@ -845,6 +874,7 @@
EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */, EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */,
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */, EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */,
EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */, EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */,
EB721F252E75AFA900F2099F /* QuickDropIcon.icon in Resources */,
EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */, EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
EB7654782E525ACC006B4AA3 /* MainMenu.xib in Resources */, EB7654782E525ACC006B4AA3 /* MainMenu.xib in Resources */,
); );
@@ -855,9 +885,10 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */, EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
EB7654C42E534E89006B4AA3 /* InfoPlist.strings in Resources */, EB1325FD2E7034B000FD749C /* InfoPlist.xcstrings in Resources */,
EB97A0142E51DD00008B8C5C /* License.txt in Resources */, EB97A0142E51DD00008B8C5C /* License.txt in Resources */,
EB97A0152E51DD00008B8C5C /* timeIsNow.mp3 in Resources */, EB97A0152E51DD00008B8C5C /* timeIsNow.mp3 in Resources */,
EB721F242E75AFA900F2099F /* QuickDropIcon.icon in Resources */,
EB886E552E4E50F40052B7D9 /* Localizable.xcstrings in Resources */, EB886E552E4E50F40052B7D9 /* Localizable.xcstrings in Resources */,
EB7654C02E534A8E006B4AA3 /* Assets.xcassets in Resources */, EB7654C02E534A8E006B4AA3 /* Assets.xcassets in Resources */,
); );
@@ -868,7 +899,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB950B5B2E52065A002D68FA /* timeIsNow.mp3 in Resources */, EB950B5B2E52065A002D68FA /* timeIsNow.mp3 in Resources */,
EB7654C62E534E89006B4AA3 /* InfoPlist.strings in Resources */, EB1325FE2E7034B000FD749C /* InfoPlist.xcstrings in Resources */,
EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */, EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */,
EB950B5C2E52065A002D68FA /* justMaybe.mp3 in Resources */, EB950B5C2E52065A002D68FA /* justMaybe.mp3 in Resources */,
EB950B5D2E52065A002D68FA /* License.txt in Resources */, EB950B5D2E52065A002D68FA /* License.txt in Resources */,
@@ -885,6 +916,7 @@
files = ( files = (
EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */, EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */,
EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */, EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */,
EB1325F82E70288300FD749C /* SendToExtension.swift in Sources */,
EB16B6172E58B4C8007D9792 /* TrustStore.swift in Sources */, EB16B6172E58B4C8007D9792 /* TrustStore.swift in Sources */,
EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */, EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */,
EBC05C862E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */, EBC05C862E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */,
@@ -901,13 +933,12 @@
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */, EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */, EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB950B742E520957002D68FA /* Attachments.swift in Sources */, EB950B742E520957002D68FA /* Attachments.swift in Sources */,
EB1325D32E701D8E00FD749C /* Settings.swift in Sources */,
EB16B7782E59BAA1007D9792 /* AppGroup.swift in Sources */, EB16B7782E59BAA1007D9792 /* AppGroup.swift in Sources */,
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */, EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */, EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */, EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */,
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */, EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */,
EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */,
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */, EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */,
@@ -918,6 +949,9 @@
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */, EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */, EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */, EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */,
EB1325EB2E7025D900FD749C /* ApIsolationTester.swift in Sources */,
EB1325EC2E7025D900FD749C /* ActiveNetworkInterface.swift in Sources */,
EB1325ED2E7025D900FD749C /* ApIsolation6Tester.swift in Sources */,
EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */, EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */,
EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */, EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
); );
@@ -931,11 +965,13 @@
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */, EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */,
EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */, EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
EB869DB52E92AA3A009AAAD4 /* QuickDropToastView.swift in Sources */,
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */, EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */, EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */, EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */,
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */, EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */, EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
EB1325D42E701D8E00FD749C /* Settings.swift in Sources */,
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */, EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */, EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */,
EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */, EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */,
@@ -948,15 +984,12 @@
EB16B6152E58B4B9007D9792 /* IdentityManager.swift in Sources */, EB16B6152E58B4B9007D9792 /* IdentityManager.swift in Sources */,
EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */, EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */,
EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */, EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */,
EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */, EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */, EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */,
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */, EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */, EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */, EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
EBC05C792E33B58100EECC18 /* ApIsolation6Tester.swift in Sources */,
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */, EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */,
EBC05C7A2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */, EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */,
EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */, EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */,
EB886E682E4F2F800052B7D9 /* License.swift in Sources */, EB886E682E4F2F800052B7D9 /* License.swift in Sources */,
@@ -971,6 +1004,10 @@
EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */, EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */,
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */, EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */, EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
EB1325FA2E70288300FD749C /* SendToExtension.swift in Sources */,
EB1325F42E7025D900FD749C /* ApIsolationTester.swift in Sources */,
EB1325F52E7025D900FD749C /* ActiveNetworkInterface.swift in Sources */,
EB1325F62E7025D900FD749C /* ApIsolation6Tester.swift in Sources */,
EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */, EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */,
EBC05C852E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */, EBC05C852E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */,
); );
@@ -980,6 +1017,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB821C992E76FE5300036963 /* SmallSheetView.swift in Sources */,
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */, EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */,
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */, EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */,
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */, EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */,
@@ -998,6 +1036,7 @@
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */, EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */, EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */, EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB1325D62E701D8E00FD749C /* Settings.swift in Sources */,
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */, EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */,
EB97A00F2E511124008B8C5C /* ReceiveModel.swift in Sources */, EB97A00F2E511124008B8C5C /* ReceiveModel.swift in Sources */,
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */, EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */,
@@ -1044,7 +1083,9 @@
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */, EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */,
EB950B542E520436002D68FA /* HKDF.swift in Sources */, EB950B542E520436002D68FA /* HKDF.swift in Sources */,
EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */, EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */,
EB1325D52E701D8E00FD749C /* Settings.swift in Sources */,
EB950B562E520436002D68FA /* DataModel.swift in Sources */, EB950B562E520436002D68FA /* DataModel.swift in Sources */,
EB821C9A2E76FE5300036963 /* SmallSheetView.swift in Sources */,
EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */, EB950B3E2E52038A002D68FA /* SendModel.swift in Sources */,
EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */, EB950B772E521712002D68FA /* AttachmentDetails.swift in Sources */,
EB16B6192E58B4C8007D9792 /* TrustStore.swift in Sources */, EB16B6192E58B4C8007D9792 /* TrustStore.swift in Sources */,
@@ -1085,29 +1126,6 @@
name = ShareViewController.xib; name = ShareViewController.xib;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EB7654C32E534E89006B4AA3 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
EB7654C22E534E89006B4AA3 /* InfoPlist.strings */,
EB7654C82E534F2C006B4AA3 /* ar */,
EB7654C92E534F2D006B4AA3 /* zh-Hans */,
EB7654CA2E534F31006B4AA3 /* nl */,
EB7654CB2E534F32006B4AA3 /* fr */,
EB7654CC2E534F33006B4AA3 /* hu */,
EB7654CD2E534F33006B4AA3 /* it */,
EB7654CE2E534F34006B4AA3 /* ja */,
EB7654CF2E534F35006B4AA3 /* ko */,
EB7654D02E534F35006B4AA3 /* pt-BR */,
EB7654D12E534F41006B4AA3 /* pt-PT */,
EB7654D22E534F42006B4AA3 /* ro */,
EB7654D32E534F43006B4AA3 /* ru */,
EB7654D42E534F43006B4AA3 /* es */,
EB7654D52E534F44006B4AA3 /* th */,
EB7654D62E534F45006B4AA3 /* uk */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
@@ -1121,7 +1139,11 @@
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QuickDrop; INFOPLIST_KEY_CFBundleDisplayName = QuickDrop;
@@ -1155,7 +1177,11 @@
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QuickDrop; INFOPLIST_KEY_CFBundleDisplayName = QuickDrop;
@@ -1241,8 +1267,9 @@
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
SHARED_BUILD = 68; SHARED_BUILD = 84;
SHARED_VERSION = 2.2.1; SHARED_VERSION = 2.5.4;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -1303,8 +1330,9 @@
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = macosx; SDKROOT = macosx;
SHARED_BUILD = 68; SHARED_BUILD = 84;
SHARED_VERSION = 2.2.1; SHARED_VERSION = 2.5.4;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -1314,7 +1342,7 @@
69DA9A1C29E0BF5200A442DA /* Debug */ = { 69DA9A1C29E0BF5200A442DA /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = QuickDropIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = QuickDrop/QuickDrop.entitlements; CODE_SIGN_ENTITLEMENTS = QuickDrop/QuickDrop.entitlements;
@@ -1324,7 +1352,12 @@
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER = readwrite;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_USER_SELECTED_FILES = readwrite;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QuickDrop/Info.plist; INFOPLIST_FILE = QuickDrop/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QuickDrop; INFOPLIST_KEY_CFBundleDisplayName = QuickDrop;
@@ -1355,7 +1388,7 @@
69DA9A1D29E0BF5200A442DA /* Release */ = { 69DA9A1D29E0BF5200A442DA /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = QuickDropIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = QuickDrop/QuickDrop.entitlements; CODE_SIGN_ENTITLEMENTS = QuickDrop/QuickDrop.entitlements;
@@ -1365,7 +1398,12 @@
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES;
ENABLE_FILE_ACCESS_DOWNLOADS_FOLDER = readwrite;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_USER_SELECTED_FILES = readwrite;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QuickDrop/Info.plist; INFOPLIST_FILE = QuickDrop/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QuickDrop; INFOPLIST_KEY_CFBundleDisplayName = QuickDrop;
@@ -1395,7 +1433,7 @@
EB5C37EB2E377C310052EFD7 /* Debug */ = { EB5C37EB2E377C310052EFD7 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = QuickDropIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements; CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -1408,6 +1446,7 @@
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Leon Böttger";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network."; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1438,7 +1477,7 @@
EB5C37EC2E377C310052EFD7 /* Release */ = { EB5C37EC2E377C310052EFD7 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = QuickDropIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements; CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@@ -1451,6 +1490,7 @@
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Leon Böttger";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network."; INFOPLIST_KEY_NSLocalNetworkUsageDescription = "Local network access is needed to detect devices and transfer files over your Wi-Fi network.";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -1622,14 +1662,6 @@
kind = branch; kind = branch;
}; };
}; };
EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/souzainf3/RNDeviceName";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.1.0;
};
};
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */ = { EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dagronf/qrcode.git"; repositoryURL = "https://github.com/dagronf/qrcode.git";
@@ -1638,6 +1670,14 @@
minimumVersion = 26.10.0; minimumVersion = 26.10.0;
}; };
}; };
EBADEB8B2E8029420021B5E4 /* XCRemoteSwiftPackageReference "DeviceKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/devicekit/DeviceKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.7.0;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@@ -1701,16 +1741,6 @@
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */; package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
productName = LUI; productName = LUI;
}; };
EB950B662E520680002D68FA /* RNDeviceName */ = {
isa = XCSwiftPackageProductDependency;
package = EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */;
productName = RNDeviceName;
};
EB97A0122E51D6B2008B8C5C /* RNDeviceName */ = {
isa = XCSwiftPackageProductDependency;
package = EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */;
productName = RNDeviceName;
};
EB9925842E45EF0900357055 /* QRCode */ = { EB9925842E45EF0900357055 /* QRCode */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */; package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
@@ -1726,6 +1756,16 @@
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */; package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
productName = QRCode; productName = QRCode;
}; };
EBADEB8E2E8029750021B5E4 /* DeviceKit */ = {
isa = XCSwiftPackageProductDependency;
package = EBADEB8B2E8029420021B5E4 /* XCRemoteSwiftPackageReference "DeviceKit" */;
productName = DeviceKit;
};
EBADEB902E802A830021B5E4 /* DeviceKit */ = {
isa = XCSwiftPackageProductDependency;
package = EBADEB8B2E8029420021B5E4 /* XCRemoteSwiftPackageReference "DeviceKit" */;
productName = DeviceKit;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 69DA9A0629E0BF5100A442DA /* Project object */; rootObject = 69DA9A0629E0BF5100A442DA /* Project object */;
@@ -1,5 +1,5 @@
{ {
"originHash" : "212cf044c2b0bab89578ca93a3666a656e0c44fb54a69f813c4cc4fae07827d7", "originHash" : "270e8bfd245fae589b12faf08b86042cbbe7c809d59c5b0b8b735c7fb3a8d4bd",
"pins" : [ "pins" : [
{ {
"identity" : "applewatchscreensize", "identity" : "applewatchscreensize",
@@ -33,8 +33,17 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/leif-ibsen/BigInt", "location" : "https://github.com/leif-ibsen/BigInt",
"state" : { "state" : {
"revision" : "afb70a0038bfbba845271b60fa9a58d5840f8017", "revision" : "8c6f93aa37504b7b1ba3954335b5548a19fbbd82",
"version" : "1.21.0" "version" : "1.22.0"
}
},
{
"identity" : "devicekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/devicekit/DeviceKit",
"state" : {
"revision" : "581df61650bc457ec00373a592a84be3e7468eb1",
"version" : "5.7.0"
} }
}, },
{ {
@@ -43,7 +52,7 @@
"location" : "https://github.com/leonboe1/LUI.git", "location" : "https://github.com/leonboe1/LUI.git",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "5b2fe7161069c2314c5be17810ec7471b9decc8a" "revision" : "0b862decf853e1f542eeab1f8d548f8ff629deca"
} }
}, },
{ {
@@ -55,15 +64,6 @@
"version" : "26.10.0" "version" : "26.10.0"
} }
}, },
{
"identity" : "rndevicename",
"kind" : "remoteSourceControl",
"location" : "https://github.com/souzainf3/RNDeviceName",
"state" : {
"revision" : "68ada02a7135381425b01778aeb8719ce3bf8f62",
"version" : "2.1.0"
}
},
{ {
"identity" : "swift-cross-kit-types", "identity" : "swift-cross-kit-types",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
@@ -87,8 +87,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git", "location" : "https://github.com/apple/swift-protobuf.git",
"state" : { "state" : {
"revision" : "e3f69fd321d0c9fcdc16fb576a0cdd956675face", "revision" : "c169a5744230951031770e27e475ff6eefe51f9d",
"version" : "1.31.0" "version" : "1.33.3"
} }
}, },
{ {
@@ -124,7 +124,7 @@
"location" : "https://github.com/siteline/swiftui-introspect", "location" : "https://github.com/siteline/swiftui-introspect",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "9bedf1e79c7b7c34b35b74bef126f56d2400ef7f" "revision" : "11d9782bea80656e8a65a643d6ac6396e4a8662e"
} }
} }
], ],
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -32,8 +32,8 @@
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES" wasCreatedForAppExtension = "YES"
version = "2.0"> version = "2.0">
<BuildAction <BuildAction
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1640" LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES" wasCreatedForAppExtension = "YES"
version = "2.0"> version = "2.0">
<BuildAction <BuildAction
+27 -15
View File
@@ -38,26 +38,28 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
let menu = NSMenu() let menu = NSMenu()
let visibleItem = NSMenuItem( let visibleItem = NSMenuItem(
title: "VisibleToEveryone".localized(), title: getDefaultVisibleLabel(),
action: nil, action: nil,
keyEquivalent: "" keyEquivalent: ""
) )
self.visibleItem = visibleItem self.visibleItem = visibleItem
menu.addItem(visibleItem) menu.addItem(visibleItem)
menu.addItem(withTitle: String(format: "DeviceName".localized(), arguments: [Host.current().localizedName ?? "Mac"]), action: nil, keyEquivalent: "")
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
let sendClipboardItem = NSMenuItem(title: "SendClipboard".localized(), action: #selector(sendClipboard), keyEquivalent: "") let sendClipboardItem = NSMenuItem(title: "SendClipboard".localized(), action: #selector(sendClipboard), keyEquivalent: "")
sendClipboardItem.image = NSImage(systemSymbolName: "doc.on.clipboard", accessibilityDescription: nil)
menu.addItem(sendClipboardItem) menu.addItem(sendClipboardItem)
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
let userManualItem = NSMenuItem(title: "UserManual".localized(), action: #selector(openWelcomeScreen), keyEquivalent: "") let userManualItem = NSMenuItem(title: "UserManual".localized(), action: #selector(openWelcomeScreen), keyEquivalent: "")
userManualItem.image = NSImage(systemSymbolName: "gear", accessibilityDescription: nil)
menu.addItem(userManualItem) menu.addItem(userManualItem)
menu.addItem(withTitle: "Quit".localized(), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "") let quitItem = NSMenuItem(title: "Quit".localized(), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "")
quitItem.image = NSImage(systemSymbolName: "xmark", accessibilityDescription: nil)
menu.addItem(quitItem)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem?.button?.image = NSImage(named: "MenuBarIcon") statusItem?.button?.image = NSImage(named: "MenuBarIcon")
@@ -67,26 +69,31 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
iapManager = IAPManager.sharedInstance iapManager = IAPManager.sharedInstance
iapManager?.startObserving() iapManager?.startObserving()
log("[AppDelegate] Downloaded directly: \(DistributionDetector.isDirectDistributionEnabled)") if DistributionDetector.isDirectDistributionEnabled {
log("[AppDelegate] Downloaded from GitHub.")
}
else {
log("[AppDelegate] Downloaded from App Store.")
}
// app did not lauch before // app did not lauch before
if !UserDefaults.standard.bool(forKey: UserDefaultsKeys.appLaunchedBefore.rawValue) { if !Settings.shared.appLaunchedBefore {
log("[AppDelegate] Opening Welcome Screen") log("[AppDelegate] Opening Welcome Screen")
// open welcome screen // open welcome screen
openWelcomeScreen() openWelcomeScreen()
UserDefaults.standard.set(true, forKey: UserDefaultsKeys.appLaunchedBefore.rawValue) Settings.shared.appLaunchedBefore = true
// user installed the app after the IAP was implemented, set the user as eligible for IAP // user installed the app after the IAP was implemented, set the user as eligible for IAP
UserDefaults.standard.set(true, forKey: UserDefaultsKeys.isEligibleForIap.rawValue) Settings.shared.isEligibleForIap = true
} else { } else {
// app launched before // app launched before
// user installed the app before the IAP was implemented, grant the plus version // user installed the app before the IAP was implemented, grant the plus version
if !UserDefaults.standard.bool(forKey: UserDefaultsKeys.isEligibleForIap.rawValue) { if !Settings.shared.isEligibleForIap {
log("[AppDelegate] Granting QuickDrop+ for old user") log("[AppDelegate] Granting QuickDrop+ for old user")
UserDefaults.standard.set(true, forKey: UserDefaultsKeys.plusVersion.rawValue) Settings.shared.gotPlus = true
} else { } else {
if !isPlusVersion() { if !fullVersion() {
log("[AppDelegate] New user - QuickDrop+ not available") log("[AppDelegate] New user - QuickDrop+ not available")
} }
else { else {
@@ -116,7 +123,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
NearbyConnectionManager.shared.connectionUpdateCallback = { isConnected in NearbyConnectionManager.shared.connectionUpdateCallback = { isConnected in
if isConnected { if isConnected {
self.statusItem?.button?.image = NSImage(named: "MenuBarIcon") self.statusItem?.button?.image = NSImage(named: "MenuBarIcon")
self.visibleItem?.title = "VisibleToEveryone".localized() self.visibleItem?.title = self.getDefaultVisibleLabel()
} }
else { else {
self.statusItem?.button?.image = NSImage(named: "MenuBarIconSlash") self.statusItem?.button?.image = NSImage(named: "MenuBarIconSlash")
@@ -124,7 +131,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
} }
} }
log("Application did finish launching") log("[AppDelegate] Application did finish launching")
} }
@@ -191,8 +198,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
} }
} }
else { else {
let sharingService = NSSharingService(named: NSSharingService.Name("com.leonboettger.neardrop.ShareExtension")) sendToSharingService(items: [clipboardString])
sharingService?.perform(withItems: [clipboardString])
} }
} }
@@ -338,6 +344,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
} }
} }
} }
private func getDefaultVisibleLabel() -> String {
let name = NearbyConnectionManager.shared.deviceInfo.name ?? "Unknown".localized()
return String(format: "VisibleAs".localized(), name)
}
} }
struct TransferInfo { struct TransferInfo {
Binary file not shown.

After

Width:  |  Height:  |  Size: 585 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 672 KiB

@@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "1024-1024 1.png", "filename" : "1024-1024 1 2.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 672 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "FileTransfer Kopie-1024.png", "filename" : "FileTransfer Kopie-512.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

+2
View File
@@ -15,5 +15,7 @@
</array> </array>
</dict> </dict>
</array> </array>
<key>UIDesignRequiresCompatibility</key>
<true/>
</dict> </dict>
</plist> </plist>
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -13,7 +13,7 @@ let plusViewHeight: CGFloat = 200
struct PlusView: View { struct PlusView: View {
@AppStorage(UserDefaultsKeys.plusVersion.rawValue) var isPlusVersion = false @ObservedObject private var settings = Settings.shared
@ObservedObject private var iaphelper = IAPManager.sharedInstance @ObservedObject private var iaphelper = IAPManager.sharedInstance
let closeView: () -> Void let closeView: () -> Void
@@ -115,7 +115,7 @@ struct PlusView: View {
if success { if success {
log("[LUI] Restored purchases successfully") log("[LUI] Restored purchases successfully")
withAnimation { withAnimation {
isPlusVersion = true settings.gotPlus = true
} }
boughtSuccessAlert = true boughtSuccessAlert = true
} else { } else {
@@ -146,7 +146,7 @@ struct PlusView: View {
if success { if success {
withAnimation { withAnimation {
isPlusVersion = true settings.gotPlus = true
} }
boughtSuccessAlert = true boughtSuccessAlert = true
@@ -193,7 +193,7 @@ struct PlusView: View {
case .success(_): case .success(_):
withAnimation { withAnimation {
isPlusVersion = true settings.gotPlus = true
boughtSuccessAlert = true boughtSuccessAlert = true
warning = false warning = false
} }
-10
View File
@@ -2,19 +2,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.com.leonboettger.neardrop</string> <string>group.com.leonboettger.neardrop</string>
</array> </array>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict> </dict>
</plist> </plist>
+79
View File
@@ -0,0 +1,79 @@
//
// Settings.swift
// QuickDrop
//
// Created by Leon Böttger on 09.09.25.
//
import SwiftUI
class Settings: ObservableObject {
private init() {}
static var shared = Settings()
// MARK: - Published UserDefaults Backed Properties
@Published var gotPlus = UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue)
{ didSet { UserDefaults.standard.set(gotPlus, forKey: UserDefaultsKeys.plusVersion.rawValue) }}
@Published var automaticallyAcceptFiles: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
{ didSet { UserDefaults.standard.set(automaticallyAcceptFiles, forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue) }}
@Published var openFinderAfterReceiving: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue)
{ didSet { UserDefaults.standard.set(openFinderAfterReceiving, forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) }}
@Published var saveFolderBookmark: Data? = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue)
{ didSet { UserDefaults.standard.set(saveFolderBookmark, forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) }}
// MARK: - Non-Published UserDefaults Backed Properties
var incomingTransmissionCount: Int = UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue)
{ didSet { UserDefaults.standard.set(incomingTransmissionCount, forKey: UserDefaultsKeys.transmissionCount.rawValue) }}
var endpointID: String? = UserDefaults.standard.string(forKey: UserDefaultsKeys.endpointID.rawValue)
{ didSet { UserDefaults.standard.set(endpointID, forKey: UserDefaultsKeys.endpointID.rawValue) }}
var isEligibleForIap: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.isEligibleForIap.rawValue)
{ didSet { UserDefaults.standard.set(isEligibleForIap, forKey: UserDefaultsKeys.isEligibleForIap.rawValue) }}
var appLaunchedBefore: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.appLaunchedBefore.rawValue)
{ didSet { UserDefaults.standard.set(appLaunchedBefore, forKey: UserDefaultsKeys.appLaunchedBefore.rawValue) }}
// MARK: - Debug
func deleteAllUserDefaults() {
for key in UserDefaultsKeys.allCases {
UserDefaults.standard.removeObject(forKey: key.rawValue)
}
Settings.shared = Settings()
}
// MARK: - UserDefaults Keys
public enum UserDefaultsKeys: String, CaseIterable {
case isEligibleForIap = "isEligibleForIap"
case appLaunchedBefore = "ShowedWelcomeScreen"
case plusVersion = "isPlusVersion"
case transmissionCount = "reviewRequestCountKey"
case automaticallyAcceptFiles = "automaticallyAcceptFiles"
case saveFolderBookmark = "saveFolderBookmark"
case openFinderAfterReceiving = "openFinderAfterReceiving"
case endpointID = "endpointID"
}
}
+3 -3
View File
@@ -84,7 +84,7 @@ class ErrorAlertHandler {
let title = String(format: "TransferError".localized(), arguments: [deviceName]) let title = String(format: "TransferError".localized(), arguments: [deviceName])
log("Showing alert with title: \"\(title)\" and description: \"\(description)\"") log("Showing alert with title: \"\(title)\" and description: \"\(description)\"")
log("Unsuccessful transmission. Already successful transmissions: \(incomingTransmissionCount())") log("Unsuccessful transmission. Already successful transmissions: \(Settings.shared.incomingTransmissionCount)")
#if os(macOS) #if os(macOS)
@@ -103,8 +103,8 @@ class ErrorAlertHandler {
self.isAlertShown = false self.isAlertShown = false
if result == .alertFirstButtonReturn { if result == .alertFirstButtonReturn {
log("Sending logging string") //log("Sending logging string")
sendLoggingString() //sendLoggingString()
} }
#else #else
showAlert(title: title, message: description) showAlert(title: title, message: description)
+27
View File
@@ -0,0 +1,27 @@
//
// SendToExtension.swift
// QuickDrop
//
// Created by Leon Böttger on 09.09.25.
//
import Foundation
import AppKit
func sendToSharingService(items: [Any]) {
if let sharingService = NSSharingService(named: NSSharingService.Name("com.leonboettger.neardrop.ShareExtension")) {
sharingService.perform(withItems: items)
}
else {
DispatchQueue.main.async {
// show alert
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = "ShareExtensionDisabledTitle".localized()
alert.informativeText = "ShareExtensionDisabledDescription".localized()
alert.addButton(withTitle: "CloseAlert".localized())
alert.runModal()
}
}
}
+147
View File
@@ -0,0 +1,147 @@
//
// IncomingFileTransmissionView.swift
// QuickDrop
//
// Created by Leon Böttger on 05.10.25.
//
import SwiftUI
import AppKit
let toastViewSize = CGSize(width: 400, height: 100)
struct QuickDropToastView: View {
@ObservedObject var settings = Settings.shared
@ObservedObject var receiveModel: ReceiveModel
public var onCancel: () -> Void = {}
init(receiveModel: ReceiveModel,
onCancel: @escaping () -> Void = {}) {
self.receiveModel = receiveModel
self.onCancel = onCancel
}
public var body: some View {
VStack(spacing: 12) {
HStack(spacing: 12) {
ZStack(alignment: .bottomLeading) {
Image(.quickDropIcon)
.resizable()
.scaledToFill()
.frame(width: 44, height: 44)
.clipShape(Circle())
}
VStack(alignment: .leading, spacing: 2) {
Text(String("QuickDrop"))
.font(.system(size: 16, weight: .semibold))
Text("Receiving")
.font(.system(size: 13))
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.tail)
}
Spacer(minLength: 12)
}
HStack() {
CapsuleProgress(value: receiveModel.progress ?? 1.0)
Button(action: onCancel) {
Image(systemName: "xmark")
.font(.system(size: 10, weight: .semibold))
.foregroundColor(.secondary)
.frame(width: 18, height: 18)
}
.buttonStyle(PlainButtonStyle())
}
}
.padding(16)
.background(
VisualEffectView(material: .hudWindow)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(Color.black.opacity(0.06), lineWidth: 1)
)
.shadow(color: Color.black.opacity(0.1), radius: 20)
.frame(maxWidth: .infinity)
.opacity(isFileTransferRestricted() ? 0 : 1)
}
}
// NSVisualEffectView wrapper for macOS 11 compatible blur/vibrancy
struct VisualEffectView: NSViewRepresentable {
var material: NSVisualEffectView.Material = .hudWindow
var blendingMode: NSVisualEffectView.BlendingMode = .withinWindow
var state: NSVisualEffectView.State = .active
func makeNSView(context: Context) -> NSVisualEffectView {
let v = NSVisualEffectView()
v.material = material
v.blendingMode = blendingMode
v.state = state
v.isEmphasized = true
return v
}
func updateNSView(_ v: NSVisualEffectView, context: Context) {
v.material = material
v.blendingMode = blendingMode
v.state = state
}
}
// Capsule-style linear progress (0...1)
struct CapsuleProgress: View {
var value: Double
var track = Color(NSColor.separatorColor).opacity(0.28)
var fill = Color(NSColor.systemBlue)
var body: some View {
GeometryReader { geo in
let progress = max(0, min(1, value))
ZStack(alignment: .leading) {
Capsule()
.fill(track)
Capsule() // Use a plain rectangle
.fill(fill)
.mask(
Capsule()
.frame(width: progress * geo.size.width)
.frame(maxWidth: .infinity, alignment: .leading)
)
}
}
.frame(height: 8)
.animation(.easeInOut(duration: 0.25), value: value)
}
}
// MARK: - Preview
struct QuickDropToastView_Previews: PreviewProvider {
struct Demo: View {
@State var model = ReceiveModel()
var body: some View {
QuickDropToastView(
receiveModel: model,
onCancel: { }
)
.padding()
.frame(width: toastViewSize.width, height: toastViewSize.height)
.onAppear {
model.progress = 0.01
}
}
}
static var previews: some View { Demo() }
}
+26 -9
View File
@@ -10,15 +10,13 @@ import AppKit
struct SettingsView: View { struct SettingsView: View {
@AppStorage(UserDefaultsKeys.automaticallyAcceptFiles.rawValue) private var automaticallyAcceptFiles = false @ObservedObject var settings = Settings.shared
@AppStorage(UserDefaultsKeys.openFinderAfterReceiving.rawValue) private var openFinderAfterReceiving = false
@AppStorage(UserDefaultsKeys.saveFolderBookmark.rawValue) private var saveFolderPath: Data = Data()
var body: some View { var body: some View {
LargeAppIconView(title: "Settings") { LargeAppIconView(title: "Settings") {
Form { Form {
Toggle("AutomaticallyAcceptFiles", isOn: $automaticallyAcceptFiles) Toggle("AutomaticallyAcceptFiles", isOn: $settings.automaticallyAcceptFiles)
.padding(.top, 10) .padding(.top, 10)
Text("AutomaticallyAcceptFilesFooter") Text("AutomaticallyAcceptFilesFooter")
@@ -33,7 +31,7 @@ struct SettingsView: View {
Text("SaveFilesTo") Text("SaveFilesTo")
Spacer() Spacer()
Text(getSavedFolderPath() ?? "DownloadsFolder".localized()) Text(getSavedFolderPath() ?? "DownloadsFolder".localized())
.foregroundColor(saveFolderPath.isEmpty ? .gray : .primary) .foregroundColor((settings.saveFolderBookmark ?? Data()).isEmpty ? .gray : .primary)
Button("SaveFilesToButton") { Button("SaveFilesToButton") {
selectFolder() selectFolder()
} }
@@ -42,7 +40,7 @@ struct SettingsView: View {
Divider() Divider()
.padding(.vertical, 10) .padding(.vertical, 10)
Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving) Toggle("OpenFinderAfterReceiving", isOn: $settings.openFinderAfterReceiving)
.padding(.top, 5) .padding(.top, 5)
if #available(macOS 13.0, *) { if #available(macOS 13.0, *) {
@@ -72,10 +70,10 @@ struct SettingsView: View {
private func getSavedFolderPath() -> String? { private func getSavedFolderPath() -> String? {
if !saveFolderPath.isEmpty { if let bookmark = settings.saveFolderBookmark, !bookmark.isEmpty {
var isStale = false var isStale = false
do { do {
let url = try URL(resolvingBookmarkData: saveFolderPath, options: [], relativeTo: nil, bookmarkDataIsStale: &isStale) let url = try URL(resolvingBookmarkData: bookmark, options: [], relativeTo: nil, bookmarkDataIsStale: &isStale)
return url.lastPathComponent // Returns the folder path as a string return url.lastPathComponent // Returns the folder path as a string
} catch { } catch {
log("Failed to resolve bookmark: \(error)") log("Failed to resolve bookmark: \(error)")
@@ -96,11 +94,30 @@ struct SettingsView: View {
do { do {
log("Selected folder: \(url)") log("Selected folder: \(url)")
if url.description == "file:///" {
throw NSError(domain: "InvalidSelection", code: 0, userInfo: [NSLocalizedDescriptionKey: "Cannot select root directory. Please choose a specific folder."])
}
let bookmarkData = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) let bookmarkData = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
UserDefaults.standard.set(bookmarkData, forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) Settings.shared.saveFolderBookmark = bookmarkData
Settings.shared.openFinderAfterReceiving = true
} catch { } catch {
log("Failed to save security-scoped bookmark: \(error)") log("Failed to save security-scoped bookmark: \(error)")
DispatchQueue.main.asyncAfter(deadline: .now()) {
AudioManager.playErrorSound()
// show alert
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = "FolderSelectionFailedTitle".localized()
alert.informativeText = "FolderSelectionFailedMessage".localized()
alert.addButton(withTitle: "CloseAlert".localized())
alert.runModal()
}
} }
} }
} }
+4 -6
View File
@@ -15,7 +15,7 @@ struct TutorialView: View {
let openPlus: () -> Void let openPlus: () -> Void
@State private var licenseWindow: NSWindow? @State private var licenseWindow: NSWindow?
@AppStorage(UserDefaultsKeys.plusVersion.rawValue) var isPlusVersion = false @ObservedObject var settings = Settings.shared
var body: some View { var body: some View {
@@ -40,7 +40,7 @@ struct TutorialView: View {
} }
.buttonStyle(.plain) .buttonStyle(.plain)
if !isPlusVersion { if !settings.gotPlus {
Button { Button {
openPlus() openPlus()
} label: { } label: {
@@ -54,11 +54,9 @@ struct TutorialView: View {
#if DEBUG #if DEBUG
Button { Button {
for key in UserDefaultsKeys.allCases { Settings.shared.deleteAllUserDefaults()
UserDefaults.standard.removeObject(forKey: key.rawValue)
}
} label: { } label: {
Text("Reset UD") Text("ResetAllSettings")
.underline() .underline()
.font(.footnote) .font(.footnote)
.opacity(0.5) .opacity(0.5)
+1 -7
View File
@@ -58,7 +58,7 @@ struct WelcomeScreen: View {
.listStyle(SidebarListStyle()) .listStyle(SidebarListStyle())
Divider() Divider()
.opacity(colorScheme == .light ? 1 : 0) .opacity(colorScheme == .light ? 1 : 0.5)
.edgesIgnoringSafeArea(.vertical) .edgesIgnoringSafeArea(.vertical)
ZStack { ZStack {
@@ -138,12 +138,6 @@ struct WelcomeScreen: View {
} }
} }
} }
func sendToSharingService(items: [Any]) {
if let sharingService = NSSharingService(named: NSSharingService.Name("com.leonboettger.neardrop.ShareExtension")) {
sharingService.perform(withItems: items)
}
}
} }
+323
View File
@@ -0,0 +1,323 @@
//
// ReceiveModel.swift
// QuickDrop
//
// Created by Leon Böttger on 16.08.25.
//
import SwiftUI
import StoreKit
import UserNotifications
#if os(macOS)
import BezelNotification
#else
import LUI
#endif
class ReceiveModel: ObservableObject, InboundAppDelegate {
/// For each connection ID, store the last reported progress value
private var processes: [String: Double] = [:]
#if os(macOS)
@Published var progress: Double? = nil
private var toastWindow: NSWindow?
private var toastHosting: NSHostingView<QuickDropToastView>?
#endif
let controlPlusScreen: (Bool) -> Void
init(controlPlusScreen: @escaping (Bool) -> Void = { _ in }) {
self.controlPlusScreen = controlPlusScreen
NearbyConnectionManager.shared.addInboundAppDelegate(self)
NearbyConnectionManager.shared.becomeVisible()
}
deinit {
NearbyConnectionManager.shared.removeInboundAppDelegate(self)
}
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo) {
#if os(macOS)
NSApp.activate(ignoringOtherApps: true)
AudioManager.playIncomingFileSound()
#endif
let mainMessage = transfer.getDescription(deviceName: device.name ?? "Android", alreadyAccepted: false)
let pinCodeMessage = transfer.getPinCodeMessage()
let transferID = transfer.id
let title = "QuickDrop - \(pinCodeMessage)"
let primaryButtonTitle = "Accept".localized()
let primaryButtonAction = { (trustDevice: Bool) in
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, trustDevice: trustDevice)
#if os(macOS)
if transfer.type == .text {
DispatchQueue.main.async {
BezelNotification.show(messageText: "InsertedIntoClipboard".localized(), icon: .clipboard)
}
}
#endif
}
let secondaryButtonTitle = "Decline".localized()
let secondaryButtonAction = { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: false, trustDevice: false) }
#if os(macOS)
let alert = NSAlert()
alert.alertStyle = .informational
alert.messageText = title
alert.informativeText = mainMessage
alert.addButton(withTitle: primaryButtonTitle)
alert.addButton(withTitle: secondaryButtonTitle)
if transfer.allowsToBeAddedAsTrustedDevice {
alert.showsSuppressionButton = true
alert.suppressionButton?.title = "AutoAcceptFromThisDevice".localized()
}
let result = alert.runModal()
if result == .alertFirstButtonReturn {
primaryButtonAction(alert.suppressionButton?.state == .on)
} else if result == .alertSecondButtonReturn {
secondaryButtonAction()
}
#else
// iOS
let alwaysAcceptLabel = transfer.allowsToBeAddedAsTrustedDevice ? "AlwaysAccept".localized() : nil
ProgressAlert.shared.askForUserPermission(title: title, message: mainMessage, acceptLabel: primaryButtonTitle, acceptAlwaysLabel: alwaysAcceptLabel, rejectLabel: secondaryButtonTitle) { accepted in
switch accepted {
case .Accept:
primaryButtonAction(false)
case .AcceptAlways:
primaryButtonAction(true)
case .Decline:
secondaryButtonAction()
}
} onCancel: {
NearbyConnectionManager.shared.cancelTransfer(id: transferID)
}
#endif
}
func obtainedUserConsentAutomatically(transfer: TransferMetadata, device: RemoteDeviceInfo) {
let mainMessage = transfer.getDescription(deviceName: device.name ?? "Android", alreadyAccepted: true)
#if os(macOS)
NSApp.activate(ignoringOtherApps: true)
AudioManager.playIncomingFileSound()
// If file -> no notification, as there is the QuickDrop progress toast
if transfer.type != .file {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
if granted {
log("[ReceiveModel] User granted notification permissions")
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "QuickDrop"
notificationContent.body = mainMessage
notificationContent.sound = nil
let notificationId = UUID().uuidString
let notificationReq = UNNotificationRequest(identifier: notificationId, content: notificationContent, trigger: nil)
UNUserNotificationCenter.current().add(notificationReq)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationId])
}
}
}
}
#else
// If text is received, nothing is shown, as it is directly inserted into clipboard automatically
// Therefore, give a feedback dialog in this case
if transfer.type == .text {
showAlert(title: "QuickDrop", message: mainMessage)
}
else {
ProgressAlert.shared.showProgressAlert(onCancel: {
NearbyConnectionManager.shared.cancelTransfer(id: transfer.id)
})
}
#endif
}
func transferProgress(connectionID: String, progress: Double) {
// Update process dictionary
processes[connectionID] = progress
// Calcualate average value for all ongoing transfers
let totalProgress = processes.values.reduce(0, +)
let averageProgress = totalProgress / Double(processes.count)
#if os(iOS)
ProgressAlert.shared.updateProgress(averageProgress)
#else
DispatchQueue.main.async {
self.progress = averageProgress
self.showQuickDropToast(for: connectionID)
}
#endif
}
func connectionWasTerminated(connectionID: String, from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: (any Error)?) {
processes.removeValue(forKey: connectionID)
#if os(macOS)
DispatchQueue.main.async {
self.hideQuickDropToast()
if self.processes.isEmpty {
self.progress = nil
}
}
finish()
#else
if self.processes.isEmpty {
ProgressAlert.shared.updateProgress(nil) {
finish()
}
}
#endif
func finish() {
if let error = error {
#if os(iOS)
errorVibration()
#endif
controlPlusScreen(false)
ErrorAlertHandler.shared.showErrorAlert(for: device.name ?? "Android", error: error)
} else {
#if os(iOS)
doubleVibration()
#endif
let currentCount = Settings.shared.incomingTransmissionCount
#if os(macOS)
// If distributed directly, do not request review here as it does not work
if currentCount == 0 && !DistributionDetector.isDirectDistributionEnabled {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
SKStoreReviewController.requestReview()
}
}
#endif
Settings.shared.incomingTransmissionCount = currentCount + 1
log("[ReceiveModel] Successful transmission. Current count: \(currentCount)")
}
}
}
func showPlusScreen() {
DispatchQueue.main.async {
self.controlPlusScreen(true)
}
}
// MARK: - macOS Progress Overlay
#if os(macOS)
func showQuickDropToast(for connectionID: String) {
guard toastWindow == nil else { return }
let contentView = QuickDropToastView(
receiveModel: self,
onCancel: {
NearbyConnectionManager.shared.cancelTransfer(id: connectionID)
}
)
let hostingView = NSHostingView(rootView: contentView)
hostingView.frame.size = toastViewSize
// Prefer the screen under the mouse, then the key window’s screen, then main
let screen = NSScreen.screens.first { $0.frame.contains(NSEvent.mouseLocation) }
?? NSApp.keyWindow?.screen
?? NSScreen.main
guard let screen else { return }
let frame = screen.frame
let visible = screen.visibleFrame
let marginX: CGFloat = 24
let marginFromTop: CGFloat = 30
// If the menu bar is auto-hidden, visible.maxY == frame.maxY; subtract the status bar thickness to stay clear
let menuBarHidden = abs(frame.maxY - visible.maxY) < 0.5
let extraTopInset = menuBarHidden ? NSStatusBar.system.thickness : 0
let targetX = visible.maxX - toastViewSize.width - marginX
let targetY = visible.maxY - toastViewSize.height - marginFromTop - extraTopInset
// Start slightly above the final spot for a smooth slide-in
let startY = min(frame.maxY + 10, targetY + 10)
let startFrame = CGRect(x: targetX, y: startY, width: toastViewSize.width, height: toastViewSize.height)
let window = NSWindow(contentRect: startFrame,
styleMask: [.borderless],
backing: .buffered,
defer: false)
window.isOpaque = false
window.backgroundColor = .clear
window.hasShadow = true
window.level = .statusBar
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.contentView = hostingView
window.ignoresMouseEvents = false
window.alphaValue = 0
window.makeKeyAndOrderFront(nil)
self.toastWindow = window
self.toastHosting = hostingView
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = 0.35
ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
window.animator().setFrameOrigin(CGPoint(x: targetX, y: targetY))
window.animator().alphaValue = 1
}
}
func hideQuickDropToast() {
guard let window = toastWindow,
let screen = NSScreen.main else { return }
let offscreenY = screen.visibleFrame.origin.y + screen.visibleFrame.height + 20
NSAnimationContext.runAnimationGroup({ ctx in
ctx.duration = 0.35
ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
window.animator().setFrameOrigin(CGPoint(x: window.frame.origin.x, y: offscreenY))
window.animator().alphaValue = 0
}, completionHandler: {
window.orderOut(nil)
self.toastWindow = nil
self.toastHosting = nil
})
}
#endif
}
@@ -12,6 +12,8 @@ import LUI
class SendModel: ObservableObject, OutboundAppDelegate { class SendModel: ObservableObject, OutboundAppDelegate {
@Published var showQrCodeView: Bool = false
@Published var foundDevices: [RemoteDeviceInfo] = [] @Published var foundDevices: [RemoteDeviceInfo] = []
@Published var selectedDevice: RemoteDeviceInfo? @Published var selectedDevice: RemoteDeviceInfo?
@@ -24,49 +26,62 @@ class SendModel: ObservableObject, OutboundAppDelegate {
private var connectionEstablished = false private var connectionEstablished = false
private var timeoutDispatchWorkItem: DispatchWorkItem? = nil private var timeoutDispatchWorkItem: DispatchWorkItem? = nil
init() { init() {
NearbyConnectionManager.shared.startDeviceDiscovery() NearbyConnectionManager.shared.startDeviceDiscovery()
NearbyConnectionManager.shared.addShareExtensionDelegate(self) NearbyConnectionManager.shared.addOutboundAppDelegate(self)
} }
deinit {
NearbyConnectionManager.shared.removeOutboundAppDelegate(self)
}
func addDevice(device: RemoteDeviceInfo) { func addDevice(device: RemoteDeviceInfo) {
withAnimation(.smooth) { withAnimation(.smooth) {
foundDevices.append(device) foundDevices.append(device)
} }
} }
func removeDevice(id: String) { func removeDevice(id: String) {
withAnimation(.smooth) { withAnimation(.smooth) {
foundDevices.removeAll { $0.id == id } foundDevices.removeAll { $0.id == id }
} }
} }
func connectionWasEstablished(pinCode: String) { func connectionWasEstablished(pinCode: String) {
connectionEstablished = true connectionEstablished = true
progressState = String(format: "PinCode".localized(), arguments: [pinCode]) progressState = String(format: "PinCode".localized(), arguments: [pinCode])
progressValue = 0 progressValue = 0
} }
func connectionFailed(error: any Error) { func connectionFailed(error: any Error) {
#if os(iOS) #if os(iOS)
errorVibration() errorVibration()
#endif #endif
ErrorAlertHandler.shared.showErrorAlert(for: selectedDevice?.name ?? "?", error: error) ErrorAlertHandler.shared.showErrorAlert(for: selectedDevice?.name ?? "UnknownDevice".localized(), error: error)
progressValue = nil progressValue = nil
selectedDevice = nil selectedDevice = nil
} }
func transferAccepted() { func transferAccepted() {
progressState = "Sending".localized() progressState = "Sending".localized()
} }
func transferProgress(progress: Double) { func transferProgress(progress: Double) {
progressValue = progress progressValue = progress
} }
func transferFinished() { func transferFinished() {
#if os(iOS) #if os(iOS)
@@ -78,22 +93,38 @@ class SendModel: ObservableObject, OutboundAppDelegate {
progressState = "TransferFinished".localized() progressState = "TransferFinished".localized()
NearbyConnectionManager.shared.attachments?.closeView?() NearbyConnectionManager.shared.attachments?.closeView?()
#if !EXTENSION
requestReviewOnce()
#endif
} }
func startTransferWithQrCode(device: RemoteDeviceInfo) { 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 // already selected, cancel transfer
if device == selectedDevice { if device == selectedDevice {
progressValue = 0 progressValue = 0
progressState = nil progressState = nil
selectedDevice = nil selectedDevice = nil
NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!) NearbyConnectionManager.shared.cancelTransfer(id: device.id!)
} }
else { else {
progressValue = 0 progressValue = 0
progressState = "Connecting".localized() progressState = "Connecting".localized()
selectedDevice = device selectedDevice = device
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+37
View File
@@ -0,0 +1,37 @@
{
"fill" : {
"linear-gradient" : [
"display-p3:0.31273,0.68878,0.97563,1.00000",
"display-p3:0.22962,0.42793,0.95758,1.00000"
]
},
"groups" : [
{
"layers" : [
{
"image-name" : "QuickDropArrows.png",
"name" : "QuickDropArrows"
},
{
"image-name" : "QuickDropCircle.png",
"name" : "QuickDropCircle",
"opacity" : 0.5
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.52
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
+6
View File
@@ -10,6 +10,10 @@ import LUI
let configuration = LUIConfiguration( let configuration = LUIConfiguration(
appName: "QuickDrop", appName: "QuickDrop",
inAppPurchaseName: "plusversion",
plusVersionInfo: GetPlusViewInformation(description: "plusview_description_phone", plusVersionFeatures: [], settingsLabelColor: .orange, settingsFooter: "plusview_footer_phone"),
hasRoundedFont: false, hasRoundedFont: false,
copyrightInformation: licenseText, copyrightInformation: licenseText,
@@ -24,6 +28,8 @@ let configuration = LUIConfiguration(
supportsSplitView: false, supportsSplitView: false,
supportsLiquidGlass: true,
supportsPortraitSplitView: false, supportsPortraitSplitView: false,
supportsDarkModeShadow: false supportsDarkModeShadow: false
+275
View File
@@ -0,0 +1,275 @@
{
"sourceLanguage" : "en",
"strings" : {
"CFBundleDisplayName" : {
"comment" : "Bundle display name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "QuickDrop"
}
}
}
},
"CFBundleName" : {
"comment" : "Bundle name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "QuickDropiOS"
}
}
}
},
"NSHumanReadableCopyright" : {
"comment" : "Copyright (human-readable)",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
}
},
"NSLocalNetworkUsageDescription" : {
"comment" : "Privacy - Local Network Usage Description",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "الوصول إلى الشبكة المحلية مطلوب لاكتشاف الأجهزة ونقل الملفات عبر شبكة Wi-Fi الخاصة بك."
}
},
"ca" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cal accés a la xarxa local per detectar dispositius i transferir fitxers a través de la teva xarxa Wi-Fi."
}
},
"cs" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pro přenos souborů a detekci zařízení přes vaši Wi-Fi síť je třeba povolit přístup k místní síti."
}
},
"da" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adgang til lokalt netværk er nødvendig for at finde enheder og overføre filer via dit Wi-Fi-netværk."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Der Zugriff auf das lokale Netzwerk ist erforderlich, um Geräte zu erkennen und Dateien über WLAN zu übertragen."
}
},
"el" : {
"stringUnit" : {
"state" : "translated",
"value" : "Απαιτείται πρόσβαση στο τοπικό δίκτυο για ανίχνευση συσκευών και μεταφορά αρχείων μέσω του Wi-Fi δικτύου σας."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Local network access is needed to detect devices and transfer files over your Wi-Fi network."
}
},
"en-AU" : {
"stringUnit" : {
"state" : "translated",
"value" : "Local network access is required to find devices and transfer files over your Wi-Fi network."
}
},
"en-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Local network access is required to find devices and transfer files over your Wi-Fi network."
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Local network access is required to discover devices and transfer files via your Wi-Fi network."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se necesita acceso a la red local para detectar dispositivos y transferir archivos a través de su red Wi-Fi."
}
},
"es-419" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se necesita acceso a la red local para detectar dispositivos y transferir archivos por tu red Wi-Fi."
}
},
"fi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lähiverkkoyhteys tarvitaan laitteiden tunnistamiseen ja tiedostojen siirtoon Wi-Fi-verkossasi."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "L’accès au réseau local est nécessaire pour détecter les appareils et transférer des fichiers via votre réseau Wi-Fi."
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "L’accès au réseau local est nécessaire pour détecter les appareils et transférer des fichiers sur votre réseau Wi-Fi."
}
},
"he" : {
"stringUnit" : {
"state" : "translated",
"value" : "נדרש גישה לרשת המקומית כדי לזהות מכשירים ולהעביר קבצים דרך רשת ה-Wi-Fi שלך."
}
},
"hi" : {
"stringUnit" : {
"state" : "translated",
"value" : "डिवाइस का पता लगाने और अपने Wi-Fi नेटवर्क पर फाइल ट्रांसफर के लिए लोकल नेटवर्क एक्सेस आवश्यक है।"
}
},
"hr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pristup lokalnoj mreži potreban je za pronalaženje uređaja i prijenos datoteka putem vaše Wi-Fi mreže."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Helyi hálózati hozzáférés szükséges az eszközök felismeréséhez és a fájlok átviteléhez a Wi-Fi hálózatán keresztül."
}
},
"id" : {
"stringUnit" : {
"state" : "translated",
"value" : "Akses jaringan lokal diperlukan untuk mendeteksi perangkat dan mentransfer file melalui jaringan Wi-Fi Anda."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "È necessario l’accesso alla rete locale per rilevare i dispositivi e trasferire file tramite la tua rete Wi-Fi."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "ローカルネットワークへのアクセスは、デバイスを検出し、Wi-Fi ネットワーク経由でファイルを転送するために必要です。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "장치를 감지하고 Wi-Fi 네트워크를 통해 파일을 전송하려면 로컬 네트워크 접근이 필요합니다."
}
},
"ms" : {
"stringUnit" : {
"state" : "translated",
"value" : "Akses rangkaian tempatan diperlukan untuk mengesan peranti dan memindahkan fail melalui rangkaian Wi-Fi anda."
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tilgang til lokalt nettverk er nødvendig for å oppdage enheter og overføre filer via Wi-Fi-nettverket ditt."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lokale netwerktoegang is nodig om apparaten te detecteren en bestanden over uw Wi-Fi-netwerk te verzenden."
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dostęp do sieci lokalnej jest potrzebny, aby wykrywać urządzenia i przesyłać pliki przez Twoją sieć Wi-Fi."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "O acesso à rede local é necessário para detectar dispositivos e transferir arquivos pela sua rede Wi-Fi."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "O acesso à rede local é necessário para detetar dispositivos e transferir ficheiros através da sua rede Wi-Fi."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Accesul la rețeaua locală este necesar pentru a detecta dispozitivele și a transfera fișiere prin rețeaua Wi-Fi."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Доступ к локальной сети необходим для обнаружения устройств и передачи файлов через вашу сеть Wi-Fi."
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"value" : "Åtkomst till lokalt nätverk krävs för att upptäcka enheter och överföra filer via ditt Wi-Fi-nätverk."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "การเข้าถึงเครือข่ายภายในเครื่องจำเป็นสำหรับการตรวจหาดีไวซ์และการถ่ายโอนไฟล์ผ่านเครือข่าย Wi-Fi ของคุณ"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cihazları algılamak ve dosya aktarmak için yerel ağa erişim gereklidir."
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Доступ до локальної мережі потрібен для виявлення пристроїв та передавання файлів через вашу мережу Wi-Fi."
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cần truy cập mạng nội bộ để phát hiện thiết bị và truyền tệp qua Wi-Fi của bạn."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要本地网络访问以检测设备并通过您的 Wi-Fi 网络传输文件。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "需要本機網路存取權才能偵測裝置並透過 Wi-Fi 網路傳輸檔案。"
}
}
}
}
},
"version" : "1.0"
}
-212
View File
@@ -1,212 +0,0 @@
//
// ReceiveModel.swift
// QuickDrop
//
// Created by Leon Böttger on 16.08.25.
//
import SwiftUI
import StoreKit
import UserNotifications
#if os(macOS)
import BezelNotification
#else
import LUI
#endif
class ReceiveModel: ObservableObject, InboundAppDelegate {
let controlPlusScreen: (Bool) -> Void
init(controlPlusScreen: @escaping (Bool) -> Void = { _ in }) {
self.controlPlusScreen = controlPlusScreen
NearbyConnectionManager.shared.mainAppDelegate = self
NearbyConnectionManager.shared.becomeVisible()
}
func obtainUserConsent(transfer: TransferMetadata, device: RemoteDeviceInfo, acceptAutomatically: Bool) {
#if os(macOS)
NSApp.activate(ignoringOtherApps: true)
AudioManager.playIncomingFileSound()
#endif
let fileStr: String
if let textTitle = transfer.textDescription {
fileStr = textTitle
} else if transfer.files.count == 1 {
fileStr = transfer.files[0].name
} else {
fileStr = String.localizedStringWithFormat("NFiles".localized(), transfer.files.count)
}
let mainMessage: String
let name = device.name ?? "Android"
switch transfer.type {
case .file:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [name, fileStr])
case .text:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingText" : "DeviceSendingText").localized(), arguments: [name, fileStr])
case .url:
mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingUrl" : "DeviceSendingUrl").localized(), arguments: [name, fileStr])
}
let pinCodeMessage = String(format: "PinCode".localized(), arguments: [transfer.pinCode ?? "?"])
let transferID = transfer.id
#if os(macOS)
let isMac = true
#else
let isMac = false
#endif
if acceptAutomatically && isMac {
pressAcceptButton(transferID: transfer.id, trustDevice: false)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
if granted {
log("[ReceiveModel] User granted notification permissions")
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "QuickDrop"
notificationContent.body = mainMessage
notificationContent.sound = nil
let notificationId = UUID().uuidString
let notificationReq = UNNotificationRequest(identifier: notificationId, content: notificationContent, trigger: nil)
UNUserNotificationCenter.current().add(notificationReq)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationId])
}
}
}
} else {
let title = "QuickDrop - \(pinCodeMessage)"
let primaryButtonTitle = "Accept".localized()
let primaryButtonAction = { (trustDevice: Bool) in self.pressAcceptButton(transferID: transferID, trustDevice: trustDevice) }
let secondaryButtonTitle = "Decline".localized()
let secondaryButtonAction = { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: false, trustDevice: false, storeInTemp: false) }
#if os(macOS)
let alert = NSAlert()
alert.alertStyle = .informational
alert.messageText = title
alert.informativeText = mainMessage
alert.addButton(withTitle: primaryButtonTitle)
alert.addButton(withTitle: secondaryButtonTitle)
if transfer.allowsToBeAddedAsTrustedDevice {
alert.showsSuppressionButton = true
alert.suppressionButton?.title = "AutoAcceptFromThisDevice".localized()
}
let result = alert.runModal()
if result == .alertFirstButtonReturn {
primaryButtonAction(alert.suppressionButton?.state == .on)
} else if result == .alertSecondButtonReturn {
secondaryButtonAction()
}
#else
// iOS
let alwaysAcceptLabel = transfer.allowsToBeAddedAsTrustedDevice ? "AlwaysAccept".localized() : nil
ProgressAlert.shared.askForUserPermission(title: title, message: mainMessage, acceptLabel: primaryButtonTitle, acceptAlwaysLabel: alwaysAcceptLabel, rejectLabel: secondaryButtonTitle, acceptAutomatically: acceptAutomatically) { accepted in
switch accepted {
case .Accept:
primaryButtonAction(false)
case .AcceptAlways:
primaryButtonAction(true)
case .Decline:
secondaryButtonAction()
}
}
#endif
}
}
func transferProgress(progress: Double) {
#if os(iOS)
ProgressAlert.shared.updateProgress(progress)
#endif
}
func connectionWasTerminated(from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: (any Error)?) {
#if os(macOS)
finish()
#else
ProgressAlert.shared.updateProgress(nil) {
finish()
}
#endif
func finish() {
if let error = error {
#if os(iOS)
errorVibration()
#endif
controlPlusScreen(false)
ErrorAlertHandler.shared.showErrorAlert(for: device.name ?? "Android", error: error)
} else {
#if os(iOS)
doubleVibration()
#endif
if wasPlainTextTransfer {
showCopiedToClipboardAlert()
}
let currentCount = incomingTransmissionCount()
#if os(macOS)
if currentCount == 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
SKStoreReviewController.requestReview()
}
}
#endif
UserDefaults.standard.set(currentCount + 1, forKey: UserDefaultsKeys.transmissionCount.rawValue)
log("[ReceiveModel] Successful transmission. Current count: \(currentCount)")
}
}
}
private func showCopiedToClipboardAlert() {
#if os(macOS)
DispatchQueue.main.async {
BezelNotification.show(messageText: "InsertedIntoClipboard".localized(), icon: .clipboard)
}
#endif
}
private func pressAcceptButton(transferID: String, trustDevice: Bool) {
if isFileTransferRestricted() {
log("[ReceiveModel] Showing plus screen...")
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, trustDevice: trustDevice, storeInTemp: true)
controlPlusScreen(true)
} else {
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: true, trustDevice: trustDevice, storeInTemp: false)
}
}
}
@@ -12,13 +12,14 @@ import StoreKit
struct ContentView: View { struct ContentView: View {
@ObservedObject var luiSettings = LUISettings.sharedInstance @ObservedObject var luiSettings = LUISettings.sharedInstance
@ObservedObject var settings = Settings.shared
var body: some View { var body: some View {
AppRootView(isPlus: .constant(true), phoneView: { AppRootView(isPlus: $settings.gotPlus, phoneView: {
DeviceListView() DeviceListView()
.environment(\.sheetActive, isShareExtension()) .environment(\.sheetActive, isShareExtension())
}, settingsView: { }, settingsView: {
CustomSection { CustomSection(header: settings.gotPlus ? "" : "General", footer: "TrustedDevicesFooterShort") {
LUIButton { LUIButton {
SaveFilesManager.shared.openDownloadedFilesFolder() SaveFilesManager.shared.openDownloadedFilesFolder()
} label: { } label: {
@@ -37,6 +38,7 @@ struct ContentView: View {
struct DeviceListView: View { struct DeviceListView: View {
@State private var qrCode: Image? = nil
@StateObject var sendModel = SendModel() @StateObject var sendModel = SendModel()
#if !EXTENSION #if !EXTENSION
@@ -44,12 +46,15 @@ struct DeviceListView: View {
#endif #endif
@Environment(\.scenePhase) var scenePhase @Environment(\.scenePhase) var scenePhase
@AppStorage("requestedReview") private var requestedReview = false @Environment(\.sheetActive) var sheetActive
@ObservedObject var nearbyConnectionManager = NearbyConnectionManager.shared @ObservedObject var nearbyConnectionManager = NearbyConnectionManager.shared
@ObservedObject var settings = Settings.shared
var body: some View { var body: some View {
NavigationSubView(header: "QuickDrop ", navigationBarLayout: isShareExtension() ? .SmallOnlyAlways : .Default) { let showsLoadingIndicator = nearbyConnectionManager.hasLocalNetworkPermission && nearbyConnectionManager.isConnectedToLocalNetwork
BottomBarView(header: "QuickDrop ", navigationBarLayout: isShareExtension() ? .SmallOnlyAlways : .Default, bottomViewHeight: 30) {
VStack { VStack {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
@@ -72,14 +77,14 @@ struct DeviceListView: View {
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 6) .padding(.vertical, 6)
.background(Color.defaultForegroundColor) .background(sheetActive ? Color.sheetForegroundColor : Color.defaultForegroundColor)
.cornerRadius(24) .cornerRadius(24)
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, 8)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
if !nearbyConnectionManager.hasLocalNetworkAccess { if !nearbyConnectionManager.hasLocalNetworkPermission {
CardView(backgroundColor: .red, title: "NoNetworkAccess", titleSymbol: "network.slash") { CardView(backgroundColor: .red, title: "NoNetworkAccess", titleSymbol: "network.slash") {
CardSubView(symbol: "exclamationmark.triangle.fill", text: "NoLocalNetworkAccessDescription") CardSubView(symbol: "exclamationmark.triangle.fill", text: "NoLocalNetworkAccessDescription")
} }
@@ -93,16 +98,22 @@ struct DeviceListView: View {
CustomSection(header: "NoDevicesFound") { CustomSection(header: "NoDevicesFound") {
let hasWifi = nearbyConnectionManager.isConnectedViaWiFi let hasWifi = nearbyConnectionManager.isConnectedToLocalNetwork
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: hasWifi ? "arrow.down.circle.fill" : "wifi.slash") Image(systemName: hasWifi ? "arrow.down.circle.fill" : "wifi.slash")
.font(.system(size: 30)) .font(.system(size: 30))
.foregroundColor(.blue) .foregroundColor(.blue)
.padding(.leading, -10) .background(
.frame(width: 30) Circle()
.fill(Color.white)
.padding(3)
.opacity(hasWifi ? 1 : 0)
)
.frame(width: 32)
Text(hasWifi ? "DownloadQuickDropOnPlayStore" : "NoWiFiConnectionDescription") LUIText(hasWifi ? "DownloadQuickDropOnPlayStore" : "NoWiFiConnectionDescription")
.frame(maxWidth: .infinity, alignment: .leading)
} }
.animation(.default, value: hasWifi) .animation(.default, value: hasWifi)
.padding(.vertical) .padding(.vertical)
@@ -119,9 +130,7 @@ struct DeviceListView: View {
if let attachments = NearbyConnectionManager.shared.attachments { if let attachments = NearbyConnectionManager.shared.attachments {
LUIButton { LUIButton {
lightVibration() lightVibration()
sendModel.urls = attachments.urls sendModel.selectDevice(device: device, with: attachments)
sendModel.textToSend = attachments.textToSend
sendModel.selectDevice(device: device)
} label: { } label: {
DeviceButtonLabel(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue) DeviceButtonLabel(device: device, isSelected: isSelected, progress: sendModel.progressState, progressValue: sendModel.progressValue)
} }
@@ -137,7 +146,7 @@ struct DeviceListView: View {
} }
if nearbyConnectionManager.hasLocalNetworkAccess && nearbyConnectionManager.isConnectedViaWiFi { if showsLoadingIndicator {
HStack(spacing: 8) { HStack(spacing: 8) {
Text("SearchingForDevices") Text("SearchingForDevices")
.font(.system(size: 14)) .font(.system(size: 14))
@@ -151,28 +160,37 @@ struct DeviceListView: View {
} }
} }
.frame(maxWidth: 700) .frame(maxWidth: 700)
} bottomView: {
LUIButton {
qrCode = NearbyConnectionManager.shared.generateQrCodeKey()
sendModel.showQrCodeView = true
} label: {
UnderlineText(label: "DeviceNotShown")
.padding()
}
.opacity(showsLoadingIndicator ? 1 : 0)
} }
.luiSheet(isPresented: $sendModel.showQrCodeView, content: {
NavigationView {
NavigationSubView(header: "ConnectDevice", navigationBarLayout: .SmallOnlyAlways) {
SmallSheetView(type: .sendToDeviceQrCode, dynamicQrCode: qrCode, closeView: {})
}
.navigationBarItems(trailing: XButton(action: { sendModel.showQrCodeView = false }))
}
})
.onChange(of: scenePhase) { newValue in .onChange(of: scenePhase) { newValue in
if newValue == .active { if newValue == .active {
log("App became active") log("[ScenePhase] App became active")
NearbyConnectionManager.shared.startDeviceDiscovery() NearbyConnectionManager.shared.startDeviceDiscovery()
#if !EXTENSION #if !EXTENSION
NearbyConnectionManager.shared.becomeVisible() NearbyConnectionManager.shared.becomeVisible()
if incomingTransmissionCount() > 0 && !requestedReview { if Settings.shared.incomingTransmissionCount > 0 {
runAfter(seconds: 0.3) { runAfter(seconds: 0.3) {
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene { requestReviewOnce()
log("Requesting Review...")
SKStoreReviewController.requestReview(in: scene)
requestedReview = true
}
else {
log("Error Requesting Review!")
}
} }
} }
#endif #endif
@@ -180,14 +198,15 @@ struct DeviceListView: View {
if newValue == .background { if newValue == .background {
log("App went to background") log("[ScenePhase] App went to background")
#if !EXTENSION #if !EXTENSION
NearbyConnectionManager.shared.becomeInvisible() NearbyConnectionManager.shared.becomeInvisible()
#endif #endif
NearbyConnectionManager.shared.stopDeviceDiscovery() NearbyConnectionManager.shared.stopDeviceDiscovery()
} }
} }
.animation(.smooth, value: nearbyConnectionManager.hasLocalNetworkAccess) .animation(.smooth, value: nearbyConnectionManager.hasLocalNetworkPermission)
.animation(.smooth, value: nearbyConnectionManager.isConnectedToLocalNetwork)
.navigationBarItems(trailing: ZStack { .navigationBarItems(trailing: ZStack {
if isShareExtension() { if isShareExtension() {
XButton(action: { NearbyConnectionManager.shared.attachments?.closeView?() }) XButton(action: { NearbyConnectionManager.shared.attachments?.closeView?() })
@@ -215,9 +234,7 @@ struct DeviceFilePickerButton: View {
DeviceButtonLabel(device: device, isSelected: isPreparing || isSelected, progress: isPreparing ? "Preparing".localized() : progressState, progressValue: progressValue) DeviceButtonLabel(device: device, isSelected: isPreparing || isSelected, progress: isPreparing ? "Preparing".localized() : progressState, progressValue: progressValue)
} onResult: { urls, text in } onResult: { urls, text in
isPreparing = false isPreparing = false
sendModel.urls = urls ?? [] sendModel.selectDevice(device: device, with: AttachmentDetails(urls: urls ?? [], textToSend: text, shortDescription: ""))
sendModel.textToSend = text
sendModel.selectDevice(device: device)
} onPrepare: { } onPrepare: {
isPreparing = true isPreparing = true
} }
@@ -162,10 +162,10 @@ fileprivate struct DataUrl: Transferable {
.cornerRadius(8) .cornerRadius(8)
} onResult: { urls, text in } onResult: { urls, text in
if let urls = urls { if let urls = urls {
print("Selected URLs: \(urls)") log("Selected URLs: \(urls)")
} }
if let text = text { if let text = text {
print("Clipboard text: \(text)") log("Clipboard text: \(text)")
} }
} }
} }
File renamed without changes.
@@ -18,41 +18,41 @@ final class ProgressAlert {
private weak var progressView: UIProgressView? private weak var progressView: UIProgressView?
// MARK: - Initial Accept / Decline // MARK: - Initial Accept / Decline
func askForUserPermission(title: String, message: String, acceptLabel: String, acceptAlwaysLabel: String?, rejectLabel: String, acceptAutomatically: Bool, onAccept: @escaping (AcceptAction) -> Void) { func askForUserPermission(title: String, message: String, acceptLabel: String, acceptAlwaysLabel: String?, rejectLabel: String, onAccept: @escaping (AcceptAction) -> Void, onCancel: @escaping () -> Void) {
guard let vc = topMostViewController() else { return } guard let vc = topMostViewController() else { return }
if acceptAutomatically { let alert = UIAlertController(title: title,
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: rejectLabel, style: .cancel, handler: { _ in
onAccept(.Decline)
}))
alert.addAction(UIAlertAction(title: acceptLabel, style: .default, handler: { _ in
onAccept(.Accept) onAccept(.Accept)
self.showProgressAlert(on: vc) self.showProgressAlert(on: vc, onCancel: onCancel)
} }))
else {
if let acceptAlways = acceptAlwaysLabel {
let alert = UIAlertController(title: title, alert.addAction(UIAlertAction(title: acceptAlways, style: .default, handler: { _ in
message: message, onAccept(.AcceptAlways)
preferredStyle: .alert) self.showProgressAlert(on: vc, onCancel: onCancel)
alert.addAction(UIAlertAction(title: rejectLabel, style: .cancel, handler: { _ in
onAccept(.Decline)
})) }))
alert.addAction(UIAlertAction(title: acceptLabel, style: .default, handler: { _ in
onAccept(.Accept)
self.showProgressAlert(on: vc)
}))
if let acceptAlways = acceptAlwaysLabel {
alert.addAction(UIAlertAction(title: acceptAlways, style: .default, handler: { _ in
onAccept(.AcceptAlways)
self.showProgressAlert(on: vc)
}))
}
vc.present(alert, animated: true)
} }
vc.present(alert, animated: true)
} }
func showProgressAlert(onCancel: @escaping () -> Void) {
guard let vc = topMostViewController() else { return }
self.showProgressAlert(on: vc, onCancel: onCancel)
}
// MARK: - Progress Alert // MARK: - Progress Alert
private func showProgressAlert(on vc: UIViewController) { private func showProgressAlert(on vc: UIViewController, onCancel: @escaping () -> Void) {
let alert = UIAlertController(title: "Receiving".localized(), let alert = UIAlertController(title: "Receiving".localized(),
message: "\n\n", message: "\n\n",
preferredStyle: .alert) preferredStyle: .alert)
@@ -67,7 +67,8 @@ final class ProgressAlert {
]) ])
let cancel = UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { _ in let cancel = UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { _ in
print("Cancel during progress") log("Cancel during progress")
onCancel()
}) })
alert.addAction(cancel) alert.addAction(cancel)
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "الوصول إلى الشبكة المحلية مطلوب لاكتشاف الأجهزة ونقل الملفات عبر شبكة Wi-Fi الخاصة بك.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "Der Zugriff auf das lokale Netzwerk ist erforderlich, um Geräte zu erkennen und Dateien über WLAN zu übertragen.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "Se necesita acceso a la red local para detectar dispositivos y transferir archivos a través de su red Wi-Fi.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "L’accès au réseau local est nécessaire pour détecter les appareils et transférer des fichiers via votre réseau Wi-Fi.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "Helyi hálózati hozzáférés szükséges az eszközök felismeréséhez és a fájlok átviteléhez a Wi-Fi hálózatán keresztül.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "È necessario l’accesso alla rete locale per rilevare i dispositivi e trasferire file tramite la tua rete Wi-Fi.";
-10
View File
@@ -1,10 +0,0 @@
/*
InfoPlist.strings
AirGuard
Created by Leon Böttger on 23.08.22.
*/
NSLocalNetworkUsageDescription = "ローカルネットワークへのアクセスは、デバイスを検出し、Wi-Fi ネットワーク経由でファイルを転送するために必要です。";
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more