Fix app icons, refactor device pages to node pages based on new data models

This commit is contained in:
Garth Vander Houwen 2021-09-18 15:33:35 -07:00
parent ccf5bf1610
commit f4e52d3799
55 changed files with 549 additions and 534 deletions

View file

@ -12,7 +12,13 @@
DD47E3D026F1073F00029299 /* NodeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CF26F1073F00029299 /* NodeRow.swift */; };
DD47E3D226F1210600029299 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D126F1210600029299 /* HelperFunctions.swift */; };
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; };
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D826F3093800029299 /* MessageBubble.swift */; };
DD47E3DB26F3901B00029299 /* MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DA26F3901A00029299 /* MessageList.swift */; };
DD47E3DD26F390A000029299 /* MessageDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DC26F390A000029299 /* MessageDetail.swift */; };
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */; };
DD7AA3F326F05C120077AF76 /* NodeInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */; };
DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; };
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; };
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; };
DDAF8C5B26ED08D30058C060 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF8C5A26ED08D30058C060 /* SwiftProtobuf */; };
@ -33,10 +39,6 @@
DDC2E17A26CE248F0042C5E4 /* MeshtasticClientUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E17926CE248F0042C5E4 /* MeshtasticClientUITests.swift */; };
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */; };
DDC2E19126CE26290042C5E4 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19026CE26290042C5E4 /* Messages.swift */; };
DDC2E19326CE266B0042C5E4 /* DeviceHome.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19226CE266B0042C5E4 /* DeviceHome.swift */; };
DDC2E19526CE26760042C5E4 /* DeviceDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19426CE26760042C5E4 /* DeviceDetail.swift */; };
DDC2E19726CE26840042C5E4 /* DeviceRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19626CE26840042C5E4 /* DeviceRow.swift */; };
DDC2E19926CE26940042C5E4 /* DeviceMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19826CE26940042C5E4 /* DeviceMap.swift */; };
DDC2E19B26CE27150042C5E4 /* CircleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19A26CE27150042C5E4 /* CircleImage.swift */; };
DDC2E19D26CE27580042C5E4 /* ModelData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19C26CE27580042C5E4 /* ModelData.swift */; };
DDC2E19F26CE27630042C5E4 /* Device.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E19E26CE27630042C5E4 /* Device.swift */; };
@ -69,8 +71,15 @@
DD47E3CF26F1073F00029299 /* NodeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeRow.swift; sourceTree = "<group>"; };
DD47E3D126F1210600029299 /* HelperFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = "<group>"; };
DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = "<group>"; };
DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = "<group>"; };
DD47E3DA26F3901A00029299 /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = "<group>"; };
DD47E3DC26F390A000029299 /* MessageDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDetail.swift; sourceTree = "<group>"; };
DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = "<group>"; };
DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoModel.swift; sourceTree = "<group>"; };
DD7AA3F426F05D660077AF76 /* nodeInfoModel.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = nodeInfoModel.json; sourceTree = "<group>"; };
DD90860A26F645B700DC5189 /* MeshtasticClient.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MeshtasticClient.entitlements; sourceTree = "<group>"; };
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = "<group>"; };
DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = "<group>"; };
DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = "<group>"; };
@ -98,10 +107,6 @@
DDC2E18A26CE25690042C5E4 /* deviceData.json */ = {isa = PBXFileReference; explicitFileType = text.json; fileEncoding = 4; path = deviceData.json; sourceTree = "<group>"; };
DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
DDC2E19026CE26290042C5E4 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = "<group>"; };
DDC2E19226CE266B0042C5E4 /* DeviceHome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHome.swift; sourceTree = "<group>"; };
DDC2E19426CE26760042C5E4 /* DeviceDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceDetail.swift; sourceTree = "<group>"; };
DDC2E19626CE26840042C5E4 /* DeviceRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRow.swift; sourceTree = "<group>"; };
DDC2E19826CE26940042C5E4 /* DeviceMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMap.swift; sourceTree = "<group>"; };
DDC2E19A26CE27150042C5E4 /* CircleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = "<group>"; };
DDC2E19C26CE27580042C5E4 /* ModelData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelData.swift; sourceTree = "<group>"; };
DDC2E19E26CE27630042C5E4 /* Device.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = "<group>"; };
@ -142,10 +147,18 @@
DD47E3CB26F0E51D00029299 /* NodeDetail.swift */,
DD47E3CD26F103C600029299 /* NodeList.swift */,
DD47E3CF26F1073F00029299 /* NodeRow.swift */,
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
);
path = Nodes;
sourceTree = "<group>";
};
DD47E3D726F2F21A00029299 /* BlueTooth */ = {
isa = PBXGroup;
children = (
);
path = BlueTooth;
sourceTree = "<group>";
};
DDAF8C5626ED07740058C060 /* Protobufs */ = {
isa = PBXGroup;
children = (
@ -185,6 +198,7 @@
DDC2E15626CE248E0042C5E4 /* MeshtasticClient */ = {
isa = PBXGroup;
children = (
DD90860A26F645B700DC5189 /* MeshtasticClient.entitlements */,
DDAF8C5626ED07740058C060 /* Protobufs */,
DDC2E1A526CEB32B0042C5E4 /* Helpers */,
DDC2E18726CE24E40042C5E4 /* Views */,
@ -227,6 +241,7 @@
DDC2E18726CE24E40042C5E4 /* Views */ = {
isa = PBXGroup;
children = (
DD47E3D726F2F21A00029299 /* BlueTooth */,
DD47E3CA26F0E50300029299 /* Nodes */,
DDC2E18C26CE25B00042C5E4 /* Devices */,
DDC2E18B26CE25A70042C5E4 /* Messages */,
@ -243,6 +258,7 @@
DDC2E19E26CE27630042C5E4 /* Device.swift */,
DDAF8C6F26ED1DD20058C060 /* Device2.swift */,
DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */,
DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */,
);
path = Model;
sourceTree = "<group>";
@ -262,6 +278,8 @@
isa = PBXGroup;
children = (
DDC2E19026CE26290042C5E4 /* Messages.swift */,
DD47E3DA26F3901A00029299 /* MessageList.swift */,
DD47E3DC26F390A000029299 /* MessageDetail.swift */,
);
path = Messages;
sourceTree = "<group>";
@ -269,10 +287,6 @@
DDC2E18C26CE25B00042C5E4 /* Devices */ = {
isa = PBXGroup;
children = (
DDC2E19226CE266B0042C5E4 /* DeviceHome.swift */,
DDC2E19426CE26760042C5E4 /* DeviceDetail.swift */,
DDC2E19626CE26840042C5E4 /* DeviceRow.swift */,
DDC2E19826CE26940042C5E4 /* DeviceMap.swift */,
DDC2E1A326CE2F940042C5E4 /* DeviceBLE.swift */,
);
path = Devices;
@ -283,6 +297,8 @@
children = (
DDC2E19A26CE27150042C5E4 /* CircleImage.swift */,
DD47E3D526F17ED900029299 /* CircleText.swift */,
DD47E3D826F3093800029299 /* MessageBubble.swift */,
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -442,28 +458,30 @@
DD7AA3F326F05C120077AF76 /* NodeInfoModel.swift in Sources */,
DDC2E19F26CE27630042C5E4 /* Device.swift in Sources */,
DD47E3D226F1210600029299 /* HelperFunctions.swift in Sources */,
DDC2E19926CE26940042C5E4 /* DeviceMap.swift in Sources */,
DDC2E19526CE26760042C5E4 /* DeviceDetail.swift in Sources */,
DDAF8C5F26ED09B50058C060 /* radioconfig.pb.swift in Sources */,
DDC2E19726CE26840042C5E4 /* DeviceRow.swift in Sources */,
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */,
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */,
DDC2E19126CE26290042C5E4 /* Messages.swift in Sources */,
DD47E3DB26F3901B00029299 /* MessageList.swift in Sources */,
DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */,
DDC2E19D26CE27580042C5E4 /* ModelData.swift in Sources */,
DDAF8C6B26ED0DD80058C060 /* environmental_measurement.pb.swift in Sources */,
DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */,
DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */,
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */,
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
DD47E3D026F1073F00029299 /* NodeRow.swift in Sources */,
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
DDC2E19326CE266B0042C5E4 /* DeviceHome.swift in Sources */,
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
DDC2E19B26CE27150042C5E4 /* CircleImage.swift in Sources */,
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */,
DDC2E1A426CE2F940042C5E4 /* DeviceBLE.swift in Sources */,
DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */,
DD47E3DD26F390A000029299 /* MessageDetail.swift in Sources */,
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -621,6 +639,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\"";
DEVELOPMENT_TEAM = GCH7VS5Y9R;
@ -631,8 +650,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.04;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -643,6 +664,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\"";
DEVELOPMENT_TEAM = GCH7VS5Y9R;
@ -653,8 +675,10 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.04;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,97 +1,109 @@
{
"images" : [
{
"filename" : "40-2.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "play_store_icon_114px-4.png",
"filename" : "60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "play_store_icon_114px-3.png",
"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" : "play_store_icon_114px-1.png",
"filename" : "120-1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "play_store_icon_114px.png",
"filename" : "120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "play_store_icon_114px-2.png",
"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" : "play_store_icon_114px-5.png",
"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",
"size" : "1024x1024"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -5,11 +5,12 @@
"scale" : "1x"
},
{
"filename" : "RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "rak4631@3x.png",
"filename" : "RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

View file

@ -1,15 +1,17 @@
{
"images" : [
{
"filename" : "tbeam-2.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "tbeam-1.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "TTGO T-Beam ESP32 LoRa 923MHz+OLED WiFi GPS NEO-M8N 18650-1-550x550.jpg",
"filename" : "tbeam.jpg",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View file

@ -17,13 +17,21 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We use bluetooth to connect to nearby Meshtastic Devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Bluetooth is used to connect an iPhone to a user&apos;s meshtastic device to allow text messaging and location data for the mesh network.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to center maps of the mesh</string>
<key>Privacy Bluetooth Always Usage Description</key>
<string>We use bluetooth to connect to nearby Meshtastic Devices</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
@ -37,16 +45,14 @@
<array>
<string>armv7</string>
</array>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>We use bluetooth to connect to nearby Meshtastic Devices</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>Privacy Bluetooth Always Usage Description</key>
<string>We use bluetooth to connect to nearby Meshtastic Devices</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,22 @@
//
// MyInfoModel.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 9/16/21.
//
import Foundation
struct MyInfoModel: Hashable, Codable, Identifiable {
let id = UUID()
var myNodeNum: UInt32
var hasGps: Bool
var numBands: UInt32
var maxChannels: UInt32
var firmwareVersion: String
var rebootCount: UInt32
var messageTimeoutMsec: UInt32
var minAppVersion: UInt32
}

View file

@ -26,7 +26,37 @@ struct NodeInfoModel: Hashable, Codable, Identifiable {
var position: Position
struct Position: Hashable, Codable {
var latitudeI: Int32?
var latitude: Double? {
if let unwrappedLat = latitudeI {
let d = Double(unwrappedLat)
return d / 10000000
}
else {
return nil
}
}
var longitudeI: Int32?
var longitude: Double? {
if let unwrappedLong = longitudeI {
let d = Double(unwrappedLong)
return d / 10000000
}
else {
return nil
}
}
var coordinate: CLLocationCoordinate2D? {
if longitude != nil {
let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
return coord
}
else {
return nil
}
}
var altitude: Int32?
var batteryLevel: Int32?
var time: Int32?

View file

@ -5,14 +5,11 @@ Abstract: Default App View
import SwiftUI
struct ContentView: View {
@State private var selection: Tab = .ble
@State private var selection: Tab = .nodes
enum Tab {
case messages
case devices
case map
case featured
case list
case ble
case nodes
}
@ -24,16 +21,11 @@ struct ContentView: View {
Label("Messages", systemImage: "message")
}
.tag(Tab.messages)
DeviceMap()
NodeMap()
.tabItem {
Label("Mesh Map", systemImage: "map")
}
.tag(Tab.map)
DeviceHome()
.tabItem {
Label("Devices", systemImage: "flipphone")
}
.tag(Tab.devices)
NodeList()
.tabItem {
Label("Nodes", systemImage: "flipphone")

View file

@ -24,86 +24,89 @@ struct DeviceBLE: View {
var body: some View {
NavigationView {
VStack {
List {
Section(header: Text("Connected Device").font(.title)) {
if(bleManager.connectedPeripheral != nil){
HStack{
Image(systemName: "dot.radiowaves.left.and.right").imageScale(.large).foregroundColor(.green)
Text(bleManager.connectedPeripheral.name!).font(.title2)
}
}
else {
Text("No device connected").font(.title2)
}
}.textCase(nil)
Section(header: Text("Other Meshtastic Devices").font(.title)) {
ForEach(bleManager.peripherals.sorted(by: { $0.rssi > $1.rssi })) { peripheral in
HStack {
Image(systemName: "circle.fill").imageScale(.large).foregroundColor(.gray)
Button(action: {
self.bleManager.stopScanning()
self.bleManager.disconnectDevice()
self.bleManager.connectToDevice(id: peripheral.id)
}) {
Text(peripheral.name).font(.title2)
if bleManager.isSwitchedOn {
List {
Section(header: Text("Connected Device").font(.largeTitle)) {
if(bleManager.connectedPeripheral != nil){
HStack{
Image(systemName: "dot.radiowaves.left.and.right").imageScale(.large).foregroundColor(.green)
Text(bleManager.connectedPeripheral.name!).font(.title)
}
Spacer()
Text(String(peripheral.rssi) + " dB").font(.title3)
}
else {
Text("No device connected").font(.title)
}
}.textCase(nil)
Section(header: Text("Other Meshtastic Devices").font(.title)) {
ForEach(bleManager.peripherals.sorted(by: { $0.rssi > $1.rssi })) { peripheral in
HStack {
Image(systemName: "circle.fill").imageScale(.large).foregroundColor(.gray)
Button(action: {
self.bleManager.stopScanning()
self.bleManager.disconnectDevice()
self.bleManager.connectToDevice(id: peripheral.id)
}) {
Text(peripheral.name).font(.title2)
}
Spacer()
Text(String(peripheral.rssi) + " dB").font(.title3)
}
}
}.textCase(nil)
}
HStack (alignment: .center) {
Spacer()
Button(action: {
self.bleManager.startScanning()
}) {
Image(systemName: "play.fill").imageScale(.large).foregroundColor(.gray)
Text("Start Scanning").font(.caption)
.font(.caption)
.foregroundColor(.gray)
}
}.textCase(nil)
}
Spacer()
HStack (spacing: 15) {
Button(action: {
self.bleManager.startScanning()
}) {
Image(systemName: "play.fill").imageScale(.large).foregroundColor(.gray)
Text("Start Scanning").font(.caption)
.font(.caption)
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
Spacer()
Button(action: {
self.bleManager.stopScanning()
}) {
Image(systemName: "stop.fill").imageScale(.large).foregroundColor(.gray)
Text("Stop Scanning")
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
Spacer(minLength: 10)
Button(action: {
self.bleManager.stopScanning()
}) {
Image(systemName: "stop.fill").imageScale(.large).foregroundColor(.gray)
Text("Stop Scanning")
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
}.padding()
Spacer()
}
.navigationTitle("Nearby BLE Devices")
.navigationBarItems(leading:
HStack {
Button(action: {
self.bleManager.startScanning()
}) {
Image(systemName: "arrow.clockwise.circle").imageScale(.large)
}}, trailing:
HStack {
if bleManager.isSwitchedOn {
Text("Bluetooth: ON")
.foregroundColor(.green)
.font(.caption2)
}
else {
Text("Bluetooth: OFF")
.foregroundColor(.red)
.font(.caption2)
}
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
Spacer()
}.padding(.bottom, 25)
}
)
else {
Text("Bluetooth: OFF")
.foregroundColor(.red)
.font(.title)
}
}
.navigationTitle("Bluetooth Radios")
//.navigationBarItems(leading:
//HStack {
//Button(action: {
// self.bleManager.startScanning()
//}) {
// Image(systemName: "arrow.clockwise.circle").imageScale(.large)
//}}, trailing:
//HStack {
//}
//)
}.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -1,143 +0,0 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
A view showing the details for a device.
*/
import SwiftUI
import MapKit
import CoreLocation
import CoreBluetooth
struct DeviceDetail: View {
@EnvironmentObject var modelData: ModelData
var device: Device
var deviceIndex: Int {
modelData.devices.firstIndex(where: { $0.id == device.id })!
}
struct MapLocation: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
var body: some View {
let currentCoordinatePosition = CLLocationCoordinate2D(latitude: device.position.latitude, longitude: device.position.longitude)
let regionBinding = Binding<MKCoordinateRegion>(
get: {
MKCoordinateRegion(center: currentCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
},
set: { _ in }
)
VStack{
// Map or Device Image
if(device.hasGPS) {
let annotations = [
MapLocation(name: device.shortName, coordinate: CLLocationCoordinate2D(latitude:device.position.latitude, longitude: device.position.longitude))
]
Map(coordinateRegion: regionBinding, annotationItems: annotations) { location in
MapAnnotation(
coordinate: location.coordinate,
content: {
Text(device.shortName).font(.subheadline).foregroundColor(.white)
.background(Circle()
.fill(Color.blue)
.frame(width: 40, height: 40))
}
)
}.frame(minHeight: 150, maxHeight: 1000)
}
else
{
device.image
.resizable()
.frame(minHeight: 300, maxHeight: 1000)
}
}
ZStack {
VStack(alignment: .leading) {
HStack {
if(device.hasGPS){
device.image
.resizable()
.frame(width: 100, height: 100)
}
Text(device.longName).font(.largeTitle)
}
Divider()
HStack{
Image(systemName: "clock").font(.title3).foregroundColor(.blue)
let lastHeard = Date(timeIntervalSince1970: device.lastHeard)
Text("Last Heard:").font(.headline)
Text(lastHeard, style: .date).font(.subheadline)
Text(lastHeard, style: .time).font(.subheadline)
}
Divider()
HStack {
Text("Meshtastic Version: " + device.firmwareVersion)
Spacer()
Text("Id: " + device.id)
}
.font(.subheadline)
.foregroundColor(.secondary)
HStack {
Text("Hardware Model: " + device.hardwareModel)
Spacer()
Text("Region: " + device.region)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
Image(systemName: "antenna.radiowaves.left.and.right").font(.title).foregroundColor(.blue)
VStack(alignment: .leading) {
Text("AKA").font(.caption)
Text(device.shortName).font(.caption2).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Latitude").font(.caption)
Text(String(format: "%.4f", device.position.latitude) + "°").font(.caption2).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Longitude").font(.caption)
let fourDecimalPlaces = String(format: "%.4f", device.position.longitude)
Text(String(fourDecimalPlaces) + "°").font(.caption2).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Altitude").font(.caption)
Text(String(device.position.altitude) + " m").font(.caption2).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Battery").font(.caption)
Text(String(device.position.batteryLevel) + "%").font(.caption2).foregroundColor(.gray)
}
}
}
.padding()
}
.navigationTitle(device.longName)
.navigationBarTitleDisplayMode(.inline)
}
}
struct DeviceDetail_Previews: PreviewProvider {
static let modelData = ModelData()
static var previews: some View {
DeviceDetail(device: modelData.devices[0])
.environmentObject(modelData)
}
}

View file

@ -1,48 +0,0 @@
//
// DeviceHome.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
//
// Abstract:
// A view showing a list of devices that have been seen on the mesh network
import SwiftUI
struct DeviceHome: View {
@EnvironmentObject var modelData: ModelData
@State private var showGPSOnly = false
var filteredDevices: [Device] {
modelData.devices.filter { device in
(!showGPSOnly || device.hasGPS)
}
}
var body: some View {
NavigationView {
List {
Toggle(isOn: $showGPSOnly) {
Text("GPS only")
}
ForEach(filteredDevices) { device in
NavigationLink(destination: DeviceDetail(device: device)) {
DeviceRow(device: device)
}
}
}
.navigationTitle("All Devices")
}.navigationViewStyle(StackNavigationViewStyle()) // Force Full screen master details
}
}
struct DeviceHome_Previews: PreviewProvider {
static var previews: some View {
DeviceHome()
.environmentObject(ModelData())
}
}

View file

@ -1,57 +0,0 @@
//
// DeviceMap.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
//
// Abstract:
// A single row to be displayed in a list of landmarks.
import SwiftUI
struct DeviceRow: View {
var device: Device
var body: some View {
HStack {
device.image.resizable().frame(width: 150, height: 150)
VStack(alignment: .leading) {
Text(device.longName).font(.title2)
HStack {
if device.hasGPS {
Image(systemName: "location.fill.viewfinder")
.foregroundColor(.blue).font(.title3)
}
if device.isRouter {
Image(systemName: "dot.radiowaves.left.and.right")
.foregroundColor(.blue).font(.title3)
}
if device.hardwareModel == "TBEAM" || device.hardwareModel == "TLORA" {
Image(systemName: "wifi")
.foregroundColor(.blue).font(.title3)
}
if false {
Image(systemName: "rectangle.connected.to.line.below")
.foregroundColor(.green).font(.title2)
}
}
}
Spacer()
}
}
}
struct DeviceRow_Previews: PreviewProvider {
static var devices = ModelData().devices
static var previews: some View {
Group {
DeviceRow(device: devices[0])
DeviceRow(device: devices[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}

View file

@ -0,0 +1,47 @@
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")
.font(font)
.foregroundColor(color)
}
else if batteryLevel! < 100 && batteryLevel! > 74 {
Image(systemName: "battery.75")
.font(font)
.foregroundColor(color)
}
else if batteryLevel! < 75 && batteryLevel! > 49 {
Image(systemName: "battery.50")
.font(font)
.foregroundColor(color)
}
else if batteryLevel! < 50 && batteryLevel! > 24 {
Image(systemName: "battery.25")
.font(font)
.foregroundColor(color)
}
else {
Image(systemName: "battery.0")
.font(font)
.foregroundColor(color)
}
}
}
struct BatteryIcon_Previews: PreviewProvider {
static var previews: some View {
BatteryIcon(batteryLevel: 100, font: .title2, color: Color.blue)
}
}

View file

@ -9,10 +9,11 @@ import SwiftUI
struct CircleImage: View {
var image: Image
var body: some View {
image
.resizable()
//.resizable()
.clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 7)

View file

@ -1,26 +1,25 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
A view that clips an image to a circle and adds a stroke and shadow.
A view draws a circle in the background of the shortName text
*/
import SwiftUI
struct CircleText: View {
var text: String
var color: Color
var body: some View {
Text(text).font(.subheadline).foregroundColor(.white)
Text(text).font(.caption2).foregroundColor(.white)
.background(Circle()
.fill(Color.blue)
.frame(width: 40, height: 40, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/))
.fill(color)
.frame(width: 36, height: 36, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/))
}
}
struct CircleText_Previews: PreviewProvider {
static var previews: some View {
CircleText(text: "RDN")
CircleText(text: "RDN", color: Color.blue)
}
}

View file

@ -0,0 +1,35 @@
import SwiftUI
struct MessageBubble: View {
var contentMessage: String
var isCurrentUser: Bool
var time: Int32
var shortName: String
var body: some View {
VStack(alignment: isCurrentUser ? .leading : .trailing) {
HStack {
CircleText(text: shortName, color: isCurrentUser ? Color.blue : Color(.darkGray)).padding(.all, 5)
Text(contentMessage)
.padding(10)
.foregroundColor(.white)
.background(isCurrentUser ? Color.blue : Color(.darkGray))
.cornerRadius(10)
Spacer()
}.padding(isCurrentUser ? .leading : .trailing, 70)
}.padding(.bottom, 1)
}
}
struct MessageBubble_Previews: PreviewProvider {
static var previews: some View {
Group {
MessageBubble(contentMessage: "this is the best text ever", isCurrentUser: true, time: 0, shortName: "EB")
}
.previewLayout(.fixed(width: 300, height: 100))
}
}

View file

@ -0,0 +1,35 @@
import SwiftUI
import MapKit
import CoreLocation
struct MessageDetail: View {
@State var typingMessage: String = ""
var body: some View {
NavigationView {
VStack(alignment: .leading) {
ScrollView {
MessageBubble(contentMessage: "I sent a super great message with amazing text", isCurrentUser: true, time: 1, shortName: "GVH")
MessageBubble(contentMessage: "It was amazing to read such a fantastical text", isCurrentUser: false, time: 1, shortName: "RS1")
MessageBubble(contentMessage: "It was the best message", isCurrentUser: false, time: 1, shortName: "RDN")
MessageBubble(contentMessage: "This is a terse response to an amazing text", isCurrentUser: true, time: 1, shortName: "GVH")
MessageBubble(contentMessage: "yo", isCurrentUser: true, time: 1, shortName: "GVH")
}.padding([.top, .leading])
HStack (alignment: .bottom) {
TextField("Message", text: $typingMessage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(minHeight: CGFloat(30))
Button(action: sendMessage) {
Image(systemName: "arrow.up.circle.fill").font(.title).foregroundColor(.blue)
}
}.padding(5)
}
.navigationTitle("Broadcast Channel")
.navigationBarTitleDisplayMode(.inline)
}
//.navigationViewStyle//(StackNavigationViewStyle())
}
}

View file

@ -0,0 +1,38 @@
import Foundation
import SwiftUI
struct MessageList: View {
@State var typingMessage: String = ""
var body: some View {
NavigationView {
GeometryReader { bounds in
ScrollView {
}.padding(.all)
}
.navigationTitle("Channels")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct MessageList_Previews: PreviewProvider {
static let modelData = ModelData()
static var previews: some View {
Group {
//NodeDetail(node: modelData.nodes[0]).environmentObject(modelData)
// NodeDetail(node: modelData.nodes[1]).environmentObject(modelData)
}
}
}
func sendMessage() {
//chatHelper.sendMessage(Message(content: typingMessage, user: DataSource.secondUser))
// typingMessage = ""
}

View file

@ -3,55 +3,34 @@ import MapKit
import CoreLocation
struct Messages: View {
@State var typingMessage: String = ""
var body: some View {
NavigationView {
List {
HStack {
Text("RGN").font(.subheadline).foregroundColor(.white).background(Circle().fill(Color.blue).frame(width: 40, height: 40))
VStack(alignment: .trailing) {
Text("I sent a super great message with amazing text").padding().foregroundColor(.white).background(Capsule().fill(Color.green))
Text("8/7/21 8:39 PM").font(.subheadline).foregroundColor(.gray)
}
VStack(alignment: .leading) {
ScrollView {
MessageBubble(contentMessage: "I sent a super great message with amazing text", isCurrentUser: true, time: 1, shortName: "GVH")
MessageBubble(contentMessage: "It was amazing to read such a fantastical text", isCurrentUser: false, time: 1, shortName: "RS1")
MessageBubble(contentMessage: "It was the best message", isCurrentUser: false, time: 1, shortName: "RDN")
MessageBubble(contentMessage: "This is a terse response to an amazing text", isCurrentUser: true, time: 1, shortName: "GVH")
MessageBubble(contentMessage: "yo", isCurrentUser: true, time: 1, shortName: "GVH")
}.padding([.top, .leading])
HStack (alignment: .bottom) {
}
VStack {
HStack {
Text("RGN").font(.subheadline).foregroundColor(.white).background(Circle().fill(Color.blue).frame(width: 40, height: 40))
HStack {
Text("I sent a super great message with amazing text").padding().foregroundColor(.white).background(Capsule().fill(Color.green))
Text("8/7/21 8:39 PM").font(.subheadline).foregroundColor(.gray)
TextField("Message", text: $typingMessage)
.keyboardType(.asciiCapable)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(minHeight: CGFloat(30))
Button(action: sendMessage) {
Image(systemName: "arrow.up.circle.fill").font(.title).foregroundColor(.blue)
}
}
HStack {
Text("8/7/21 8:39 PM").font(.subheadline).foregroundColor(.gray)
}
}
VStack {
HStack {
Text("RS1").font(.subheadline).foregroundColor(.white).background(Circle().fill(Color.blue).frame(width: 40, height: 40))
Text("the best message").padding().foregroundColor(.white).background(Capsule().fill(Color.green))
}
HStack {
Text("8/7/21 8:45 PM").font(.subheadline).foregroundColor(.gray)
}
}
VStack {
HStack{
Text("YB").font(.subheadline).foregroundColor(.white).background(Circle().fill(Color.green).frame(width: 40, height: 40))
Spacer(minLength: 50)
Text("This is a terse response to an amazing text").padding().foregroundColor(.white).background(Capsule().fill(Color.green))
}
HStack {
Text("8/7/21 8:53 PM").font(.subheadline).foregroundColor(.gray)
Spacer()
}
}
}.navigationTitle("Broadcast Channel")
}.padding(5)
}
.navigationTitle("Broadcast Channel")
.navigationBarTitleDisplayMode(.inline)
}.navigationViewStyle(StackNavigationViewStyle())
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

View file

@ -1,9 +1,6 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
A view showing the details for a device.
A view showing the details for a node.
*/
import SwiftUI
@ -16,34 +13,45 @@ struct NodeDetail: View {
@EnvironmentObject var modelData: ModelData
var node: NodeInfoModel
var nodeIndex: Int {
modelData.nodes.firstIndex(where: { $0.id == node.id })!
}
var coord = CLLocationCoordinate2D()
struct MapLocation: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
}
var body: some View {
let location = LocationHelper.currentLocation
let currentCoordinatePosition = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
let regionBinding = Binding<MKCoordinateRegion>(
get: {
MKCoordinateRegion(center: currentCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
},
set: { _ in }
)
GeometryReader { bounds in
VStack {
// Map or Device Image
if(node.position.latitudeI != nil && node.position.latitudeI! > 0) {
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .constant(.follow))
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
if(node.position.coordinate != nil) {
let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: node.position.latitude!, longitude: node.position.longitude!)
let regionBinding = Binding<MKCoordinateRegion>(
get: {
MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.002, longitudeDelta: 0.002))
},
set: { _ in }
)
let annotations = [MapLocation(name: node.user.shortName, coordinate: node.position.coordinate!)]
Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .constant(.none), annotationItems: annotations) { location in
MapAnnotation(
coordinate: location.coordinate,
content: {
Text(node.user.shortName).font(.subheadline).foregroundColor(.white)
.background(Circle()
.fill(Color.blue)
.frame(width: 36, height: 36))
}
)
}.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
}
else
{
@ -53,34 +61,48 @@ struct NodeDetail: View {
.frame(width: bounds.size.width, height: bounds.size.height / 2)
}
ScrollView {
HStack {
Spacer()
Image(systemName: "flipphone").font(.largeTitle).foregroundColor(.blue)
Text("Model: " + String(node.user.hwModel)).font(.title)
Spacer()
}.padding()
Image(node.user.hwModel.lowercased())
.resizable()
.frame(width: 70, height: 70)
Text("Model: " + String(node.user.hwModel))
.font(.title)
}
.padding()
Divider()
HStack {
Image(systemName: "antenna.radiowaves.left.and.right").font(.title2).foregroundColor(.blue)
VStack(alignment: .center) {
Text("SNR").font(.title3)
Text(String(node.snr!)).font(.title3).foregroundColor(.gray)
}
Divider()
VStack(alignment: .center) {
Text("AKA").font(.title3)
Text(node.user.shortName).font(.title3).foregroundColor(.gray)
CircleText(text: node.user.shortName, color: Color.blue)
.offset(y:10)
}
.padding([.leading, .trailing, .bottom])
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title2)
.foregroundColor(.blue)
Text("SNR").font(.title3)
Text(String(node.snr ?? 0))
.font(.title3)
.foregroundColor(.gray)
}
Divider()
VStack(alignment: .leading) {
VStack(alignment: .center) {
BatteryIcon(batteryLevel: node.position.batteryLevel, font: .title, color: Color.blue)
Text("Battery").font(.title3)
Text(String(node.position.batteryLevel!) + "%").font(.title3).foregroundColor(.gray)
Text(String(node.position.batteryLevel!) + "%")
.font(.title3)
.foregroundColor(.gray)
}
}.padding(4)
Divider()
HStack{
Image(systemName: "clock").font(.title2).foregroundColor(.blue)
let lastHeard = Date(timeIntervalSince1970: node.lastHeard)
Text("Last Heard:").font(.title3)
@ -88,42 +110,58 @@ struct NodeDetail: View {
Text("ago").font(.title3)
}.padding()
Divider()
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
Image(systemName: "mappin").font(.title).foregroundColor(.blue)
VStack(alignment: .leading) {
Text("Latitude").font(.headline)
Text(String(node.position.latitudeI!)).font(.caption).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Longitude").font(.headline)
Text(String(node.position.longitudeI!)).font(.caption).foregroundColor(.gray)
}
VStack(alignment: .leading) {
Text("Altitude").font(.headline)
Text(String(node.position.altitude!) + " m").font(.caption).foregroundColor(.gray)
}
}.padding()
Divider()
HStack {
Spacer()
Image(systemName: "person").font(.title3).foregroundColor(.blue)
Text("Unique Id: " + String(node.user.id)).font(.headline)
if node.position.latitudeI != nil {
HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) {
Image(systemName: "mappin").font(.title).foregroundColor(.blue)
VStack(alignment: .leading) {
Text("Latitude").font(.headline)
Text(String(node.position.latitude ?? 0)).font(.caption).foregroundColor(.gray)
}
Divider()
VStack(alignment: .leading) {
Text("Longitude").font(.headline)
Text(String(node.position.longitude ?? 0)).font(.caption).foregroundColor(.gray)
}
Divider()
VStack(alignment: .leading) {
Text("Altitude").font(.headline)
Text(String(node.position.altitude ?? 0) + " m").font(.caption).foregroundColor(.gray)
}
}.padding()
Divider()
Image(systemName: "number").font(.title3).foregroundColor(.blue)
Text("Node Num: " + String(node.num)).font(.headline)
Spacer()
}
HStack (alignment: .center) {
VStack {
HStack{
Image(systemName: "person").font(.title3).foregroundColor(.blue)
Text("Unique Id:").font(.title3)
}
Text(node.user.id).font(.headline).foregroundColor(.gray)
}
Divider()
VStack {
HStack {
Image(systemName: "number").font(.title3).foregroundColor(.blue)
Text("Node Number:").font(.title3)
}
Text(String(node.num)).font(.headline).foregroundColor(.gray)
}
}.padding()
}
}.navigationTitle(node.user.longName)
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
HStack {
CircleText(text: node.user.shortName).offset(y: -2)
}
)
}.ignoresSafeArea(.all, edges: [.leading, .trailing])
}
}
struct NodeDetail_Previews: PreviewProvider {
static let modelData = ModelData()
static var previews: some View {
Group {
NodeDetail(node: modelData.nodes[0]).environmentObject(modelData)
NodeDetail(node: modelData.nodes[1]).environmentObject(modelData)
}
}
}

View file

@ -13,23 +13,29 @@ import SwiftUI
struct NodeList: View {
@EnvironmentObject var modelData: ModelData
@State private var showLocationOnly = false
var filteredDevices: [NodeInfoModel] {
modelData.nodes.filter { node in
(!showLocationOnly || node.position.coordinate != nil)
}
}
var body: some View {
NavigationView {
List {
ForEach(modelData.nodes) { node in
Toggle(isOn: $showLocationOnly) {
Text("Nodes with Location only")
}
ForEach(filteredDevices.sorted(by: { $0.lastHeard > $1.lastHeard })) { node in
NavigationLink(destination: NodeDetail(node: node)) {
NodeRow(node: node)
NodeRow(node: node, index : 0)
}
}
}
.navigationTitle("All Nodes")
}.navigationViewStyle(StackNavigationViewStyle()) // Force Full screen master details
}
}
}

View file

@ -10,14 +10,15 @@ import SwiftUI
import MapKit
import CoreLocation
struct DeviceMap: View {
struct NodeMap: View {
@EnvironmentObject var modelData: ModelData
var devices: [Device] {
modelData.devices
var locationNodes: [NodeInfoModel] {
modelData.nodes.filter { node in
(node.position.coordinate != nil)
}
}
struct MapLocation: Identifiable {
let id = UUID()
let name: String
@ -33,27 +34,20 @@ struct DeviceMap: View {
},
set: { _ in }
)
let annotations = [
MapLocation(name: devices[0].shortName, coordinate: CLLocationCoordinate2D(latitude: devices[0].position.latitude, longitude: devices[0].position.longitude)),
MapLocation(name: devices[1].shortName, coordinate: CLLocationCoordinate2D(latitude: devices[1].position.latitude, longitude: devices[1].position.longitude)),
MapLocation(name: devices[2].shortName, coordinate: CLLocationCoordinate2D(latitude: devices[2].position.latitude, longitude: devices[2].position.longitude)),
MapLocation(name: devices[3].shortName, coordinate: CLLocationCoordinate2D(latitude: devices[3].position.latitude, longitude: devices[3].position.longitude))
]
NavigationView {
ZStack {
Map(coordinateRegion: regionBinding,
interactionModes: [.all],
showsUserLocation: true,
userTrackingMode: .constant(.follow), annotationItems: annotations) { location in
userTrackingMode: .constant(.follow), annotationItems: locationNodes) { location in
MapAnnotation(
coordinate: location.coordinate,
coordinate: location.position.coordinate!,
content: {
Text(location.name).font(.caption2).foregroundColor(.white)
.background(Circle()
.fill(Color.blue)
.frame(width: 40, height: 40)) }
CircleText(text: location.user.shortName, color: Color.blue)
}
)
}.frame(maxHeight:.infinity)
}

View file

@ -1,37 +1,24 @@
//
// DeviceMap.swift
// MeshtasticClient
//
// Created by Garth Vander Houwen on 8/7/21.
//
// Abstract:
// A single row to be displayed in a list of landmarks.
import SwiftUI
struct NodeRow: View {
var node: NodeInfoModel
var index: Int
var body: some View {
HStack {
Image(node.user.hwModel.lowercased()).resizable().frame(width: 150, height: 150)
VStack(alignment: .leading) {
VStack (alignment: .leading) {
HStack() {
Text(node.user.longName).font(.title2)
HStack {
if node.user.hwModel == "TBEAM" || node.user.hwModel == "TLORA" {
Image(systemName: "wifi")
.foregroundColor(.blue).font(.title3)
}
if false {
Image(systemName: "rectangle.connected.to.line.below")
.foregroundColor(.green).font(.title2)
}
}
CircleText(text: node.user.shortName, color: Color.blue).offset(y: 1).padding(.trailing, 5)
Text(node.user.longName).font(.title)
}.padding(.bottom, 2)
HStack (alignment: .top){
Image(systemName: "clock").font(.subheadline).foregroundColor(.blue)
let lastHeard = Date(timeIntervalSince1970: node.lastHeard)
Text("Last Heard:").font(.subheadline).foregroundColor(.gray)
Text(lastHeard, style: .relative).font(.subheadline).foregroundColor(.gray)
}
Spacer()
}
}.padding([.leading, .top, .bottom])
}
}
@ -40,9 +27,7 @@ struct NodeRow_Previews: PreviewProvider {
static var previews: some View {
Group {
NodeRow(node: nodes[0])
NodeRow(node: nodes[1])
NodeRow(node: nodes[2])
NodeRow(node: nodes[0], index: 0)
}
.previewLayout(.fixed(width: 300, height: 70))
}