diff --git a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift index 890811c..1228140 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/AccessoryController.swift @@ -9,15 +9,15 @@ import Combine import Foundation -import SwiftUI import OSLog +import SwiftUI class AccessoryController: ObservableObject { @Published var accessories: [Accessory] var selfObserver: AnyCancellable? var listElementsObserver = [AnyCancellable]() let findMyController: FindMyController - + init(accessories: [Accessory], findMyController: FindMyController) { self.accessories = accessories self.findMyController = findMyController @@ -91,7 +91,7 @@ class AccessoryController: ObservableObject { } return accessory } - + /// Export the accessories property list so it can be imported at another location func export(accessories: [Accessory]) throws -> URL { let propertyList = try PropertyListEncoder().encode(accessories) @@ -109,7 +109,8 @@ class AccessoryController: ObservableObject { let result = savePanel.runModal() if result == .OK, - let url = savePanel.url { + let url = savePanel.url + { // Store the accessory file try propertyList.write(to: url) @@ -130,47 +131,46 @@ class AccessoryController: ObservableObject { let result = openPanel.runModal() if result == .OK, - let url = openPanel.url { + let url = openPanel.url + { let propertyList = try Data(contentsOf: url) var importedAccessories = try PropertyListDecoder().decode([Accessory].self, from: propertyList) var updatedAccessories = self.accessories // Filter out accessories with the same id (no duplicates) - importedAccessories = importedAccessories.filter({acc in !self.accessories.contains(where: {acc.id == $0.id})}) + importedAccessories = importedAccessories.filter({ acc in !self.accessories.contains(where: { acc.id == $0.id }) }) updatedAccessories.append(contentsOf: importedAccessories) - updatedAccessories.sort(by: {$0.name < $1.name}) - - + updatedAccessories.sort(by: { $0.name < $1.name }) + self.accessories = updatedAccessories - + //Update reports automatically. Do not report errors from here - self.downloadLocationReports { result in} + self.downloadLocationReports { result in } } } - + enum ImportError: Error { case cancelled } - - + //MARK: Location reports - - - /// Download the location reports from - /// - Parameter completion: called when the reports have been succesfully downloaded or the request has failed - func downloadLocationReports(completion: @escaping (Result) -> Void) { + + /// Download the location reports from + /// - Parameter completion: called when the reports have been succesfully downloaded or the request has failed + func downloadLocationReports(completion: @escaping (Result) -> Void) { AnisetteDataManager.shared.requestAnisetteData { result in switch result { case .failure(_): completion(.failure(.activatePlugin)) case .success(let accountData): - + guard let token = accountData.searchPartyToken, - token.isEmpty == false else { + token.isEmpty == false + else { completion(.failure(.searchPartyToken)) return } - + self.findMyController.fetchReports(for: self.accessories, with: token) { result in switch result { case .failure(let error): @@ -180,17 +180,17 @@ class AccessoryController: ObservableObject { let reports = devices.compactMap({ $0.reports }).flatMap({ $0 }) if reports.isEmpty { completion(.failure(.noReportsFound)) - }else { + } else { self.updateWithDecryptedReports(devices: devices) completion(.success(())) } } } - + } } } - + } class AccessoryControllerPreview: AccessoryController { diff --git a/OpenHaystack/OpenHaystack/HaystackApp/Views/ManageAccessoriesView.swift b/OpenHaystack/OpenHaystack/HaystackApp/Views/ManageAccessoriesView.swift index 36faa20..d28baa7 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/Views/ManageAccessoriesView.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/Views/ManageAccessoriesView.swift @@ -1,4 +1,4 @@ - // +// // OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network // // Copyright © 2021 Secure Mobile Networking Lab (SEEMOO) @@ -21,7 +21,7 @@ struct ManageAccessoriesView: View { @Binding var focusedAccessory: Accessory? @Binding var accessoryToDeploy: Accessory? @Binding var showESP32DeploySheet: Bool - + @State var showMailPopup = false var body: some View { @@ -74,23 +74,28 @@ struct ManageAccessoriesView: View { .listStyle(SidebarListStyle()) } - - - /// All toolbar buttons shown + + /// All toolbar buttons shown var toolbarView: some View { Group { Spacer() - - Button(action: self.importAccessories, label: { - Label("Import accessories", systemImage: "square.and.arrow.down") - }) + + Button( + action: self.importAccessories, + label: { + Label("Import accessories", systemImage: "square.and.arrow.down") + } + ) .help("Import accessories from a file") - - Button(action: self.exportAccessories, label: { - Label("Export accessories", systemImage: "square.and.arrow.up") - }) + + Button( + action: self.exportAccessories, + label: { + Label("Export accessories", systemImage: "square.and.arrow.up") + } + ) .help("Export all accessories to a file") - + Button(action: self.addAccessory) { Label("Add accessory", systemImage: "plus") } @@ -120,25 +125,26 @@ struct ManageAccessoriesView: View { self.alertType = .keyError } } - + func exportAccessories() { do { _ = try self.accessoryController.export(accessories: self.accessories) - }catch { + } catch { self.alertType = .exportFailed } } - + func importAccessories() { do { try self.accessoryController.importAccessories() - }catch { + } catch { if let importError = error as? AccessoryController.ImportError, - importError == .cancelled { + importError == .cancelled + { //User cancelled the import. No error return } - + self.alertType = .importFailed } } diff --git a/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift index 02e9073..97a993b 100644 --- a/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift +++ b/OpenHaystack/OpenHaystack/HaystackApp/Views/OpenHaystackMainView.swift @@ -15,7 +15,7 @@ struct OpenHaystackMainView: View { @State var loading = false @EnvironmentObject var accessoryController: AccessoryController - + var accessories: [Accessory] { return self.accessoryController.accessories } @@ -30,9 +30,9 @@ struct OpenHaystackMainView: View { @State var focusedAccessory: Accessory? @State var accessoryToDeploy: Accessory? @State var showMailPlugInPopover = false - + @State var mailPluginIsActive = false - + @State var showESP32DeploySheet = false var body: some View { @@ -104,39 +104,43 @@ struct OpenHaystackMainView: View { } } } - + /// All toolbar items shown var toolbarView: some View { Group { - + Picker("", selection: self.$mapType) { Text("Satellite").tag(MKMapType.hybrid) Text("Standard").tag(MKMapType.standard) } .pickerStyle(SegmentedPickerStyle()) - - - Button(action: { - if !self.mailPluginIsActive { - self.showMailPlugInPopover.toggle() - }else { - self.downloadLocationReports() + + Button( + action: { + if !self.mailPluginIsActive { + self.showMailPlugInPopover.toggle() + } else { + self.downloadLocationReports() + } + + }, + label: { + HStack { + Circle() + .fill(self.mailPluginIsActive ? Color.green : Color.orange) + .frame(width: 8, height: 8) + Label("Reload", systemImage: "arrow.clockwise") + .disabled(!self.mailPluginIsActive) + } + } - - },label: { - HStack { - Circle() - .fill(self.mailPluginIsActive ? Color.green : Color.orange) - .frame(width: 8, height: 8) - Label("Reload", systemImage: "arrow.clockwise") - .disabled(!self.mailPluginIsActive) - } - - }) + ) .disabled(self.accessories.isEmpty) - .popover(isPresented: $showMailPlugInPopover, content: { - self.mailStatePopover - }) + .popover( + isPresented: $showMailPlugInPopover, + content: { + self.mailStatePopover + }) } } @@ -171,7 +175,7 @@ struct OpenHaystackMainView: View { case .failure(let alert): if alert == .noReportsFound { self.popUpAlertType = .noReportsFound - }else { + } else { self.alertType = alert } case .success(_): @@ -179,15 +183,15 @@ struct OpenHaystackMainView: View { } } } - + var mailStatePopover: some View { HStack { Image(systemName: "envelope") .foregroundColor(self.mailPluginIsActive ? .green : .red) - + if self.mailPluginIsActive { Text("The mail plug-in is up and running") - }else { + } else { Text("Cannot connect to the mail plug-in. Open Apple Mail and make sure the plug-in is enabled") .lineLimit(10) .multilineTextAlignment(.leading) @@ -234,7 +238,7 @@ struct OpenHaystackMainView: View { } } - func checkPluginIsRunning(silent: Bool=false, _ completion: ((Bool) -> Void)?) { + func checkPluginIsRunning(silent: Bool = false, _ completion: ((Bool) -> Void)?) { // Check if Mail plugin is active AnisetteDataManager.shared.requestAnisetteData { (result) in DispatchQueue.main.async { @@ -242,7 +246,7 @@ struct OpenHaystackMainView: View { case .success(let accountData): withAnimation { - if let token = accountData.searchPartyToken { + if let token = accountData.searchPartyToken { self.searchPartyToken = String(data: token, encoding: .ascii) ?? "" if self.searchPartyToken.isEmpty == false { self.searchPartyTokenLoaded = true @@ -263,11 +267,13 @@ struct OpenHaystackMainView: View { } self.mailPluginIsActive = false completion?(false) - + //Check again in 5s - DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: { - self.checkPluginIsRunning(silent: true, nil) - }) + DispatchQueue.main.asyncAfter( + deadline: .now() + 5, + execute: { + self.checkPluginIsRunning(silent: true, nil) + }) } } } @@ -362,9 +368,10 @@ struct OpenHaystackMainView: View { primaryButton: microbitButton, secondaryButton: esp32Button) case .downloadingReportsFailed: - return Alert(title: Text("Downloading locations failed"), - message: Text("We could not download any locations from Apple. Please try again later"), - dismissButton: Alert.Button.okay()) + return Alert( + title: Text("Downloading locations failed"), + message: Text("We could not download any locations from Apple. Please try again later"), + dismissButton: Alert.Button.okay()) case .exportFailed: return Alert( title: Text("Export failed"), diff --git a/OpenHaystack/OpenHaystack/OpenHaystackApp.swift b/OpenHaystack/OpenHaystack/OpenHaystackApp.swift index 0cef803..7da6249 100644 --- a/OpenHaystack/OpenHaystack/OpenHaystackApp.swift +++ b/OpenHaystack/OpenHaystack/OpenHaystackApp.swift @@ -31,22 +31,5 @@ struct OpenHaystackApp: App { .commands { SidebarCommands() } - } - -} - -//MARK: Environment objects -private struct FindMyControllerEnvironmentKey: EnvironmentKey { - static let defaultValue: FindMyController = FindMyController() -} - -private struct AccessoryControllerEnvironmentKey: EnvironmentKey { - static let defaultValue: AccessoryController = { - if ProcessInfo().arguments.contains("-preview") { - return AccessoryControllerPreview(accessories: PreviewData.accessories, findMyController: FindMyController()) - } else { - return AccessoryController() - } - }() } diff --git a/OpenHaystack/OpenHaystackMail/AppleAccountData.h b/OpenHaystack/OpenHaystackMail/AppleAccountData.h index 8e87538..a159770 100644 --- a/OpenHaystack/OpenHaystackMail/AppleAccountData.h +++ b/OpenHaystack/OpenHaystackMail/AppleAccountData.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy) NSLocale *locale; @property(nonatomic, copy) NSTimeZone *timeZone; -@property(nonatomic, copy) NSData * _Nullable searchPartyToken; +@property(nonatomic, copy) NSData *_Nullable searchPartyToken; - (instancetype)initWithMachineID:(NSString *)machineID oneTimePassword:(NSString *)oneTimePassword