diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index a4336f9..4de677c 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -52,8 +52,6 @@ class Connection { Timer? _gamePadSearchTimer; - final _dontAllowReconnectDevices = {}; - void initialize() { actionStream.listen((log) { lastLogEntries.add((date: DateTime.now(), entry: log.toString())); @@ -211,9 +209,20 @@ class Connection { } void addDevices(List dev) { - final newDevices = dev - .where((device) => !devices.contains(device) && !_dontAllowReconnectDevices.contains(device.name)) - .toList(); + final ignoredDevices = settings.getIgnoredDevices(); + final ignoredDeviceIds = ignoredDevices.map((d) => d.id).toSet(); + final newDevices = dev.where((device) { + if (devices.contains(device)) return false; + + // Check if device is in the ignored list + if (device is BluetoothDevice) { + if (ignoredDeviceIds.contains(device.device.deviceId)) { + return false; + } + } + + return true; + }).toList(); devices.addAll(newDevices); _connectionQueue.addAll(newDevices); @@ -258,7 +267,7 @@ class Connection { device.isConnected = state; _connectionStreams.add(device); if (!device.isConnected) { - disconnect(device, forget: true); + disconnect(device, forget: false); // try reconnect performScanning(); } @@ -332,13 +341,26 @@ class Connection { if (device.isConnected) { await device.disconnect(); } - if (!forget && device is BluetoothDevice) { + + if (device is BluetoothDevice) { + if (forget) { + // Add device to ignored list when forgetting + await settings.addIgnoredDevice(device.device.deviceId, device.name); + _actionStreams.add(LogNotification('Device ignored: ${device.name}')); + } + + // Clean up subscriptions and scan results for reconnection _lastScanResult.removeWhere((b) => b.deviceId == device.device.deviceId); _streamSubscriptions[device]?.cancel(); _streamSubscriptions.remove(device); _connectionSubscriptions[device]?.cancel(); _connectionSubscriptions.remove(device); + + // Remove device from the list + devices.remove(device); + hasDevices.value = devices.isNotEmpty; } + signalChange(device); } diff --git a/lib/utils/settings/settings.dart b/lib/utils/settings/settings.dart index b5f1660..2f3d176 100644 --- a/lib/utils/settings/settings.dart +++ b/lib/utils/settings/settings.dart @@ -182,4 +182,48 @@ class Settings { Future setMiuiWarningDismissed(bool dismissed) async { await prefs.setBool('miui_warning_dismissed', dismissed); } + + List _getIgnoredDeviceIds() { + return prefs.getStringList('ignored_device_ids') ?? []; + } + + List _getIgnoredDeviceNames() { + return prefs.getStringList('ignored_device_names') ?? []; + } + + Future addIgnoredDevice(String deviceId, String deviceName) async { + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); + + if (!ids.contains(deviceId)) { + ids.add(deviceId); + names.add(deviceName); + await prefs.setStringList('ignored_device_ids', ids); + await prefs.setStringList('ignored_device_names', names); + } + } + + Future removeIgnoredDevice(String deviceId) async { + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); + + final index = ids.indexOf(deviceId); + if (index != -1) { + ids.removeAt(index); + names.removeAt(index); + await prefs.setStringList('ignored_device_ids', ids); + await prefs.setStringList('ignored_device_names', names); + } + } + + List<({String id, String name})> getIgnoredDevices() { + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); + + final result = <({String id, String name})>[]; + for (int i = 0; i < ids.length && i < names.length; i++) { + result.add((id: ids[i], name: names[i])); + } + return result; + } } diff --git a/lib/widgets/ignored_devices_dialog.dart b/lib/widgets/ignored_devices_dialog.dart new file mode 100644 index 0000000..27f384e --- /dev/null +++ b/lib/widgets/ignored_devices_dialog.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:swift_control/main.dart'; + +class IgnoredDevicesDialog extends StatefulWidget { + const IgnoredDevicesDialog({super.key}); + + @override + State createState() => _IgnoredDevicesDialogState(); +} + +class _IgnoredDevicesDialogState extends State { + List<({String id, String name})> _ignoredDevices = []; + + @override + void initState() { + super.initState(); + _loadIgnoredDevices(); + } + + void _loadIgnoredDevices() { + setState(() { + _ignoredDevices = settings.getIgnoredDevices(); + }); + } + + Future _removeDevice(String deviceId) async { + await settings.removeIgnoredDevice(deviceId); + _loadIgnoredDevices(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Ignored Devices'), + content: SizedBox( + width: double.maxFinite, + child: _ignoredDevices.isEmpty + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'No ignored devices.', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ) + : ListView.builder( + shrinkWrap: true, + itemCount: _ignoredDevices.length, + itemBuilder: (context, index) { + final device = _ignoredDevices[index]; + return ListTile( + title: Text(device.name), + subtitle: Text( + device.id, + style: TextStyle(fontSize: 12), + ), + trailing: IconButton( + icon: Icon(Icons.delete_outline), + tooltip: 'Remove from ignored list', + onPressed: () => _removeDevice(device.id), + ), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Close'), + ), + ], + ); + } +} diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index 08fcc96..b414156 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -13,6 +13,7 @@ import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../pages/device.dart'; +import 'ignored_devices_dialog.dart'; List buildMenuButtons() { return [ @@ -174,7 +175,15 @@ class MenuButton extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'CHANGELOG.md'))); }, ), - + PopupMenuItem( + child: Text('Ignored Devices'), + onTap: () { + showDialog( + context: context, + builder: (context) => IgnoredDevicesDialog(), + ); + }, + ), PopupMenuItem( child: Text('License'), onTap: () {