From bcfb0adc6a321377f88c77b1946338742a1d9140 Mon Sep 17 00:00:00 2001 From: Foti Dim Date: Mon, 19 Jan 2026 05:34:50 +0100 Subject: [PATCH] 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 --- example/CHANGELOG.md | 2 + .../peripheral_detail_page.dart | 79 ++++++++++++------- .../widgets/services_side_widget.dart | 18 +++++ 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/example/CHANGELOG.md b/example/CHANGELOG.md index ecd978e..5e8a581 100644 --- a/example/CHANGELOG.md +++ b/example/CHANGELOG.md @@ -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 \ No newline at end of file diff --git a/example/lib/peripheral_details/peripheral_detail_page.dart b/example/lib/peripheral_details/peripheral_detail_page.dart index 62ae5d1..ee1561a 100644 --- a/example/lib/peripheral_details/peripheral_detail_page.dart +++ b/example/lib/peripheral_details/peripheral_detail_page.dart @@ -126,19 +126,28 @@ class _PeripheralDetailPageState extends State { () 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 { serviceListBuilder: () => _buildServicesList(onSelect: (_, __) { Navigator.pop(context); }), + onCopyServices: discoveredServices.isNotEmpty + ? () async { + await _copyServicesToClipboard(); + if (context.mounted) { + Navigator.pop(context); + } + } + : null, ), ); }, ); } + Future _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 { } } + String _formattedServices(List 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 { child: ServicesSideWidget( discoveredServices: discoveredServices, serviceListBuilder: _buildServicesList, + onCopyServices: discoveredServices.isNotEmpty + ? _copyServicesToClipboard + : null, ), ), // Main content @@ -1151,29 +1195,6 @@ class _PeripheralDetailPageState extends State { ), ), ), - 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( diff --git a/example/lib/peripheral_details/widgets/services_side_widget.dart b/example/lib/peripheral_details/widgets/services_side_widget.dart index 1d7be01..d2c5504 100644 --- a/example/lib/peripheral_details/widgets/services_side_widget.dart +++ b/example/lib/peripheral_details/widgets/services_side_widget.dart @@ -4,10 +4,12 @@ import 'package:universal_ble/universal_ble.dart'; class ServicesSideWidget extends StatelessWidget { final List 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,