Skip to content

Commit 5bc8ff4

Browse files
feat: Add Support for Renaming (Alias) BLE Devices in App
fix: fixed the issues according to review fix: format code to match Dart standards
1 parent f35b827 commit 5bc8ff4

File tree

8 files changed

+225
-69
lines changed

8 files changed

+225
-69
lines changed

lib/bademagic_module/bluetooth/connect_state.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ import 'base_ble_state.dart';
66
class ConnectState extends RetryBleState {
77
final ScanResult scanResult;
88
final DataTransferManager manager;
9+
final String displayName;
910

10-
ConnectState({required this.manager, required this.scanResult});
11+
ConnectState({
12+
required this.manager,
13+
required this.scanResult,
14+
required this.displayName,
15+
});
1116

1217
@override
1318
Future<BleState?> processState() async {
@@ -21,15 +26,15 @@ class ConnectState extends RetryBleState {
2126
if (connectionState == BluetoothConnectionState.connected) {
2227
connected = true;
2328

24-
logger.d("Device connected");
25-
toast.showToast('Device connected successfully.');
29+
logger.d("Device '$displayName' connected");
30+
toast.showToast('Device "$displayName" connected successfully.');
2631

2732
return WriteState(device: scanResult.device, manager: manager);
2833
} else {
2934
throw Exception("Failed to connect to the device");
3035
}
3136
} catch (e) {
32-
toast.showErrorToast('Failed to connect retrying...');
37+
toast.showErrorToast('Failed to connect to "$displayName", retrying...');
3338
rethrow;
3439
} finally {
3540
if (!connected) {

lib/bademagic_module/bluetooth/scan_state.dart

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ import 'dart:async';
22
import 'package:badgemagic/bademagic_module/bluetooth/connect_state.dart';
33
import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart';
44
import 'package:badgemagic/providers/BadgeScanProvider.dart';
5+
import 'package:badgemagic/providers/BadgeAliasProvider.dart';
56
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
6-
77
import 'base_ble_state.dart';
88

99
class ScanState extends NormalBleState {
1010
final DataTransferManager manager;
1111
final BadgeScanMode mode;
1212
final List<String> allowedNames;
13+
final BadgeAliasProvider aliasProvider;
1314

1415
ScanState({
1516
required this.manager,
1617
required this.mode,
1718
required this.allowedNames,
19+
required this.aliasProvider,
1820
});
1921

2022
@override
@@ -24,6 +26,14 @@ class ScanState extends NormalBleState {
2426

2527
final Completer<BleState?> nextStateCompleter = Completer();
2628
bool isCompleted = false;
29+
bool stopScanCalled = false;
30+
31+
void stopScanSafely() {
32+
if (!stopScanCalled) {
33+
stopScanCalled = true;
34+
FlutterBluePlus.stopScan();
35+
}
36+
}
2737

2838
try {
2939
subscription = FlutterBluePlus.scanResults.listen(
@@ -42,21 +52,44 @@ class ScanState extends NormalBleState {
4252
.contains(Guid("0000fee0-0000-1000-8000-00805f9b34fb"));
4353

4454
final deviceName = result.device.name.trim().toLowerCase();
45-
final matchesName = mode == BadgeScanMode.any ||
55+
56+
final matchesDirectName = mode == BadgeScanMode.any ||
4657
normalizedAllowedNames.contains(deviceName);
4758

48-
return matchesUuid && matchesName;
59+
final aliasMatch = normalizedAllowedNames.any((realName) {
60+
final alias =
61+
aliasProvider.getAlias(realName)?.trim().toLowerCase();
62+
return alias == deviceName;
63+
});
64+
65+
return matchesUuid && (matchesDirectName || aliasMatch);
4966
},
5067
orElse: () => throw Exception("Matching device not found."),
5168
);
5269

5370
isCompleted = true;
54-
FlutterBluePlus.stopScan();
71+
stopScanSafely();
72+
5573
toast.showToast('Device found. Connecting...');
5674

75+
final foundName = foundDevice.device.name.trim();
76+
String? resolvedAlias;
77+
78+
for (final real in allowedNames) {
79+
final alias = aliasProvider.getAlias(real)?.trim();
80+
if (alias != null &&
81+
alias.toLowerCase() == foundName.toLowerCase()) {
82+
resolvedAlias = alias;
83+
break;
84+
}
85+
}
86+
87+
final displayName = resolvedAlias ?? foundName;
88+
5789
nextStateCompleter.complete(ConnectState(
5890
scanResult: foundDevice,
5991
manager: manager,
92+
displayName: displayName,
6093
));
6194
} catch (e) {
6295
logger.w("No matching device found in this batch: $e");
@@ -65,6 +98,7 @@ class ScanState extends NormalBleState {
6598
onError: (e) async {
6699
if (!isCompleted) {
67100
isCompleted = true;
101+
stopScanSafely(); // ✅ Guarded again
68102
logger.e("Scan error: $e");
69103
toast.showErrorToast('Scan error occurred.');
70104
nextStateCompleter.completeError(Exception("Scan error: $e"));
@@ -76,23 +110,25 @@ class ScanState extends NormalBleState {
76110
withServices: [Guid("0000fee0-0000-1000-8000-00805f9b34fb")],
77111
removeIfGone: Duration(seconds: 5),
78112
continuousUpdates: true,
79-
timeout: const Duration(seconds: 15), // Reduced scan timeout
113+
timeout: const Duration(seconds: 15),
80114
);
81115

82116
await Future.delayed(const Duration(seconds: 1));
83117

84-
// If no device is found after the scan timeout, complete with an error.
85118
if (!isCompleted) {
119+
isCompleted = true;
120+
stopScanSafely();
86121
toast.showToast('Device not found.');
87122
nextStateCompleter.completeError(Exception('Device not found.'));
88123
}
89124

90125
return await nextStateCompleter.future;
91126
} catch (e) {
92127
logger.e("Exception during scanning: $e");
93-
throw Exception("please check the device is turned on and retry.");
128+
throw Exception("Please check if the device is turned on and retry.");
94129
} finally {
95130
await subscription?.cancel();
131+
stopScanSafely();
96132
}
97133
}
98134
}

lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:badgemagic/providers/BadgeAliasProvider.dart';
12
import 'package:badgemagic/providers/BadgeScanProvider.dart';
23
import 'package:badgemagic/providers/getitlocator.dart';
34
import 'package:badgemagic/providers/imageprovider.dart';
@@ -25,6 +26,7 @@ void main() {
2526
ChangeNotifierProvider<InlineImageProvider>(
2627
create: (context) => getIt<InlineImageProvider>()),
2728
ChangeNotifierProvider(create: (_) => BadgeScanProvider()),
29+
ChangeNotifierProvider(create: (_) => BadgeAliasProvider()),
2830
],
2931
child: const MyApp(),
3032
));

lib/providers/BadgeAliasProvider.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:flutter/material.dart';
2+
3+
class BadgeAliasProvider with ChangeNotifier {
4+
final Map<String, String> _aliases = {};
5+
6+
String? getAlias(String deviceId) => _aliases[deviceId];
7+
8+
void setAlias(String deviceId, String alias) {
9+
_aliases[deviceId] = alias;
10+
notifyListeners();
11+
}
12+
13+
void removeAlias(String deviceId) {
14+
_aliases.remove(deviceId);
15+
notifyListeners();
16+
}
17+
18+
void clearAll() {
19+
_aliases.clear();
20+
notifyListeners();
21+
}
22+
23+
Map<String, String> get allAliases => Map.unmodifiable(_aliases);
24+
}

lib/providers/badge_message_provider.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:badgemagic/bademagic_module/models/speed.dart';
99
import 'package:badgemagic/bademagic_module/utils/converters.dart';
1010
import 'package:badgemagic/bademagic_module/utils/file_helper.dart';
1111
import 'package:badgemagic/bademagic_module/utils/toast_utils.dart';
12+
import 'package:badgemagic/providers/BadgeAliasProvider.dart';
1213
import 'package:badgemagic/providers/BadgeScanProvider.dart';
1314
import 'package:badgemagic/providers/imageprovider.dart';
1415
import 'package:flutter/widgets.dart';
@@ -104,6 +105,7 @@ class BadgeMessageProvider {
104105
manager: manager,
105106
mode: scanProvider.mode,
106107
allowedNames: scanProvider.badgeNames,
108+
aliasProvider: context.read<BadgeAliasProvider>(),
107109
);
108110

109111
BleState? state = initialState;

lib/view/badgeScanSettingsWidget.dart

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:badgemagic/providers/BadgeAliasProvider.dart';
12
import 'package:badgemagic/providers/BadgeScanProvider.dart';
23
import 'package:flutter/material.dart';
34
import 'package:provider/provider.dart';
@@ -14,39 +15,63 @@ class BadgeScanSettingsWidget extends StatefulWidget {
1415

1516
class _BadgeScanSettingsWidgetState extends State<BadgeScanSettingsWidget> {
1617
late BadgeScanMode _mode;
17-
final List<TextEditingController> _controllers = [];
18+
final List<TextEditingController> _nameControllers = [];
19+
final List<TextEditingController> _aliasControllers = [];
1820

1921
@override
2022
void initState() {
2123
super.initState();
22-
final provider = Provider.of<BadgeScanProvider>(context, listen: false);
23-
_mode = provider.mode;
24-
for (var name in provider.badgeNames) {
25-
_controllers.add(TextEditingController(text: name));
24+
final scanProvider = Provider.of<BadgeScanProvider>(context, listen: false);
25+
final aliasProvider =
26+
Provider.of<BadgeAliasProvider>(context, listen: false);
27+
28+
_mode = scanProvider.mode;
29+
for (var name in scanProvider.badgeNames) {
30+
_nameControllers.add(TextEditingController(text: name));
31+
final alias = aliasProvider.getAlias(name) ?? "";
32+
_aliasControllers.add(TextEditingController(text: alias));
2633
}
2734
}
2835

2936
void _addBadgeName() {
3037
setState(() {
31-
_controllers.add(TextEditingController());
38+
_nameControllers.add(TextEditingController());
39+
_aliasControllers.add(TextEditingController());
3240
});
3341
}
3442

3543
void _removeBadgeName(int index) {
3644
setState(() {
37-
_controllers.removeAt(index).dispose();
45+
_nameControllers.removeAt(index).dispose();
46+
_aliasControllers.removeAt(index).dispose();
3847
});
3948
}
4049

4150
void _onSave() {
42-
final updatedNames = _controllers
43-
.map((c) => c.text.trim())
44-
.where((name) => name.isNotEmpty)
45-
.toList();
51+
final updatedNames = <String>[];
52+
final Map<String, String> aliases = {};
53+
54+
for (int i = 0; i < _nameControllers.length; i++) {
55+
final name = _nameControllers[i].text.trim();
56+
final alias = _aliasControllers[i].text.trim();
57+
if (name.isNotEmpty) {
58+
updatedNames.add(name);
59+
if (alias.isNotEmpty) {
60+
aliases[name] = alias;
61+
}
62+
}
63+
}
4664

47-
final provider = Provider.of<BadgeScanProvider>(context, listen: false);
48-
provider.setMode(_mode);
49-
provider.setBadgeNames(updatedNames);
65+
// Save mode and names
66+
final scanProvider = Provider.of<BadgeScanProvider>(context, listen: false);
67+
scanProvider.setMode(_mode);
68+
scanProvider.setBadgeNames(updatedNames);
69+
70+
// Save aliases
71+
final aliasProvider =
72+
Provider.of<BadgeAliasProvider>(context, listen: false);
73+
aliasProvider.clearAll(); // optional: reset aliases
74+
aliases.forEach((name, alias) => aliasProvider.setAlias(name, alias));
5075

5176
ScaffoldMessenger.of(context).showSnackBar(
5277
const SnackBar(content: Text('Scan settings saved successfully')),
@@ -58,8 +83,11 @@ class _BadgeScanSettingsWidgetState extends State<BadgeScanSettingsWidget> {
5883

5984
@override
6085
void dispose() {
61-
for (var c in _controllers) {
62-
c.dispose();
86+
for (var controller in _nameControllers) {
87+
controller.dispose();
88+
}
89+
for (var controller in _aliasControllers) {
90+
controller.dispose();
6391
}
6492
super.dispose();
6593
}
@@ -93,25 +121,40 @@ class _BadgeScanSettingsWidgetState extends State<BadgeScanSettingsWidget> {
93121
if (_mode == BadgeScanMode.specific)
94122
Expanded(
95123
child: ListView.builder(
96-
itemCount: _controllers.length,
124+
itemCount: _nameControllers.length,
97125
itemBuilder: (context, index) {
98-
return Row(
126+
return Column(
99127
children: [
100-
Expanded(
101-
child: Padding(
102-
padding: const EdgeInsets.symmetric(horizontal: 12.0),
103-
child: TextField(
104-
controller: _controllers[index],
105-
decoration: const InputDecoration(
106-
labelText: 'Badge Name',
128+
Row(
129+
children: [
130+
Expanded(
131+
child: Padding(
132+
padding: const EdgeInsets.symmetric(
133+
horizontal: 12.0, vertical: 4),
134+
child: TextField(
135+
controller: _nameControllers[index],
136+
decoration: const InputDecoration(
137+
labelText: 'Real Badge Name',
138+
),
139+
),
107140
),
108141
),
109-
),
142+
IconButton(
143+
icon: const Icon(Icons.remove_circle_outline),
144+
onPressed: () => _removeBadgeName(index),
145+
),
146+
],
110147
),
111-
IconButton(
112-
icon: const Icon(Icons.remove_circle_outline),
113-
onPressed: () => _removeBadgeName(index),
148+
Padding(
149+
padding: const EdgeInsets.symmetric(horizontal: 12.0),
150+
child: TextField(
151+
controller: _aliasControllers[index],
152+
decoration: const InputDecoration(
153+
labelText: 'Alias (Optional)',
154+
),
155+
),
114156
),
157+
const Divider(),
115158
],
116159
);
117160
},

lib/view/homescreen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ class _HomeScreenState extends State<HomeScreen>
312312
animationProvider.getAnimationIndex()],
313313
null,
314314
false,
315-
context, // <== This is needed and now passed
315+
context,
316316
);
317317
},
318318
child: Container(

0 commit comments

Comments
 (0)