Improve format for discovered services (#209)

* Update the peripheral detail page to format and display discovered services more clearly, including characteristics.

* Address AI comments

* Refactor characteristic display format in peripheral detail page for improved clarity
This commit is contained in:
Foti Dim
2026-01-19 05:34:50 +01:00
committed by GitHub
parent 9e5603dc29
commit bcfb0adc6a
3 changed files with 70 additions and 29 deletions
+2
View File
@@ -11,6 +11,8 @@
* Display RSSI values in device details
* Persist filters
* Fix clear log button
* Move "Copy Services" button to Services panel header
* Enhance services format to be more detailed and human-readable
## 1.0.0
* Initial release
@@ -126,19 +126,28 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
() async {
var services = await bleDevice.discoverServices(withDescriptors: false);
debugPrint('${services.length} services discovered');
debugPrint(services.toString());
debugPrint(_formattedServices(services));
setState(() {
discoveredServices = services;
});
// Build log message step-by-step for clarity
final StringBuffer logMessage = StringBuffer();
logMessage.write('${services.length} services discovered');
if (kIsWeb) {
_addLog(
"DiscoverServices",
'${services.length} services discovered,\n$webWarning',
);
logMessage.write(',\n$webWarning');
}
logMessage.write('\n\n${_formattedServices(services)}');
_addLog("DiscoverServices", logMessage.toString());
},
onError: (error) {
_addLog("DiscoverServicesError", '$error\n${kIsWeb ? webWarning : ""}');
final StringBuffer errorMessage = StringBuffer();
errorMessage.write(error);
if (kIsWeb) {
errorMessage.write('\n$webWarning');
}
_addLog("DiscoverServicesError", errorMessage.toString());
},
);
}
@@ -313,12 +322,30 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
serviceListBuilder: () => _buildServicesList(onSelect: (_, __) {
Navigator.pop(context);
}),
onCopyServices: discoveredServices.isNotEmpty
? () async {
await _copyServicesToClipboard();
if (context.mounted) {
Navigator.pop(context);
}
}
: null,
),
);
},
);
}
Future<void> _copyServicesToClipboard() async {
final servicesText = _formattedServices(discoveredServices);
await Clipboard.setData(
ClipboardData(text: servicesText),
);
if (context.mounted) {
_showSnackBar('All services copied to clipboard');
}
}
void _showSnackBar(String message) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -329,6 +356,20 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
}
}
String _formattedServices(List<BleService> services) {
final buffer = StringBuffer();
for (var service in services) {
buffer.writeln('Service: ${service.uuid}');
for (var characteristic in service.characteristics) {
final properties =
characteristic.properties.map((p) => p.name).join(', ');
buffer.writeln(' ${characteristic.uuid} ($properties)');
}
buffer.writeln();
}
return buffer.toString().trim();
}
@override
void setState(VoidCallback fn) {
if (mounted) super.setState(fn);
@@ -431,6 +472,9 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
child: ServicesSideWidget(
discoveredServices: discoveredServices,
serviceListBuilder: _buildServicesList,
onCopyServices: discoveredServices.isNotEmpty
? _copyServicesToClipboard
: null,
),
),
// Main content
@@ -1151,29 +1195,6 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
),
),
),
if (discoveredServices.isNotEmpty)
OutlinedButton.icon(
onPressed: () async {
final servicesText =
discoveredServices.map((s) => s.uuid).join('\n');
await Clipboard.setData(
ClipboardData(text: servicesText),
);
_showSnackBar('All services copied to clipboard');
},
icon: const Icon(Icons.copy),
label: const Text('Copy Services'),
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.onSurface,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
OutlinedButton.icon(
onPressed: () async {
_addLog(
@@ -4,10 +4,12 @@ import 'package:universal_ble/universal_ble.dart';
class ServicesSideWidget extends StatelessWidget {
final List<BleService> discoveredServices;
final Function() serviceListBuilder;
final VoidCallback? onCopyServices;
const ServicesSideWidget({
super.key,
required this.discoveredServices,
required this.serviceListBuilder,
this.onCopyServices,
});
@override
@@ -54,6 +56,22 @@ class ServicesSideWidget extends StatelessWidget {
),
),
const Spacer(),
if (onCopyServices != null)
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
onPressed: onCopyServices,
icon: const Icon(Icons.copy),
iconSize: 18,
tooltip: 'Copy Services',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(
minWidth: 32,
minHeight: 32,
),
color: colorScheme.onSurface,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,