// Copyright 2017, Paul DeMarco. // All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; class ScanResultTile extends StatelessWidget { const ScanResultTile({Key key, this.result, this.onTap}) : super(key: key); final ScanResult result; final VoidCallback onTap; Widget _buildTitle(BuildContext context) { if (result.device.name.length > 0) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(result.device.name), Text( result.device.id.toString(), style: Theme.of(context).textTheme.caption, ) ], ); } else { return Text(result.device.id.toString()); } } Widget _buildAdvRow(BuildContext context, String title, String value) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context).textTheme.caption), SizedBox( width: 12.0, ), Expanded( child: Text( value, style: Theme.of(context) .textTheme .caption .apply(color: Colors.black), softWrap: true, ), ), ], ), ); } String getNiceHexArray(List bytes) { return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]' .toUpperCase(); } String getNiceManufacturerData(Map> data) { if (data.isEmpty) { return null; } List res = []; data.forEach((id, bytes) { res.add( '${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}'); }); return res.join(', '); } String getNiceServiceData(Map> data) { if (data.isEmpty) { return null; } List res = []; data.forEach((id, bytes) { res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}'); }); return res.join(', '); } @override Widget build(BuildContext context) { return ExpansionTile( title: _buildTitle(context), leading: Text(result.rssi.toString()), trailing: RaisedButton( child: Text('CONNECT'), color: Colors.black, textColor: Colors.white, onPressed: (result.advertisementData.connectable) ? onTap : null, ), children: [ _buildAdvRow( context, 'Complete Local Name', result.advertisementData.localName), _buildAdvRow(context, 'Tx Power Level', '${result.advertisementData.txPowerLevel ?? 'N/A'}'), _buildAdvRow( context, 'Manufacturer Data', getNiceManufacturerData( result.advertisementData.manufacturerData) ?? 'N/A'), _buildAdvRow( context, 'Service UUIDs', (result.advertisementData.serviceUuids.isNotEmpty) ? result.advertisementData.serviceUuids.join(', ').toUpperCase() : 'N/A'), _buildAdvRow(context, 'Service Data', getNiceServiceData(result.advertisementData.serviceData) ?? 'N/A'), ], ); } } class ServiceTile extends StatelessWidget { final BluetoothService service; final List characteristicTiles; const ServiceTile({Key key, this.service, this.characteristicTiles}) : super(key: key); @override Widget build(BuildContext context) { if (characteristicTiles.length > 0) { return new ExpansionTile( title: new Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Service'), new Text( '0x${service.uuid.toString().toUpperCase().substring(4, 8)}', style: Theme.of(context) .textTheme .body1 .copyWith(color: Theme.of(context).textTheme.caption.color)) ], ), children: characteristicTiles, ); } else { return new ListTile( title: const Text('Service'), subtitle: new Text( '0x${service.uuid.toString().toUpperCase().substring(4, 8)}'), ); } } } class CharacteristicTile extends StatelessWidget { final BluetoothCharacteristic characteristic; final List descriptorTiles; final VoidCallback onReadPressed; final VoidCallback onWritePressed; final VoidCallback onNotificationPressed; const CharacteristicTile( {Key key, this.characteristic, this.descriptorTiles, this.onReadPressed, this.onWritePressed, this.onNotificationPressed}) : super(key: key); @override Widget build(BuildContext context) { var actions = new Row( mainAxisSize: MainAxisSize.min, children: [ new IconButton( icon: new Icon( Icons.file_download, color: Theme.of(context).iconTheme.color.withOpacity(0.5), ), onPressed: onReadPressed, ), new IconButton( icon: new Icon(Icons.file_upload, color: Theme.of(context).iconTheme.color.withOpacity(0.5)), onPressed: onWritePressed, ), new IconButton( icon: new Icon( characteristic.isNotifying ? Icons.sync_disabled : Icons.sync, color: Theme.of(context).iconTheme.color.withOpacity(0.5)), onPressed: onNotificationPressed, ) ], ); var title = new Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Characteristic'), new Text( '0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}', style: Theme.of(context) .textTheme .body1 .copyWith(color: Theme.of(context).textTheme.caption.color)) ], ); if (descriptorTiles.length > 0) { return new ExpansionTile( title: new ListTile( title: title, subtitle: new Text(characteristic.value.toString()), contentPadding: EdgeInsets.all(0.0), ), trailing: actions, children: descriptorTiles, ); } else { return new ListTile( title: title, subtitle: new Text(characteristic.value.toString()), trailing: actions, ); } } } class DescriptorTile extends StatelessWidget { final BluetoothDescriptor descriptor; final VoidCallback onReadPressed; final VoidCallback onWritePressed; const DescriptorTile( {Key key, this.descriptor, this.onReadPressed, this.onWritePressed}) : super(key: key); @override Widget build(BuildContext context) { var title = new Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Descriptor'), new Text( '0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}', style: Theme.of(context) .textTheme .body1 .copyWith(color: Theme.of(context).textTheme.caption.color)) ], ); return new ListTile( title: title, subtitle: new Text(descriptor.value.toString()), trailing: new Row( mainAxisSize: MainAxisSize.min, children: [ new IconButton( icon: new Icon( Icons.file_download, color: Theme.of(context).iconTheme.color.withOpacity(0.5), ), onPressed: onReadPressed, ), new IconButton( icon: new Icon( Icons.file_upload, color: Theme.of(context).iconTheme.color.withOpacity(0.5), ), onPressed: onWritePressed, ) ], ), ); } }