hondavert-dev/lib/ui/screens/bt_picker/bt_picker_screen.dart
HVBT Dev 763821171c feat: Add ECU Frame Debug Screen and DTC Screen
- 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.
2026-07-26 23:14:58 +05:30

179 lines
6.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/bluetooth/bt_poller.dart';
import '../../../core/providers/bt_provider.dart';
import '../../../core/providers/settings_provider.dart';
import '../../widgets/main_nav.dart';
class BtPickerScreen extends ConsumerStatefulWidget {
const BtPickerScreen({super.key});
@override
ConsumerState<BtPickerScreen> createState() => _BtPickerScreenState();
}
class _BtPickerScreenState extends ConsumerState<BtPickerScreen> {
String? _error;
bool _initDone = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _initBluetooth());
}
Future<void> _initBluetooth() async {
setState(() => _error = null);
final statuses = await [
Permission.bluetoothConnect,
Permission.bluetoothScan,
Permission.locationWhenInUse,
].request();
final denied = statuses.entries
.where((e) => e.value.isDenied || e.value.isPermanentlyDenied)
.map((e) => e.key.toString())
.toList();
if (denied.isNotEmpty) {
setState(() {
_error = 'Permissions denied: ${denied.join(", ")}\n\n'
'Go to Settings → Apps → HV BT Dashboard → Permissions.';
_initDone = true;
});
return;
}
final btState = await FlutterBluetoothSerial.instance.state;
if (btState != BluetoothState.STATE_ON) {
await FlutterBluetoothSerial.instance.requestEnable();
await Future<void>.delayed(const Duration(seconds: 1));
}
setState(() => _initDone = true);
ref.invalidate(pairedDevicesProvider);
}
Future<void> _connect(BluetoothDevice device, EcuType ecuType) async {
ref.read(settingsProvider.notifier).setEcuType(ecuType);
await ref.read(btProvider.notifier).connect(device);
if (!mounted) return;
if (ref.read(btProvider).isConnected) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const MainNav()),
);
}
}
void _showProtocolDialog(BluetoothDevice device) {
showDialog<void>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(device.name ?? device.address),
content: const Text('Select ECU protocol:'),
actions: [
TextButton(
onPressed: () { Navigator.pop(ctx); _connect(device, EcuType.s300); },
child: const Text('S300'),
),
TextButton(
onPressed: () { Navigator.pop(ctx); _connect(device, EcuType.kpro); },
child: const Text('KPro'),
),
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Cancel'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final btState = ref.watch(btProvider);
final devicesAsync = ref.watch(pairedDevicesProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Connect to ECU'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loading ? null : _initBluetooth,
),
],
),
body: Column(
children: [
if (btState.status == BtStatus.error)
_banner('Connection failed: ${btState.error}'),
if (_error != null) _banner(_error!),
Expanded(
child: !_initDone
? const Center(child: CircularProgressIndicator())
: devicesAsync.when(
loading: () => const Center(
child: CircularProgressIndicator()),
error: (e, _) =>
Center(child: _banner('Failed to load: $e')),
data: (devices) => devices.isEmpty
? const Center(
child: Padding(
padding: EdgeInsets.all(24),
child: Text(
'No paired BT devices.\nPair your ECU in Android Settings first.',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
),
)
: ListView.separated(
itemCount: devices.length,
separatorBuilder: (_, __) =>
const Divider(height: 1),
itemBuilder: (_, i) {
final d = devices[i];
final connecting = btState.isConnecting &&
btState.deviceName ==
(d.name ?? d.address);
return ListTile(
leading: const Icon(Icons.bluetooth),
title: Text(d.name ?? 'Unknown'),
subtitle: Text(d.address),
trailing: connecting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2))
: const Icon(
Icons.arrow_forward_ios,
size: 14),
onTap: connecting
? null
: () => _showProtocolDialog(d),
);
},
),
),
),
],
),
);
}
bool get _loading => !_initDone || ref.read(btProvider).isConnecting;
Widget _banner(String msg) => Container(
width: double.infinity,
color: const Color(0xFF4A0000),
padding: const EdgeInsets.all(12),
child: Text(msg,
style: const TextStyle(color: Colors.redAccent, fontSize: 12)),
);
}