283 lines
9.7 KiB
Dart
283 lines
9.7 KiB
Dart
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<BluetoothScreen> createState() => _BluetoothScreen();
|
|
}
|
|
|
|
// debugPrint("r: ${r.device.name} ${r.device.address} ${r.rssi}");
|
|
class _BluetoothScreen extends State<BluetoothScreen> {
|
|
final List<BluetoothObject> _discoveryResults = [];
|
|
|
|
late BluetoothObject? _activeObject;
|
|
|
|
late Stream<BluetoothDiscoveryResult>? _stream;
|
|
late StreamSubscription<BluetoothDiscoveryResult>? _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();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Avoid memory leak (`setState` after dispose) and cancel discovery
|
|
_streamSubscription?.cancel();
|
|
_activeObject?.disconnectDevice();
|
|
super.dispose();
|
|
debugPrint("called dispose");
|
|
}
|
|
|
|
Future<void> _initAsync() async {
|
|
await _enablePermissions();
|
|
await _enableBluetooth();
|
|
await _startDiscovery();
|
|
}
|
|
|
|
Future<void> _enablePermissions() async {
|
|
PermissionStatus bluetoothScan = await Permission.bluetoothScan.request();
|
|
PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.request();
|
|
bool granted = bluetoothScan.isGranted && bluetoothConnect.isGranted;
|
|
setState(() {
|
|
_enabledPermissions = granted;
|
|
});
|
|
}
|
|
|
|
Future<void> _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<void> _startDiscovery() async {
|
|
debugPrint("enabled: $_enabledPermissions");
|
|
if (!_enabledPermissions && !_enabledBluetooth) {
|
|
return;
|
|
}
|
|
setState(() => _isDiscovering = true);
|
|
_stream = FlutterBluetoothSerial.instance.startDiscovery();
|
|
_streamSubscription = _stream!.listen(_discoveryOnListen);
|
|
_streamSubscription!.onDone(_discoveryOnDone);
|
|
}
|
|
|
|
Future<void> _discoveryOnDone() async {
|
|
setState(() => _isDiscovering = false);
|
|
}
|
|
|
|
Future<void> _cancelDiscovery() async {
|
|
await _streamSubscription?.cancel();
|
|
setState(() => _isDiscovering = false);
|
|
}
|
|
|
|
Future<void> _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<void> _restartDiscovery() async {
|
|
_discoveryResults.clear();
|
|
if (_hasActiveObject) {
|
|
_discoveryResults.add(_activeObject!);
|
|
}
|
|
if (_streamSubscription != null) {
|
|
await _cancelDiscovery();
|
|
}
|
|
await _startDiscovery();
|
|
}
|
|
|
|
Future<void> _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: <Widget>[
|
|
_isDiscovering
|
|
? FittedBox(
|
|
child: Container(
|
|
margin: const EdgeInsets.all(16.0),
|
|
child: const CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
),
|
|
)
|
|
: IconButton(
|
|
icon: const Icon(Icons.replay),
|
|
onPressed: _restartDiscovery,
|
|
)
|
|
],
|
|
),
|
|
body: Center(
|
|
child: ListView(children: [
|
|
SingleChildScrollView(
|
|
physics: const ScrollPhysics(),
|
|
child: Column(
|
|
children: <Widget>[
|
|
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,
|
|
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,
|
|
onTap: () async {
|
|
await _enablePermissions();
|
|
},
|
|
),
|
|
CustomListTile(
|
|
title: "App Settings (if permissions denied)",
|
|
icon: Icons.settings,
|
|
onTap: () async {
|
|
await AppSettings.openAppSettings();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
|
// const EdgeInsets defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () async {
|
|
/*await Permission.bluetoothConnect.request();
|
|
await Permission.bluetoothScan.request();
|
|
if (await Permission.bluetoothScan.request().isGranted) {
|
|
// Either the permission was already granted before or the user just granted it.
|
|
debugPrint("Location Permission is granted");
|
|
} else {
|
|
debugPrint("Location Permission is denied.");
|
|
}*/
|
|
},
|
|
style: buttonStyle,
|
|
child: const Text("Get BT Permissions"),
|
|
),
|
|
),
|
|
const SizedBox(width: 16.0),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () async {
|
|
_restartDiscovery();
|
|
},
|
|
style: buttonStyle,
|
|
child: const Text("Restart Scan"),
|
|
),
|
|
)
|
|
]),
|
|
),
|
|
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);
|
|
},
|
|
onLongPress: () async {
|
|
await bluetoothObject.bondDevice();
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
//drawer: const Sidebar(),
|
|
);
|
|
}
|
|
}
|