import 'dart:async'; import 'dart:typed_data'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../bluetooth/bt_poller.dart'; import '../bluetooth/bt_service.dart'; import 'settings_provider.dart'; // ─── Connection status enum ────────────────────────────────────────────────── enum BtStatus { disconnected, connecting, connected, error } // ─── State ─────────────────────────────────────────────────────────────────── class BtState { final BtStatus status; final String? deviceName; final String? error; final int frameCount; final int droppedFrames; const BtState({ required this.status, this.deviceName, this.error, this.frameCount = 0, this.droppedFrames = 0, }); const BtState.disconnected() : this(status: BtStatus.disconnected); BtState connecting(String name) => BtState(status: BtStatus.connecting, deviceName: name); BtState connected(String name) => BtState(status: BtStatus.connected, deviceName: name); BtState withError(String msg) => BtState(status: BtStatus.error, deviceName: deviceName, error: msg); BtState withFrameStats(int frames, int dropped) => BtState( status: status, deviceName: deviceName, frameCount: frames, droppedFrames: dropped, ); bool get isConnected => status == BtStatus.connected; bool get isConnecting => status == BtStatus.connecting; } // ─── Notifier ──────────────────────────────────────────────────────────────── class BtNotifier extends StateNotifier { final Ref _ref; BtPoller? _poller; StreamSubscription? _pollSub; final StreamController _frameController = StreamController.broadcast(); BtNotifier(this._ref) : super(const BtState.disconnected()); /// Raw validated 128-byte frame stream — consumed by sensorStateProvider. Stream get frameStream => _frameController.stream; Future connect(BluetoothDevice device) async { state = state.connecting(device.name ?? device.address); final service = _ref.read(btServiceProvider); final settings = _ref.read(settingsProvider); try { await service.connect(device); _poller = BtPoller( service, ecuType: settings.ecuType, pollingInterval: settings.pollingInterval, ); _pollSub = _poller!.frameStream.listen( (frame) { _frameController.add(frame); state = state.withFrameStats( _poller!.validFrames, _poller!.droppedFrames); }, onError: (Object e) { _frameController.addError(e); state = state.withError(e.toString()); }, onDone: () => state = state.withError('BT stream closed'), ); _poller!.start(); state = state.connected(device.name ?? device.address); } catch (e) { await service.disconnect(); state = state.withError(e.toString()); } } Future disconnect() async { await _pollSub?.cancel(); _poller?.stop(); _poller?.dispose(); _poller = null; await _ref.read(btServiceProvider).disconnect(); state = const BtState.disconnected(); } @override void dispose() { _pollSub?.cancel(); _poller?.dispose(); _frameController.close(); super.dispose(); } } // ─── Providers ─────────────────────────────────────────────────────────────── /// Singleton BtService — lives as long as the app. final btServiceProvider = Provider((ref) { final service = BtService(); ref.onDispose(service.dispose); return service; }); /// BT connection state notifier. final btProvider = StateNotifierProvider((ref) => BtNotifier(ref)); /// Paired BT devices list (re-fetchable via ref.refresh). final pairedDevicesProvider = FutureProvider>((ref) { return FlutterBluetoothSerial.instance.getBondedDevices(); });