diff --git a/NearDrop/AppDelegate.swift b/NearDrop/AppDelegate.swift index f652740..841d340 100644 --- a/NearDrop/AppDelegate.swift +++ b/NearDrop/AppDelegate.swift @@ -5,89 +5,83 @@ // Created by Grishka on 08.04.2023. // -import Network -import Cocoa -import UserNotifications -import NearbyShare -import SwiftUI -import StoreKit import AudioToolbox import BezelNotification +import Cocoa +import NearbyShare import Network +import StoreKit +import SwiftUI +import UserNotifications @main class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, MainAppDelegate { - private var statusItem: NSStatusItem? - private var activeIncomingTransfers: [String : TransferInfo] = [:] - + private var activeIncomingTransfers: [String: TransferInfo] = [:] + var welcomeWindow: NSWindow? var plusWindow: NSWindow? var firewallAlertWindow: NSWindow? var apIsolationAlertWindow: NSWindow? var networkFilterAlertWindow: NSWindow? private var iapManager: IAPManager? - + var showsFirewallAlert = false - - func applicationDidFinishLaunching(_ aNotification: Notification) { - + + func applicationDidFinishLaunching(_: Notification) { let menu = NSMenu() - + menu.addItem(withTitle: NSLocalizedString("VisibleToEveryone", value: "Visible to everyone", comment: ""), action: nil, keyEquivalent: "") menu.addItem(withTitle: String(format: NSLocalizedString("DeviceName", value: "Device name: %@", comment: ""), arguments: [Host.current().localizedName ?? "Mac"]), action: nil, keyEquivalent: "") - + menu.addItem(NSMenuItem.separator()) - + let sendClipboardItem = NSMenuItem(title: "SendClipboard".localized(), action: #selector(sendClipboard), keyEquivalent: "") menu.addItem(sendClipboardItem) - + menu.addItem(NSMenuItem.separator()) - + let userManualItem = NSMenuItem(title: NSLocalizedString("UserManual", value: "User Manual", comment: ""), action: #selector(openWelcomeScreen), keyEquivalent: "") menu.addItem(userManualItem) - + menu.addItem(withTitle: NSLocalizedString("Quit", value: "Quit QuickDrop", comment: ""), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "") - + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem?.button?.image = NSImage(named: "MenuBarIcon") statusItem?.menu = menu statusItem?.behavior = .removalAllowed - - NearbyConnectionManager.shared.mainAppDelegate=self + + NearbyConnectionManager.shared.mainAppDelegate = self NearbyConnectionManager.shared.becomeVisible() - + iapManager = IAPManager.sharedInstance iapManager?.startObserving() - + // app did not lauch before - if !UserDefaults.standard.bool(forKey: UserDefaultsKeys.appLaunchedBefore.rawValue){ - + if !UserDefaults.standard.bool(forKey: UserDefaultsKeys.appLaunchedBefore.rawValue) { log("Opening Welcome Screen") // open welcome screen openWelcomeScreen() UserDefaults.standard.set(true, forKey: UserDefaultsKeys.appLaunchedBefore.rawValue) - + // user installed the app after the IAP was implemented, set the user as eligible for IAP UserDefaults.standard.set(true, forKey: UserDefaultsKeys.isEligibleForIap.rawValue) - } - else { + } else { // app launched before - + // user installed the app before the IAP was implemented, grant the plus version if !UserDefaults.standard.bool(forKey: UserDefaultsKeys.isEligibleForIap.rawValue) { log("Granting QuickDrop+ for old user") UserDefaults.standard.set(true, forKey: UserDefaultsKeys.plusVersion.rawValue) - } - else { + } else { if !isPlusVersion() { log("New user - QuickDrop+ not available") } } - + BezelNotification.show(messageText: "ReadyToReceive".localized(), icon: .receiveIcon) } - + UNUserNotificationCenter.current().delegate = self // let monitor = NWPathMonitor() @@ -107,15 +101,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele // } else { // log("Network unavailable") // } -// +// // monitor.cancel() // } // // let queue = DispatchQueue(label: "NetworkMonitor") // monitor.start(queue: queue) } - - + func performDeviceToDeviceCheck() { let scanner = DeviceToDeviceHeuristicScanner() scanner.scan { allowed in @@ -127,21 +120,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } } } - - + @objc func sendClipboard() { if let fileURL = ClipboardManager.saveClipboardToTempFile() { - let sharingService = NSSharingService(named: NSSharingService.Name("com.leonboettger.neardrop.ShareExtension")) sharingService?.perform(withItems: [fileURL]) } } - - + @objc func openWelcomeScreen() { // Create the welcome screen SwiftUI view let welcomeView = WelcomeScreen(openPlusScreen: openPlusScreen, checkForNetworkIssues: performDeviceToDeviceCheck) - + // Create an NSWindow to host the SwiftUI view welcomeWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 400, height: 280), @@ -149,31 +139,29 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele backing: .buffered, defer: false ) - + welcomeWindow?.styleMask.insert(.fullSizeContentView) welcomeWindow?.titleVisibility = .hidden welcomeWindow?.titlebarAppearsTransparent = true - + welcomeWindow?.center() welcomeWindow?.isReleasedWhenClosed = false welcomeWindow?.setFrameAutosaveName("WelcomeScreen") welcomeWindow?.contentView = NSHostingView(rootView: welcomeView) - + // Ensure the window is always on top NSApp.activate(ignoringOtherApps: true) // Brings the whole app to the front welcomeWindow?.makeKeyAndOrderFront(nil) welcomeWindow?.level = .normal } - - + @objc func openPlusScreen() { - // Create the welcome screen SwiftUI view let plusView = GetPlusView(closeView: { log("Closing plus screen") self.plusWindow?.close() }) - + // Create an NSWindow to host the SwiftUI view plusWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: plusViewWidth, height: plusViewHeight), @@ -181,35 +169,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele backing: .buffered, defer: false ) - + plusWindow?.styleMask.insert(.fullSizeContentView) plusWindow?.titleVisibility = .hidden plusWindow?.titlebarAppearsTransparent = true - + plusWindow?.center() plusWindow?.isReleasedWhenClosed = false plusWindow?.setFrameAutosaveName("PlusScreen") plusWindow?.contentView = NSHostingView(rootView: plusView) - + // Ensure the window is always on top NSApp.activate(ignoringOtherApps: true) // Brings the whole app to the front plusWindow?.makeKeyAndOrderFront(nil) plusWindow?.level = .normal } - + enum AlertType: String { case ApIsolation case NetworkFilter case Firewall } - - + func openAlert(type: AlertType) { - log("Opening Alert for \(type)") - + DispatchQueue.main.async { [self] in - // Create an NSWindow to host the SwiftUI view let alertWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: issueViewWidth, height: issueViewHeight), @@ -217,7 +202,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele backing: .buffered, defer: false ) - + switch type { case .ApIsolation: apIsolationAlertWindow = alertWindow @@ -229,75 +214,68 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele firewallAlertWindow = alertWindow alertWindow.contentView = NSHostingView(rootView: FirewallIssueView()) } - + alertWindow.title = "QuickDrop" alertWindow.center() alertWindow.isReleasedWhenClosed = false alertWindow.setFrameAutosaveName(type.rawValue) - + NSApp.activate(ignoringOtherApps: true) alertWindow.makeKeyAndOrderFront(nil) alertWindow.level = .normal } } - func transmissionCount() -> Int { return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue) } - - + // Action for "Recommended Apps" menu item @objc func openRecommendedApps() { if let url = URL(string: "https://apps.apple.com/de/developer/leon-boettger/id1537384790") { NSWorkspace.shared.open(url) } } - - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - + + func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { openWelcomeScreen() - statusItem?.isVisible=true + statusItem?.isVisible = true return true } - - - public func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler([.sound, .banner]) - } - - - func applicationWillTerminate(_ aNotification: Notification) { + + public func userNotificationCenter(_: UNUserNotificationCenter, + willPresent _: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) + { + completionHandler([.sound, .banner]) + } + + func applicationWillTerminate(_: Notification) { UNUserNotificationCenter.current().removeAllDeliveredNotifications() iapManager?.stopObserving() } - - - func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + + func applicationSupportsSecureRestorableState(_: NSApplication) -> Bool { return true } - - + public func continueTransmission(accept: Bool, transferID: String, storeInTemp: Bool = false) { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: accept, storeInTemp: storeInTemp) - + if !accept { activeIncomingTransfers.removeValue(forKey: transferID) } } - - + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) { - self.activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer) - + activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer) + AudioManager.playSound() - + 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 { @@ -305,45 +283,43 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } else { fileStr = String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count) } - + let mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [device.name, fileStr]) - let pinCodeMessage = String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode ?? "?"]) + let pinCodeMessage = String(format: NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode ?? "?"]) let transferID = transfer.id - - + if acceptAutomatically { pressAcceptButton(transferID: transfer.id) - - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, err in + + 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 { + } else { let alert = NSAlert() alert.alertStyle = .informational - + alert.messageText = "QuickDrop - \(pinCodeMessage)" alert.informativeText = mainMessage alert.addButton(withTitle: NSLocalizedString("Accept", comment: "")) alert.addButton(withTitle: NSLocalizedString("Decline", comment: "")) - + let result = alert.runModal() - + if result == .alertFirstButtonReturn { pressAcceptButton(transferID: transferID) } else if result == .alertSecondButtonReturn { @@ -351,38 +327,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } } } - - + private func pressAcceptButton(transferID: String) { if false || (!isPlusVersion() && transmissionCount() > 1) { - - self.continueTransmission(accept: true, transferID: transferID, storeInTemp: true) + continueTransmission(accept: true, transferID: transferID, storeInTemp: true) log("Showing plus screen...") - - self.openPlusScreen() - } - else { + + openPlusScreen() + } else { continueTransmission(accept: true, transferID: transferID) } } - - + func showFirewallAlert() { openAlert(type: .Firewall) } - - + func incomingTransfer(id: String, didFinishWith error: Error?) { - guard let transfer = self.activeIncomingTransfers[id] else { return } + guard let transfer = activeIncomingTransfers[id] else { return } if let error = error { - var description = "" - - if let ne = (error as? NearbyError){ + + if let ne = (error as? NearbyError) { switch ne { case .inputOutput: - description = "I/O Error"; - case .protocolError(_): + description = "I/O Error" + case .protocolError: description = NSLocalizedString("Error.Protocol", value: "Communication error", comment: "") + "(\(ne.localizedDescription))" case .packetFilterError: openAlert(type: .NetworkFilter) @@ -398,46 +368,44 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } else { description = error.localizedDescription } - + let alert = NSAlert() alert.alertStyle = .critical alert.messageText = String(format: NSLocalizedString("TransferError", value: "Failed to receive files from %@", comment: ""), arguments: [transfer.device.name]) alert.informativeText = description - + alert.addButton(withTitle: "InformDeveloper".localized()) alert.addButton(withTitle: "CloseAlert".localized()) - + if let plusWindow = plusWindow { log("Closing plus screen because of error") plusWindow.close() } - + log("Showing alert with message: \"\(alert.messageText)\" and description: \"\(alert.informativeText)\"") - + let result = alert.runModal() - + if result == .alertFirstButtonReturn { sendLoggingString() } - } - else { + } else { let currentCount = transmissionCount() - + if currentCount % 20 == 0 { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { SKStoreReviewController.requestReview() } } - + UserDefaults.standard.set(currentCount + 1, forKey: UserDefaultsKeys.transmissionCount.rawValue) } - - self.activeIncomingTransfers.removeValue(forKey: id) + + activeIncomingTransfers.removeValue(forKey: id) } } - struct TransferInfo { let device: RemoteDeviceInfo let transfer: TransferMetadata diff --git a/ShareExtension/DeviceListCell.swift b/ShareExtension/DeviceListCell.swift index 9fc5405..06d0296 100644 --- a/ShareExtension/DeviceListCell.swift +++ b/ShareExtension/DeviceListCell.swift @@ -7,20 +7,20 @@ import Cocoa -class DeviceListCell:NSCollectionViewItem { - public var clickHandler:(()->Void)? - +class DeviceListCell: NSCollectionViewItem { + public var clickHandler: (() -> Void)? + override func viewDidLoad() { super.viewDidLoad() - let btn:NSButton=view as! NSButton - btn.isEnabled=true - btn.setButtonType(.momentaryPushIn) - btn.action=#selector(onClick) - btn.target=self + let btn: NSButton = view as! NSButton + btn.isEnabled = true + btn.setButtonType(.momentaryPushIn) + btn.action = #selector(onClick) + btn.target = self + } + + @IBAction func onClick(_: Any?) { + guard let handler = clickHandler else { return } + handler() } - - @IBAction func onClick(_ sender:Any?){ - guard let handler=clickHandler else {return} - handler() - } } diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index b8caa8d..0d44148 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -5,68 +5,66 @@ // Created by Grishka on 12.09.2023. // -import Foundation import Cocoa -import SwiftUI +import Foundation import NearbyShare +import SwiftUI + +class ShareViewController: NSViewController, ShareExtensionDelegate { + private var urls: [URL] = [] + private var foundDevices: [RemoteDeviceInfo] = [] + private var chosenDevice: RemoteDeviceInfo? + private var lastError: Error? -class ShareViewController: NSViewController, ShareExtensionDelegate{ - - private var urls:[URL]=[] - private var foundDevices:[RemoteDeviceInfo]=[] - private var chosenDevice:RemoteDeviceInfo? - private var lastError:Error? - private var connectionEstablished = false private var timeoutDispatchWorkItem: DispatchWorkItem? = nil - - @IBOutlet var filesIcon:NSImageView? - @IBOutlet var filesLabel:NSTextField? - @IBOutlet var loadingOverlay:NSStackView? - @IBOutlet var largeProgress:NSProgressIndicator? - @IBOutlet var listView:NSCollectionView? - @IBOutlet var listViewWrapper:NSView? - @IBOutlet var contentWrap:NSView? - @IBOutlet var progressView:NSView? - @IBOutlet var progressDeviceIcon:NSImageView? - @IBOutlet var progressDeviceName:NSTextField? - @IBOutlet var progressProgressBar:NSProgressIndicator? - @IBOutlet var progressState:NSTextField? - @IBOutlet var progressDeviceIconWrap:NSView? - @IBOutlet var progressDeviceSecondaryIcon:NSImageView? + + @IBOutlet var filesIcon: NSImageView? + @IBOutlet var filesLabel: NSTextField? + @IBOutlet var loadingOverlay: NSStackView? + @IBOutlet var largeProgress: NSProgressIndicator? + @IBOutlet var listView: NSCollectionView? + @IBOutlet var listViewWrapper: NSView? + @IBOutlet var contentWrap: NSView? + @IBOutlet var progressView: NSView? + @IBOutlet var progressDeviceIcon: NSImageView? + @IBOutlet var progressDeviceName: NSTextField? + @IBOutlet var progressProgressBar: NSProgressIndicator? + @IBOutlet var progressState: NSTextField? + @IBOutlet var progressDeviceIconWrap: NSView? + @IBOutlet var progressDeviceSecondaryIcon: NSImageView? @IBOutlet var dontSeeDeviceButton: NSButton? - + private var qrCodeSheetView: NSPanel? = nil private var sheetAttachedWindow: NSWindow? = nil - + override var nibName: NSNib.Name? { return NSNib.Name("ShareViewController") } - + override func loadView() { super.loadView() - - let item = self.extensionContext!.inputItems[0] as! NSExtensionItem + + let item = extensionContext!.inputItems[0] as! NSExtensionItem if let attachments = item.attachments { - if let text = item.attributedContentText?.string, attachments.isEmpty, let tempUrl = ClipboardManager.saveTextToTempFile(text: text) { - self.urls.append(tempUrl) - + urls.append(tempUrl) + DispatchQueue.main.async { self.urlsReady() } - } - else { + } else { for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { - attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { data, err in + attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { data, _ in if let urlData = data as? Data, - let url = URL(dataRepresentation: urlData, relativeTo: nil, isAbsolute: false) { + let url = URL(dataRepresentation: urlData, relativeTo: nil, isAbsolute: false) + { self.urls.append(url) } else if let url = data as? NSURL { self.urls.append(url as URL) } - + if self.urls.count == attachments.count { DispatchQueue.main.async { self.urlsReady() @@ -76,336 +74,301 @@ class ShareViewController: NSViewController, ShareExtensionDelegate{ } } } - } - else { + } else { let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) - self.extensionContext!.cancelRequest(withError: cancelError) + extensionContext!.cancelRequest(withError: cancelError) return } - + contentWrap!.addSubview(listViewWrapper!) contentWrap!.addSubview(loadingOverlay!) contentWrap!.addSubview(progressView!) - progressView!.isHidden=true - - listViewWrapper!.translatesAutoresizingMaskIntoConstraints=false - loadingOverlay!.translatesAutoresizingMaskIntoConstraints=false - progressView!.translatesAutoresizingMaskIntoConstraints=false - + progressView!.isHidden = true + + listViewWrapper!.translatesAutoresizingMaskIntoConstraints = false + loadingOverlay!.translatesAutoresizingMaskIntoConstraints = false + progressView!.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ NSLayoutConstraint(item: listViewWrapper!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), NSLayoutConstraint(item: listViewWrapper!, attribute: .height, relatedBy: .equal, toItem: contentWrap, attribute: .height, multiplier: 1, constant: 0), - + NSLayoutConstraint(item: loadingOverlay!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), NSLayoutConstraint(item: loadingOverlay!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0), - + NSLayoutConstraint(item: progressView!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0), - NSLayoutConstraint(item: progressView!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0) + NSLayoutConstraint(item: progressView!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0), ]) - + largeProgress!.startAnimation(nil) - + let flowLayout = NSCollectionViewFlowLayout() - flowLayout.itemSize=NSSize(width: 75, height: 90) - flowLayout.sectionInset=NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) - flowLayout.minimumInteritemSpacing=10 - flowLayout.minimumLineSpacing=10 - listView!.collectionViewLayout=flowLayout - listView!.dataSource=self - - progressDeviceIconWrap!.wantsLayer=true - progressDeviceIconWrap!.layer!.masksToBounds=false + flowLayout.itemSize = NSSize(width: 75, height: 90) + flowLayout.sectionInset = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + flowLayout.minimumInteritemSpacing = 10 + flowLayout.minimumLineSpacing = 10 + listView!.collectionViewLayout = flowLayout + listView!.dataSource = self + + progressDeviceIconWrap!.wantsLayer = true + progressDeviceIconWrap!.layer!.masksToBounds = false } - - - override func viewDidLoad(){ + + override func viewDidLoad() { super.viewDidLoad() - + dicoverDevices() NearbyConnectionManager.shared.addShareExtensionDelegate(self) } - - + override func viewWillDisappear() { - log("ShareViewController: viewWillDisappear") - + timeoutDispatchWorkItem?.cancel() - - if chosenDevice==nil{ + + if chosenDevice == nil { NearbyConnectionManager.shared.stopDeviceDiscovery() } NearbyConnectionManager.shared.removeShareExtensionDelegate(self) } - - - @IBAction func cancel(_ sender: AnyObject?) { - - if let device=chosenDevice{ + + @IBAction func cancel(_: AnyObject?) { + if let device = chosenDevice { NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!) } let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) - self.extensionContext!.cancelRequest(withError: cancelError) + extensionContext!.cancelRequest(withError: cancelError) } - - - @IBAction func dontSeeDeviceButton(_ sender: AnyObject?) { + + @IBAction func dontSeeDeviceButton(_: AnyObject?) { openQrCodeView() } - - + private func openQrCodeView() { if qrCodeSheetView == nil { let contentView = QrCodeView { self.closeQrCodeView() } - + let hostingView = NSHostingView(rootView: contentView) - + let panel = NSPanel(contentRect: NSRect(x: 0, y: 0, width: qrCodeViewSize.width, height: qrCodeViewSize.height), styleMask: [.titled, .closable, .utilityWindow], backing: .buffered, defer: false) - + panel.contentView = hostingView - - self.qrCodeSheetView = panel - + + qrCodeSheetView = panel + if let mainWindow = NSApp.mainWindow { - - self.sheetAttachedWindow = mainWindow + sheetAttachedWindow = mainWindow mainWindow.beginSheet(panel) { _ in } } } } - - + private func closeQrCodeView() { - if let mainWindow = sheetAttachedWindow, let qrCodeView = self.qrCodeSheetView { + if let mainWindow = sheetAttachedWindow, let qrCodeView = qrCodeSheetView { mainWindow.endSheet(qrCodeView) - - self.qrCodeSheetView = nil - self.sheetAttachedWindow = nil + + qrCodeSheetView = nil + sheetAttachedWindow = nil } } - - + private func urlsReady() { for url in urls { if url.isFileURL { let isDirectory = UnsafeMutablePointer.allocate(capacity: 1) if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue { - do { let zipUrl = try createZipAtTmp(zipFilename: url.lastPathComponent, fromDirectory: url) - + let index = urls.firstIndex(of: url) urls[index!] = zipUrl - } - catch { + } catch { log("Error creating zip file: \(error)") - + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { log("Canceling share request because URL \(url) is a directory") - + let alert = NSAlert() alert.alertStyle = .critical - + alert.messageText = "TypeNotSupported".localized() alert.informativeText = "TypeNotSupportedDescription".localized() alert.addButton(withTitle: "TypeNotSupportedButton".localized()) - - alert.beginSheetModal(for: self.view.window!) { resp in + + alert.beginSheetModal(for: self.view.window!) { _ in let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) self.extensionContext!.cancelRequest(withError: cancelError) } } - + return } } } } - + if urls.count == 1 { if urls[0].isFileURL { filesLabel!.stringValue = urls[0].lastPathComponent filesIcon!.image = NSWorkspace.shared.icon(forFile: urls[0].path) - } - else if urls[0].scheme == "http" || urls[0].scheme == "https" { - + } else if urls[0].scheme == "http" || urls[0].scheme == "https" { filesLabel!.stringValue = urls[0].absoluteString filesIcon!.image = NSImage(named: NSImage.networkName) } - } - else { + } else { filesLabel!.stringValue = String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), urls.count) filesIcon!.image = NSImage(named: NSImage.multipleDocumentsName) } } - - + func addDevice(device: RemoteDeviceInfo) { if foundDevices.isEmpty { loadingOverlay?.animator().isHidden = true } foundDevices.append(device) - listView?.animator().insertItems(at: [[0, foundDevices.count-1]]) - + listView?.animator().insertItems(at: [[0, foundDevices.count - 1]]) + closeQrCodeView() } - - + func removeDevice(id: String) { if chosenDevice != nil { return } for i in foundDevices.indices { - if foundDevices[i].id==id { + if foundDevices[i].id == id { foundDevices.remove(at: i) listView?.animator().deleteItems(at: [[0, i]]) break } } if foundDevices.isEmpty { - loadingOverlay?.animator().isHidden=false + loadingOverlay?.animator().isHidden = false } } - - + func connectionWasEstablished(pinCode: String) { - connectionEstablished = true - - progressState?.stringValue=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [pinCode]) + + progressState?.stringValue = String(format: NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [pinCode]) progressProgressBar?.isIndeterminate = false progressProgressBar?.maxValue = 1000 progressProgressBar?.doubleValue = 0 } - - + func connectionFailed(with error: Error) { - progressProgressBar?.isIndeterminate = false progressProgressBar?.maxValue = 1000 progressProgressBar?.doubleValue = 0 lastError = error - - if let ne = (error as? NearbyError), case let .canceled(reason) = ne{ + + if let ne = (error as? NearbyError), case let .canceled(reason) = ne { switch reason { case .userRejected: - progressState?.stringValue=NSLocalizedString("TransferDeclined", value: "Declined", comment: "") + progressState?.stringValue = NSLocalizedString("TransferDeclined", value: "Declined", comment: "") case .userCanceled: - progressState?.stringValue=NSLocalizedString("TransferCanceled", value: "Canceled", comment: "") + progressState?.stringValue = NSLocalizedString("TransferCanceled", value: "Canceled", comment: "") case .notEnoughSpace: - progressState?.stringValue=NSLocalizedString("NotEnoughSpace", value: "Not enough disk space", comment: "") + progressState?.stringValue = NSLocalizedString("NotEnoughSpace", value: "Not enough disk space", comment: "") case .unsupportedType: - progressState?.stringValue=NSLocalizedString("UnsupportedType", value: "Attachment type not supported", comment: "") + progressState?.stringValue = NSLocalizedString("UnsupportedType", value: "Attachment type not supported", comment: "") case .timedOut: - progressState?.stringValue=NSLocalizedString("TransferTimedOut", value: "Timed out", comment: "") + progressState?.stringValue = NSLocalizedString("TransferTimedOut", value: "Timed out", comment: "") } - progressDeviceSecondaryIcon?.isHidden=false + progressDeviceSecondaryIcon?.isHidden = false dismissDelayed() } else { - let alert = NSAlert(error: error) - - alert.beginSheetModal(for: view.window!) { resp in + + alert.beginSheetModal(for: view.window!) { _ in self.extensionContext!.cancelRequest(withError: error) } } } - - + func transferAccepted() { - progressState?.stringValue=NSLocalizedString("Sending", value: "Sending...", comment: "") + progressState?.stringValue = NSLocalizedString("Sending", value: "Sending...", comment: "") } - - + func transferProgress(progress: Double) { - progressProgressBar!.doubleValue=progress*progressProgressBar!.maxValue + progressProgressBar!.doubleValue = progress * progressProgressBar!.maxValue } - - + func transferFinished() { - progressState?.stringValue=NSLocalizedString("TransferFinished", value: "Transfer finished", comment: "") + progressState?.stringValue = NSLocalizedString("TransferFinished", value: "Transfer finished", comment: "") dismissDelayed() } - - - func selectDevice(device:RemoteDeviceInfo){ - + + func selectDevice(device: RemoteDeviceInfo) { NearbyConnectionManager.shared.stopDeviceDiscovery() - - listViewWrapper?.animator().isHidden=true - dontSeeDeviceButton?.animator().isHidden=true - progressView?.animator().isHidden=false - progressDeviceName?.stringValue=getDeviceName(device: device) - progressDeviceIcon?.image=imageForDeviceType(type: device.type) + + listViewWrapper?.animator().isHidden = true + dontSeeDeviceButton?.animator().isHidden = true + progressView?.animator().isHidden = false + progressDeviceName?.stringValue = getDeviceName(device: device) + progressDeviceIcon?.image = imageForDeviceType(type: device.type) progressProgressBar?.startAnimation(nil) - progressState?.stringValue=NSLocalizedString("Connecting", value: "Connecting...", comment: "") - chosenDevice=device + progressState?.stringValue = NSLocalizedString("Connecting", value: "Connecting...", comment: "") + chosenDevice = device NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls) - + let timeoutAlert = DispatchWorkItem { if !self.connectionEstablished { - let alert = NSAlert() alert.alertStyle = .critical - + alert.messageText = "TimeoutTitle".localized() alert.informativeText = "TimeoutDescription".localized() alert.addButton(withTitle: "TimeoutButton".localized()) - + alert.beginSheetModal(for: self.view.window!) { _ in } } } - - self.timeoutDispatchWorkItem = timeoutAlert + + timeoutDispatchWorkItem = timeoutAlert DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: timeoutAlert) } - - - private func dismissDelayed(){ + + private func dismissDelayed() { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - - if let error=self.lastError { + if let error = self.lastError { self.extensionContext!.cancelRequest(withError: error) - } - else { + } else { self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) } } } - - + private func dicoverDevices() { let bundleIdentifier = "com.leonboettger.neardrop" let runningApps = NSWorkspace.shared.runningApplications - + // Check if the app is already running let isRunning = runningApps.contains { $0.bundleIdentifier == bundleIdentifier } - + if !isRunning { log("Launching main app") NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil) - + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { NearbyConnectionManager.shared.startDeviceDiscovery() self.scheduleAutomaticQrCodeView() } - } - else { + } else { log("Main app is already running") NearbyConnectionManager.shared.startDeviceDiscovery() - self.scheduleAutomaticQrCodeView() + scheduleAutomaticQrCodeView() } } - - + private func scheduleAutomaticQrCodeView() { DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { if self.foundDevices.isEmpty { @@ -415,48 +378,44 @@ class ShareViewController: NSViewController, ShareExtensionDelegate{ } } - -fileprivate func imageForDeviceType(type:RemoteDeviceInfo.DeviceType)->NSImage{ - let imageName:String - switch type{ +private func imageForDeviceType(type: RemoteDeviceInfo.DeviceType) -> NSImage { + let imageName: String + switch type { case .tablet: - imageName="com.apple.ipad" + imageName = "com.apple.ipad" case .computer: - imageName="com.apple.macbookpro-13-unibody" + imageName = "com.apple.macbookpro-13-unibody" default: // also .phone - imageName="com.apple.iphone" + imageName = "com.apple.iphone" } return NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/\(imageName).icns")! } - -extension ShareViewController:NSCollectionViewDataSource { - - func numberOfSections(in collectionView: NSCollectionView) -> Int { +extension ShareViewController: NSCollectionViewDataSource { + func numberOfSections(in _: NSCollectionView) -> Int { return 1 } - - func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + + func collectionView(_: NSCollectionView, numberOfItemsInSection _: Int) -> Int { return foundDevices.count } - + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { - let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DeviceListCell"), for: indexPath) - + guard let collectionViewItem = item as? DeviceListCell else { return item } - + let device = foundDevices[indexPath[1]] - + collectionViewItem.textField?.stringValue = getDeviceName(device: device) collectionViewItem.imageView?.image = imageForDeviceType(type: device.type) collectionViewItem.clickHandler = { self.selectDevice(device: device) } - + return collectionViewItem } - + func getDeviceName(device: RemoteDeviceInfo) -> String { if device.name.count <= 1 { return "Android"