mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
WIP: Add abiity to receive using iOS app
This commit is contained in:
1 parent
608ab41c0b
commit
6ec3902788
13 files changed
+473
-214
No files matched your search
@@ -7,6 +7,7 @@
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
import RNDeviceName
|
||||
#endif
|
||||
|
||||
import Foundation
|
||||
@@ -31,12 +32,19 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
private var qrCodeAdvertisingToken: Data?
|
||||
private var qrCodeNameEncryptionKey: SymmetricKey?
|
||||
|
||||
public let deviceInfo: EndpointInfo
|
||||
public let endpointID: [UInt8] = generateEndpointID()
|
||||
public var mainAppDelegate: (any InboundAppDelegate)?
|
||||
public static let shared = NearbyConnectionManager()
|
||||
|
||||
|
||||
override init() {
|
||||
#if os(macOS)
|
||||
self.deviceInfo = EndpointInfo(name: Host.current().localizedName ?? "Mac", deviceType: .computer)
|
||||
#else
|
||||
self.deviceInfo = EndpointInfo(name: UIDevice.current.marketingName, deviceType: .phone)
|
||||
#endif
|
||||
|
||||
tcpListener = try! NWListener(using: NWParameters(tls: .none))
|
||||
super.init()
|
||||
}
|
||||
@@ -104,7 +112,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
let service = NetService(domain: "", type: serviceType, name: name, port: port)
|
||||
service.delegate = self
|
||||
service.setTXTRecord(NetService.data(fromTXTRecord: [
|
||||
"n": getEndpointInfo().serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
"n": deviceInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
]))
|
||||
service.publish()
|
||||
return service
|
||||
@@ -112,17 +120,6 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
}
|
||||
|
||||
|
||||
func getEndpointInfo() -> EndpointInfo {
|
||||
#if os(macOS)
|
||||
let endpointInfo = EndpointInfo(name: Host.current().localizedName ?? "Mac", deviceType: .computer)
|
||||
#else
|
||||
let endpointInfo = EndpointInfo(name: UIDevice.current.name, deviceType: .phone)
|
||||
#endif
|
||||
|
||||
return endpointInfo
|
||||
}
|
||||
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
|
||||
mainAppDelegate?.obtainUserConsent(for: transfer, from: device)
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class OutboundNearbyConnection: NearbyConnection {
|
||||
|
||||
private func sendConnectionRequest() throws {
|
||||
|
||||
let endpointInfo = NearbyConnectionManager.shared.getEndpointInfo()
|
||||
let endpointInfo = NearbyConnectionManager.shared.deviceInfo
|
||||
|
||||
var frame = Location_Nearby_Connections_OfflineFrame()
|
||||
frame.version = .v1
|
||||
|
||||
@@ -129,7 +129,7 @@ public struct FileMetadata {
|
||||
}
|
||||
|
||||
|
||||
struct EndpointInfo {
|
||||
public struct EndpointInfo {
|
||||
var name: String?
|
||||
let deviceType: RemoteDeviceInfo.DeviceType
|
||||
let qrCodeData: Data?
|
||||
|
||||
@@ -105,8 +105,13 @@ public enum UserDefaultsKeys: String, CaseIterable {
|
||||
|
||||
public func isPlusVersion() -> Bool {
|
||||
|
||||
#if os(macOS)
|
||||
// Enable full functionality if app distributed directly
|
||||
return DistributionDetector.isDirectDistributionEnabled || UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue)
|
||||
#else
|
||||
// iOS
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -105,10 +105,12 @@
|
||||
EB979F942E50CA23008B8C5C /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
|
||||
EB979F952E50CA23008B8C5C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8E2E50CA23008B8C5C /* ContentView.swift */; };
|
||||
EB979F962E50CA23008B8C5C /* FilePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8F2E50CA23008B8C5C /* FilePicker.swift */; };
|
||||
EB979F972E50CA23008B8C5C /* ShareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F902E50CA23008B8C5C /* ShareViewModel.swift */; };
|
||||
EB979F972E50CA23008B8C5C /* SendModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F902E50CA23008B8C5C /* SendModel.swift */; };
|
||||
EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */; };
|
||||
EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F992E50CAA6008B8C5C /* PieProgressView.swift */; };
|
||||
EB97A00F2E511124008B8C5C /* 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 */; };
|
||||
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
|
||||
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
|
||||
EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
|
||||
@@ -239,7 +241,7 @@
|
||||
EB979F8D2E50CA23008B8C5C /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
EB979F8E2E50CA23008B8C5C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
EB979F8F2E50CA23008B8C5C /* FilePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePicker.swift; sourceTree = "<group>"; };
|
||||
EB979F902E50CA23008B8C5C /* ShareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewModel.swift; sourceTree = "<group>"; };
|
||||
EB979F902E50CA23008B8C5C /* SendModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendModel.swift; sourceTree = "<group>"; };
|
||||
EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickDropiOS.entitlements; sourceTree = "<group>"; };
|
||||
EB979F922E50CA23008B8C5C /* QuickDropiOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDropiOSApp.swift; sourceTree = "<group>"; };
|
||||
EB979F992E50CAA6008B8C5C /* PieProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PieProgressView.swift; sourceTree = "<group>"; };
|
||||
@@ -287,6 +289,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EB97A0132E51D6B2008B8C5C /* RNDeviceName in Frameworks */,
|
||||
EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */,
|
||||
EB886E582E4E66420052B7D9 /* LUI in Frameworks */,
|
||||
EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */,
|
||||
@@ -438,7 +441,7 @@
|
||||
EB979F8D2E50CA23008B8C5C /* Configuration.swift */,
|
||||
EB979F8E2E50CA23008B8C5C /* ContentView.swift */,
|
||||
EB979F8F2E50CA23008B8C5C /* FilePicker.swift */,
|
||||
EB979F902E50CA23008B8C5C /* ShareViewModel.swift */,
|
||||
EB979F902E50CA23008B8C5C /* SendModel.swift */,
|
||||
EB97A00E2E511120008B8C5C /* ReceiveModel.swift */,
|
||||
EB979F992E50CAA6008B8C5C /* PieProgressView.swift */,
|
||||
EB979F912E50CA23008B8C5C /* QuickDropiOS.entitlements */,
|
||||
@@ -578,6 +581,7 @@
|
||||
EB5C37EF2E377C3F0052EFD7 /* SwiftECC */,
|
||||
EB9925882E45F13500357055 /* QRCode */,
|
||||
EB886E572E4E66420052B7D9 /* LUI */,
|
||||
EB97A0122E51D6B2008B8C5C /* RNDeviceName */,
|
||||
);
|
||||
productName = QuickDropiOS;
|
||||
productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */;
|
||||
@@ -636,6 +640,7 @@
|
||||
EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */,
|
||||
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */,
|
||||
EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */,
|
||||
EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */,
|
||||
);
|
||||
productRefGroup = 69DA9A0F29E0BF5100A442DA /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -747,6 +752,7 @@
|
||||
EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
|
||||
EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */,
|
||||
EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */,
|
||||
EB97A0102E51CD13008B8C5C /* ReceiveModel.swift in Sources */,
|
||||
EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
|
||||
EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
|
||||
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */,
|
||||
@@ -803,7 +809,7 @@
|
||||
EB979F952E50CA23008B8C5C /* ContentView.swift in Sources */,
|
||||
EB979F962E50CA23008B8C5C /* FilePicker.swift in Sources */,
|
||||
EB979F9A2E50CAA6008B8C5C /* PieProgressView.swift in Sources */,
|
||||
EB979F972E50CA23008B8C5C /* ShareViewModel.swift in Sources */,
|
||||
EB979F972E50CA23008B8C5C /* SendModel.swift in Sources */,
|
||||
EB979F982E50CA23008B8C5C /* QuickDropiOSApp.swift in Sources */,
|
||||
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
|
||||
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */,
|
||||
@@ -1264,6 +1270,14 @@
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/souzainf3/RNDeviceName";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.1.0;
|
||||
};
|
||||
};
|
||||
EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/dagronf/qrcode.git";
|
||||
@@ -1315,6 +1329,11 @@
|
||||
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
|
||||
productName = LUI;
|
||||
};
|
||||
EB97A0122E51D6B2008B8C5C /* RNDeviceName */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = EB97A0112E51D6B2008B8C5C /* XCRemoteSwiftPackageReference "RNDeviceName" */;
|
||||
productName = RNDeviceName;
|
||||
};
|
||||
EB9925842E45EF0900357055 /* QRCode */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = EB9925832E45EF0900357055 /* XCRemoteSwiftPackageReference "qrcode" */;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "053324b22e9d13d48c4bf9290d746eb2c1a2e80e5900f9dde545bb150c5eff6a",
|
||||
"originHash" : "6596056df2297c97fbb8a90f8b23b4feec22b8d0fbdb59f154e1c543f10c631c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "applewatchscreensize",
|
||||
@@ -43,7 +43,7 @@
|
||||
"location" : "https://github.com/leonboe1/LUI.git",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "49e61e6ff710a1548e352d4179fceec785849b1b"
|
||||
"revision" : "b4a9766db0f328c8251b1fa8bed2472c783d7128"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55,6 +55,15 @@
|
||||
"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",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
+17
-139
@@ -14,7 +14,7 @@ import SwiftUI
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, NSWindowDelegate, InboundAppDelegate {
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, NSWindowDelegate {
|
||||
|
||||
private var statusItem: NSStatusItem?
|
||||
|
||||
@@ -26,6 +26,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
private var errorAlertHandler = ErrorAlertHandler.shared
|
||||
|
||||
private var iapManager: IAPManager?
|
||||
private var receiveModel: ReceiveModel?
|
||||
|
||||
var showsFirewallAlert = false
|
||||
var visibleItem: NSMenuItem? = nil
|
||||
@@ -64,9 +65,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
statusItem?.menu = menu
|
||||
statusItem?.behavior = .removalAllowed
|
||||
|
||||
NearbyConnectionManager.shared.mainAppDelegate = self
|
||||
NearbyConnectionManager.shared.becomeVisible()
|
||||
|
||||
iapManager = IAPManager.sharedInstance
|
||||
iapManager?.startObserving()
|
||||
|
||||
@@ -111,6 +109,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
|
||||
let queue2 = DispatchQueue(label: "NetworkConnectionMonitor")
|
||||
hasConnectionMonitor.start(queue: queue2)
|
||||
|
||||
receiveModel = ReceiveModel(controlPlusScreen: { shouldOpen in
|
||||
if shouldOpen {
|
||||
self.openPlusScreen()
|
||||
}
|
||||
else {
|
||||
if let window = self.plusWindow {
|
||||
log("[AppDelegate] Closing plus screen because of error")
|
||||
window.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -231,8 +241,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
if let window = plusWindow, window.isVisible {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
log("Plus screen already open, bringing to front")
|
||||
return
|
||||
}
|
||||
|
||||
log("Opening Plus screen")
|
||||
|
||||
// Create the welcome screen SwiftUI view
|
||||
let plusView = PlusView(closeView: {
|
||||
@@ -306,141 +319,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Alerts and Notifications
|
||||
|
||||
|
||||
func showCopiedToClipboardAlert() {
|
||||
DispatchQueue.main.async {
|
||||
BezelNotification.show(messageText: "InsertedIntoClipboard".localized(), icon: .clipboard)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Transfer Handling
|
||||
|
||||
public func continueTransmission(accept: Bool, transferID: String, storeInTemp: Bool = false) {
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: accept, storeInTemp: storeInTemp)
|
||||
}
|
||||
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) {
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
AudioManager.playIncomingFileSound()
|
||||
|
||||
let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
|
||||
|
||||
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 acceptAutomatically {
|
||||
pressAcceptButton(transferID: transfer.id)
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
|
||||
if granted {
|
||||
log("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 alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
|
||||
alert.messageText = "QuickDrop - \(pinCodeMessage)"
|
||||
alert.informativeText = mainMessage
|
||||
alert.addButton(withTitle: "Accept".localized())
|
||||
alert.addButton(withTitle: "Decline".localized())
|
||||
|
||||
let result = alert.runModal()
|
||||
|
||||
if result == .alertFirstButtonReturn {
|
||||
pressAcceptButton(transferID: transferID)
|
||||
} else if result == .alertSecondButtonReturn {
|
||||
continueTransmission(accept: false, transferID: transferID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func pressAcceptButton(transferID: String) {
|
||||
if isFileTransferRestricted() {
|
||||
continueTransmission(accept: true, transferID: transferID, storeInTemp: true)
|
||||
log("Showing plus screen...")
|
||||
|
||||
openPlusScreen()
|
||||
} else {
|
||||
continueTransmission(accept: true, transferID: transferID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func connectionWasTerminated(from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: Error?) {
|
||||
|
||||
if let error = error {
|
||||
|
||||
if let plusWindow = plusWindow {
|
||||
log("Closing plus screen because of error")
|
||||
plusWindow.close()
|
||||
}
|
||||
|
||||
ErrorAlertHandler.shared.showErrorAlert(for: device.name ?? "Android", error: error)
|
||||
} else {
|
||||
|
||||
if wasPlainTextTransfer {
|
||||
showCopiedToClipboardAlert()
|
||||
}
|
||||
|
||||
let currentCount = incomingTransmissionCount()
|
||||
if currentCount == 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
SKStoreReviewController.requestReview()
|
||||
}
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(currentCount + 1, forKey: UserDefaultsKeys.transmissionCount.rawValue)
|
||||
log("Successful transmission. Current count: \(currentCount)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Helper Functions
|
||||
|
||||
private func performDeviceToDeviceCheck() {
|
||||
|
||||
+141
-34
@@ -2892,103 +2892,103 @@
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "يرغب %1$@ في إدراج \"%2$@\" في حافظة جهاز Mac الخاص بك."
|
||||
"value" : "%1$@ يريد إدراج \"%2$@\" في الحافظة الخاصة بك."
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ möchte \"%2$@\" in die Zwischenablage deines Macs einfügen."
|
||||
"value" : "%1$@ möchte \"%2$@\" in Deine Zwischenablage einfügen."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ wants to insert \"%2$@\" to your Mac's clipboard."
|
||||
"value" : "%1$@ wants to insert \"%2$@\" to your clipboard."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ quiere insertar \"%2$@\" en el portapapeles de tu Mac."
|
||||
"value" : "%1$@ quiere insertar \"%2$@\" en tu portapapeles."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ souhaite insérer \"%2$@\" dans le presse-papiers de votre Mac."
|
||||
"value" : "%1$@ souhaite insérer \"%2$@\" dans votre presse-papiers."
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ be szeretné illeszteni a(z) \"%2$@\" szöveget a Mac-ed vágólapjára."
|
||||
"value" : "%1$@ be szeretné illeszteni a(z) \"%2$@\" elemet a vágólapodra."
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ vuole inserire \"%2$@\" negli appunti del tuo Mac."
|
||||
"value" : "%1$@ vuole inserire \"%2$@\" negli appunti."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ が \"%2$@\" をあなたの Mac のクリップボードに挿入しようとしています。"
|
||||
"value" : "%1$@があなたのクリップボードに「%2$@」を挿入しようとしています。"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@이(가) \"%2$@\"을(를) Mac의 클립보드에 삽입하려고 합니다."
|
||||
"value" : "%1$@님이 클립보드에 \"%2$@\"을(를) 삽입하려고 합니다."
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ wil \"%2$@\" in het klembord van je Mac plaatsen."
|
||||
"value" : "%1$@ wil \"%2$@\" naar uw klembord kopiëren."
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ deseja inserir \"%2$@\" na área de transferência do seu Mac."
|
||||
"value" : "%1$@ deseja inserir \"%2$@\" na sua área de transferência."
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ pretende inserir \"%2$@\" na área de transferência do seu Mac."
|
||||
"value" : "%1$@ quer inserir \"%2$@\" na sua área de transferência."
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ dorește să introducă \"%2$@\" în clipboard-ul Mac-ului tău."
|
||||
"value" : "%1$@ dorește să insereze \"%2$@\" în clipboard-ul tău."
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ хочет вставить \"%2$@\" в буфер обмена вашего Mac."
|
||||
"value" : "%1$@ хочет вставить \"%2$@\" в ваш буфер обмена."
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ ต้องการแทรก \"%2$@\" ไปยังคลิปบอร์ดของ Mac ของคุณ"
|
||||
"value" : "%1$@ ต้องการเพิ่ม \"%2$@\" ไปยังคลิปบอร์ดของคุณ"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ хоче вставити \"%2$@\" до буфера обміну вашого Mac."
|
||||
"value" : "%1$@ хоче вставити \"%2$@\" у ваш буфер обміну."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ 想要将 \"%2$@\" 插入到你的 Mac 剪贴板。"
|
||||
"value" : "%1$@ 想将“%2$@”插入到你的剪贴板。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2999,103 +2999,103 @@
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "يرغب %1$@ في فتح \"%2$@\" على جهاز Mac الخاص بك."
|
||||
"value" : "%1$@ يريد فتح \"%2$@\" على هذا الجهاز."
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ möchte \"%2$@\" auf deinem Mac öffnen."
|
||||
"value" : "%1$@ möchte \"%2$@\" auf diesem Gerät öffnen."
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ wants to open \"%2$@\" on your Mac."
|
||||
"value" : "%1$@ wants to open \"%2$@\" on this device."
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ quiere abrir \"%2$@\" en tu Mac."
|
||||
"value" : "%1$@ quiere abrir \"%2$@\" en este dispositivo."
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ souhaite ouvrir \"%2$@\" sur votre Mac."
|
||||
"value" : "%1$@ souhaite ouvrir \"%2$@\" sur cet appareil."
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ meg szeretné nyitni a(z) \"%2$@\" hivatkozást a Mac-eden."
|
||||
"value" : "%1$@ meg akarja nyitni a(z) \"%2$@\" elemet ezen az eszközön."
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ vuole aprire \"%2$@\" sul tuo Mac."
|
||||
"value" : "%1$@ vuole aprire \"%2$@\" su questo dispositivo."
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ があなたの Mac で \"%2$@\" を開こうとしています。"
|
||||
"value" : "%1$@がこのデバイスで「%2$@」を開こうとしています。"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@이(가) Mac에서 \"%2$@\"을(를) 열려고 합니다."
|
||||
"value" : "%1$@님이 이 장치에서 \"%2$@\"을(를) 열려고 합니다."
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ wil \"%2$@\" openen op je Mac."
|
||||
"value" : "%1$@ wil \"%2$@\" openen op dit apparaat."
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ deseja abrir \"%2$@\" no seu Mac."
|
||||
"value" : "%1$@ deseja abrir \"%2$@\" neste dispositivo."
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ pretende abrir \"%2$@\" no seu Mac."
|
||||
"value" : "%1$@ quer abrir \"%2$@\" neste dispositivo."
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ dorește să deschidă \"%2$@\" pe Mac-ul tău."
|
||||
"value" : "%1$@ dorește să deschidă \"%2$@\" pe acest dispozitiv."
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ хочет открыть \"%2$@\" на вашем Mac."
|
||||
"value" : "%1$@ хочет открыть \"%2$@\" на этом устройстве."
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ ต้องการเปิด \"%2$@\" บน Mac ของคุณ"
|
||||
"value" : "%1$@ ต้องการเปิด \"%2$@\" บนอุปกรณ์นี้"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ хоче відкрити \"%2$@\" на вашому Mac."
|
||||
"value" : "%1$@ хоче відкрити \"%2$@\" на цьому пристрої."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ 想要在你的 Mac 上打开 \"%2$@\"。"
|
||||
"value" : "%1$@ 想在此设备上打开“%2$@”。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9622,6 +9622,113 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unknown" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "غير معروف"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unbekannt"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Unknown"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Desconocido"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inconnu"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ismeretlen"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sconosciuto"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "不明"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "알 수 없음"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Onbekend"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Desconhecido"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Desconhecido"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Necunoscut"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Неизвестно"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ไม่ทราบ"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Невідомо"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "未知"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UnsupportedType" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
|
||||
@@ -87,13 +87,21 @@ class ErrorAlertHandler {
|
||||
log("Unsuccessful transmission. Already successful transmissions: \(incomingTransmissionCount())")
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
let primaryButtonTitle = "InformDeveloper".localized()
|
||||
let primaryButtonAction: () -> Void = {
|
||||
log("Sending logging string")
|
||||
sendLoggingString()
|
||||
}
|
||||
let secondaryButtonTitle = "CloseAlert".localized()
|
||||
|
||||
self.isAlertShown = true
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .critical
|
||||
alert.messageText = title
|
||||
alert.informativeText = description
|
||||
alert.addButton(withTitle: "InformDeveloper".localized())
|
||||
alert.addButton(withTitle: "CloseAlert".localized())
|
||||
alert.addButton(withTitle: primaryButtonTitle)
|
||||
alert.addButton(withTitle: secondaryButtonTitle)
|
||||
|
||||
let result = alert.runModal()
|
||||
self.isAlertShown = false
|
||||
|
||||
@@ -10,11 +10,12 @@ import LUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@StateObject var model = ShareViewModel()
|
||||
@StateObject var sendModel = SendModel()
|
||||
@StateObject var receiveModel = ReceiveModel()
|
||||
|
||||
var body: some View {
|
||||
AppRootView(isPlus: .constant(true), phoneView: {
|
||||
DeviceListView(model: model)
|
||||
DeviceListView(sendModel: sendModel, receiveModel: receiveModel)
|
||||
}, settingsView: {
|
||||
EmptyView()
|
||||
})
|
||||
@@ -23,7 +24,8 @@ struct ContentView: View {
|
||||
|
||||
struct DeviceListView: View {
|
||||
|
||||
@ObservedObject var model: ShareViewModel
|
||||
@ObservedObject var sendModel: SendModel
|
||||
@ObservedObject var receiveModel: ReceiveModel
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -33,7 +35,7 @@ struct DeviceListView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
FooterView(text: "YouAreVisibleAs")
|
||||
|
||||
Text(NearbyConnectionManager.shared.getEndpointInfo().name ?? "QuickDrop")
|
||||
Text(NearbyConnectionManager.shared.deviceInfo.name ?? "Unknown".localized())
|
||||
.fontWeight(.bold)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 6)
|
||||
@@ -45,7 +47,7 @@ struct DeviceListView: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
|
||||
if model.foundDevices.isEmpty {
|
||||
if sendModel.foundDevices.isEmpty {
|
||||
|
||||
CustomSection(header: "NoDevicesFound") {
|
||||
|
||||
@@ -61,9 +63,9 @@ struct DeviceListView: View {
|
||||
} else {
|
||||
|
||||
CustomSection(header: "AvailableDevices") {
|
||||
ForEach(model.foundDevices) { device in
|
||||
ForEach(sendModel.foundDevices) { device in
|
||||
|
||||
let isSelected = model.selectedDevice == device
|
||||
let isSelected = sendModel.selectedDevice == device
|
||||
|
||||
SendPickerButton {
|
||||
HStack(spacing: 12) {
|
||||
@@ -92,14 +94,14 @@ struct DeviceListView: View {
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(Color.primary)
|
||||
|
||||
Text(isSelected ? model.progressState ?? "..." : "Available".localized())
|
||||
Text(isSelected ? sendModel.progressState ?? "..." : "Available".localized())
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Color.primary.opacity(0.6))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isSelected, let progress = model.progressValue {
|
||||
if isSelected, let progress = sendModel.progressValue {
|
||||
PieProgressView(progress: progress, size: 20)
|
||||
}
|
||||
else {
|
||||
@@ -109,11 +111,11 @@ struct DeviceListView: View {
|
||||
}
|
||||
.padding(.vertical, 13)
|
||||
} onResult: { urls, text in
|
||||
model.urls = urls ?? []
|
||||
model.textToSend = text
|
||||
model.selectDevice(device: device)
|
||||
sendModel.urls = urls ?? []
|
||||
sendModel.textToSend = text
|
||||
sendModel.selectDevice(device: device)
|
||||
}
|
||||
.animation(.easeInOut, value: model.progressValue)
|
||||
.animation(.easeInOut, value: sendModel.progressValue)
|
||||
.animation(.easeInOut, value: isSelected)
|
||||
}
|
||||
}
|
||||
@@ -135,14 +137,14 @@ struct DeviceListView: View {
|
||||
|
||||
extension DeviceListView {
|
||||
static var preview: DeviceListView {
|
||||
let model = ShareViewModel()
|
||||
let model = SendModel()
|
||||
model.foundDevices = [
|
||||
RemoteDeviceInfo(name: "MacBook Pro", type: .computer, id: "macbook-pro-id"),
|
||||
RemoteDeviceInfo(name: "iPhone 14", type: .phone, id: "iphone-14-id"),
|
||||
RemoteDeviceInfo(name: "Samsung Galaxy S21", type: .phone, id: "samsung-galaxy-s21-id")
|
||||
]
|
||||
|
||||
return DeviceListView(model: model)
|
||||
return DeviceListView(sendModel: model, receiveModel: ReceiveModel())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,164 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
import UserNotifications
|
||||
|
||||
#if os(macOS)
|
||||
import BezelNotification
|
||||
#else
|
||||
import LUI
|
||||
#endif
|
||||
|
||||
class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
|
||||
let controlPlusScreen: (Bool) -> Void
|
||||
|
||||
init() {
|
||||
|
||||
init(controlPlusScreen: @escaping (Bool) -> Void = { _ in }) {
|
||||
self.controlPlusScreen = controlPlusScreen
|
||||
NearbyConnectionManager.shared.mainAppDelegate = self
|
||||
NearbyConnectionManager.shared.becomeVisible()
|
||||
}
|
||||
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) {
|
||||
|
||||
#if os(macOS)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
AudioManager.playIncomingFileSound()
|
||||
#endif
|
||||
|
||||
let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
|
||||
|
||||
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 acceptAutomatically {
|
||||
pressAcceptButton(transferID: transfer.id)
|
||||
|
||||
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 = { self.pressAcceptButton(transferID: transferID) }
|
||||
let secondaryButtonTitle = "Decline".localized()
|
||||
let secondaryButtonAction = { self.continueTransmission(accept: false, transferID: transferID) }
|
||||
|
||||
#if os(macOS)
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
|
||||
alert.messageText = title
|
||||
alert.informativeText = mainMessage
|
||||
alert.addButton(withTitle: primaryButtonTitle)
|
||||
alert.addButton(withTitle: secondaryButtonTitle)
|
||||
|
||||
let result = alert.runModal()
|
||||
|
||||
if result == .alertFirstButtonReturn {
|
||||
primaryButtonAction()
|
||||
} else if result == .alertSecondButtonReturn {
|
||||
secondaryButtonAction()
|
||||
}
|
||||
#else
|
||||
// iOS
|
||||
showAlert(title: title, message: mainMessage, primaryButton: LUIAlertButton(title: secondaryButtonTitle, action: secondaryButtonAction), secondaryButton: LUIAlertButton(title: primaryButtonTitle, action: primaryButtonAction))
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func connectionWasTerminated(from device: RemoteDeviceInfo, wasPlainTextTransfer: Bool, error: (any Error)?) {
|
||||
|
||||
if let error = error {
|
||||
controlPlusScreen(false)
|
||||
ErrorAlertHandler.shared.showErrorAlert(for: device.name ?? "Android", error: error)
|
||||
} else {
|
||||
|
||||
if wasPlainTextTransfer {
|
||||
showCopiedToClipboardAlert()
|
||||
}
|
||||
|
||||
let currentCount = incomingTransmissionCount()
|
||||
if currentCount == 0 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
SKStoreReviewController.requestReview()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
#else
|
||||
// iOS
|
||||
doubleVibration()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private func pressAcceptButton(transferID: String) {
|
||||
if isFileTransferRestricted() {
|
||||
continueTransmission(accept: true, transferID: transferID, storeInTemp: true)
|
||||
log("[ReceiveModel] Showing plus screen...")
|
||||
controlPlusScreen(true)
|
||||
} else {
|
||||
continueTransmission(accept: true, transferID: transferID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func continueTransmission(accept: Bool, transferID: String, storeInTemp: Bool = false) {
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: accept, storeInTemp: storeInTemp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Model.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 28.07.25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class SendModel: ObservableObject, OutboundAppDelegate {
|
||||
|
||||
@Published var foundDevices: [RemoteDeviceInfo] = []
|
||||
@Published var selectedDevice: RemoteDeviceInfo?
|
||||
|
||||
@Published var progressState: String? = nil
|
||||
@Published var progressValue: Double? = nil
|
||||
|
||||
var urls: [URL] = []
|
||||
var textToSend: String? = nil
|
||||
|
||||
private var connectionEstablished = false
|
||||
private var timeoutDispatchWorkItem: DispatchWorkItem? = nil
|
||||
|
||||
init() {
|
||||
NearbyConnectionManager.shared.startDeviceDiscovery()
|
||||
NearbyConnectionManager.shared.becomeVisible()
|
||||
NearbyConnectionManager.shared.addShareExtensionDelegate(self)
|
||||
}
|
||||
|
||||
func addDevice(device: RemoteDeviceInfo) {
|
||||
withAnimation {
|
||||
foundDevices.append(device)
|
||||
}
|
||||
}
|
||||
|
||||
func removeDevice(id: String) {
|
||||
withAnimation {
|
||||
foundDevices.removeAll { $0.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
func connectionWasEstablished(pinCode: String) {
|
||||
connectionEstablished = true
|
||||
progressState = String(format: "PinCode".localized(), arguments: [pinCode])
|
||||
progressValue = 0
|
||||
}
|
||||
|
||||
func connectionFailed(with error: any Error) {
|
||||
|
||||
ErrorAlertHandler.shared.showErrorAlert(for: selectedDevice?.name ?? "?", error: error)
|
||||
|
||||
progressValue = nil
|
||||
selectedDevice = nil
|
||||
}
|
||||
|
||||
func transferAccepted() {
|
||||
progressState = "Sending".localized()
|
||||
}
|
||||
|
||||
func transferProgress(progress: Double) {
|
||||
progressValue = progress
|
||||
}
|
||||
|
||||
func transferFinished() {
|
||||
progressValue = 0
|
||||
selectedDevice = nil
|
||||
progressState = "TransferFinished".localized()
|
||||
}
|
||||
|
||||
func startTransferWithQrCode(device: RemoteDeviceInfo) {
|
||||
selectDevice(device: device)
|
||||
}
|
||||
|
||||
func selectDevice(device: RemoteDeviceInfo) {
|
||||
|
||||
// already selected, cancel transfer
|
||||
if device == selectedDevice {
|
||||
progressValue = 0
|
||||
progressState = nil
|
||||
selectedDevice = nil
|
||||
NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!)
|
||||
}
|
||||
else {
|
||||
progressValue = 0
|
||||
progressState = "Connecting".localized()
|
||||
selectedDevice = device
|
||||
NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls, textToSend: textToSend)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class ShareViewModel: ObservableObject, OutboundAppDelegate {
|
||||
class SendModel: ObservableObject, OutboundAppDelegate {
|
||||
|
||||
@Published var foundDevices: [RemoteDeviceInfo] = []
|
||||
@Published var selectedDevice: RemoteDeviceInfo?
|
||||
|
||||
Reference in new issue
Block a user