diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 21537c54..0c811938 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -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 = ""; }; DD47E3D126F1210600029299 /* HelperFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; + DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = ""; }; + DD47E3DA26F3901A00029299 /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = ""; }; + DD47E3DC26F390A000029299 /* MessageDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDetail.swift; sourceTree = ""; }; + DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = ""; }; DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoModel.swift; sourceTree = ""; }; DD7AA3F426F05D660077AF76 /* nodeInfoModel.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = nodeInfoModel.json; sourceTree = ""; }; + DD90860A26F645B700DC5189 /* MeshtasticClient.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MeshtasticClient.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 = ""; }; DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = ""; }; DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = ""; }; DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = ""; }; @@ -98,10 +107,6 @@ DDC2E18A26CE25690042C5E4 /* deviceData.json */ = {isa = PBXFileReference; explicitFileType = text.json; fileEncoding = 4; path = deviceData.json; sourceTree = ""; }; DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; DDC2E19026CE26290042C5E4 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; - DDC2E19226CE266B0042C5E4 /* DeviceHome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHome.swift; sourceTree = ""; }; - DDC2E19426CE26760042C5E4 /* DeviceDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceDetail.swift; sourceTree = ""; }; - DDC2E19626CE26840042C5E4 /* DeviceRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRow.swift; sourceTree = ""; }; - DDC2E19826CE26940042C5E4 /* DeviceMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMap.swift; sourceTree = ""; }; DDC2E19A26CE27150042C5E4 /* CircleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImage.swift; sourceTree = ""; }; DDC2E19C26CE27580042C5E4 /* ModelData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelData.swift; sourceTree = ""; }; DDC2E19E26CE27630042C5E4 /* Device.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = ""; }; @@ -142,10 +147,18 @@ DD47E3CB26F0E51D00029299 /* NodeDetail.swift */, DD47E3CD26F103C600029299 /* NodeList.swift */, DD47E3CF26F1073F00029299 /* NodeRow.swift */, + DD90860D26F69BAE00DC5189 /* NodeMap.swift */, ); path = Nodes; sourceTree = ""; }; + DD47E3D726F2F21A00029299 /* BlueTooth */ = { + isa = PBXGroup; + children = ( + ); + path = BlueTooth; + sourceTree = ""; + }; 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 = ""; @@ -262,6 +278,8 @@ isa = PBXGroup; children = ( DDC2E19026CE26290042C5E4 /* Messages.swift */, + DD47E3DA26F3901A00029299 /* MessageList.swift */, + DD47E3DC26F390A000029299 /* MessageDetail.swift */, ); path = Messages; sourceTree = ""; @@ -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 = ""; @@ -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"; }; diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/1024.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 00000000..403f4f2f Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120-1.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120-1.png new file mode 100644 index 00000000..6e571624 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 00000000..6e571624 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/152.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 00000000..a930e23a Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/167.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 00000000..35658530 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/180.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 00000000..fff06a76 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/20.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 00000000..988c8ade Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/29.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 00000000..553bb7a3 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-1.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-1.png new file mode 100644 index 00000000..e78f4105 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-2.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-2.png new file mode 100644 index 00000000..e78f4105 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40-2.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 00000000..e78f4105 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58-1.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58-1.png new file mode 100644 index 00000000..ea3829e7 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 00000000..ea3829e7 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/60.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 00000000..f384f426 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/76.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 00000000..bd7db1b2 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80-1.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80-1.png new file mode 100644 index 00000000..ed42f91e Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 00000000..ed42f91e Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/87.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 00000000..a3720d5a Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/Contents.json b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/Contents.json index 4962da8d..1cd4ae9f 100644 --- a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-1.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-1.png deleted file mode 100644 index 60a9a0f9..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-1.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-2.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-2.png deleted file mode 100644 index fad2ffa1..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-2.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-3.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-3.png deleted file mode 100644 index cf2f2ace..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-3.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-4.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-4.png deleted file mode 100644 index 613a0af5..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-4.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-5.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-5.png deleted file mode 100644 index 4998870c..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px-5.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px.png b/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px.png deleted file mode 100644 index 60a9a0f9..00000000 Binary files a/MeshtasticClient/Assets.xcassets/AppIcon.appiconset/play_store_icon_114px.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json b/MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json index bcdf2ebd..7f7e3a4b 100644 --- a/MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json +++ b/MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json @@ -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" } diff --git a/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png b/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png new file mode 100644 index 00000000..984c1117 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive-1.png differ diff --git a/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png b/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png new file mode 100644 index 00000000..984c1117 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/rak4631.imageset/RAK7205_Enclosure-With-Solar-Panel_Top-View_01_9ed42002-fb51-4c49-a69e-43fcef692ef6_739x@2x.progressive.png differ diff --git a/MeshtasticClient/Assets.xcassets/rak4631.imageset/rak4631@3x.png b/MeshtasticClient/Assets.xcassets/rak4631.imageset/rak4631@3x.png deleted file mode 100644 index 442efe09..00000000 Binary files a/MeshtasticClient/Assets.xcassets/rak4631.imageset/rak4631@3x.png and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json b/MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json index 4f6ed0a0..fb2eadda 100644 --- a/MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json +++ b/MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json @@ -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" } diff --git a/MeshtasticClient/Assets.xcassets/tbeam.imageset/TTGO T-Beam ESP32 LoRa 923MHz+OLED WiFi GPS NEO-M8N 18650-1-550x550.jpg b/MeshtasticClient/Assets.xcassets/tbeam.imageset/TTGO T-Beam ESP32 LoRa 923MHz+OLED WiFi GPS NEO-M8N 18650-1-550x550.jpg deleted file mode 100644 index 69790047..00000000 Binary files a/MeshtasticClient/Assets.xcassets/tbeam.imageset/TTGO T-Beam ESP32 LoRa 923MHz+OLED WiFi GPS NEO-M8N 18650-1-550x550.jpg and /dev/null differ diff --git a/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-1.jpg b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-1.jpg new file mode 100644 index 00000000..69865801 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-1.jpg differ diff --git a/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-2.jpg b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-2.jpg new file mode 100644 index 00000000..69865801 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam-2.jpg differ diff --git a/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam.jpg b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam.jpg new file mode 100644 index 00000000..69865801 Binary files /dev/null and b/MeshtasticClient/Assets.xcassets/tbeam.imageset/tbeam.jpg differ diff --git a/MeshtasticClient/Info.plist b/MeshtasticClient/Info.plist index 97e59b03..126d0802 100644 --- a/MeshtasticClient/Info.plist +++ b/MeshtasticClient/Info.plist @@ -17,13 +17,21 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion 1 + LSApplicationCategoryType + public.app-category.utilities LSRequiresIPhoneOS + NSBluetoothAlwaysUsageDescription + We use bluetooth to connect to nearby Meshtastic Devices + NSBluetoothPeripheralUsageDescription + Bluetooth is used to connect an iPhone to a user's meshtastic device to allow text messaging and location data for the mesh network. NSLocationWhenInUseUsageDescription We use your location to center maps of the mesh + Privacy – Bluetooth Always Usage Description + We use bluetooth to connect to nearby Meshtastic Devices UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -37,16 +45,14 @@ armv7 - NSBluetoothAlwaysUsageDescription - We use bluetooth to connect to nearby Meshtastic Devices UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - Privacy – Bluetooth Always Usage Description - We use bluetooth to connect to nearby Meshtastic Devices + ITSAppUsesNonExemptEncryption + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/MeshtasticClient/MeshtasticClient.entitlements b/MeshtasticClient/MeshtasticClient.entitlements new file mode 100644 index 00000000..a86982db --- /dev/null +++ b/MeshtasticClient/MeshtasticClient.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.bluetooth + + com.apple.security.network.client + + com.apple.security.personal-information.location + + + diff --git a/MeshtasticClient/Model/MyInfoModel.swift b/MeshtasticClient/Model/MyInfoModel.swift new file mode 100644 index 00000000..0f707567 --- /dev/null +++ b/MeshtasticClient/Model/MyInfoModel.swift @@ -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 + +} diff --git a/MeshtasticClient/Model/NodeInfoModel.swift b/MeshtasticClient/Model/NodeInfoModel.swift index 2df777a9..2aab4941 100644 --- a/MeshtasticClient/Model/NodeInfoModel.swift +++ b/MeshtasticClient/Model/NodeInfoModel.swift @@ -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? diff --git a/MeshtasticClient/Views/ContentView.swift b/MeshtasticClient/Views/ContentView.swift index 6aee7886..39c8c676 100644 --- a/MeshtasticClient/Views/ContentView.swift +++ b/MeshtasticClient/Views/ContentView.swift @@ -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") diff --git a/MeshtasticClient/Views/Devices/DeviceBLE.swift b/MeshtasticClient/Views/Devices/DeviceBLE.swift index 6206ef8b..768375de 100644 --- a/MeshtasticClient/Views/Devices/DeviceBLE.swift +++ b/MeshtasticClient/Views/Devices/DeviceBLE.swift @@ -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()) } } diff --git a/MeshtasticClient/Views/Devices/DeviceDetail.swift b/MeshtasticClient/Views/Devices/DeviceDetail.swift deleted file mode 100644 index f92e7fe8..00000000 --- a/MeshtasticClient/Views/Devices/DeviceDetail.swift +++ /dev/null @@ -1,143 +0,0 @@ -/* -See LICENSE folder for this sample’s 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( - 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) - } -} diff --git a/MeshtasticClient/Views/Devices/DeviceHome.swift b/MeshtasticClient/Views/Devices/DeviceHome.swift deleted file mode 100644 index 387d8a33..00000000 --- a/MeshtasticClient/Views/Devices/DeviceHome.swift +++ /dev/null @@ -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()) - } -} diff --git a/MeshtasticClient/Views/Devices/DeviceRow.swift b/MeshtasticClient/Views/Devices/DeviceRow.swift deleted file mode 100644 index 0cf1aa93..00000000 --- a/MeshtasticClient/Views/Devices/DeviceRow.swift +++ /dev/null @@ -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)) - } -} diff --git a/MeshtasticClient/Views/Helpers/BatteryIcon.swift b/MeshtasticClient/Views/Helpers/BatteryIcon.swift new file mode 100644 index 00000000..3010617f --- /dev/null +++ b/MeshtasticClient/Views/Helpers/BatteryIcon.swift @@ -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) + } +} diff --git a/MeshtasticClient/Views/Helpers/CircleImage.swift b/MeshtasticClient/Views/Helpers/CircleImage.swift index f8e1ef04..314a2a44 100644 --- a/MeshtasticClient/Views/Helpers/CircleImage.swift +++ b/MeshtasticClient/Views/Helpers/CircleImage.swift @@ -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) diff --git a/MeshtasticClient/Views/Helpers/CircleText.swift b/MeshtasticClient/Views/Helpers/CircleText.swift index 84d5b15d..109c1eab 100644 --- a/MeshtasticClient/Views/Helpers/CircleText.swift +++ b/MeshtasticClient/Views/Helpers/CircleText.swift @@ -1,26 +1,25 @@ /* -See LICENSE folder for this sample’s 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) } } diff --git a/MeshtasticClient/Views/Helpers/MessageBubble.swift b/MeshtasticClient/Views/Helpers/MessageBubble.swift new file mode 100644 index 00000000..af0eacc9 --- /dev/null +++ b/MeshtasticClient/Views/Helpers/MessageBubble.swift @@ -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)) + } +} + + diff --git a/MeshtasticClient/Views/Messages/MessageDetail.swift b/MeshtasticClient/Views/Messages/MessageDetail.swift new file mode 100644 index 00000000..5d60e3ff --- /dev/null +++ b/MeshtasticClient/Views/Messages/MessageDetail.swift @@ -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()) + } +} diff --git a/MeshtasticClient/Views/Messages/MessageList.swift b/MeshtasticClient/Views/Messages/MessageList.swift new file mode 100644 index 00000000..62303ff0 --- /dev/null +++ b/MeshtasticClient/Views/Messages/MessageList.swift @@ -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 = "" + } diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index 10c79910..3a49ee71 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -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()) } } diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index 38415786..7c0533de 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -1,9 +1,6 @@ - /* -See LICENSE folder for this sample’s 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( - 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( + 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) + } } } diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift index 77e2a9d4..c2ad3c3d 100644 --- a/MeshtasticClient/Views/Nodes/NodeList.swift +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -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 + } } } diff --git a/MeshtasticClient/Views/Devices/DeviceMap.swift b/MeshtasticClient/Views/Nodes/NodeMap.swift similarity index 55% rename from MeshtasticClient/Views/Devices/DeviceMap.swift rename to MeshtasticClient/Views/Nodes/NodeMap.swift index 1a05dfbb..f7d41716 100644 --- a/MeshtasticClient/Views/Devices/DeviceMap.swift +++ b/MeshtasticClient/Views/Nodes/NodeMap.swift @@ -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) } diff --git a/MeshtasticClient/Views/Nodes/NodeRow.swift b/MeshtasticClient/Views/Nodes/NodeRow.swift index 39b8b83f..4963e12e 100644 --- a/MeshtasticClient/Views/Nodes/NodeRow.swift +++ b/MeshtasticClient/Views/Nodes/NodeRow.swift @@ -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)) }