diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index d442b3a0..9af895ae 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -688,6 +688,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.altitude = Int32(LocationHelper.currentAltitude) + positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed) + positionPacket.groundTrack = UInt32(LocationHelper.currentHeading) var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) @@ -849,6 +851,76 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func saveDeviceConfig(config: Config.DeviceConfig, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.device = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.display = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() @@ -883,4 +955,39 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + + public func savePositionConfig(config: Config.PositionConfig, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.position = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].deviceConfig == nil { + + let newDeviceConfig = DeviceConfigEntity(context: context) + + if isDefault { + + // Client default protobuf value of 0 + newDeviceConfig.role = 0 + newDeviceConfig.serialEnabled = true + newDeviceConfig.debugLogEnabled = false + + } else { + + // Client default protobuf value of 0 + newDeviceConfig.role = Int32(config.device.role.rawValue) + newDeviceConfig.serialEnabled = !config.device.serialDisabled + newDeviceConfig.debugLogEnabled = config.device.debugLogEnabled + } + fetchedNode[0].deviceConfig = newDeviceConfig + + } else { + + if isDefault { + + // Client default protobuf value of 0 + fetchedNode[0].deviceConfig?.role = 0 + fetchedNode[0].deviceConfig?.serialEnabled = true + fetchedNode[0].deviceConfig?.debugLogEnabled = false + + } else { + // Client default protobuf value of 0 + fetchedNode[0].deviceConfig?.role = Int32(config.device.role.rawValue) + fetchedNode[0].deviceConfig?.serialEnabled = !config.device.serialDisabled + fetchedNode[0].deviceConfig?.debugLogEnabled = config.device.debugLogEnabled + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Device Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data DeviceConfigEntity: \(nsError)") + } + } + + } catch { + + } + } + + if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { + + var isDefault = false + + if (try! config.display.jsonString()) == "{}" { + + isDefault = true + print("🖥️ Default Display config") + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].displayConfig == nil { + + let newDisplayConfig = DisplayConfigEntity(context: context) + + if isDefault { + + newDisplayConfig.screenOnSeconds = 0 + newDisplayConfig.screenCarouselInterval = 0 + newDisplayConfig.gpsFormat = 0 + + } else { + + newDisplayConfig.gpsFormat = Int32(config.display.gpsFormat.rawValue) + newDisplayConfig.screenOnSeconds = Int32(config.display.screenOnSecs) + newDisplayConfig.screenCarouselInterval = Int32(config.display.autoScreenCarouselSecs) + } + fetchedNode[0].displayConfig = newDisplayConfig + + } else { + + if isDefault { + + fetchedNode[0].displayConfig?.screenOnSeconds = 0 + fetchedNode[0].displayConfig?.screenCarouselInterval = 0 + fetchedNode[0].displayConfig?.gpsFormat = 0 + + } else { + + fetchedNode[0].displayConfig?.gpsFormat = Int32(config.display.gpsFormat.rawValue) + fetchedNode[0].displayConfig?.screenOnSeconds = Int32(config.display.screenOnSecs) + fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(config.display.autoScreenCarouselSecs) + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Display Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data DisplayConfigEntity: \(nsError)") + } + } + + } catch { + + } + } if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { @@ -119,14 +239,99 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont do { try context.save() - if meshlogging { MeshLogger.log("💾 Updated LoRaConfig for node number: \(String(nodeNum))") } + if meshlogging { MeshLogger.log("💾 Updated LoRa Config for node number: \(String(nodeNum))") } } catch { context.rollback() let nsError = error as NSError - print("💥 Error Updating Core Data MyInfoEntity: \(nsError)") + print("💥 Error Updating Core Data LoRaConfigEntity: \(nsError)") + } + } + + } catch { + + } + } + + if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { + + var isDefault = false + + if (try! config.position.jsonString()) == "{}" { + + isDefault = true + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save LoRa Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].positionConfig == nil { + + let newPositionConfig = PositionConfigEntity(context: context) + + if isDefault { + + newPositionConfig.smartPositionEnabled = true + newPositionConfig.deviceGpsEnabled = true + newPositionConfig.fixedPosition = false + newPositionConfig.gpsUpdateInterval = 0 + newPositionConfig.gpsAttemptTime = 0 + newPositionConfig.positionBroadcastSeconds = 0 + + } else { + + newPositionConfig.smartPositionEnabled = !config.position.positionBroadcastSmartDisabled + newPositionConfig.deviceGpsEnabled = !config.position.gpsDisabled + newPositionConfig.fixedPosition = config.position.fixedPosition + newPositionConfig.gpsUpdateInterval = Int32(config.position.gpsUpdateInterval) + newPositionConfig.gpsAttemptTime = Int32(config.position.gpsAttemptTime) + newPositionConfig.positionBroadcastSeconds = Int32(config.position.positionBroadcastSecs) + } + + fetchedNode[0].positionConfig = newPositionConfig + + } else { + + if isDefault { + + fetchedNode[0].positionConfig?.smartPositionEnabled = true + fetchedNode[0].positionConfig?.deviceGpsEnabled = true + fetchedNode[0].positionConfig?.fixedPosition = false + fetchedNode[0].positionConfig?.gpsUpdateInterval = 0 + fetchedNode[0].positionConfig?.gpsAttemptTime = 0 + fetchedNode[0].positionConfig?.positionBroadcastSeconds = 0 + + } else { + + fetchedNode[0].positionConfig?.smartPositionEnabled = !config.position.positionBroadcastSmartDisabled + fetchedNode[0].positionConfig?.deviceGpsEnabled = !config.position.gpsDisabled + fetchedNode[0].positionConfig?.fixedPosition = config.position.fixedPosition + fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.position.gpsUpdateInterval) + fetchedNode[0].positionConfig?.gpsAttemptTime = Int32(config.position.gpsAttemptTime) + fetchedNode[0].positionConfig?.positionBroadcastSeconds = Int32(config.position.positionBroadcastSecs) + + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Position Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data PositionConfigEntity: \(nsError)") } } diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 3039ae3b..c32d279b 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -1,9 +1,23 @@ + + + + + + + + + + + + + + - + @@ -53,8 +67,11 @@ + + + @@ -64,6 +81,16 @@ + + + + + + + + + + @@ -104,9 +131,12 @@ - + + + + \ No newline at end of file diff --git a/MeshtasticApple/Views/Settings/DeviceConfig.swift b/MeshtasticApple/Views/Settings/DeviceConfig.swift index 75099874..795f34ab 100644 --- a/MeshtasticApple/Views/Settings/DeviceConfig.swift +++ b/MeshtasticApple/Views/Settings/DeviceConfig.swift @@ -20,29 +20,48 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { switch self { case .client: - return "Client (default)" + return "Client (default) - App connected client." case .clientMute: return "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh." case .router: - return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep." + return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep." case .routerClient: return "Router Client - Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client." } } } + func protoEnumValue() -> Config.DeviceConfig.Role { + + switch self { + + case .client: + return Config.DeviceConfig.Role.client + case .clientMute: + return Config.DeviceConfig.Role.clientMute + case .router: + return Config.DeviceConfig.Role.router + case .routerClient: + return Config.DeviceConfig.Role.routerClient + } + } } struct DeviceConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + + var node: NodeInfoEntity + + @State private var isPresentingFactoryResetConfirm: Bool = false + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false + @State var deviceRole = 0 @State var serialEnabled = true @State var debugLogEnabled = false - @State private var isPresentingFactoryResetConfirm: Bool = false - var body: some View { VStack { @@ -77,24 +96,64 @@ struct DeviceConfig: View { } } - Button("Factory Reset", role: .destructive) { + HStack { - isPresentingFactoryResetConfirm = true - } - .disabled(bleManager.connectedPeripheral == nil) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingFactoryResetConfirm - ) { - Button("Erase all device settings?", role: .destructive) { + Button { + + isPresentingSaveConfirm = true - if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Device Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - print("Factory Reset Failed") + var dc = Config.DeviceConfig() + dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() + dc.serialDisabled = !serialEnabled + dc.debugLogEnabled = debugLogEnabled + + if bleManager.saveDeviceConfig(config: dc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } + } + } + + Button("Factory Reset", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm + ) { + Button("Erase all device settings?", role: .destructive) { + + if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + print("Factory Reset Failed") + } } } } @@ -110,7 +169,37 @@ struct DeviceConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + + self.deviceRole = Int(node.deviceConfig?.role ?? 0) + self.serialEnabled = (node.deviceConfig?.serialEnabled ?? true) + self.debugLogEnabled = node.deviceConfig?.debugLogEnabled ?? false + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: deviceRole) { newRole in + + if newRole != node.deviceConfig!.role { + + hasChanges = true + } + } + .onChange(of: serialEnabled) { newSerial in + + if newSerial != node.deviceConfig!.serialEnabled { + + hasChanges = true + } + } + .onChange(of: debugLogEnabled) { newDebugLog in + + if newDebugLog != node.deviceConfig!.debugLogEnabled { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticApple/Views/Settings/DisplayConfig.swift b/MeshtasticApple/Views/Settings/DisplayConfig.swift index d7c9936f..6c33fcb8 100644 --- a/MeshtasticApple/Views/Settings/DisplayConfig.swift +++ b/MeshtasticApple/Views/Settings/DisplayConfig.swift @@ -7,7 +7,7 @@ import SwiftUI -enum GpsFormat: Int, CaseIterable, Identifiable { +enum GpsFormats: Int, CaseIterable, Identifiable { case gpsFormatDec = 0 case gpsFormatDms = 1 @@ -35,6 +35,24 @@ enum GpsFormat: Int, CaseIterable, Identifiable { } } } + func protoEnumValue() -> Config.DisplayConfig.GpsCoordinateFormat { + + switch self { + + case .gpsFormatDec: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatDec + case .gpsFormatDms: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatDms + case .gpsFormatUtm: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatUtm + case .gpsFormatMgrs: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatMgrs + case .gpsFormatOlc: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatOlc + case .gpsFormatOsgr: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatOsgr + } + } } // Default of 0 is One Minute @@ -109,6 +127,11 @@ struct DisplayConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false @State var screenOnSeconds = 0 @State var screenCarouselInterval = 0 @@ -146,7 +169,7 @@ struct DisplayConfig: View { } Section(header: Text("Format")) { Picker("GPS Format", selection: $gpsFormat ) { - ForEach(GpsFormat.allCases) { lu in + ForEach(GpsFormats.allCases) { lu in Text(lu.description) } } @@ -157,6 +180,43 @@ struct DisplayConfig: View { .listRowSeparator(.visible) } } + + Button { + + isPresentingSaveConfirm = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var dc = Config.DisplayConfig() + dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() + dc.screenOnSecs = UInt32(screenOnSeconds) + dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) + + if bleManager.saveDisplayConfig(config: dc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } + } + } } .navigationTitle("Display Config") .navigationBarItems(trailing: @@ -167,7 +227,37 @@ struct DisplayConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + + self.gpsFormat = Int(node.displayConfig?.gpsFormat ?? 0) + self.screenOnSeconds = Int(node.displayConfig?.screenOnSeconds ?? 0) + self.screenCarouselInterval = Int(node.displayConfig?.screenCarouselInterval ?? 0) + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: screenOnSeconds) { newScreenSecs in + + if newScreenSecs != node.displayConfig!.screenOnSeconds { + + hasChanges = true + } + } + .onChange(of: screenCarouselInterval) { newCarouselSecs in + + if newCarouselSecs != node.displayConfig!.screenCarouselInterval { + + hasChanges = true + } + } + .onChange(of: gpsFormat) { newGpsFormat in + + if newGpsFormat != node.displayConfig!.gpsFormat { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticApple/Views/Settings/LoRaConfig.swift b/MeshtasticApple/Views/Settings/LoRaConfig.swift index 7bef9ffa..de7f76e3 100644 --- a/MeshtasticApple/Views/Settings/LoRaConfig.swift +++ b/MeshtasticApple/Views/Settings/LoRaConfig.swift @@ -189,11 +189,11 @@ struct LoRaConfig: View { @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true + @State var hasChanges = false @State var region = 0 @State var modemPreset = 0 @State var hopLimit = 0 - @State var hasChanges = false var body: some View { @@ -242,6 +242,7 @@ struct LoRaConfig: View { isPresentingSaveConfirm = true } label: { + Label("Save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) @@ -250,6 +251,7 @@ struct LoRaConfig: View { .controlSize(.large) .padding() .confirmationDialog( + "Are you sure?", isPresented: $isPresentingSaveConfirm ) { @@ -285,7 +287,6 @@ struct LoRaConfig: View { if self.initialLoad{ self.bleManager.context = context - print("got hops \(node.loRaConfig?.hopLimit ?? 0)") self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0) self.region = Int(node.loRaConfig?.regionCode ?? 0) self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0) @@ -314,7 +315,6 @@ struct LoRaConfig: View { hasChanges = true } } - .navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/MeshtasticApple/Views/Settings/PositionConfig.swift b/MeshtasticApple/Views/Settings/PositionConfig.swift index 05c27867..c16d99c9 100644 --- a/MeshtasticApple/Views/Settings/PositionConfig.swift +++ b/MeshtasticApple/Views/Settings/PositionConfig.swift @@ -111,6 +111,11 @@ struct PositionConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + var node: NodeInfoEntity + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false + @State var smartPositionEnabled = true @State var deviceGpsEnabled = true @State var fixedPosition = false @@ -125,8 +130,6 @@ struct PositionConfig: View { @State var includePosSpeed = false @State var includePosHeading = false - - var body: some View { VStack { @@ -181,6 +184,8 @@ struct PositionConfig: View { } } + .disabled(!(node.myInfo?.hasGps ?? true)) + Section(header: Text("Position Packet")) { Toggle(isOn: $smartPositionEnabled) { @@ -203,7 +208,7 @@ struct PositionConfig: View { } } - Section(header: Text("Position Flags")) { + Section(header: Text("Position Flags - Non Functional")) { Text("Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss") .font(.caption) @@ -213,44 +218,84 @@ struct PositionConfig: View { Label("Altitude", systemImage: "arrow.up") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosSatsinview) { Label("Number of satellites", systemImage: "skew") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .disabled(!(node.myInfo?.hasGps ?? true)) .listRowSeparator(.visible) Toggle(isOn: $includePosSeqNos) { //64 Label("Sequence number", systemImage: "number") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosTimestamp) { //128 Label("Timestamp", systemImage: "clock") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosHeading) { //128 Label("Vehicle heading", systemImage: "location.circle") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosSpeed) { //128 Label("Vehicle speed", systemImage: "speedometer") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) + } + } + + Button { + + isPresentingSaveConfirm = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + var pc = Config.PositionConfig() + pc.positionBroadcastSmartDisabled = !smartPositionEnabled + pc.gpsDisabled = !deviceGpsEnabled + pc.fixedPosition = fixedPosition + pc.gpsUpdateInterval = UInt32(gpsUpdateInterval) + pc.gpsAttemptTime = UInt32(gpsAttemptTime) + pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) + + if bleManager.savePositionConfig(config: pc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } } } } @@ -263,7 +308,39 @@ struct PositionConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + self.smartPositionEnabled = node.positionConfig?.smartPositionEnabled ?? true + self.deviceGpsEnabled = node.positionConfig?.deviceGpsEnabled ?? true + self.fixedPosition = node.positionConfig?.fixedPosition ?? false + self.gpsUpdateInterval = Int(node.positionConfig?.gpsUpdateInterval ?? 0) + self.gpsAttemptTime = Int(node.positionConfig?.gpsAttemptTime ?? 0) + self.positionBroadcastSeconds = Int(node.positionConfig?.positionBroadcastSeconds ?? 0) + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: smartPositionEnabled) { newSmartPosition in + + if newSmartPosition != node.positionConfig!.smartPositionEnabled { + + hasChanges = true + } + } + .onChange(of: deviceGpsEnabled) { newDeviceGps in + + if newDeviceGps != node.positionConfig!.deviceGpsEnabled { + + hasChanges = true + } + } + .onChange(of: fixedPosition) { newFixed in + + if newFixed != node.positionConfig!.fixedPosition { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index c580f17e..ba137128 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -25,6 +25,8 @@ struct Settings: View { NavigationView { List { + let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 + Section("General") { NavigationLink { AppSettings() @@ -43,26 +45,27 @@ struct Settings: View { } } - Section("Radio Configuration (Non-Functional Interaction Previews except for LoRa Config)") { + Section("Radio Configuration") { NavigationLink { - DeviceConfig() + DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "flipphone") .symbolRenderingMode(.hierarchical) Text("Device") } + .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { - DisplayConfig() + DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "display") .symbolRenderingMode(.hierarchical) Text("Display (Device Screen)") } - - let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 + .disabled(bleManager.connectedPeripheral == nil) NavigationLink() { @@ -77,7 +80,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - PositionConfig() + PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "location") @@ -85,11 +88,12 @@ struct Settings: View { Text("Position") } + .disabled(bleManager.connectedPeripheral == nil) } Section("Module Configuration") { NavigationLink { - DisplayConfig() + PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "list.bullet.rectangle.fill") @@ -107,6 +111,8 @@ struct Settings: View { Text("Range Test") } + .disabled(!(nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true) || bleManager.connectedPeripheral == nil) + NavigationLink { TelemetryConfig() } label: { @@ -116,6 +122,7 @@ struct Settings: View { Text("Telemetry (Sensors)") } + .disabled(true) } // Not Implemented: // External Notifications - Not Working