diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c2db3afc..4d2e9cf9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; }; DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; }; DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; + DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; + DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; }; DD4033C228B286B70096A444 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4033C128B286B70096A444 /* Onboarding.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; @@ -38,14 +40,15 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158B2824A91E0032668E /* module_config.pb.swift */; }; DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158D2824AA7E0032668E /* config.pb.swift */; }; DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */; }; - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */; }; + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.swift */; }; + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; }; @@ -58,7 +61,6 @@ DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; }; DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */; }; DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; }; - DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; }; DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; @@ -136,6 +138,8 @@ DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; + DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; + DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = ""; }; DD4033C128B286B70096A444 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; @@ -147,13 +151,14 @@ DD4C158B2824A91E0032668E /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = ""; }; DD4C158D2824AA7E0032668E /* config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = config.pb.swift; sourceTree = ""; }; DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cannedmessages.pb.swift; sourceTree = ""; }; - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryLog.swift; sourceTree = ""; }; + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; - DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = ""; }; + DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -167,7 +172,6 @@ DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfig.swift; sourceTree = ""; }; DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingError.swift; sourceTree = ""; }; DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = ""; }; - DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = ""; }; DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = ""; }; DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; @@ -266,8 +270,9 @@ DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, - DD73FD1028750779000852D6 /* LocationHistory.swift */, - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */, + DD73FD1028750779000852D6 /* PositionLog.swift */, + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, ); path = Nodes; sourceTree = ""; @@ -287,11 +292,11 @@ DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, + DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD61937A2863876A00E59241 /* Config */, - DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, ); path = Settings; sourceTree = ""; @@ -346,6 +351,7 @@ DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */, DD1925B828CDA93900720036 /* SerialConfigEnums.swift */, DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */, + DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */, ); path = Enums; sourceTree = ""; @@ -494,12 +500,12 @@ children = ( DD47E3D526F17ED900029299 /* CircleText.swift */, DD47E3D826F3093800029299 /* MessageBubble.swift */, - DD90860B26F684AF00DC5189 /* BatteryIcon.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */, DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */, + DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */, ); path = Helpers; sourceTree = ""; @@ -697,21 +703,22 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, - DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */, DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, + DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */, DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, @@ -759,7 +766,8 @@ DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */, + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, + DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, @@ -939,7 +947,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -971,7 +979,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index 403f4f2f..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png deleted file mode 100644 index 6e571624..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index 6e571624..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index a930e23a..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index 35658530..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png deleted file mode 100644 index fff06a76..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png deleted file mode 100644 index 988c8ade..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png deleted file mode 100644 index 553bb7a3..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png deleted file mode 100644 index e78f4105..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png deleted file mode 100644 index ea3829e7..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index ea3829e7..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index f384f426..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index bd7db1b2..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png deleted file mode 100644 index ed42f91e..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png deleted file mode 100644 index ed42f91e..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png deleted file mode 100644 index a3720d5a..00000000 Binary files a/Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png and /dev/null differ diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json index 1cd4ae9f..937092d6 100644 --- a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,111 +1,9 @@ { "images" : [ { - "filename" : "40-2.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "120-1.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "40-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "80-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "filename" : "logo-3.png", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png new file mode 100644 index 00000000..4e2504d4 Binary files /dev/null and b/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png differ diff --git a/Meshtastic/Enums/MessagingEnums.swift b/Meshtastic/Enums/MessagingEnums.swift new file mode 100644 index 00000000..637cfdc6 --- /dev/null +++ b/Meshtastic/Enums/MessagingEnums.swift @@ -0,0 +1,11 @@ +// +// MessagingEnums.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/30/22. +// + +enum BubblePosition { + case left + case right +} diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index accb70a3..e6a8fa66 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -34,14 +34,14 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin } } - } else { + } else if metricsType == 1 { // Create Environment Telemetry Header csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, Voltage, Current" for dm in telemetry{ - if dm.metricsType == 0 { + if dm.metricsType == 1 { csvString += "\n" csvString += String("\(dm.temperature)°") @@ -69,17 +69,26 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { var csvString: String = "" // Create Position Header - csvString = "Latitude, Longitude, Altitude, Timestamp" + csvString = "SeqNo, Latitude, Longitude, Alt, Sats, Speed, Heading, Timestamp" for pos in positions { csvString += "\n" + csvString += String(pos.seqNo) + csvString += ", " csvString += String(pos.latitude ?? 0) csvString += ", " csvString += String(pos.longitude ?? 0) csvString += ", " csvString += String(pos.altitude) csvString += ", " + csvString += String(pos.satsInView) + csvString += ", " + csvString += String(pos.speed) + csvString += ", " + csvString += String(pos.heading) + csvString += ", " + csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 1e378eca..425a9822 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -252,6 +252,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.isConnected = true + if userSettings?.preferredPeripheralId.count ?? 0 < 1 { + self.userSettings?.preferredPeripheralId = peripheral.identifier.uuidString + self.preferredPeripheral = true + } else if userSettings!.preferredPeripheralId == peripheral.identifier.uuidString { + self.preferredPeripheral = true + } else { + print("Trying to connect a non prefered peripheral") + } + // Invalidate and reset connection timer count, remove any connection errors self.lastConnectionError = "" self.timeoutTimerCount = 0 @@ -259,7 +268,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.timeoutTimer!.invalidate() } - + // Map the peripheral to the connectedPeripheral ObservedObjects connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first @@ -731,7 +740,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Get all the channels var i: UInt32 = 1; - var max: Int32 = self.connectedPeripheral.maxChannels + let max: Int32 = self.connectedPeripheral.maxChannels Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in @@ -1146,6 +1155,56 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func sendNodeDBReset(destNum: Int64) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.nodedbReset = 1 + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = 0//UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 1137860a..547b0f62 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -10,6 +10,7 @@ class LocationHelper: NSObject, ObservableObject { static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) static let DefaultHeading = CLLocationDirection(integerLiteral: 0) + static let DefaultTime = Date.init(timeIntervalSince1970: 0) static var currentLocation: CLLocationCoordinate2D { @@ -46,7 +47,7 @@ class LocationHelper: NSObject, ObservableObject { static var currentTimestamp: Date { guard let timestamp = shared.locationManager.location?.timestamp else { - return Date.now + return DefaultTime } return timestamp } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index e5b40a46..cf6f6a14 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -867,10 +867,13 @@ func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedOb if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 { let position = PositionEntity(context: context) + position.seqNo = Int32(nodeInfo.position.seqNumber) position.latitudeI = nodeInfo.position.latitudeI position.longitudeI = nodeInfo.position.longitudeI position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) + position.speed = Int32(nodeInfo.position.groundSpeed) + position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) var newPostions = [PositionEntity]() @@ -1152,11 +1155,20 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb if fetchedNode.count == 1 { let position = PositionEntity(context: context) + + position.seqNo = Int32(positionMessage.seqNumber) position.latitudeI = positionMessage.latitudeI position.longitudeI = positionMessage.longitudeI position.altitude = positionMessage.altitude position.satsInView = Int32(positionMessage.satsInView) - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + position.speed = Int32(positionMessage.groundSpeed) + position.heading = Int32(positionMessage.groundTrack) + + if positionMessage.timestamp != 0 { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) + } else { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet mutablePositions.add(position) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents index a7c26afb..b59226c5 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents @@ -169,9 +169,12 @@ + + + diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 679ebb1d..227f70c4 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -31,7 +31,7 @@ struct MeshtasticAppleApp: App { saveQR = true } - print("User wants to open URL: \(channelUrl?.relativeString)") + print("User wants to open URL: \(String(describing: channelUrl?.relativeString))") } .sheet(isPresented: $saveQR) { diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index c1ef080d..9c0c2e9f 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -13,11 +13,6 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshtasticUsername, forKey: "meshtasticusername") } } - @Published var preferredPeripheralName: String { - didSet { - UserDefaults.standard.set(preferredPeripheralName, forKey: "preferredPeripheralName") - } - } @Published var preferredPeripheralId: String { didSet { UserDefaults.standard.set(preferredPeripheralId, forKey: "preferredPeripheralId") @@ -52,7 +47,6 @@ class UserSettings: ObservableObject { init() { self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" - self.preferredPeripheralName = UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index d5eb57c1..1a554947 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -63,17 +63,10 @@ struct Connect: View { if bleManager.connectedPeripheral != nil { Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion) .font(.caption).foregroundColor(Color.gray) - Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - - - Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) } if bleManager.connectedPeripheral.subscribed { Text("Properly Subscribed").font(.caption) + .foregroundColor(.green) } else { Text("Attempting to connect. . . ").font(.caption) .foregroundColor(.orange) @@ -86,38 +79,37 @@ struct Connect: View { Text("Preferred").font(.caption2) Text("Radio").font(.caption2) - Toggle("Preferred Radio", isOn: $isPreferredRadio) + Toggle("Preferred Radio", isOn: $bleManager.preferredPeripheral) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .labelsHidden() - .onChange(of: isPreferredRadio) { value in + .onChange(of: bleManager.preferredPeripheral) { value in if value { if bleManager.connectedPeripheral != nil { - let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") - userSettings.preferredPeripheralName = deviceName - - } else { - - userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName + + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + bleManager.preferredPeripheral = true + isPreferredRadio = true + } - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - bleManager.preferredPeripheral = true } else { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { userSettings.preferredPeripheralId = "" - userSettings.preferredPeripheralName = "" bleManager.preferredPeripheral = false + isPreferredRadio = false } } } } } + .font(.caption).foregroundColor(Color.gray) + .padding([.top, .bottom]) .swipeActions { Button(role: .destructive) { @@ -129,8 +121,18 @@ struct Connect: View { Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } } - .padding([.top, .bottom]) - + .contextMenu{ + + Text("Num: \(String(bleManager.connectedPeripheral.num))") + Text("Short Name: \(bleManager.connectedPeripheral.shortName)") + Text("Long Name: \(bleManager.connectedPeripheral.longName)") + Text("Unique Code: \(bleManager.connectedPeripheral.lastFourCode)") + Text("Max Channels: \(String(bleManager.connectedPeripheral.maxChannels))") + Text("Bitrate: \(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))") + Text("Ch. Utilization: \(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))") + Text("Air Time: \(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))") + Text("RSSI: \(bleManager.connectedPeripheral.rssi)") + } } else { HStack { @@ -184,7 +186,7 @@ struct Connect: View { } } - HStack(alignment: .center) { + HStack(alignment: .center) { Spacer() diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift new file mode 100644 index 00000000..a68eeec0 --- /dev/null +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -0,0 +1,67 @@ +// +// BatteryGauge.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/28/22. +// + +import SwiftUI +import Charts + +struct BatteryGauge: View { + + @State var batteryLevel = 0.0 + + private let minValue = 1.0 + private let maxValue = 100.00 + + var body: some View { + VStack { + + if batteryLevel == 0.0 { + // Plugged in + Image(systemName: "powerplug") + .font(.largeTitle) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + } else { + + let gradient = Gradient(colors: [.red, .orange, .green]) + Gauge(value: batteryLevel, in: minValue...maxValue) { + if batteryLevel > 1.0 && batteryLevel < 10 { + Label("Battery Level %", systemImage: "battery.0") + } else if batteryLevel >= 10.0 && batteryLevel < 25.00 { + Label("Battery Level %", systemImage: "battery.25") + } else if batteryLevel >= 25.0 && batteryLevel < 50.00 { + Label("Battery Level %", systemImage: "battery.50") + } else if batteryLevel >= 50.0 && batteryLevel < 75.00 { + Label("Battery Level %", systemImage: "battery.75") + } else if batteryLevel >= 75.0 && batteryLevel <= 99.00 { + Label("Battery Level %", systemImage: "battery.100") + } else { + Label("Battery Level %", systemImage: "battery.100.bolt") + } + } currentValueLabel: { + Text(Int(batteryLevel), format: .percent) + } + .tint(gradient) + .gaugeStyle(.accessoryCircular) + } + } + } +} + +struct BatteryGauge_Previews: PreviewProvider { + static var previews: some View { + + VStack { + BatteryGauge(batteryLevel: 0.0) + BatteryGauge(batteryLevel: 9.0) + BatteryGauge(batteryLevel: 24.0) + BatteryGauge(batteryLevel: 49.0) + BatteryGauge(batteryLevel: 74.0) + BatteryGauge(batteryLevel: 99.0) + BatteryGauge(batteryLevel: 100.0) + } + } +} diff --git a/Meshtastic/Views/Helpers/BatteryIcon.swift b/Meshtastic/Views/Helpers/BatteryIcon.swift deleted file mode 100644 index a67e580d..00000000 --- a/Meshtastic/Views/Helpers/BatteryIcon.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI - -struct BatteryIcon: View { - var batteryLevel: Int32? - var font: Font - var color: Color - - var body: some View { - - if batteryLevel == 100 { - - Image(systemName: "battery.100.bolt") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 100 && batteryLevel! > 74 { - - Image(systemName: "battery.75") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 75 && batteryLevel! > 49 { - - Image(systemName: "battery.50") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 50 && batteryLevel! > 14 { - - Image(systemName: "battery.25") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! == 0 { - - Image(systemName: "powerplug") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else { - - Image(systemName: "battery.0") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } - } -} - -struct BatteryIcon_Previews: PreviewProvider { - static var previews: some View { - BatteryIcon(batteryLevel: 100, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 99, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 74, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 49, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 14, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - } -} diff --git a/Meshtastic/Views/Helpers/DistanceText.swift b/Meshtastic/Views/Helpers/DistanceText.swift index 1709b5f4..59d58bb8 100644 --- a/Meshtastic/Views/Helpers/DistanceText.swift +++ b/Meshtastic/Views/Helpers/DistanceText.swift @@ -16,7 +16,19 @@ struct DistanceText: View { var body: some View { let distanceFormatter = MKDistanceFormatter() - Text("Distance: \(distanceFormatter.string(fromDistance: Double(meters)))") } } +struct DistanceText_Previews: PreviewProvider { + static var previews: some View { + + VStack { + + DistanceText(meters: 100) + DistanceText(meters: 1000) + DistanceText(meters: 10000) + DistanceText(meters: 100000) + DistanceText(meters: 1000000) + } + } +} diff --git a/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift new file mode 100644 index 00000000..5cb63190 --- /dev/null +++ b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift @@ -0,0 +1,38 @@ +// +// MessageTemplate.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/18/22. +// +import SwiftUI + +struct MessageTemplate: View { + + var user: UserEntity + var message: MessageEntity + var messageReply: MessageEntity? + + var body: some View { + + // Display the message being replied to and the arrow + if message.replyID > 0 { + + HStack { + + Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2) + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.blue, lineWidth: 0.5) + ) + Image(systemName: "arrowshape.turn.up.left.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.blue) + .padding(.trailing) + } + } + + // Message + + } +} diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index a9995e14..6edae7cf 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -359,6 +359,7 @@ struct UserMessageList: View { .listRowSeparator(.hidden) } } + .scrollDismissesKeyboard(.immediately) .onAppear(perform: { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift new file mode 100644 index 00000000..77c6dfd9 --- /dev/null +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -0,0 +1,153 @@ +// +// DeviceMetricsLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI +import Charts + +struct DeviceMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + struct MountPrice: Identifiable { + var id = UUID() + var mount: String + var value: Double + } + + var body: some View { + + NavigationStack { + + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) + + let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) + + if data.count > 0 { + + GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) { + + Chart(data.array as! [TelemetryEntity], id: \.self) { + LineMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + } + .frame(height: 150) + } + } + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Batt") + .font(.callout) + .fontWeight(.bold) + Text("Voltage") + .font(.callout) + .fontWeight(.bold) + Text("ChUtil") + .font(.callout) + .fontWeight(.bold) + Text("AirTm") + .font(.callout) + .fontWeight(.bold) + Text("Timestamp") + .font(.callout) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (dm: TelemetryEntity) in + + if dm.metricsType == 0 { + + GridRow { + + if dm.batteryLevel == 0 { + + Text("USB") + .font(.callout) + + } else { + + Text("\(String(dm.batteryLevel))%") + .font(.callout) + } + + Text(String(dm.voltage)) + .font(.callout) + Text("\(String(format: "%.2f", dm.channelUtilization))%") + .font(.callout) + Text("\(String(format: "%.2f", dm.airUtilTx))%") + .font(.callout) + Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.callout) + } + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .navigationTitle("Device Metrics Log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user!.longName ?? "Node") Device Telemetry Log"), + onCompletion: { result in + + if case .success = result { + + print("Device Telemetry log download succeeded.") + + self.isExporting = false + + } else { + + print("Device Telemetry log download failed: \(result).") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift new file mode 100644 index 00000000..6b17cbbc --- /dev/null +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -0,0 +1,127 @@ +// +// DeviceMetricsLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI + +struct EnvironmentMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Temp") + .font(.caption) + .fontWeight(.bold) + Text("Hum") + .font(.caption) + .fontWeight(.bold) + Text("Bar") + .font(.caption) + .fontWeight(.bold) + Text("Gas") + .font(.caption) + .fontWeight(.bold) + Text("DC") + .font(.caption) + .fontWeight(.bold) + Text("Volt") + .font(.caption) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (em: TelemetryEntity) in + + if em.metricsType == 1 { + + let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" + + GridRow { + + Text("\(String(format: "%.2f", em.temperature))\(tempReadingType)") + .font(.caption) + Text("\(String(format: "%.2f", em.relativeHumidity))") + .font(.caption) + Text("\(String(format: "%.2f", em.barometricPressure))") + .font(.caption) + Text("\(String(format: "%.2f", em.gasResistance))") + .font(.caption) + Text("\(String(format: "%.2f", em.current))") + .font(.caption) + Text("\(String(format: "%.2f", em.voltage))") + .font(.caption) + Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption) + } + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .navigationTitle("Environment Metrics Log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"), + onCompletion: { result in + + if case .success = result { + + print("Environment metrics log download succeeded.") + + self.isExporting = false + + } else { + + print("Environment metrics log download failed: \(result).") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/LocationHistory.swift deleted file mode 100644 index 52165e2d..00000000 --- a/Meshtastic/Views/Nodes/LocationHistory.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// LocationHistory.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 7/5/22. -// -import SwiftUI - -struct LocationHistory: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @State var isExporting = false - @State var exportString = "" - - var node: NodeInfoEntity - - var body: some View { - - VStack { - - List { - - ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in - - VStack { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - HStack { - - Image(systemName: "mappin.and.ellipse") - .foregroundColor(.accentColor) - .font(.callout) - - Text("Lat/Long:").font(.callout) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.callout) - - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.callout) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .font(.callout) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.callout) - - } - - } else { - - HStack { - - Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) - Text("Lat/Long:").font(.caption) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.caption) - - } - - HStack { - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.caption) - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.caption) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.caption) - } - } - } - } - } - Button { - - exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) - isExporting = true - - } label: { - - Label("Save", systemImage: "square.and.arrow.down") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), - onCompletion: { result in - - if case .success = result { - - print("Position log download succeeded.") - self.isExporting = false - - } else { - - print("Position log download failed: \(result).") - } - } - ) - .navigationTitle("Location History \(node.positions?.count ?? 0) Points") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } - } -} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 80b9ccbb..43d9eb7e 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -12,10 +12,12 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var showingDetailsPopover = false + @State var initialLoad: Bool = true @State var satsInView = 0 - @State private var isPresentingShutdownConfirm: Bool = false - @State private var isPresentingRebootConfirm: Bool = false + @State private var showingShutdownConfirm: Bool = false + @State private var showingRebootConfirm: Bool = false var node: NodeInfoEntity @@ -23,7 +25,7 @@ struct NodeDetail: View { let hwModelString = node.user?.hwModel ?? "UNSET" - ZStack { + NavigationStack { GeometryReader { bounds in @@ -63,95 +65,25 @@ struct NodeDetail: View { } ) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.6) + .ignoresSafeArea(.all, edges: [.leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.70) } } Text("Sats: \(mostRecent.satsInView)").offset( y:-40) } else { HStack { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(10) - .frame(width: bounds.size.width, height: bounds.size.height / 2.3) - .padding([.top], 40) + } - .offset( y:-40) + .padding([.top], 40) } ScrollView { - - if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - - Divider() - HStack { - - if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { - - Button(action: { - - isPresentingShutdownConfirm = true - }) { - - Label("Power Off", systemImage: "power") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingShutdownConfirm - ) { - Button("Shutdown Node?", role: .destructive) { - - if !bleManager.sendShutdown(destNum: node.num) { - - print("Shutdown Failed") - } - } - } - } - - Button(action: { - - isPresentingRebootConfirm = true - - }) { - - Label("Reboot", systemImage: "arrow.triangle.2.circlepath") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - - "Are you sure?", - isPresented: $isPresentingRebootConfirm - ) { - - Button("Reboot Node?", role: .destructive) { - - if !bleManager.sendReboot(destNum: node.num) { - - print("Reboot Failed") - } - } - } - } - .padding(5) - } + + Divider() if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - // Add a divider if there is no map - if (node.positions?.count ?? 0) == 0 { - - Divider() - } + HStack { @@ -159,7 +91,7 @@ struct NodeDetail: View { Text("AKA").font(.largeTitle) .foregroundColor(.gray).fixedSize() - .offset(y:20) + .offset(y:5) CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) } .padding() @@ -172,8 +104,8 @@ struct NodeDetail: View { Image(hwModelString) .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 90, height: 90) + .aspectRatio(contentMode: .fill) + .frame(width: 200, height: 200) .cornerRadius(5) Text(String(hwModelString)) @@ -198,7 +130,11 @@ struct NodeDetail: View { .font(.largeTitle) .foregroundColor(.gray) .fixedSize() + + } + + } if node.telemetries?.count ?? 0 >= 1 { @@ -206,33 +142,26 @@ struct NodeDetail: View { let mostRecent = node.telemetries?.lastObject as! TelemetryEntity Divider() - + VStack(alignment: .center) { - - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .largeTitle, color: .accentColor) - .padding(.bottom, 10) - if mostRecent.batteryLevel > 0 { - Text(String(mostRecent.batteryLevel) + "%") - .font(.largeTitle) - .frame(width: 100) - .foregroundColor(.gray) - .fixedSize() - } + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) + if mostRecent.voltage > 0 { - + Text(String(format: "%.2f", mostRecent.voltage) + " V") - .font(.largeTitle) + .font(.title) .foregroundColor(.gray) .fixedSize() } } .padding() } - Divider() + } .padding() + Divider() HStack(alignment: .center) { VStack { @@ -293,11 +222,9 @@ struct NodeDetail: View { HStack { VStack(alignment: .center) { - Text("AKA").font(.title2).fixedSize() + CircleText(text: node.user?.shortName ?? "???", color: .accentColor) - .offset(y: 10) } - .padding(5) Divider() @@ -342,31 +269,23 @@ struct NodeDetail: View { VStack(alignment: .center) { - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) - .padding(.bottom) + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) - if mostRecent.batteryLevel > 0 { - Text(String(mostRecent.batteryLevel) + "%") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } if mostRecent.voltage > 0 { Text(String(format: "%.2f", mostRecent.voltage) + " V") - .font(.title3) + .font(.callout) .foregroundColor(.gray) .fixedSize() } + } - .padding(5) } } - .padding(4) - Divider() - HStack(alignment: .center) { + + VStack { HStack { Image(systemName: "person") @@ -399,70 +318,148 @@ struct NodeDetail: View { Text("MAC Address: ") Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } + .padding([.bottom], 0) + Divider() } - List { + VStack { if (node.positions?.count ?? 0) > 0 { + + NavigationLink { - LocationHistory(node: node) + PositionLog(node: node) } label: { - + Image(systemName: "building.columns") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Position History \(node.positions?.count ?? 0) Points") + + Text("Position Log (\(node.positions?.count ?? 0) Points)") .font(.title3) } .fixedSize(horizontal: false, vertical: true) + Divider() } + if (node.telemetries?.count ?? 0) > 0 { + NavigationLink { - TelemetryLog(node: node) + DeviceMetricsLog(node: node) } label: { - + + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Device Metrics Log") + .font(.title3) + } + Divider() + NavigationLink { + EnvironmentMetricsLog(node: node) + } label: { + Image(systemName: "chart.xyaxis.line") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + + Text("Environment Metrics Log") .font(.title3) } - .fixedSize(horizontal: false, vertical: true) + Divider() } } - .listStyle(GroupedListStyle()) - .frame(minHeight: 170) - .padding(0) + + if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { + + HStack { + + if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { + + Button(action: { + + showingShutdownConfirm = true + }) { + + Label("Power Off", systemImage: "power") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $showingShutdownConfirm + ) { + Button("Shutdown Node?", role: .destructive) { + + if !bleManager.sendShutdown(destNum: node.num) { + + print("Shutdown Failed") + } + } + } + } + + Button(action: { + + showingRebootConfirm = true + + }) { + + Label("Reboot", systemImage: "arrow.triangle.2.circlepath") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $showingRebootConfirm + ) { + + Button("Reboot Node?", role: .destructive) { + + if !bleManager.sendReboot(destNum: node.num) { + + print("Reboot Failed") + } + } + } + } + .padding(5) + } } - .offset( y: (node.myInfo?.hasGps ?? false ? 0 : -40)) + .offset( y:-40) + .padding(.bottom, -40) } .edgesIgnoringSafeArea([.leading, .trailing]) + .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") + .navigationBarTitleDisplayMode(.inline) + .padding(.bottom, 10) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + } + ) + .onAppear { + + if self.initialLoad{ + + self.bleManager.context = context + self.initialLoad = false + } + } } } - .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - } - ) - .onAppear { - - if self.initialLoad{ - - self.bleManager.context = context - self.initialLoad = false - } - } - } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 80239cf4..2ab5327a 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -20,35 +20,35 @@ struct NodeList: View { @State var initialLoad: Bool = true @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "user.shortName", ascending: true)], + sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) private var nodes: FetchedResults - @State private var selection: String? = "" + @State private var selection: NodeInfoEntity? = nil // Nothing selected by default. var body: some View { - - NavigationView { - - List { + + NavigationSplitView { + + List (nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { - Text("Scan for Radios").font(.largeTitle) - Text("No Meshtastic Nodes Found").font(.title2) - Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") - .font(.body) - Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") - Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.") + Text("Scan for Radios").font(.largeTitle) + Text("No Meshtastic Nodes Found").font(.title2) + Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") + .font(.body) + Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") + Text("Views with bluetooth functionality will show an indicator in the upper right hand corner showing if bluetooth is on, and if a device is connected.") .listRowSeparator(.visible) - } else { - ForEach( nodes ) { node in + } else { + //ForEach( nodes ) { node in - let index = nodes.firstIndex(where: { $0.id == node.id }) + // let index = nodes.firstIndex(where: { $0.id == node.id }) - NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { + NavigationLink(value: node) { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == node.num) @@ -123,11 +123,10 @@ struct NodeList: View { } .padding([.leading, .top, .bottom]) } - .isDetailLink(false) - } - } - } - .navigationTitle("All Nodes") + //} + } + } + .navigationTitle("All Nodes") .onAppear { if initialLoad { @@ -137,8 +136,16 @@ struct NodeList: View { self.initialLoad = false } } - } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .navigationViewStyle(DoubleColumnNavigationViewStyle()) + } detail: { + + if let node = selection { + + NodeDetail(node:node) + + } else { + + Text("Select a node") + } + } } } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift new file mode 100644 index 00000000..9602514d --- /dev/null +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -0,0 +1,117 @@ +// +// LocationHistory.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/5/22. +// +import SwiftUI + +struct PositionLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + //Add a table for mac and ipad + } + + GridRow { + + Text("Lat / Long") + .font(.caption2) + .fontWeight(.bold) + Text("Sat") + .font(.caption2) + .fontWeight(.bold) + Text("Alt") + .font(.caption2) + .fontWeight(.bold) + Text("Spd") + .font(.caption2) + .fontWeight(.bold) + Text("Hd") + .font(.caption2) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption2) + .fontWeight(.bold) + } + Divider() + ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + GridRow { + Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") + .font(.caption2) + Text(String(mappin.satsInView)) + .font(.caption2) + Text(String(mappin.altitude)) + .font(.caption2) + Text(String(mappin.speed)) + .font(.caption2) + Text(String(mappin.heading)) + .font(.caption2) + Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption2) + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + Button { + + exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), + onCompletion: { result in + + if case .success = result { + + print("Position log download succeeded.") + self.isExporting = false + + } else { + + print("Position log download failed: \(result).") + } + } + ) + .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + } +} diff --git a/Meshtastic/Views/Nodes/TelemetryLog.swift b/Meshtastic/Views/Nodes/TelemetryLog.swift deleted file mode 100644 index b43cfc09..00000000 --- a/Meshtastic/Views/Nodes/TelemetryLog.swift +++ /dev/null @@ -1,396 +0,0 @@ -// -// TelemetryLog.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 7/7/22. -// -import SwiftUI - -struct TelemetryLog: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - - @State var isExporting = false - @State var exportString = "" - - var node: NodeInfoEntity - - var body: some View { - - List { - - ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (tel: TelemetryEntity) in - - VStack (alignment: .leading) { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - - if tel.metricsType == 0 { - - // Device Metrics - HStack { - - Text("Device Metrics") - .font(.title) - - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - - } else if tel.metricsType == 1 { - - // Environment Metrics - HStack { - - Text("Environment Metrics") - .font(.title) - - - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.barometricPressure > 0 { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.gasResistance > 0 { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.current > 0 { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - - } - } - - } else { - - if tel.metricsType == 0 { - - // Device Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Device Metrics") - .font(.title3) - Spacer() - } - - - HStack { - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - } - HStack { - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - HStack { - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - } - } else if tel.metricsType == 1 { - - // Environment Metrics - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? true)) ? "°C" : "°F" - - - // Environment Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Environment Metrics") - .font(.title3) - Spacer() - } - - HStack { - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.current > 0 { - - HStack { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - } - - HStack { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.barometricPressure > 0 { - - HStack { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.gasResistance > 0 { - - HStack { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - } - } - } - } - } - } - Button { - - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) - isExporting = true - - } label: { - - Label("Save", systemImage: "square.and.arrow.down") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .navigationTitle("Telemetry Log \(node.telemetries?.count ?? 0) Readings") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(node.user!.longName ?? "Node") Telemetry Log"), - onCompletion: { result in - - if case .success = result { - - print("Telemetry log download succeeded.") - - self.isExporting = false - - } else { - - print("Telemetry log download failed: \(result).") - } - } - ) - } -} diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 5bce4f0b..c56fbe46 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -105,18 +105,18 @@ struct AppSettings: View { .disableAutocorrection(true) .listRowSeparator(.visible) - HStack { - Label("Radio", systemImage: "flipphone") - Text(userSettings.preferredPeripheralName) - .foregroundColor(.gray) - - } - Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") - .font(.caption) - .listRowSeparator(.hidden) - Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.") - .font(.caption2) - .foregroundColor(.gray) +// HStack { +// Label("Radio", systemImage: "flipphone") +// Text(userSettings.preferredPeripheralName) +// .foregroundColor(.gray) +// +// } +// Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") +// .font(.caption) +// .listRowSeparator(.hidden) +// Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.") +// .font(.caption2) +// .foregroundColor(.gray) } Section(header: Text("Options")) { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 2ac06bac..86911931 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -54,9 +54,58 @@ struct DeviceConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } + } .disabled(bleManager.connectedPeripheral == nil) + HStack { + + Button("Reset NodeDB", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm, + titleVisibility: .visible + ) { + Button("Erase the NodeDB from node and app?", role: .destructive) { + + if !bleManager.sendNodeDBReset(destNum: bleManager.connectedPeripheral.num) { + + print("NodeDB Reset Failed") + } + } + } + Button("Factory Reset", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm, + titleVisibility: .visible + ) { + Button("Erase all device settings?", role: .destructive) { + + if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) { + + print("Factory Reset Failed") + } + } + } + } + HStack { Button { @@ -102,28 +151,6 @@ struct DeviceConfig: View { Text("After device config saves the node will reboot.") } - - 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) { - - print("Factory Reset Failed") - } - } - } } Spacer() } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index ea86a66f..622dd08b 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -205,6 +205,7 @@ struct CannedMessagesConfig: View { } .disabled(configPreset > 0) } + .scrollDismissesKeyboard(.immediately) .disabled(bleManager.connectedPeripheral == nil) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 7e987ca4..8d103493 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -80,8 +80,9 @@ struct MQTTConfig: View { hasChanges = true }) .foregroundColor(.gray) + .keyboardType(.default) } - .keyboardType(.default) + .autocorrectionDisabled() HStack { Label("Username", systemImage: "person.text.rectangle") @@ -109,6 +110,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) HStack { @@ -137,8 +139,10 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index c015b307..a3cce9f8 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -112,7 +112,7 @@ struct TelemetryConfig: View { Section(header: Text("Sensor Options")) { - Text("I2C Connected sensors will be detected automatically. Supported sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") + Text("Supported I2C Connected sensors will be detected automatically, sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") .font(.caption) Toggle(isOn: $environmentMeasurementEnabled) { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 707fea69..89ae09fb 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -104,6 +104,7 @@ struct NetworkConfig: View { .keyboardType(.default) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 24b2e00c..2c335e4f 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -13,7 +13,7 @@ struct PositionFlags: OptionSet static let Altitude = PositionFlags(rawValue: 1) static let AltitudeMsl = PositionFlags(rawValue: 2) - static let GeoSep = PositionFlags(rawValue: 4) + static let GeoidalSeparation = PositionFlags(rawValue: 4) static let Dop = PositionFlags(rawValue: 8) static let Hvdop = PositionFlags(rawValue: 16) static let Satsinview = PositionFlags(rawValue: 32) @@ -48,7 +48,7 @@ struct PositionConfig: View { /// Altitude value is MSL - 2 @State var includeAltitudeMsl = false /// Include geoidal separation - 4 - @State var includeGeoSep = false + @State var includeGeoidalSeparation = false /// Include the DOP value ; PDOP used by default, see below - 8 @State var includeDop = false /// If POS_DOP set, send separate HDOP / VDOP values instead of PDOP - 16 @@ -56,7 +56,7 @@ struct PositionConfig: View { /// Include number of "satellites in view" - 32 @State var includeSatsinview = false /// Include a sequence number incremented per packet - 64 - @State var includeSeqNos = false + @State var includeSeqNo = false /// Include positional timestamp (from GPS solution) - 128 @State var includeTimestamp = false /// Include positional heading - 256 @@ -162,7 +162,7 @@ struct PositionConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle(isOn: $includeSeqNos) { //64 + Toggle(isOn: $includeSeqNo) { //64 Label("Sequence number", systemImage: "number") } @@ -188,7 +188,7 @@ struct PositionConfig: View { } Section(header: Text("Advanced Position Flags")) { - Toggle(isOn: $includeGeoSep) { + Toggle(isOn: $includeGeoidalSeparation) { Text("Geoidal Seperation") } @@ -242,11 +242,11 @@ struct PositionConfig: View { if includeAltitude { pf.insert(.Altitude) } if includeAltitudeMsl { pf.insert(.AltitudeMsl) } - if includeGeoSep { pf.insert(.GeoSep) } + if includeGeoidalSeparation { pf.insert(.GeoidalSeparation) } if includeDop { pf.insert(.Dop) } if includeHvdop { pf.insert(.Hvdop) } if includeSatsinview { pf.insert(.Satsinview) } - if includeSeqNos { pf.insert(.SeqNos) } + if includeSeqNo { pf.insert(.SeqNos) } if includeTimestamp { pf.insert(.Timestamp) } if includeSpeed { pf.insert(.Speed) } if includeHeading { pf.insert(.Heading) } @@ -296,11 +296,11 @@ struct PositionConfig: View { if pf.contains(.Altitude) { self.includeAltitude = true } else { self.includeAltitude = false } if pf.contains(.AltitudeMsl) { self.includeAltitudeMsl = true } else { self.includeAltitudeMsl = false } - if pf.contains(.GeoSep) { self.includeGeoSep = true } else { self.includeGeoSep = false } + if pf.contains(.GeoidalSeparation) { self.includeGeoidalSeparation = true } else { self.includeGeoidalSeparation = false } if pf.contains(.Dop) { self.includeDop = true } else { self.includeDop = false } if pf.contains(.Hvdop) { self.includeHvdop = true } else { self.includeHvdop = false } if pf.contains(.Satsinview) { self.includeSatsinview = true } else { self.includeSatsinview = false } - if pf.contains(.SeqNos) { self.includeSeqNos = true } else { self.includeSeqNos = false } + if pf.contains(.SeqNos) { self.includeSeqNo = true } else { self.includeSeqNo = false } if pf.contains(.Timestamp) { self.includeTimestamp = true } else { self.includeTimestamp = false } if pf.contains(.Speed) { self.includeSpeed = true } else { self.includeSpeed = false } if pf.contains(.Heading) { self.includeHeading = true } else { self.includeHeading = false } @@ -352,7 +352,7 @@ struct PositionConfig: View { if newPositionBroadcastSeconds != node!.positionConfig!.positionBroadcastSeconds { hasChanges = true } } } - .onChange(of: includeAltitude || includeAltitudeMsl || includeGeoSep || includeDop || includeHvdop || includeSatsinview || includeSeqNos || includeTimestamp || includeSpeed || includeHeading) { newFlags in + .onChange(of: includeAltitude || includeAltitudeMsl || includeGeoidalSeparation || includeDop || includeHvdop || includeSatsinview || includeSeqNo || includeTimestamp || includeSpeed || includeHeading) { newFlags in // hasChanges = true } .navigationViewStyle(StackNavigationViewStyle()) diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index 1728f5ee..a2db3e19 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -17,11 +17,33 @@ struct MeshLog: View { let url = logFile! logs.removeAll() - for try await log in url.lines { - logs.append(log) - document.logFile.append("\(log) \n") + + var lineCount = 0 + let lineLimit = 500 + + // Get the number of lines + for try await _ in url.lines { + lineCount += 1 } - logs.reverse() + + // Set the record to start with if we have more lines than the limit + var startingLog = 0 + if lineCount > lineLimit { + startingLog = lineCount - lineLimit + } + + var lineNumber = 0 + + for try await log in url.lines { + if lineNumber >= startingLog { + + logs.append(log) + document.logFile.append("\(log) \n") + } + lineNumber += 1 + } + logs.reverse() + } catch { // Stop adding logs when an error is thrown } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 82dbb747..ea89cb6d 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -35,9 +35,18 @@ struct ShareChannels: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings + @State var includeChannel0 = true + @State var includeChannel1 = true + @State var includeChannel2 = true + @State var includeChannel3 = false + @State var includeChannel4 = false + @State var includeChannel5 = false + @State var includeChannel6 = false + @State var includeChannel7 = true + var node: NodeInfoEntity? - @State private var text = "https://meshtastic.org/E/#test" + @State private var channelsUrl = "https://meshtastic.org/e/#test" var qrCodeImage = QrCodeImage() var body: some View { @@ -51,54 +60,114 @@ struct ShareChannels: View { ScrollView { VStack { - Text("Scan the QR code below with the Apple or Android device you would like to share your channel settings with.") - .fixedSize(horizontal: false, vertical: true) - .font(.callout) - - let image = qrCodeImage.generateQRCode(from: text) - Image(uiImage: image) - .resizable() - .scaledToFit() - .frame( - minWidth: smallest * 0.8, - maxWidth: smallest * 0.8, - minHeight: smallest * 0.8, - maxHeight: smallest * 0.8, - alignment: .center - ) - - if node != nil && node!.loRaConfig != nil { + if node != nil { - HStack { + Grid(alignment: .top, horizontalSpacing: 2) { - let preset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset)) - Text("Modem Preset \(preset!.description)").font(.title3) - } - } - VStack { - - Text("Number of Channels: \(node?.myInfo?.maxChannels ?? 0)").font(.title2) - - if node != nil { + GridRow { + Spacer() + Text("Include") + .font(.caption) + .fontWeight(.bold) + Text("Name") + .font(.caption) + .fontWeight(.bold) + Spacer() + } + Divider() ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - - VStack { - - - Text("Channel: \(channel.index) Name: \(channel.name ?? "")") + + GridRow { + Spacer() + if channel.index == 0 { + Toggle("Channel 0 Included", isOn: $includeChannel0) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + Text("Primary Channel") + + } else if channel.index == 1 { + Toggle("Channel 1 Included", isOn: $includeChannel1) + .toggleStyle(.switch) + .labelsHidden() + Text("Public Channel") + } else if channel.index == 2 { + Toggle("Channel 2 Included", isOn: $includeChannel2) + .toggleStyle(.switch) + .labelsHidden() + } else if channel.index == 3 { + Toggle("Channel 3 Included", isOn: $includeChannel3) + .toggleStyle(.switch) + .labelsHidden() + } else if channel.index == 4 { + Toggle("Channel 4 Included", isOn: $includeChannel4) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 5 { + Toggle("Channel 5 Included", isOn: $includeChannel5) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 6 { + Toggle("Channel 6 Included", isOn: $includeChannel6) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 7 { + Toggle("Channel 7 Included", isOn: $includeChannel7) + .toggleStyle(.switch) + .labelsHidden() + Text("Admin Channel") + } + if channel.index > 1 && channel.index < 4{ + Text("Private Chat - \(channel.index)") + } + if channel.index > 3 && channel.index < 7{ + Text("Channel - \(channel.index)") + } + Spacer() } } } } - .frame(width: bounds.size.width, height: bounds.size.height) + } + let qrImage = qrCodeImage.generateQRCode(from: channelsUrl) + + VStack { + + Divider() + + ShareLink("Share QR Code & Link", + item: Image(uiImage: qrImage), + subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"), + message: Text(channelsUrl), + preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you", + image: Image(uiImage: qrImage)) + ) + .presentationDetents([.large, .large]) + + Divider() + + Image(uiImage: qrImage) + .resizable() + .scaledToFit() + .frame( + minWidth: smallest * 0.7, + maxWidth: smallest * 0.7, + minHeight: smallest * 0.7, + maxHeight: smallest * 0.7, + alignment: .top + ) + } } - .navigationTitle("Share Channel") - .navigationBarTitleDisplayMode(.automatic) + .navigationTitle("Generate QR Code") + .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - ZStack { + ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") })