- 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.
137 lines
4.9 KiB
Dart
137 lines
4.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/models/sensor_defs.dart';
|
|
import '../../../core/providers/sensor_history_provider.dart';
|
|
import '../../../core/providers/theme_provider.dart';
|
|
import '../../../ui/theme/app_colors.dart';
|
|
import 'widgets/graph_time_selector.dart';
|
|
import 'widgets/sensor_graph_panel.dart';
|
|
|
|
final _graphWindowProvider = StateProvider<int>((ref) => 60);
|
|
final _graphSensorsProvider = StateProvider<List<String>>(
|
|
(ref) => ['rpm', 'map', 'tps'],
|
|
);
|
|
|
|
class GraphScreen extends ConsumerWidget {
|
|
const GraphScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final history = ref.watch(sensorHistoryProvider);
|
|
final colors = AppColors.forTheme(ref.watch(themeProvider));
|
|
final window = ref.watch(_graphWindowProvider);
|
|
final sensors = ref.watch(_graphSensorsProvider);
|
|
|
|
return Column(
|
|
children: [
|
|
// Time selector bar
|
|
Container(
|
|
color: const Color(0xFF1A1A1A),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
const Text('Window:',
|
|
style: TextStyle(color: Colors.grey, fontSize: 12)),
|
|
const SizedBox(width: 8),
|
|
GraphTimeSelector(
|
|
windowSeconds: window,
|
|
onChanged: (s) =>
|
|
ref.read(_graphWindowProvider.notifier).state = s,
|
|
),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: Icon(Icons.add_chart, color: colors.accent, size: 20),
|
|
tooltip: 'Add sensor',
|
|
onPressed: () => _addSensor(context, ref, sensors),
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Graph panels
|
|
Expanded(
|
|
child: sensors.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.show_chart,
|
|
size: 48, color: colors.accent.withAlpha(80)),
|
|
const SizedBox(height: 12),
|
|
const Text('No sensors selected',
|
|
style: TextStyle(color: Colors.grey)),
|
|
const SizedBox(height: 8),
|
|
ElevatedButton.icon(
|
|
onPressed: () => _addSensor(context, ref, sensors),
|
|
icon: const Icon(Icons.add, size: 16),
|
|
label: const Text('Add sensor'),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
itemCount: sensors.length,
|
|
itemBuilder: (context, i) => SensorGraphPanel(
|
|
sensorId: sensors[i],
|
|
history: history,
|
|
windowSeconds: window,
|
|
accentColor: colors.accent,
|
|
onRemove: () {
|
|
final next = List<String>.from(sensors)..removeAt(i);
|
|
ref.read(_graphSensorsProvider.notifier).state = next;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _addSensor(
|
|
BuildContext context, WidgetRef ref, List<String> current) {
|
|
showModalBottomSheet<void>(
|
|
context: context,
|
|
backgroundColor: const Color(0xFF1A1A1A),
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
|
),
|
|
builder: (_) => ListView(
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.all(16),
|
|
child: Text('ADD SENSOR TO GRAPH',
|
|
style: TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 12,
|
|
letterSpacing: 2)),
|
|
),
|
|
const Divider(height: 1, color: Colors.white10),
|
|
...sensorDefs.map((def) {
|
|
final already = current.contains(def.id);
|
|
return ListTile(
|
|
title: Text(def.displayName,
|
|
style: TextStyle(
|
|
color: already ? Colors.grey : Colors.white)),
|
|
subtitle: Text(def.unit,
|
|
style: const TextStyle(
|
|
color: Colors.grey, fontSize: 11)),
|
|
trailing: already
|
|
? const Icon(Icons.check,
|
|
color: Colors.grey, size: 16)
|
|
: null,
|
|
onTap: already
|
|
? null
|
|
: () {
|
|
ref.read(_graphSensorsProvider.notifier).state =
|
|
[...current, def.id];
|
|
Navigator.pop(context);
|
|
},
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|