mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Node Colors!
This commit is contained in:
parent
64b3d42f2d
commit
2e416020e0
10 changed files with 136 additions and 74 deletions
|
|
@ -20,6 +20,22 @@ extension CLLocationCoordinate2D {
|
|||
}
|
||||
}
|
||||
|
||||
extension Color {
|
||||
func isLight() -> Bool {
|
||||
guard let components = cgColor?.components, components.count > 2 else {return false}
|
||||
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
|
||||
return (brightness > 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
func isLight() -> Bool {
|
||||
guard let components = cgColor.components, components.count > 2 else {return false}
|
||||
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
|
||||
return (brightness > 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
var macAddressString: String {
|
||||
let mac: String = reduce("") {$0 + String(format: "%02x:", $1)}
|
||||
|
|
@ -72,6 +88,21 @@ extension Int {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension Int64 {
|
||||
|
||||
func uiColor() -> UIColor {
|
||||
let color = UIColor(
|
||||
red: CGFloat((self & 0xFF0000) >> 16) / 255.0,
|
||||
green: CGFloat((self & 0x00FF00) >> 8) / 255.0,
|
||||
blue: CGFloat(self & 0x0000FF) / 255.0,
|
||||
alpha: CGFloat(1.0))
|
||||
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
extension UIImage {
|
||||
func rotate(radians: Float) -> UIImage? {
|
||||
var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
|
||||
|
|
|
|||
|
|
@ -766,23 +766,25 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM
|
|||
guard let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as? [MyInfoEntity] else {
|
||||
return
|
||||
}
|
||||
for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] {
|
||||
if channel.index == newMessage.channel {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
|
||||
if channel.index == newMessage.channel && !channel.mute {
|
||||
// Create an iOS Notification for the received private channel message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
|
||||
content: messageText)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
|
||||
if !fetchedMyInfo.isEmpty {
|
||||
for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] {
|
||||
if channel.index == newMessage.channel {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
|
||||
if channel.index == newMessage.channel && !channel.mute {
|
||||
// Create an iOS Notification for the received private channel message and schedule it immediately
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
id: ("notification.id.\(newMessage.messageId)"),
|
||||
title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))",
|
||||
subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")",
|
||||
content: messageText)
|
||||
]
|
||||
manager.schedule()
|
||||
print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ struct CircleText: View {
|
|||
var circleSize: CGFloat? = 60
|
||||
var fontSize: CGFloat? = 20
|
||||
var brightness: Double? = 0
|
||||
var textColor: Color? = .white
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ struct CircleText: View {
|
|||
.fill(color)
|
||||
.brightness(brightness ?? 0)
|
||||
.frame(width: circleSize, height: circleSize)
|
||||
Text(text).textCase(.uppercase).font(font).foregroundColor(.white).fixedSize()
|
||||
Text(text).textCase(.uppercase).font(font).foregroundColor(textColor).fixedSize()
|
||||
.frame(width: circleSize, height: circleSize, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/).offset(x: 0, y: 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void
|
||||
var onWaypointEdit: (_ waypointId: Int ) -> Void
|
||||
let mapView = MKMapView()
|
||||
let lineColors: [UIColor] = [UIColor.systemIndigo, UIColor.yellow, UIColor.white, UIColor.red, UIColor.purple, UIColor.orange, UIColor.magenta, UIColor.lightGray, UIColor.green, UIColor.gray, UIColor.systemMint, UIColor.darkGray, UIColor.cyan, UIColor.brown, UIColor.blue, UIColor.black, UIColor.systemPink,
|
||||
UIColor.systemTeal]
|
||||
// Parameters
|
||||
let positions: [PositionEntity]
|
||||
let waypoints: [WaypointEntity]
|
||||
|
|
@ -142,7 +140,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
return position.nodeCoordinate!
|
||||
})
|
||||
let polyline = MKPolyline(coordinates: lineCoords, count: nodePositions.count)
|
||||
polyline.title = "\(String(position.nodePosition?.num ?? 0))-\(String(lineIndex))"
|
||||
polyline.title = "\(String(position.nodePosition?.num ?? 0))"
|
||||
mapView.addOverlay(polyline)
|
||||
lineIndex += 1
|
||||
// There are 18 colors for lines, start over if we are at index 17
|
||||
|
|
@ -199,7 +197,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
annotationView.displayPriority = .required
|
||||
annotationView.titleVisibility = .visible
|
||||
} else {
|
||||
annotationView.markerTintColor = UIColor(.indigo)
|
||||
annotationView.markerTintColor = positionAnnotation.nodePosition?.num.uiColor()
|
||||
annotationView.displayPriority = .defaultHigh
|
||||
annotationView.titleVisibility = .adaptive
|
||||
}
|
||||
|
|
@ -351,10 +349,10 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
} else {
|
||||
if let routePolyline = overlay as? MKPolyline {
|
||||
|
||||
let titleString = routePolyline.title ?? "None-0"
|
||||
let titleString = routePolyline.title ?? "0"
|
||||
let index = Int(titleString.components(separatedBy: "-").last ?? "0")
|
||||
let renderer = MKPolylineRenderer(polyline: routePolyline)
|
||||
renderer.strokeColor = parent.lineColors[index ?? 0]
|
||||
renderer.strokeColor = Int64(titleString)?.uiColor()
|
||||
renderer.lineWidth = 8
|
||||
return renderer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ struct Contacts: View {
|
|||
HStack {
|
||||
VStack {
|
||||
HStack {
|
||||
CircleText(text: user.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1)
|
||||
CircleText(text: user.shortName ?? "???", color: Color(user.num.uiColor()), circleSize: 52, fontSize: 16, textColor: user.num.uiColor().isLight() ? .black : .white)
|
||||
.padding(.trailing, 5)
|
||||
VStack {
|
||||
HStack {
|
||||
|
|
|
|||
|
|
@ -21,25 +21,67 @@ struct DeviceMetricsLog: View {
|
|||
|
||||
let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())
|
||||
let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).reversed() as? [TelemetryEntity] ?? []
|
||||
let batteryChartData = deviceMetrics
|
||||
let chartData = deviceMetrics
|
||||
.filter { $0.time != nil && $0.time! >= oneDayAgo! }
|
||||
.sorted { $0.time! < $1.time! }
|
||||
|
||||
NavigationStack {
|
||||
|
||||
if batteryChartData.count > 0 {
|
||||
if chartData.count > 0 {
|
||||
|
||||
GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) {
|
||||
Chart(batteryChartData, id: \.self) {
|
||||
LineMark(
|
||||
x: .value("Hour", $0.time!.formattedDate(format: "ha")),
|
||||
y: .value("Value", $0.batteryLevel)
|
||||
)
|
||||
|
||||
Chart(chartData, id: \.self) {
|
||||
|
||||
PointMark(
|
||||
x: .value("Hour", $0.time!.formattedDate(format: "ha")),
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.channelUtilization)
|
||||
)
|
||||
.foregroundStyle(.green)
|
||||
|
||||
LineMark(
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.channelUtilization)
|
||||
)
|
||||
.foregroundStyle(.green)
|
||||
.interpolationMethod(.catmullRom)
|
||||
|
||||
PointMark(
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.batteryLevel)
|
||||
)
|
||||
.foregroundStyle(.blue)
|
||||
|
||||
LineMark(
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.batteryLevel)
|
||||
)
|
||||
.foregroundStyle(.blue)
|
||||
|
||||
PointMark(
|
||||
//x: .value("Hour", $0.time!.formattedDate(format: "ha")),
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.airUtilTx)
|
||||
)
|
||||
.foregroundStyle(.red)
|
||||
|
||||
LineMark(
|
||||
x: .value("Time", $0.time!, unit: .hour),
|
||||
y: .value("Value", $0.airUtilTx)
|
||||
)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.frame(height: 150)
|
||||
// Set color for each data in the chart
|
||||
.chartForegroundStyleScale([
|
||||
"Battery Level" : .blue,
|
||||
"Channel Utilization": .green,
|
||||
"Airtime": .red
|
||||
])
|
||||
.chartLegend(position: .automatic, alignment: .bottom)
|
||||
.chartXAxis {
|
||||
AxisMarks(values: .stride(by: .hour))
|
||||
}
|
||||
//.frame(height: 200)
|
||||
}
|
||||
}
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
|
|
|
|||
|
|
@ -19,47 +19,36 @@ struct EnvironmentMetricsLog: View {
|
|||
var node: NodeInfoEntity
|
||||
|
||||
var body: some View {
|
||||
|
||||
let environmentMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).reversed() as? [TelemetryEntity] ?? []
|
||||
|
||||
NavigationStack {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "")
|
||||
Text("\(environmentMetrics.count) Readings")
|
||||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
// Add a table for mac and ipad
|
||||
Table(node.telemetries!.reversed() as! [TelemetryEntity]) {
|
||||
Table(environmentMetrics) {
|
||||
TableColumn("Temperature") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text(em.temperature.formattedTemperature())
|
||||
}
|
||||
Text(em.temperature.formattedTemperature())
|
||||
}
|
||||
TableColumn("Humidity") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.relativeHumidity))%")
|
||||
}
|
||||
Text("\(String(format: "%.2f", em.relativeHumidity))%")
|
||||
}
|
||||
TableColumn("Barometric Pressure") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.barometricPressure)) hPa")
|
||||
}
|
||||
Text("\(String(format: "%.2f", em.barometricPressure)) hPa")
|
||||
}
|
||||
TableColumn("gas.resistance") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.gasResistance)) ohms")
|
||||
}
|
||||
Text("\(String(format: "%.2f", em.gasResistance)) ohms")
|
||||
}
|
||||
TableColumn("current") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.current))")
|
||||
}
|
||||
Text("\(String(format: "%.2f", em.current))")
|
||||
}
|
||||
TableColumn("voltage") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text("\(String(format: "%.2f", em.voltage))")
|
||||
}
|
||||
Text("\(String(format: "%.2f", em.voltage))")
|
||||
}
|
||||
TableColumn("timestamp") { em in
|
||||
if em.metricsType == 1 {
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
}
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -92,21 +81,18 @@ struct EnvironmentMetricsLog: View {
|
|||
}
|
||||
ForEach(node.telemetries?.reversed() as? [TelemetryEntity] ?? [], id: \.self) { (em: TelemetryEntity) in
|
||||
|
||||
if em.metricsType == 1 {
|
||||
GridRow {
|
||||
|
||||
GridRow {
|
||||
|
||||
Text(em.temperature.formattedTemperature())
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.relativeHumidity))%")
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.barometricPressure))")
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.gasResistance))")
|
||||
.font(.caption)
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
.font(.caption2)
|
||||
}
|
||||
Text(em.temperature.formattedTemperature())
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.relativeHumidity))%")
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.barometricPressure))")
|
||||
.font(.caption)
|
||||
Text("\(String(format: "%.2f", em.gasResistance))")
|
||||
.font(.caption)
|
||||
Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: ""))
|
||||
.font(.caption2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ struct NodeDetail: View {
|
|||
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26)
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 75, fontSize: 26, textColor: node.num.uiColor().isLight() ? .black : .white )
|
||||
}
|
||||
Divider()
|
||||
VStack {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@ struct NodeList: View {
|
|||
@State private var selection: NodeInfoEntity? // Nothing selected by default.
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
||||
|
||||
NavigationSplitView {
|
||||
List(nodes, id: \.self, selection: $selection) { node in
|
||||
List(nodes, id: \.self, selection: $selection) { node in
|
||||
if nodes.count == 0 {
|
||||
Text("no.nodes").font(.title)
|
||||
} else {
|
||||
|
|
@ -36,7 +38,7 @@ struct NodeList: View {
|
|||
let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1)
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color(node.num.uiColor()), circleSize: 52, fontSize: 16, brightness: 0.0, textColor: node.num.uiColor().isLight() ? .black : .white)
|
||||
.padding(.trailing, 5)
|
||||
VStack(alignment: .leading) {
|
||||
Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ struct RangeTestConfig: View {
|
|||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.positionConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false))
|
||||
.disabled(self.bleManager.connectedPeripheral == nil || node?.rangeTestConfig == nil || !(node != nil && node?.metadata?.hasWifi ?? false))
|
||||
Button {
|
||||
isPresentingSaveConfirm = true
|
||||
} label: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue