mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Initial Project Commit
This commit is contained in:
parent
ff017aab5e
commit
8be3ac2bba
23 changed files with 898 additions and 166 deletions
|
|
@ -8,13 +8,23 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticClientApp.swift */; };
|
||||
DDC2E15A26CE248E0042C5E4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15926CE248E0042C5E4 /* ContentView.swift */; };
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; };
|
||||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; };
|
||||
DDC2E16126CE248F0042C5E4 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E16026CE248F0042C5E4 /* Persistence.swift */; };
|
||||
DDC2E16426CE248F0042C5E4 /* MeshtasticClient.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E16226CE248F0042C5E4 /* MeshtasticClient.xcdatamodeld */; };
|
||||
DDC2E16F26CE248F0042C5E4 /* MeshtasticClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E16E26CE248F0042C5E4 /* MeshtasticClientTests.swift */; };
|
||||
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 */; };
|
||||
DDC2E1A226CE29AC0042C5E4 /* deviceData.json in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E18A26CE25690042C5E4 /* deviceData.json */; };
|
||||
DDC2E1A426CE2F940042C5E4 /* DeviceBLE.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A326CE2F940042C5E4 /* DeviceBLE.swift */; };
|
||||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; };
|
||||
DDC2E1A926CF85020042C5E4 /* DeviceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A826CF85020042C5E4 /* DeviceList.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -37,11 +47,8 @@
|
|||
/* Begin PBXFileReference section */
|
||||
DDC2E15426CE248E0042C5E4 /* MeshtasticClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeshtasticClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E15726CE248E0042C5E4 /* MeshtasticClientApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticClientApp.swift; sourceTree = "<group>"; };
|
||||
DDC2E15926CE248E0042C5E4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
DDC2E16026CE248F0042C5E4 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
DDC2E16326CE248F0042C5E4 /* MeshtasticClient.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticClient.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDC2E16526CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DDC2E16A26CE248F0042C5E4 /* MeshtasticClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeshtasticClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E16E26CE248F0042C5E4 /* MeshtasticClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticClientTests.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -49,6 +56,19 @@
|
|||
DDC2E17526CE248F0042C5E4 /* MeshtasticClientUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeshtasticClientUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DDC2E17926CE248F0042C5E4 /* MeshtasticClientUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticClientUITests.swift; sourceTree = "<group>"; };
|
||||
DDC2E17B26CE248F0042C5E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
DDC2E1A326CE2F940042C5E4 /* DeviceBLE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceBLE.swift; sourceTree = "<group>"; };
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHelper.swift; sourceTree = "<group>"; };
|
||||
DDC2E1A826CF85020042C5E4 /* DeviceList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceList.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -99,12 +119,13 @@
|
|||
DDC2E15626CE248E0042C5E4 /* MeshtasticClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E1A526CEB32B0042C5E4 /* Helpers */,
|
||||
DDC2E18726CE24E40042C5E4 /* Views */,
|
||||
DDC2E18826CE24EE0042C5E4 /* Model */,
|
||||
DDC2E18926CE24F70042C5E4 /* Resources */,
|
||||
DDC2E15726CE248E0042C5E4 /* MeshtasticClientApp.swift */,
|
||||
DDC2E15926CE248E0042C5E4 /* ContentView.swift */,
|
||||
DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */,
|
||||
DDC2E16026CE248F0042C5E4 /* Persistence.swift */,
|
||||
DDC2E16526CE248F0042C5E4 /* Info.plist */,
|
||||
DDC2E16226CE248F0042C5E4 /* MeshtasticClient.xcdatamodeld */,
|
||||
DDC2E15D26CE248F0042C5E4 /* Preview Content */,
|
||||
);
|
||||
path = MeshtasticClient;
|
||||
|
|
@ -136,6 +157,71 @@
|
|||
path = MeshtasticClientUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18726CE24E40042C5E4 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E18C26CE25B00042C5E4 /* Devices */,
|
||||
DDC2E18B26CE25A70042C5E4 /* Messages */,
|
||||
DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */,
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18826CE24EE0042C5E4 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E19C26CE27580042C5E4 /* ModelData.swift */,
|
||||
DDC2E19E26CE27630042C5E4 /* Device.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18926CE24F70042C5E4 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E18A26CE25690042C5E4 /* deviceData.json */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18B26CE25A70042C5E4 /* Messages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E19026CE26290042C5E4 /* Messages.swift */,
|
||||
);
|
||||
path = Messages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18C26CE25B00042C5E4 /* Devices */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E19226CE266B0042C5E4 /* DeviceHome.swift */,
|
||||
DDC2E19426CE26760042C5E4 /* DeviceDetail.swift */,
|
||||
DDC2E19626CE26840042C5E4 /* DeviceRow.swift */,
|
||||
DDC2E19826CE26940042C5E4 /* DeviceMap.swift */,
|
||||
DDC2E1A326CE2F940042C5E4 /* DeviceBLE.swift */,
|
||||
DDC2E1A826CF85020042C5E4 /* DeviceList.swift */,
|
||||
);
|
||||
path = Devices;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E19A26CE27150042C5E4 /* CircleImage.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DDC2E1A526CEB32B0042C5E4 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -241,6 +327,7 @@
|
|||
files = (
|
||||
DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */,
|
||||
DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */,
|
||||
DDC2E1A226CE29AC0042C5E4 /* deviceData.json in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -265,9 +352,18 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDC2E16126CE248F0042C5E4 /* Persistence.swift in Sources */,
|
||||
DDC2E16426CE248F0042C5E4 /* MeshtasticClient.xcdatamodeld in Sources */,
|
||||
DDC2E15A26CE248E0042C5E4 /* ContentView.swift in Sources */,
|
||||
DDC2E1A926CF85020042C5E4 /* DeviceList.swift in Sources */,
|
||||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */,
|
||||
DDC2E19F26CE27630042C5E4 /* Device.swift in Sources */,
|
||||
DDC2E19926CE26940042C5E4 /* DeviceMap.swift in Sources */,
|
||||
DDC2E19526CE26760042C5E4 /* DeviceDetail.swift in Sources */,
|
||||
DDC2E19726CE26840042C5E4 /* DeviceRow.swift in Sources */,
|
||||
DDC2E19126CE26290042C5E4 /* Messages.swift in Sources */,
|
||||
DDC2E19D26CE27580042C5E4 /* ModelData.swift in Sources */,
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
|
||||
DDC2E19326CE266B0042C5E4 /* DeviceHome.swift in Sources */,
|
||||
DDC2E19B26CE27150042C5E4 /* CircleImage.swift in Sources */,
|
||||
DDC2E1A426CE2F940042C5E4 /* DeviceBLE.swift in Sources */,
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticClientApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -588,19 +684,6 @@
|
|||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
DDC2E16226CE248F0042C5E4 /* MeshtasticClient.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DDC2E16326CE248F0042C5E4 /* MeshtasticClient.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DDC2E16326CE248F0042C5E4 /* MeshtasticClient.xcdatamodel */;
|
||||
path = MeshtasticClient.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
};
|
||||
rootObject = DDC2E14C26CE248E0042C5E4 /* Project object */;
|
||||
}
|
||||
|
|
|
|||
21
MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json
vendored
Normal file
21
MeshtasticClient/Assets.xcassets/rak4631.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "rak4631@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
MeshtasticClient/Assets.xcassets/rak4631.imageset/rak4631@3x.png
vendored
Normal file
BIN
MeshtasticClient/Assets.xcassets/rak4631.imageset/rak4631@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 680 KiB |
21
MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json
vendored
Normal file
21
MeshtasticClient/Assets.xcassets/tbeam.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "TTGO T-Beam ESP32 LoRa 923MHz+OLED WiFi GPS NEO-M8N 18650-1-550x550.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -1,80 +0,0 @@
|
|||
//
|
||||
// ContentView.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/18/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.managedObjectContext) private var viewContext
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
|
||||
animation: .default)
|
||||
private var items: FetchedResults<Item>
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(items) { item in
|
||||
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
|
||||
}
|
||||
.onDelete(perform: deleteItems)
|
||||
}
|
||||
.toolbar {
|
||||
#if os(iOS)
|
||||
EditButton()
|
||||
#endif
|
||||
|
||||
Button(action: addItem) {
|
||||
Label("Add Item", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addItem() {
|
||||
withAnimation {
|
||||
let newItem = Item(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteItems(offsets: IndexSet) {
|
||||
withAnimation {
|
||||
offsets.map { items[$0] }.forEach(viewContext.delete)
|
||||
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let itemFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .medium
|
||||
return formatter
|
||||
}()
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
|
||||
}
|
||||
}
|
||||
48
MeshtasticClient/Helpers/LocationHelper.swift
Normal file
48
MeshtasticClient/Helpers/LocationHelper.swift
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// LocationHelper.swift
|
||||
// RChat
|
||||
//
|
||||
// Created by Andrew Morgan on 24/11/2020.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
struct MyAnnotationItem: Identifiable {
|
||||
var coordinate: CLLocationCoordinate2D
|
||||
let id = UUID()
|
||||
}
|
||||
|
||||
class LocationHelper: NSObject, ObservableObject {
|
||||
|
||||
static let shared = LocationHelper()
|
||||
static let DefaultLocation = CLLocationCoordinate2D(latitude: 51.506520923981554, longitude: -0.10689139236939127)
|
||||
|
||||
static var currentLocation: CLLocationCoordinate2D {
|
||||
guard let location = shared.locationManager.location else {
|
||||
return DefaultLocation
|
||||
}
|
||||
return location.coordinate
|
||||
}
|
||||
|
||||
private let locationManager = CLLocationManager()
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
locationManager.requestWhenInUseAuthorization()
|
||||
locationManager.startUpdatingLocation()
|
||||
}
|
||||
}
|
||||
|
||||
extension LocationHelper: CLLocationManagerDelegate {
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { }
|
||||
|
||||
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
print("Location manager failed with error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
|
||||
print("Location manager changed the status: \(status)")
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,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>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>We use your location to center maps of the mesh</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<?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>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticClient.xcdatamodel</string>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import SwiftUI
|
|||
|
||||
@main
|
||||
struct MeshtasticClientApp: App {
|
||||
let persistenceController = PersistenceController.shared
|
||||
@StateObject private var modelData = ModelData()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
MeshtasticClient/Model/Device.swift
Normal file
37
MeshtasticClient/Model/Device.swift
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
See LICENSE folder for this app’s licensing information.
|
||||
|
||||
Abstract:
|
||||
A representation of a single device.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreLocation
|
||||
|
||||
struct Device: Hashable, Codable, Identifiable {
|
||||
|
||||
var longName: String
|
||||
var shortName: String
|
||||
var id: String
|
||||
var region: String
|
||||
var hasGPS: Bool
|
||||
var isRouter: Bool
|
||||
var firmwareVersion: String
|
||||
var hardwareModel: String
|
||||
var lastHeard: Double
|
||||
var snr: Double
|
||||
|
||||
private var imageName: String
|
||||
var image: Image {
|
||||
Image(imageName)
|
||||
}
|
||||
|
||||
var position: Position
|
||||
|
||||
struct Position: Hashable, Codable {
|
||||
var latitude: Double
|
||||
var longitude: Double
|
||||
var altitude: Int
|
||||
var batteryLevel: Int }
|
||||
}
|
||||
37
MeshtasticClient/Model/ModelData.swift
Normal file
37
MeshtasticClient/Model/ModelData.swift
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Abstract:
|
||||
Storage for model data.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class ModelData: ObservableObject {
|
||||
@Published var devices: [Device] = load("deviceData.json")
|
||||
|
||||
var nearby: [Device] {
|
||||
devices
|
||||
}
|
||||
}
|
||||
|
||||
func load<T: Decodable>(_ filename: String) -> T {
|
||||
let data: Data
|
||||
|
||||
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
|
||||
else {
|
||||
fatalError("Couldn't find \(filename) in main bundle.")
|
||||
}
|
||||
|
||||
do {
|
||||
data = try Data(contentsOf: file)
|
||||
} catch {
|
||||
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
|
||||
}
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
} catch {
|
||||
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// Persistence.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/18/21.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
struct PersistenceController {
|
||||
static let shared = PersistenceController()
|
||||
|
||||
static var preview: PersistenceController = {
|
||||
let result = PersistenceController(inMemory: true)
|
||||
let viewContext = result.container.viewContext
|
||||
for _ in 0..<10 {
|
||||
let newItem = Item(context: viewContext)
|
||||
newItem.timestamp = Date()
|
||||
}
|
||||
do {
|
||||
try viewContext.save()
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
let nsError = error as NSError
|
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||
}
|
||||
return result
|
||||
}()
|
||||
|
||||
let container: NSPersistentCloudKitContainer
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
container = NSPersistentCloudKitContainer(name: "MeshtasticClient")
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
}
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
if let error = error as NSError? {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
|
||||
* The device is out of space.
|
||||
* The store could not be migrated to the current model version.
|
||||
Check the error message to determine what the actual problem was.
|
||||
*/
|
||||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
81
MeshtasticClient/Resources/deviceData.json
Normal file
81
MeshtasticClient/Resources/deviceData.json
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
[
|
||||
{
|
||||
"longName": "RAK GPS Node",
|
||||
"shortName": "RGN",
|
||||
"region": "US",
|
||||
"id": "!438f02b0",
|
||||
"firmwareVersion": "1.2.43",
|
||||
"lastHeard": 1628145597,
|
||||
"isRouter": true,
|
||||
"hasGPS": true,
|
||||
"hardwareModel": "RAK4631",
|
||||
"macAddress": "MAC STRING",
|
||||
"snr": 4.75,
|
||||
"imageName": "rak4631",
|
||||
"position": {
|
||||
"longitude": -122.151405,
|
||||
"latitude": 47.608330,
|
||||
"batteryLevel": 55,
|
||||
"altitude": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"longName": "RAK Solar 1",
|
||||
"shortName": "RS1",
|
||||
"region": "US",
|
||||
"id": "!3ba37b3e",
|
||||
"firmwareVersion": "1.2.43",
|
||||
"lastHeard": 1628091161,
|
||||
"isRouter": false,
|
||||
"hasGPS": false,
|
||||
"hardwareModel": "RAK4631",
|
||||
"macAddress": "MAC STRING",
|
||||
"snr": 4.75,
|
||||
"imageName": "rak4631",
|
||||
"position": {
|
||||
"longitude": -122.145316,
|
||||
"latitude": 47.601440,
|
||||
"batteryLevel": 55,
|
||||
"altitude": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"longName": "RAK Solar 2",
|
||||
"shortName": "RS2",
|
||||
"region": "US",
|
||||
"id": "!a66c166f",
|
||||
"firmwareVersion": "1.2.43",
|
||||
"lastHeard": 1628094190,
|
||||
"isRouter": false,
|
||||
"hasGPS": false,
|
||||
"hardwareModel": "RAK4631",
|
||||
"macAddress": "MAC STRING",
|
||||
"snr": 4.75,
|
||||
"imageName": "rak4631",
|
||||
"position": {
|
||||
"longitude": -122.148599,
|
||||
"latitude": 47.602106,
|
||||
"batteryLevel": 77,
|
||||
"altitude": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"longName": "Yellow Beam",
|
||||
"shortName": "YB",
|
||||
"region": "US",
|
||||
"id": "!050c2538",
|
||||
"firmwareVersion": "1.2.43",
|
||||
"lastHeard": 1628091161,
|
||||
"isRouter": false,
|
||||
"hasGPS": true,
|
||||
"hardwareModel": "TBEAM",
|
||||
"macAddress": "MAC STRING",
|
||||
"snr": 4.75,
|
||||
"imageName": "tbeam",
|
||||
"position": {
|
||||
"longitude": -122.154392,
|
||||
"latitude": 47.598788,
|
||||
"batteryLevel": 55,
|
||||
"altitude": 150
|
||||
}
|
||||
}]
|
||||
57
MeshtasticClient/Views/ContentView.swift
Normal file
57
MeshtasticClient/Views/ContentView.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Abstract: Default App View
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var selection: Tab = .devices
|
||||
|
||||
enum Tab {
|
||||
case messages
|
||||
case devices
|
||||
case map
|
||||
case featured
|
||||
case list
|
||||
case ble
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selection) {
|
||||
|
||||
DeviceHome()
|
||||
.tabItem {
|
||||
Label("Devices", systemImage: "flipphone")
|
||||
}
|
||||
.tag(Tab.devices)
|
||||
DeviceList()
|
||||
.tabItem {
|
||||
Label("Device List", systemImage: "list.bullet.rectangle")
|
||||
}
|
||||
.tag(Tab.devices)
|
||||
DeviceMap()
|
||||
.tabItem {
|
||||
Label("Mesh Map", systemImage: "map")
|
||||
}
|
||||
.tag(Tab.map)
|
||||
Messages()
|
||||
.tabItem {
|
||||
Label("Messages", systemImage: "message")
|
||||
}
|
||||
.tag(Tab.messages)
|
||||
DeviceBLE()
|
||||
.tabItem {
|
||||
Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right")
|
||||
}
|
||||
.tag(Tab.ble)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
.environmentObject(ModelData())
|
||||
}
|
||||
}
|
||||
45
MeshtasticClient/Views/Devices/DeviceBLE.swift
Normal file
45
MeshtasticClient/Views/Devices/DeviceBLE.swift
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// DeviceBLE.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/18/21.
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct DeviceBLE: View {
|
||||
|
||||
@EnvironmentObject var modelData: ModelData
|
||||
|
||||
var devices: [Device] {
|
||||
modelData.devices
|
||||
}
|
||||
|
||||
var myPeripheal:CBPeripheral?
|
||||
var myCharacteristic:CBCharacteristic?
|
||||
var bleManager:CBCentralManager?
|
||||
|
||||
let serviceUUID = CBUUID(string: "ab0828b1-198e-4351-b779-901fa0e0371e")
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
|
||||
ScrollView {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
.navigationTitle("Bluetooth")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
144
MeshtasticClient/Views/Devices/DeviceDetail.swift
Normal file
144
MeshtasticClient/Views/Devices/DeviceDetail.swift
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
48
MeshtasticClient/Views/Devices/DeviceHome.swift
Normal file
48
MeshtasticClient/Views/Devices/DeviceHome.swift
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// DeviceHome.swift
|
||||
// Landmarks
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/7/21.
|
||||
// See LICENSE folder for app licensing information.
|
||||
//
|
||||
|
||||
// Abstract:
|
||||
// A view showing devices above a list of devices
|
||||
// grouped by device.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceHome_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DeviceHome()
|
||||
.environmentObject(ModelData())
|
||||
}
|
||||
}
|
||||
49
MeshtasticClient/Views/Devices/DeviceList.swift
Normal file
49
MeshtasticClient/Views/Devices/DeviceList.swift
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// DeviceHome.swift
|
||||
// Landmarks
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/7/21.
|
||||
// See LICENSE folder for app licensing information.
|
||||
//
|
||||
|
||||
// Abstract:
|
||||
// A view showing devices above a list of devices
|
||||
// grouped by device.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DeviceList: 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())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceList_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DeviceHome()
|
||||
.environmentObject(ModelData())
|
||||
}
|
||||
}
|
||||
60
MeshtasticClient/Views/Devices/DeviceMap.swift
Normal file
60
MeshtasticClient/Views/Devices/DeviceMap.swift
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// DeviceMap.swift
|
||||
// Landmarks
|
||||
//
|
||||
// Created by Garth Vander Houwen on 8/7/21.
|
||||
// Copyright © 2021 Apple. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct DeviceMap: View {
|
||||
|
||||
@EnvironmentObject var modelData: ModelData
|
||||
|
||||
var devices: [Device] {
|
||||
modelData.devices
|
||||
}
|
||||
|
||||
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 }
|
||||
)
|
||||
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))
|
||||
]
|
||||
|
||||
ZStack {
|
||||
Map(coordinateRegion: regionBinding,
|
||||
interactionModes: [.all],
|
||||
showsUserLocation: true,
|
||||
userTrackingMode: .constant(.follow), annotationItems: annotations) { location in
|
||||
|
||||
MapAnnotation(
|
||||
coordinate: location.coordinate,
|
||||
content: {
|
||||
Text(location.name).font(.caption2).foregroundColor(.white)
|
||||
.background(Circle()
|
||||
.fill(Color.blue)
|
||||
.frame(width: 40, height: 40)) }
|
||||
)
|
||||
}.frame(maxHeight:.infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
55
MeshtasticClient/Views/Devices/DeviceRow.swift
Normal file
55
MeshtasticClient/Views/Devices/DeviceRow.swift
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
See LICENSE folder for this sample’s licensing information.
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
25
MeshtasticClient/Views/Helpers/CircleImage.swift
Normal file
25
MeshtasticClient/Views/Helpers/CircleImage.swift
Normal file
|
|
@ -0,0 +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.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CircleImage: View {
|
||||
var image: Image
|
||||
|
||||
var body: some View {
|
||||
image
|
||||
.clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
|
||||
.overlay(Circle().stroke(Color.white, lineWidth: 4))
|
||||
.shadow(radius: 7)
|
||||
}
|
||||
}
|
||||
|
||||
struct CircleImage_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CircleImage(image: Image("tbeam"))
|
||||
}
|
||||
}
|
||||
57
MeshtasticClient/Views/Messages/Messages.swift
Normal file
57
MeshtasticClient/Views/Messages/Messages.swift
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import SwiftUI
|
||||
import MapKit
|
||||
import CoreLocation
|
||||
|
||||
struct Messages: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue