diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 042fc9d1..79475f4e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -688,9 +688,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage // Update our live activity if there is one running, not available on mac iOS >= 16.2 #if !targetEnvironment(macCatalyst) - let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())! - let date = Date.now...oneMinuteLater - let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel)) + let oneHourLater = Calendar.current.date(byAdding: .minute, value: (Int(60) ), to: Date())! + let date = Date.now...oneHourLater + let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel), nodes: 17, nodesOnline: 9) let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default) let updatedContent = ActivityContent(state: updatedMeshStatus, staleDate: nil) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index e0dfdc72..642a8780 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -72,8 +72,17 @@ struct Connect: View { Text("subscribed").font(.callout) .foregroundColor(.green) } else { - Text("communicating").font(.callout) - .foregroundColor(.orange) + + HStack { + if #available(iOS 17.0, macOS 14.0, *) { + Image(systemName: "square.stack.3d.down.forward") + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .foregroundColor(.orange) + } + Text("communicating").font(.callout) + .foregroundColor(.orange) + } } } } @@ -304,7 +313,7 @@ struct Connect: View { let future = Date(timeIntervalSinceNow: Double(timerSeconds)) - let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0)) + let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0), nodes: 17, nodesOnline: 9) let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index df4be238..b2f975b1 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -74,12 +74,23 @@ struct ChannelMessageList: View { .cornerRadius(15) .overlay( VStack { - isDetectionSensorMessage ? Image(systemName: "sensor.fill") + if #available(iOS 17.0, macOS 14.0, *) { + isDetectionSensorMessage ? Image(systemName: "sensor.fill") + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) + .foregroundStyle(Color.orange) + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .offset(x: 20, y: -20) + : nil + } else { + isDetectionSensorMessage ? Image(systemName: "sensor.fill") .padding() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing) .foregroundStyle(Color.orange) .offset(x: 20, y: -20) - : nil + : nil + } } ) .contextMenu { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 8cd324e5..0423f3c5 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -123,7 +123,11 @@ struct NodeList: View { if let node = selection { NodeDetail(node: node) } else { - Text("select.node") + if #available (iOS 17, *) { + ContentUnavailableView("select.node", systemImage: "flipphone", description: Text("Pick a node from the list.")) + } else { + Text("select.node") + } } } .searchable(text: nodesQuery, prompt: "Find a node") diff --git a/Widgets/MeshActivityAttributes.swift b/Widgets/MeshActivityAttributes.swift index 5159898f..916377c9 100644 --- a/Widgets/MeshActivityAttributes.swift +++ b/Widgets/MeshActivityAttributes.swift @@ -20,6 +20,8 @@ struct MeshActivityAttributes: ActivityAttributes { var channelUtilization: Float var airtime: Float var batteryLevel: UInt32 + var nodes: Int + var nodesOnline: Int } // Fixed non-changing properties about your activity go here! diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 6b67b552..43f3b52c 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -14,7 +14,7 @@ struct WidgetsLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: MeshActivityAttributes.self) { context in - LiveActivityView(nodeName: context.attributes.name, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, timerRange: context.state.timerRange) + LiveActivityView(nodeName: context.attributes.name, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, nodes: 17, nodesOnline: 7, timerRange: context.state.timerRange) .widgetURL(URL(string: "meshtastic://node/\(context.attributes.name)")) } dynamicIsland: { context in @@ -105,7 +105,7 @@ struct WidgetsLiveActivity: Widget { struct WidgetsLiveActivity_Previews: PreviewProvider { static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") static let state = MeshActivityAttributes.ContentState( - timerRange: Date.now...Date(timeIntervalSinceNow: 3600), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39) + timerRange: Date.now...Date(timeIntervalSinceNow: 3600), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39, nodes: 17, nodesOnline: 9) static var previews: some View { attributes @@ -133,6 +133,8 @@ struct LiveActivityView: View { var channelUtilization: Float var airtime: Float var batteryLevel: UInt32 + var nodes: Int + var nodesOnline: Int var timerRange: ClosedRange var body: some View { @@ -144,7 +146,7 @@ struct LiveActivityView: View { .aspectRatio(contentMode: .fit) .frame(width: 65) Spacer() - NodeInfoView(nodeName: nodeName, timerRange: timerRange, channelUtilization: channelUtilization, airtime: airtime, batteryLevel: batteryLevel) + NodeInfoView(nodeName: nodeName, timerRange: timerRange, channelUtilization: channelUtilization, airtime: airtime, batteryLevel: batteryLevel, nodes: nodes, nodesOnline: nodesOnline) Spacer() VStack { BatteryIcon(batteryLevel: Int32(batteryLevel), font: .title, color: .secondary) @@ -188,6 +190,8 @@ struct NodeInfoView: View { var channelUtilization: Float var airtime: Float var batteryLevel: UInt32 + var nodes: Int + var nodesOnline: Int var body: some View { VStack(alignment: .leading, spacing: 0) { @@ -207,6 +211,12 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() + Text("\(String(format: "Connected: %d of %d online", nodesOnline, nodes))") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() let now = Date() Text("Last Heard: \(now.formatted())") .font(.caption)