- Implemented RawFrameDebugScreen for displaying ECU frame data and diagnostics. - Created DtcScreen to show active Diagnostic Trouble Codes (DTCs) with detailed descriptions. - Added DtcRow widget for individual DTC representation. - Introduced GraphScreen for visualizing sensor data over time with customizable time windows. - Developed SensorListScreen to display available sensors and their current values. - Enhanced SettingsScreen with options for Bluetooth connection, ECU protocol selection, polling interval, and theme settings. - Added AppColors and AppTheme for consistent theming across the application. - Implemented BtStatusChip to show Bluetooth connection status in the app bar. - Created RecordFab for starting and stopping data recording sessions.
134 lines
4.3 KiB
Dart
134 lines
4.3 KiB
Dart
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:flutter/material.dart';
|
|
import '../../../../core/models/sensor_defs.dart';
|
|
import '../../../../core/providers/sensor_history_provider.dart';
|
|
|
|
class SensorGraphPanel extends StatelessWidget {
|
|
final String sensorId;
|
|
final SensorHistory history;
|
|
final int windowSeconds;
|
|
final Color accentColor;
|
|
final VoidCallback onRemove;
|
|
|
|
const SensorGraphPanel({
|
|
super.key,
|
|
required this.sensorId,
|
|
required this.history,
|
|
required this.windowSeconds,
|
|
required this.accentColor,
|
|
required this.onRemove,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final def = sensorDefs.firstWhere(
|
|
(d) => d.id == sensorId,
|
|
orElse: () => sensorDefs.first,
|
|
);
|
|
final allSpots = history.get(sensorId);
|
|
|
|
// Slice to window
|
|
final spots = _window(allSpots, windowSeconds.toDouble());
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
padding: const EdgeInsets.fromLTRB(8, 10, 12, 8),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1A1A1A),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Row(
|
|
children: [
|
|
Text(
|
|
def.displayName.toUpperCase(),
|
|
style: TextStyle(
|
|
color: accentColor,
|
|
fontSize: 11,
|
|
letterSpacing: 1.5,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
def.unit,
|
|
style: const TextStyle(color: Colors.grey, fontSize: 11),
|
|
),
|
|
const Spacer(),
|
|
if (spots.isNotEmpty)
|
|
Text(
|
|
_fmt(spots.last.y),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
GestureDetector(
|
|
onTap: onRemove,
|
|
child: const Icon(Icons.close,
|
|
size: 16, color: Colors.white38),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
// Chart
|
|
SizedBox(
|
|
height: 90,
|
|
child: spots.length < 2
|
|
? const Center(
|
|
child: Text('Waiting for data…',
|
|
style: TextStyle(color: Colors.white30,
|
|
fontSize: 12)))
|
|
: LineChart(
|
|
LineChartData(
|
|
gridData: FlGridData(
|
|
show: true,
|
|
drawVerticalLine: false,
|
|
getDrawingHorizontalLine: (_) => FlLine(
|
|
color: Colors.white10,
|
|
strokeWidth: 1,
|
|
),
|
|
),
|
|
borderData: FlBorderData(show: false),
|
|
titlesData: const FlTitlesData(show: false),
|
|
minY: def.min.toDouble(),
|
|
maxY: def.max.toDouble(),
|
|
lineBarsData: [
|
|
LineChartBarData(
|
|
spots: spots,
|
|
isCurved: true,
|
|
curveSmoothness: 0.2,
|
|
color: accentColor,
|
|
barWidth: 1.5,
|
|
dotData: const FlDotData(show: false),
|
|
belowBarData: BarAreaData(
|
|
show: true,
|
|
color: accentColor.withAlpha(25),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
duration: Duration.zero,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
List<FlSpot> _window(List<FlSpot> spots, double windowSec) {
|
|
if (spots.isEmpty) return spots;
|
|
final maxX = spots.last.x;
|
|
final minX = maxX - windowSec;
|
|
return spots.where((s) => s.x >= minX).toList();
|
|
}
|
|
|
|
String _fmt(double v) =>
|
|
v.abs() >= 100 ? v.toStringAsFixed(1) : v.toStringAsFixed(2);
|
|
}
|