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 createState() => _BtPickerScreenState(); } class _BtPickerScreenState extends ConsumerState { String? _error; bool _initDone = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _initBluetooth()); } Future _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.delayed(const Duration(seconds: 1)); } setState(() => _initDone = true); ref.invalidate(pairedDevicesProvider); } Future _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( 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)), ); }