From 1f89859a030c0a39ec9832fd150bcb2e0bdeb9af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:23:12 +0000 Subject: [PATCH 2/4] Implement persistent ignored devices feature Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com> --- lib/bluetooth/connection.dart | 23 ++++++-- lib/utils/settings/settings.dart | 44 +++++++++++++++ lib/widgets/ignored_devices_dialog.dart | 75 +++++++++++++++++++++++++ lib/widgets/menu.dart | 18 +++++- 4 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 lib/widgets/ignored_devices_dialog.dart diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 29175fe..8e4619b 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())); @@ -203,9 +201,19 @@ class Connection { } void addDevices(List dev) { - final newDevices = dev - .where((device) => !devices.contains(device) && !_dontAllowReconnectDevices.contains(device.name)) - .toList(); + final ignoredDeviceIds = settings.getIgnoredDeviceIds(); + 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); @@ -330,6 +338,11 @@ class Connection { if (device.isConnected) { await device.disconnect(); } + if (forget && device is BluetoothDevice) { + // Add device to ignored list when forgetting + await settings.addIgnoredDevice(device.device.deviceId, device.name); + _actionStreams.add(LogNotification('Device ignored: ${device.name}')); + } if (!forget && device is BluetoothDevice) { _lastScanResult.removeWhere((b) => b.deviceId == device.device.deviceId); _streamSubscriptions[device]?.cancel(); diff --git a/lib/utils/settings/settings.dart b/lib/utils/settings/settings.dart index 683cad4..ef709e1 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..e671b75 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,22 @@ class MenuButton extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (c) => MarkdownPage(assetPath: 'CHANGELOG.md'))); }, ), - + PopupMenuItem( + child: Text('Ignored Devices'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (c) => Scaffold( + appBar: AppBar(), + body: Center( + child: IgnoredDevicesDialog(), + ), + ), + ), + ); + }, + ), PopupMenuItem( child: Text('License'), onTap: () { From a53fb578ef195d84c4ac670f8019a21b2f9294c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:25:14 +0000 Subject: [PATCH 3/4] Fix disconnect logic and improve UI flow Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com> --- lib/bluetooth/connection.dart | 22 +++++++++++++++------- lib/widgets/menu.dart | 13 +++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 8e4619b..6a53669 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -264,7 +264,7 @@ class Connection { device.isConnected = state; _connectionStreams.add(device); if (!device.isConnected) { - disconnect(device, forget: true); + disconnect(device, forget: false); // try reconnect performScanning(); } @@ -338,18 +338,26 @@ class Connection { if (device.isConnected) { await device.disconnect(); } - if (forget && device is BluetoothDevice) { - // Add device to ignored list when forgetting - await settings.addIgnoredDevice(device.device.deviceId, device.name); - _actionStreams.add(LogNotification('Device ignored: ${device.name}')); - } - 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/widgets/menu.dart b/lib/widgets/menu.dart index e671b75..b414156 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -178,16 +178,9 @@ class MenuButton extends StatelessWidget { PopupMenuItem( child: Text('Ignored Devices'), onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (c) => Scaffold( - appBar: AppBar(), - body: Center( - child: IgnoredDevicesDialog(), - ), - ), - ), + showDialog( + context: context, + builder: (context) => IgnoredDevicesDialog(), ); }, ), From aee8dc2e078f2f96b4d6d622a0fa3c2c2050628f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 07:57:35 +0000 Subject: [PATCH 4/4] Make getIgnoredDeviceIds and getIgnoredDeviceNames private Co-authored-by: jonasbark <1151304+jonasbark@users.noreply.github.com> --- lib/bluetooth/connection.dart | 3 ++- lib/utils/settings/settings.dart | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 6a53669..d32d697 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -201,7 +201,8 @@ class Connection { } void addDevices(List dev) { - final ignoredDeviceIds = settings.getIgnoredDeviceIds(); + final ignoredDevices = settings.getIgnoredDevices(); + final ignoredDeviceIds = ignoredDevices.map((d) => d.id).toSet(); final newDevices = dev.where((device) { if (devices.contains(device)) return false; diff --git a/lib/utils/settings/settings.dart b/lib/utils/settings/settings.dart index ef709e1..8745d27 100644 --- a/lib/utils/settings/settings.dart +++ b/lib/utils/settings/settings.dart @@ -183,17 +183,17 @@ class Settings { await prefs.setBool('miui_warning_dismissed', dismissed); } - List getIgnoredDeviceIds() { + List _getIgnoredDeviceIds() { return prefs.getStringList('ignored_device_ids') ?? []; } - List getIgnoredDeviceNames() { + List _getIgnoredDeviceNames() { return prefs.getStringList('ignored_device_names') ?? []; } Future addIgnoredDevice(String deviceId, String deviceName) async { - final ids = getIgnoredDeviceIds(); - final names = getIgnoredDeviceNames(); + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); if (!ids.contains(deviceId)) { ids.add(deviceId); @@ -204,8 +204,8 @@ class Settings { } Future removeIgnoredDevice(String deviceId) async { - final ids = getIgnoredDeviceIds(); - final names = getIgnoredDeviceNames(); + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); final index = ids.indexOf(deviceId); if (index != -1) { @@ -217,8 +217,8 @@ class Settings { } List<({String id, String name})> getIgnoredDevices() { - final ids = getIgnoredDeviceIds(); - final names = getIgnoredDeviceNames(); + final ids = _getIgnoredDeviceIds(); + final names = _getIgnoredDeviceNames(); final result = <({String id, String name})>[]; for (int i = 0; i < ids.length && i < names.length; i++) {