Adding a button that shows if the mail plug-in is active. The button turns red if the plug-in is not active.

Architectural changes discussed with @schmittner: Moving the FindMyController out of the environment and using the AccessoryController as the main entry point, also for downloading reports
The AccessoryController is now passed as an Environment Object again
This commit is contained in:
Alexander Heinrich
2021-03-15 10:36:28 +01:00
parent b56aa1faa7
commit ab1c3eb83a
6 changed files with 82 additions and 45 deletions
@@ -15,7 +15,6 @@ import SwiftUI
class FindMyController: ObservableObject {
@Published var error: Error?
@Published var devices = [FindMyDevice]()
@Environment(\.accessoryController) var accessories: AccessoryController
func loadPrivateKeys(from data: Data, with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
do {
@@ -98,11 +97,6 @@ class FindMyController: ObservableObject {
self.fetchReports(with: token) { error in
let reports = self.devices.compactMap({ $0.reports }).flatMap({ $0 })
if reports.isEmpty == false {
self.accessories.updateWithDecryptedReports(devices: self.devices)
}
if let error = error {
completion(.failure(error))
os_log("Error: %@", String(describing: error))
@@ -16,16 +16,17 @@ class AccessoryController: ObservableObject {
@Published var accessories: [Accessory]
var selfObserver: AnyCancellable?
var listElementsObserver = [AnyCancellable]()
@Environment(\.findMyController) var findMyController: FindMyController
init(accessories: [Accessory]) {
let findMyController: FindMyController
init(accessories: [Accessory], findMyController: FindMyController) {
self.accessories = accessories
self.findMyController = findMyController
initAccessoryObserver()
initObserver()
}
convenience init() {
self.init(accessories: KeychainController.loadAccessoriesFromKeychain())
self.init(accessories: KeychainController.loadAccessoriesFromKeychain(), findMyController: FindMyController())
}
func initAccessoryObserver() {
@@ -163,8 +164,9 @@ class AccessoryController: ObservableObject {
case .failure(_):
completion(.failure(.activatePlugin))
case .success(let accountData):
let token = accountData.searchPartyToken
guard token.isEmpty == false else {
guard let token = accountData.searchPartyToken,
token.isEmpty == false else {
completion(.failure(.searchPartyToken))
return
}
@@ -179,6 +181,7 @@ class AccessoryController: ObservableObject {
if reports.isEmpty {
completion(.failure(.noReportsFound))
}else {
self.updateWithDecryptedReports(devices: devices)
completion(.success(()))
}
}
@@ -11,7 +11,7 @@ import SwiftUI
struct ManageAccessoriesView: View {
@Environment(\.accessoryController) var accessoryController: AccessoryController
@EnvironmentObject var accessoryController: AccessoryController
var accessories: [Accessory] {
return self.accessoryController.accessories
}
@@ -21,6 +21,9 @@ struct ManageAccessoriesView: View {
@Binding var focusedAccessory: Accessory?
@Binding var accessoryToDeploy: Accessory?
@Binding var showESP32DeploySheet: Bool
var mailPluginIsActive: Bool
@State var showMailPopup = false
var body: some View {
VStack {
@@ -40,17 +43,29 @@ struct ManageAccessoriesView: View {
.toolbar(content: {
Spacer()
Button(action: self.importAccessories, label: {
Label("Export accessories", systemImage: "square.and.arrow.down")
Button(action: {self.showMailPopup.toggle()}, label: {
Label("Plugin state", systemImage: "envelope")
.foregroundColor(self.mailPluginIsActive ? nil : .red)
})
.help(self.mailPluginIsActive ? "Mail plug-in is active" : "Cannot connect to Mail plug-in")
.popover(isPresented: self.$showMailPopup) {
self.mailStatePopup
}
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")
})
.help("Export all accessories to a file")
Button(action: self.addAccessory) {
Label("Add accessory", systemImage: "plus")
}
.help("Add a new accessory")
})
.sheet(
isPresented: self.$showESP32DeploySheet,
@@ -84,6 +99,23 @@ struct ManageAccessoriesView: View {
.listStyle(SidebarListStyle())
}
var mailStatePopup: some View {
HStack {
Image(systemName: "envelope")
.foregroundColor(self.mailPluginIsActive ? .green : .red)
if self.mailPluginIsActive {
Text("The mail plug-in is up and running")
}else {
Text("Cannot connect to the mail plug-in. Open Apple Mail and make sure the plug-in is enabled")
.lineLimit(10)
.multilineTextAlignment(.leading)
}
}
.frame(maxWidth: 250)
.padding()
}
/// Delete an accessory from the list of accessories.
func delete(accessory: Accessory) {
@@ -135,6 +167,6 @@ struct ManageAccessoriesView_Previews: PreviewProvider {
@State static var showESPSheet: Bool = true
static var previews: some View {
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet)
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet, mailPluginIsActive: true)
}
}
@@ -14,13 +14,12 @@ import SwiftUI
struct OpenHaystackMainView: View {
@State var loading = false
@Environment(\.accessoryController) var accessoryController: AccessoryController
@Environment(\.findMyController) var findMyController: FindMyController
@EnvironmentObject var accessoryController: AccessoryController
var accessories: [Accessory] {
return self.accessoryController.accessories
}
@State var showKeyError = false
@State var alertType: AlertType?
@State var popUpAlertType: PopUpAlertType?
@State var errorDescription: String?
@@ -30,7 +29,9 @@ struct OpenHaystackMainView: View {
@State var isLoading = false
@State var focusedAccessory: Accessory?
@State var accessoryToDeploy: Accessory?
@State var mailPluginIsActive = false
@State var showESP32DeploySheet = false
var body: some View {
@@ -41,9 +42,10 @@ struct OpenHaystackMainView: View {
alertType: self.$alertType,
focusedAccessory: self.$focusedAccessory,
accessoryToDeploy: self.$accessoryToDeploy,
showESP32DeploySheet: self.$showESP32DeploySheet
showESP32DeploySheet: self.$showESP32DeploySheet,
mailPluginIsActive: self.mailPluginIsActive
)
.frame(minWidth: 240, idealWidth: 250, maxWidth: .infinity, minHeight: 300, idealHeight: 400, maxHeight: .infinity, alignment: .center)
.frame(minWidth: 280, idealWidth: 280, maxWidth: .infinity, minHeight: 300, idealHeight: 400, maxHeight: .infinity, alignment: .center)
ZStack {
AccessoryMapView(accessoryController: self.accessoryController, mapType: self.$mapType, focusedAccessory: self.focusedAccessory)
@@ -186,7 +188,7 @@ struct OpenHaystackMainView: View {
}
}
func checkPluginIsRunning(_ 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 {
@@ -194,14 +196,17 @@ struct OpenHaystackMainView: View {
case .success(let accountData):
withAnimation {
self.searchPartyToken = String(data: accountData.searchPartyToken, encoding: .ascii) ?? ""
if self.searchPartyToken.isEmpty == false {
self.searchPartyTokenLoaded = true
if let token = accountData.searchPartyToken {
self.searchPartyToken = String(data: token, encoding: .ascii) ?? ""
if self.searchPartyToken.isEmpty == false {
self.searchPartyTokenLoaded = true
}
}
}
self.mailPluginIsActive = true
completion?(true)
case .failure(let error):
if let error = error as? AnisetteDataError {
if let error = error as? AnisetteDataError, silent == false {
switch error {
case .pluginNotFound:
self.alertType = .activatePlugin
@@ -209,7 +214,13 @@ struct OpenHaystackMainView: View {
self.alertType = .activatePlugin
}
}
self.mailPluginIsActive = false
completion?(false)
//Check again in 5s
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
self.checkPluginIsRunning(silent: true, nil)
})
}
}
}
@@ -330,11 +341,11 @@ struct OpenHaystackMainView: View {
}
struct OpenHaystackMainView_Previews: PreviewProvider {
static var accessoryController = AccessoryControllerPreview(accessories: PreviewData.accessories) as AccessoryController
static var accessoryController = AccessoryControllerPreview(accessories: PreviewData.accessories, findMyController: FindMyController()) as AccessoryController
static var previews: some View {
OpenHaystackMainView()
.environment(\.accessoryController, accessoryController)
.environmentObject(self.accessoryController)
}
}
+12 -15
View File
@@ -11,14 +11,22 @@ import SwiftUI
@main
struct OpenHaystackApp: App {
@Environment(\.accessoryController) var accessoryController: AccessoryController
@Environment(\.findMyController) var findMyController: FindMyController
@StateObject var accessoryController: AccessoryController
init() {}
init() {
let accessoryController: AccessoryController
if ProcessInfo().arguments.contains("-preview") {
accessoryController = AccessoryControllerPreview(accessories: PreviewData.accessories, findMyController: FindMyController())
} else {
accessoryController = AccessoryController()
}
self._accessoryController = StateObject(wrappedValue: accessoryController)
}
var body: some Scene {
WindowGroup {
OpenHaystackMainView()
.environmentObject(self.accessoryController)
}
.commands {
SidebarCommands()
@@ -36,20 +44,9 @@ private struct FindMyControllerEnvironmentKey: EnvironmentKey {
private struct AccessoryControllerEnvironmentKey: EnvironmentKey {
static let defaultValue: AccessoryController = {
if ProcessInfo().arguments.contains("-preview") {
return AccessoryControllerPreview(accessories: PreviewData.accessories)
return AccessoryControllerPreview(accessories: PreviewData.accessories, findMyController: FindMyController())
} else {
return AccessoryController()
}
}()
}
extension EnvironmentValues {
var findMyController: FindMyController {
get {self[FindMyControllerEnvironmentKey]}
}
var accessoryController: AccessoryController {
get{self[AccessoryControllerEnvironmentKey]}
set{self[AccessoryControllerEnvironmentKey] = newValue}
}
}
@@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, copy) NSLocale *locale;
@property(nonatomic, copy) NSTimeZone *timeZone;
@property(nonatomic, copy) NSData *searchPartyToken;
@property(nonatomic, copy) NSData * _Nullable searchPartyToken;
- (instancetype)initWithMachineID:(NSString *)machineID
oneTimePassword:(NSString *)oneTimePassword