mirror of
https://github.com/jonasbark/swiftcontrol.git
synced 2026-02-18 00:17:40 +01:00
Merge pull request #182 from jonasbark/copilot/fix-ble-device-reconnection
Persist ignored BLE devices across app restarts
This commit is contained in:
@@ -52,8 +52,6 @@ class Connection {
|
||||
|
||||
Timer? _gamePadSearchTimer;
|
||||
|
||||
final _dontAllowReconnectDevices = <String>{};
|
||||
|
||||
void initialize() {
|
||||
actionStream.listen((log) {
|
||||
lastLogEntries.add((date: DateTime.now(), entry: log.toString()));
|
||||
@@ -211,9 +209,20 @@ class Connection {
|
||||
}
|
||||
|
||||
void addDevices(List<BaseDevice> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,4 +182,48 @@ class Settings {
|
||||
Future<void> setMiuiWarningDismissed(bool dismissed) async {
|
||||
await prefs.setBool('miui_warning_dismissed', dismissed);
|
||||
}
|
||||
|
||||
List<String> _getIgnoredDeviceIds() {
|
||||
return prefs.getStringList('ignored_device_ids') ?? [];
|
||||
}
|
||||
|
||||
List<String> _getIgnoredDeviceNames() {
|
||||
return prefs.getStringList('ignored_device_names') ?? [];
|
||||
}
|
||||
|
||||
Future<void> 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<void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
75
lib/widgets/ignored_devices_dialog.dart
Normal file
75
lib/widgets/ignored_devices_dialog.dart
Normal file
@@ -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<IgnoredDevicesDialog> createState() => _IgnoredDevicesDialogState();
|
||||
}
|
||||
|
||||
class _IgnoredDevicesDialogState extends State<IgnoredDevicesDialog> {
|
||||
List<({String id, String name})> _ignoredDevices = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadIgnoredDevices();
|
||||
}
|
||||
|
||||
void _loadIgnoredDevices() {
|
||||
setState(() {
|
||||
_ignoredDevices = settings.getIgnoredDevices();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<Widget> 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: () {
|
||||
|
||||
Reference in New Issue
Block a user