import 'dart:async'; import 'package:app_settings/app_settings.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart'; import 'package:permission_handler/permission_handler.dart'; import '../objects/bluetooth_device_entry.dart'; import '../objects/bluetooth_object.dart'; import '../screens/bluetooth_device_settings.dart'; import '../widgets/custom_list_tile.dart'; import '../widgets/single_section.dart'; class BluetoothScreen extends StatefulWidget { final bool start = true; const BluetoothScreen({super.key}); @override State createState() => _BluetoothScreen(); } // debugPrint("r: ${r.device.name} ${r.device.address} ${r.rssi}"); class _BluetoothScreen extends State { final List _discoveryResults = []; late BluetoothObject? _activeObject; late Stream? _stream; late StreamSubscription? _streamSubscription; bool _isDiscovering = false; bool _enabledPermissions = false; bool _enabledBluetooth = false; ButtonStyle buttonStyle = ElevatedButton.styleFrom( foregroundColor: Colors.black, backgroundColor: const Color(0xFFFDE100), // Text Color (Foreground color) ); bool get _hasActiveObject { return _activeObject == null ? false : true; } @override void initState() { _enablePermissions(); _enableBluetooth(); super.initState(); _activeObject = null; _streamSubscription = null; if (widget.start) { _initAsync(); } } Future _initAsync() async { await _enablePermissions(); await _enableBluetooth(); await _startDiscovery(); } @override void dispose() { // Avoid memory leak (`setState` after dispose) and cancel discovery debugPrint("called dispose"); _disposeAsync(); super.dispose(); } Future _disposeAsync() async { await _streamSubscription?.cancel(); await _activeObject?.disconnectDevice(); List bondedDevices = await FlutterBluetoothSerial.instance.getBondedDevices(); debugPrint(bondedDevices.toString()); super.dispose(); debugPrint("called dispose"); } Future _enablePermissions() async { PermissionStatus bluetoothScan = await Permission.bluetoothScan.request(); PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.request(); bool granted = bluetoothScan.isGranted && bluetoothConnect.isGranted; setState(() { _enabledPermissions = granted; }); } Future _enableBluetooth() async { if (_enabledBluetooth) { return; } BluetoothState state = await FlutterBluetoothSerial.instance.state; if (state == BluetoothState.STATE_ON) { setState(() { _enabledBluetooth = true; }); return; } bool? enabled = await FlutterBluetoothSerial.instance.requestEnable(); enabled ??= false; setState(() { _enabledBluetooth = enabled!; }); } Future _startDiscovery() async { debugPrint("enabled: $_enabledPermissions"); if (!_enabledPermissions && !_enabledBluetooth) { return; } setState(() => _isDiscovering = true); _stream = FlutterBluetoothSerial.instance.startDiscovery(); _streamSubscription = _stream!.listen(_discoveryOnListen); _streamSubscription!.onDone(_discoveryOnDone); } Future _discoveryOnDone() async { setState(() => _isDiscovering = false); } Future _cancelDiscovery() async { await _streamSubscription?.cancel(); setState(() => _isDiscovering = false); } Future _discoveryOnListen(BluetoothDiscoveryResult event) async { setState(() { final int existingIndex = _discoveryResults.indexWhere((element) => element.device.address == event.device.address); if (existingIndex >= 0) { _discoveryResults[existingIndex] = BluetoothObject(event); } else { _discoveryResults.add(BluetoothObject(event)); } }); } Future _restartDiscovery() async { _discoveryResults.clear(); if (_hasActiveObject) { _discoveryResults.add(_activeObject!); } if (_streamSubscription != null) { await _cancelDiscovery(); } await _startDiscovery(); } Future _toggleConnection(BluetoothObject bluetoothObject) async { await _cancelDiscovery(); if (_hasActiveObject) { _activeObject!.disconnectDevice(); _activeObject = bluetoothObject; } else { _activeObject = bluetoothObject; } _activeObject!.isConnected ? await bluetoothObject.disconnectDevice() : await bluetoothObject.connectDevice(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: _isDiscovering ? const Text('Bluetooth Devices (searching...)') : const Text('Bluetooth Devices'), actions: [ _isDiscovering ? FittedBox( child: Container( margin: const EdgeInsets.all(16.0), child: const CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.white), ), ), ) : IconButton( icon: const Icon(Icons.replay), onPressed: _restartDiscovery, ) ], ), body: Center( child: ListView(children: [ SingleChildScrollView( physics: const ScrollPhysics(), child: Column( children: [ SingleSection( title: "Setup", children: [ CustomListTile( title: _enabledBluetooth ? "Bluetooth Enabled" : "Please Enable Bluetooth (click me)", icon: _enabledBluetooth ? Icons.check_circle_outline_rounded : Icons.info_outline_rounded, iconColor: _enabledPermissions ? Colors.green : Colors.red, onTap: () async { await _enableBluetooth(); }, ), CustomListTile( title: _enabledPermissions ? "Permissions Granted" : "Please Grant Permissions (click me)", icon: _enabledPermissions ? Icons.check_circle_outline_rounded : Icons.info_outline_rounded, iconColor: _enabledPermissions ? Colors.green : Colors.red, onTap: () async { await _enablePermissions(); }, ), CustomListTile( title: "App Settings (if permissions denied)", icon: Icons.settings, onTap: () async { await AppSettings.openAppSettings(); }, ), ], ), const Divider(), SingleSection( title: "Bluetooth Devices", children: [ const CustomListTile( title: "short Press for connect / disconnect", icon: Icons.info_outline_rounded, ), const CustomListTile( title: "long press for pair / unpair", icon: Icons.info_outline_rounded, ), const Divider(), ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: _discoveryResults.length, itemBuilder: (BuildContext context, index) { BluetoothObject bluetoothObject = _discoveryResults[index]; final device = bluetoothObject.device; return BluetoothDeviceEntry( settingsPage: BluetoothDeviceSettings(bluetoothObject: bluetoothObject), context: context, device: device, bluetoothObject: bluetoothObject, onTap: () async { await _toggleConnection(bluetoothObject); await _restartDiscovery(); }, onLongPress: () async { await bluetoothObject.bondDevice(context); await _restartDiscovery(); }, ); }, ), ], ) ], ), ), ]), ), //drawer: const Sidebar(), ); } }