import 'package:abawo_bt_app/controller/bluetooth.dart'; import 'package:abawo_bt_app/model/shifter_types.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactive_ble/flutter_reactive_ble.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class BikeScanDialog extends ConsumerStatefulWidget { const BikeScanDialog({ required this.excludedDeviceId, super.key, }); final String excludedDeviceId; static Future show( BuildContext context, { required String excludedDeviceId, }) { return showDialog( context: context, barrierDismissible: true, builder: (_) => BikeScanDialog(excludedDeviceId: excludedDeviceId), ); } @override ConsumerState createState() => _BikeScanDialogState(); } class _BikeScanDialogState extends ConsumerState { bool _showAll = false; BluetoothController? _controller; @override void initState() { super.initState(); _startScan(); } Future _startScan() async { final controller = await ref.read(bluetoothProvider.future); _controller = controller; await controller.stopScan(); await controller.startScan(); } @override void dispose() { _controller?.stopScan(); super.dispose(); } @override Widget build(BuildContext context) { final btAsync = ref.watch(bluetoothProvider); return Dialog( clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: SizedBox( width: 520, height: 520, child: btAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center(child: Text('Bluetooth error: $err')), data: (controller) { _controller ??= controller; return Column( children: [ _buildHeader(context), const Divider(height: 1), Expanded( child: StreamBuilder>( stream: controller.scanResultsStream, initialData: controller.scanResults, builder: (context, snapshot) { final devices = _filteredDevices(snapshot.data ?? const []); if (devices.isEmpty) { return const Center( child: Text('No matching devices nearby.'), ); } return ListView.separated( itemCount: devices.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (context, index) { final device = devices[index]; return ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), leading: CircleAvatar( backgroundColor: Theme.of(context) .colorScheme .primaryContainer, child: const Icon(Icons.pedal_bike), ), title: Text( device.name.isEmpty ? 'Unknown Device' : device.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( device.id, style: const TextStyle(fontFamily: 'monospace'), maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: _RssiBadge(rssi: device.rssi), onTap: () { Navigator.of(context).pop(device); }, ); }, ); }, ), ), ], ); }, ), ), ); } Widget _buildHeader(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 12, 12), child: Row( children: [ const Expanded( child: Text( 'Select Bike', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), ), ), Row( children: [ const Text('Show All'), Switch( value: _showAll, onChanged: (value) { setState(() { _showAll = value; }); }, ), ], ), IconButton( tooltip: 'Rescan', onPressed: _startScan, icon: const Icon(Icons.refresh), ), IconButton( tooltip: 'Close', onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), ), ], ), ); } List _filteredDevices(List devices) { final ftmsUuid = Uuid.parse(ftmsServiceUuid); return devices.where((device) { if (device.id == widget.excludedDeviceId) { return false; } if (_showAll) { return true; } return device.serviceUuids.contains(ftmsUuid); }).toList(growable: false); } } class _RssiBadge extends StatelessWidget { const _RssiBadge({required this.rssi}); final int rssi; @override Widget build(BuildContext context) { final color = rssi > -65 ? Colors.green : rssi > -80 ? Colors.orange : Colors.red; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), child: Text( '$rssi dBm', style: TextStyle( color: color, fontWeight: FontWeight.w600, fontSize: 12, ), ), ); } }