mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Formatting
This commit is contained in:
1 parent
3a2a8c70b9
commit
dbca8d6434
3 files changed
+282
-355
No files matched your search
+105
-137
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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<ObjCBool>.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"
|
||||
|
||||
Reference in new issue
Block a user