import 'dart:io' show Platform; import 'package:badge/device_details.dart'; import 'package:badge/device_scan_select.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:logger/logger.dart'; import 'package:permission_handler/permission_handler.dart'; var logger = Logger(printer: PrettyPrinter()); class ScanPage extends StatefulWidget { const ScanPage({super.key, required this.title}); // Original doc: Fields in a Widget subclass are always marked "final". final String title; @override State createState() => _ScanPageState(); } class _ScanPageState extends State { List scanResults = []; bool isScanning = false; ScanResult? selectedDevice; void _doConnect() async { var dev = selectedDevice?.device; if (dev == null) return; //??? Navigator.push( context, MaterialPageRoute( builder: (context) => DeviceDetailsScreen(btDevice: dev), ), ); } void _doScan() async { var system = await FlutterBluePlus.systemDevices([]); for (var d in system) { logger.i('${d.platformName} already connected to! ${d.remoteId}'); } setState(() { selectedDevice = null; scanResults = []; isScanning = true; }); var subscription = FlutterBluePlus.scanResults.listen( onScanResult, onError: (e) => logger.e(e), ); // either this, or the cancel in the finally block, should do the same? FlutterBluePlus.cancelWhenScanComplete(subscription); try { if (Platform.isAndroid) { // Ehhhh... can't have both keyword/services await FlutterBluePlus.startScan( withKeywords: ["NimBLE"], timeout: Duration(seconds: 5), ); } else { // for Linux, which can't do advNames (but platformname, for whatever reason) // msd doesn't work, either???? await FlutterBluePlus.startScan( //withMsd: [MsdFilter(0xffff, data: ascii.encode("uvok"))], timeout: Duration(seconds: 5), ); } // wait for scanning to stop await FlutterBluePlus.isScanning.where((val) => val == false).first; } finally { subscription.cancel(); setState(() { isScanning = false; }); } } void onScanResult(List results) { if (results.isNotEmpty) { for (var r in results.where( (d) => d.rssi > -90 && !_deviceInResults(d), )) { logger.i( '${r.device.remoteId}: "${r.device.platformName}" / "${r.device.advName}" / "${r.advertisementData.advName}" found!', ); scanResults.add(r); } setState(() {}); } } bool _deviceInResults(ScanResult incomingDev) => scanResults.any( (existingDev) => existingDev.device.remoteId == incomingDev.device.remoteId, ); Future getPermissions() async { try { await Permission.bluetooth.request(); } catch (e) { logger.e(e.toString()); } } void btHandler(BluetoothAdapterState event) { logger.i(event); switch (event) { case BluetoothAdapterState.on: break; default: break; } } @override void initState() { super.initState(); getPermissions(); FlutterBluePlus.adapterState.listen(btHandler); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Column( // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" // action in the IDE, or press "p" in the console), to see the // wireframe for each widget. mainAxisAlignment: MainAxisAlignment.center, spacing: 24, children: [ SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.center, spacing: 15.0, children: [ ElevatedButton( onPressed: isScanning ? null : _doScan, child: isScanning ? Text("Scanning...") : Text("Start scan"), ), ElevatedButton( onPressed: (selectedDevice == null || isScanning) ? null : _doConnect, child: Text("Connect"), ), ], ), Expanded( child: DeviceScanSelection( items: scanResults, onItemSelected: (item) { setState(() => selectedDevice = item); }, ), ), ], ), ), ); } }