Masterarbeit Richard Stern. Flutter App, sich mit einem Bluetooth-Gerät verbindet und Berührungen auf einem Sensor visualisiert.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

widgets.dart 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright 2017, Paul DeMarco.
  2. // All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_blue/flutter_blue.dart';
  6. class ScanResultTile extends StatelessWidget {
  7. const ScanResultTile({Key key, this.result, this.onTap}) : super(key: key);
  8. final ScanResult result;
  9. final VoidCallback onTap;
  10. Widget _buildTitle(BuildContext context) {
  11. if (result.device.name.length > 0) {
  12. return Column(
  13. mainAxisAlignment: MainAxisAlignment.start,
  14. crossAxisAlignment: CrossAxisAlignment.start,
  15. children: <Widget>[
  16. Text(result.device.name),
  17. Text(
  18. result.device.id.toString(),
  19. style: Theme.of(context).textTheme.caption,
  20. )
  21. ],
  22. );
  23. } else {
  24. return Text(result.device.id.toString());
  25. }
  26. }
  27. Widget _buildAdvRow(BuildContext context, String title, String value) {
  28. return Padding(
  29. padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
  30. child: Row(
  31. crossAxisAlignment: CrossAxisAlignment.start,
  32. children: <Widget>[
  33. Text(title, style: Theme.of(context).textTheme.caption),
  34. SizedBox(
  35. width: 12.0,
  36. ),
  37. Expanded(
  38. child: Text(
  39. value,
  40. style: Theme.of(context)
  41. .textTheme
  42. .caption
  43. .apply(color: Colors.black),
  44. softWrap: true,
  45. ),
  46. ),
  47. ],
  48. ),
  49. );
  50. }
  51. String getNiceHexArray(List<int> bytes) {
  52. return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'
  53. .toUpperCase();
  54. }
  55. String getNiceManufacturerData(Map<int, List<int>> data) {
  56. if (data.isEmpty) {
  57. return null;
  58. }
  59. List<String> res = [];
  60. data.forEach((id, bytes) {
  61. res.add(
  62. '${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}');
  63. });
  64. return res.join(', ');
  65. }
  66. String getNiceServiceData(Map<String, List<int>> data) {
  67. if (data.isEmpty) {
  68. return null;
  69. }
  70. List<String> res = [];
  71. data.forEach((id, bytes) {
  72. res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}');
  73. });
  74. return res.join(', ');
  75. }
  76. @override
  77. Widget build(BuildContext context) {
  78. return ExpansionTile(
  79. title: _buildTitle(context),
  80. leading: Text(result.rssi.toString()),
  81. trailing: RaisedButton(
  82. child: Text('CONNECT'),
  83. color: Colors.black,
  84. textColor: Colors.white,
  85. onPressed: (result.advertisementData.connectable) ? onTap : null,
  86. ),
  87. children: <Widget>[
  88. _buildAdvRow(
  89. context, 'Complete Local Name', result.advertisementData.localName),
  90. _buildAdvRow(context, 'Tx Power Level',
  91. '${result.advertisementData.txPowerLevel ?? 'N/A'}'),
  92. _buildAdvRow(
  93. context,
  94. 'Manufacturer Data',
  95. getNiceManufacturerData(
  96. result.advertisementData.manufacturerData) ??
  97. 'N/A'),
  98. _buildAdvRow(
  99. context,
  100. 'Service UUIDs',
  101. (result.advertisementData.serviceUuids.isNotEmpty)
  102. ? result.advertisementData.serviceUuids.join(', ').toUpperCase()
  103. : 'N/A'),
  104. _buildAdvRow(context, 'Service Data',
  105. getNiceServiceData(result.advertisementData.serviceData) ?? 'N/A'),
  106. ],
  107. );
  108. }
  109. }
  110. class ServiceTile extends StatelessWidget {
  111. final BluetoothService service;
  112. final List<CharacteristicTile> characteristicTiles;
  113. const ServiceTile({Key key, this.service, this.characteristicTiles})
  114. : super(key: key);
  115. @override
  116. Widget build(BuildContext context) {
  117. if (characteristicTiles.length > 0) {
  118. return new ExpansionTile(
  119. title: new Column(
  120. mainAxisAlignment: MainAxisAlignment.center,
  121. crossAxisAlignment: CrossAxisAlignment.start,
  122. children: <Widget>[
  123. const Text('Service'),
  124. new Text(
  125. '0x${service.uuid.toString().toUpperCase().substring(4, 8)}',
  126. style: Theme.of(context)
  127. .textTheme
  128. .body1
  129. .copyWith(color: Theme.of(context).textTheme.caption.color))
  130. ],
  131. ),
  132. children: characteristicTiles,
  133. );
  134. } else {
  135. return new ListTile(
  136. title: const Text('Service'),
  137. subtitle: new Text(
  138. '0x${service.uuid.toString().toUpperCase().substring(4, 8)}'),
  139. );
  140. }
  141. }
  142. }
  143. class CharacteristicTile extends StatelessWidget {
  144. final BluetoothCharacteristic characteristic;
  145. final List<DescriptorTile> descriptorTiles;
  146. final VoidCallback onReadPressed;
  147. final VoidCallback onWritePressed;
  148. final VoidCallback onNotificationPressed;
  149. const CharacteristicTile(
  150. {Key key,
  151. this.characteristic,
  152. this.descriptorTiles,
  153. this.onReadPressed,
  154. this.onWritePressed,
  155. this.onNotificationPressed})
  156. : super(key: key);
  157. @override
  158. Widget build(BuildContext context) {
  159. var actions = new Row(
  160. mainAxisSize: MainAxisSize.min,
  161. children: <Widget>[
  162. new IconButton(
  163. icon: new Icon(
  164. Icons.file_download,
  165. color: Theme.of(context).iconTheme.color.withOpacity(0.5),
  166. ),
  167. onPressed: onReadPressed,
  168. ),
  169. new IconButton(
  170. icon: new Icon(Icons.file_upload,
  171. color: Theme.of(context).iconTheme.color.withOpacity(0.5)),
  172. onPressed: onWritePressed,
  173. ),
  174. new IconButton(
  175. icon: new Icon(
  176. characteristic.isNotifying ? Icons.sync_disabled : Icons.sync,
  177. color: Theme.of(context).iconTheme.color.withOpacity(0.5)),
  178. onPressed: onNotificationPressed,
  179. )
  180. ],
  181. );
  182. var title = new Column(
  183. mainAxisAlignment: MainAxisAlignment.center,
  184. crossAxisAlignment: CrossAxisAlignment.start,
  185. children: <Widget>[
  186. const Text('Characteristic'),
  187. new Text(
  188. '0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}',
  189. style: Theme.of(context)
  190. .textTheme
  191. .body1
  192. .copyWith(color: Theme.of(context).textTheme.caption.color))
  193. ],
  194. );
  195. if (descriptorTiles.length > 0) {
  196. return new ExpansionTile(
  197. title: new ListTile(
  198. title: title,
  199. subtitle: new Text(characteristic.value.toString()),
  200. contentPadding: EdgeInsets.all(0.0),
  201. ),
  202. trailing: actions,
  203. children: descriptorTiles,
  204. );
  205. } else {
  206. return new ListTile(
  207. title: title,
  208. subtitle: new Text(characteristic.value.toString()),
  209. trailing: actions,
  210. );
  211. }
  212. }
  213. }
  214. class DescriptorTile extends StatelessWidget {
  215. final BluetoothDescriptor descriptor;
  216. final VoidCallback onReadPressed;
  217. final VoidCallback onWritePressed;
  218. const DescriptorTile(
  219. {Key key, this.descriptor, this.onReadPressed, this.onWritePressed})
  220. : super(key: key);
  221. @override
  222. Widget build(BuildContext context) {
  223. var title = new Column(
  224. mainAxisAlignment: MainAxisAlignment.center,
  225. crossAxisAlignment: CrossAxisAlignment.start,
  226. children: <Widget>[
  227. const Text('Descriptor'),
  228. new Text(
  229. '0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}',
  230. style: Theme.of(context)
  231. .textTheme
  232. .body1
  233. .copyWith(color: Theme.of(context).textTheme.caption.color))
  234. ],
  235. );
  236. return new ListTile(
  237. title: title,
  238. subtitle: new Text(descriptor.value.toString()),
  239. trailing: new Row(
  240. mainAxisSize: MainAxisSize.min,
  241. children: <Widget>[
  242. new IconButton(
  243. icon: new Icon(
  244. Icons.file_download,
  245. color: Theme.of(context).iconTheme.color.withOpacity(0.5),
  246. ),
  247. onPressed: onReadPressed,
  248. ),
  249. new IconButton(
  250. icon: new Icon(
  251. Icons.file_upload,
  252. color: Theme.of(context).iconTheme.color.withOpacity(0.5),
  253. ),
  254. onPressed: onWritePressed,
  255. )
  256. ],
  257. ),
  258. );
  259. }
  260. }