From 2580e01dc565302edd73211722e92c7c74ab81a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Fri, 20 Dec 2019 11:43:06 +0100 Subject: [PATCH 01/12] =?UTF-8?q?Details=20view=20=E2=80=93=20services=20l?= =?UTF-8?q?ist=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/adapter/ble_adapter.dart | 42 ++++++++++++--- .../device_details/device_detail_view.dart | 2 +- example/lib/model/ble_service.dart | 54 +++++++++++++++++++ .../components/peripheral_details_view.dart | 37 +++++++++++-- .../peripheral_details_bloc.dart | 17 +++++- .../peripheral_details_event.dart | 10 ++++ .../peripheral_details_state.dart | 7 ++- 7 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 example/lib/model/ble_service.dart diff --git a/example/lib/adapter/ble_adapter.dart b/example/lib/adapter/ble_adapter.dart index 60c5a060..83496100 100644 --- a/example/lib/adapter/ble_adapter.dart +++ b/example/lib/adapter/ble_adapter.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:blemulator_example/example_peripherals/generic_peripheral.dart'; import 'package:blemulator_example/model/ble_peripheral.dart'; import 'package:blemulator_example/example_peripherals/sensor_tag.dart'; +import 'package:blemulator_example/model/ble_service.dart'; import 'package:flutter_ble_lib/flutter_ble_lib.dart'; import 'package:blemulator/blemulator.dart'; @@ -30,6 +31,8 @@ class BleAdapter { Stream get blePeripherals => _blePeripheralsController.stream; + Map _peripherals = Map(); + factory BleAdapter(BleManager bleManager, Blemulator blemulator) { if (_instance == null) { _instance = BleAdapter._internal(bleManager, blemulator); @@ -58,14 +61,16 @@ class BleAdapter { Stream _startPeripheralScan() { return _bleManager.startPeripheralScan().map((scanResult) { + _peripherals.putIfAbsent( + scanResult.peripheral.identifier, () => scanResult.peripheral); return BlePeripheral( - scanResult.peripheral.name ?? - scanResult.advertisementData.localName ?? - 'Unknown', - scanResult.peripheral.identifier, - scanResult.rssi, - false, - BlePeripheralCategoryResolver.categoryForScanResult(scanResult), + scanResult.peripheral.name ?? + scanResult.advertisementData.localName ?? + 'Unknown', + scanResult.peripheral.identifier, + scanResult.rssi, + false, + BlePeripheralCategoryResolver.categoryForScanResult(scanResult), ); }); } @@ -82,4 +87,27 @@ class BleAdapter { _blemulator.addSimulatedPeripheral(GenericPeripheral()); _blemulator.simulate(); } + + Future> discoverAndGetServicesCharacteristics( + String peripheralId) async { + await _peripherals[peripheralId].connect(); + await _peripherals[peripheralId].discoverAllServicesAndCharacteristics(); + + List bleServices = []; + for (Service service in await _peripherals[peripheralId].services()) { + List serviceCharacteristics = + await service.characteristics(); + List bleCharacteristics = serviceCharacteristics + .map( + (characteristic) => + BleCharacteristic.fromCharacteristic(characteristic), + ) + .toList(); + bleServices.add(BleService(service.uuid, bleCharacteristics)); + } + + _peripherals[peripheralId].disconnectOrCancelConnection(); + + return bleServices; + } } diff --git a/example/lib/device_details/device_detail_view.dart b/example/lib/device_details/device_detail_view.dart index 515e2a9c..abd8cf4e 100644 --- a/example/lib/device_details/device_detail_view.dart +++ b/example/lib/device_details/device_detail_view.dart @@ -71,7 +71,7 @@ class DeviceDetailsViewState extends State { child: Scaffold( backgroundColor: Colors.grey[300], appBar: AppBar( - title: Text('Devicie Details'), + title: Text('Device Details'), bottom: TabBar( tabs: [ Tab(icon: Icon(Icons.autorenew), text: "Automatic",), diff --git a/example/lib/model/ble_service.dart b/example/lib/model/ble_service.dart new file mode 100644 index 00000000..fdc8e0e6 --- /dev/null +++ b/example/lib/model/ble_service.dart @@ -0,0 +1,54 @@ +import 'dart:typed_data'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_ble_lib/flutter_ble_lib.dart'; + +class BleService extends Equatable { + @override + List get props => [uuid, characteristics]; + + final String uuid; + final List characteristics; + + BleService(this.uuid, this.characteristics); +} + +class BleCharacteristic extends Equatable { + @override + List get props => [ + uuid, + value, + isReadable, + isWritableWithResponse, + isWritableWithoutResponse, + isNotifiable, + isIndicatable + ]; + + final String uuid; + final Uint8List value; + final bool isReadable; + final bool isWritableWithResponse; + final bool isWritableWithoutResponse; + final bool isNotifiable; + final bool isIndicatable; + + BleCharacteristic( + this.uuid, + this.value, + this.isReadable, + this.isWritableWithResponse, + this.isWritableWithoutResponse, + this.isNotifiable, + this.isIndicatable, + ); + + BleCharacteristic.fromCharacteristic(Characteristic characteristic) + : uuid = characteristic.uuid, + value = null, + isReadable = characteristic.isReadable, + isWritableWithResponse = characteristic.isWritableWithResponse, + isWritableWithoutResponse = characteristic.isWritableWithoutResponse, + isNotifiable = characteristic.isNotifiable, + isIndicatable = characteristic.isIndicatable; +} diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index c37863fb..88c60126 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -15,11 +15,17 @@ class PeripheralDetailsView extends StatelessWidget { sliver: SliverToBoxAdapter( child: BlocBuilder( builder: (context, state) { - return PropertyRow( - title: 'Identifier', - titleIcon: Icon(Icons.perm_device_information), - titleColor: Theme.of(context).primaryColor, - value: state.peripheral.id, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + PropertyRow( + title: 'Identifier', + titleIcon: Icon(Icons.perm_device_information), + titleColor: Theme.of(context).primaryColor, + value: state.peripheral.id, + ), + _createServiceView(context, state) + ], ); }, ), @@ -29,4 +35,25 @@ class PeripheralDetailsView extends StatelessWidget { ], ); } + + Widget _createServiceView( + BuildContext context, PeripheralDetailsState state) { + final PeripheralDetailsBloc bloc = BlocProvider.of(context); + + return Flexible( + fit: FlexFit.loose, + child: ListView.builder( + shrinkWrap: true, + itemCount: state.bleServices.length, + itemBuilder: (context, index) => InkWell( + onTap: () => print("Clicked $index"), //TODO + child: Row( + children: [ + Text("Id: ${state.bleServices[index].uuid}"), + ], + ), + ), + ), + ); + } } diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index e7312d5f..d3bcacd3 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -9,7 +9,15 @@ class PeripheralDetailsBloc BleAdapter _bleAdapter; final BlePeripheral _chosenPeripheral; - PeripheralDetailsBloc(this._bleAdapter, this._chosenPeripheral); + PeripheralDetailsBloc(this._bleAdapter, this._chosenPeripheral) { + _bleAdapter + .discoverAndGetServicesCharacteristics(_chosenPeripheral.id) + .then( + (bleServices) { + add(ServicesFetchedEvent(bleServices)); + }, + ); + } @override PeripheralDetailsState get initialState => @@ -18,5 +26,10 @@ class PeripheralDetailsBloc @override Stream mapEventToState( PeripheralDetailsEvent event, - ) async* {} + ) async* { + if (event is ServicesFetchedEvent) { + yield PeripheralDetailsState( + peripheral: state.peripheral, bleServices: event.services); + } + } } diff --git a/example/lib/peripheral_details/peripheral_details_event.dart b/example/lib/peripheral_details/peripheral_details_event.dart index 8f81d091..3dd19540 100644 --- a/example/lib/peripheral_details/peripheral_details_event.dart +++ b/example/lib/peripheral_details/peripheral_details_event.dart @@ -1,5 +1,15 @@ +import 'package:blemulator_example/model/ble_service.dart'; import 'package:equatable/equatable.dart'; abstract class PeripheralDetailsEvent extends Equatable { const PeripheralDetailsEvent(); } + +class ServicesFetchedEvent extends PeripheralDetailsEvent { + @override + List get props => [services]; + + final List services; + + ServicesFetchedEvent(this.services); +} diff --git a/example/lib/peripheral_details/peripheral_details_state.dart b/example/lib/peripheral_details/peripheral_details_state.dart index 4248d224..fe7fea10 100644 --- a/example/lib/peripheral_details/peripheral_details_state.dart +++ b/example/lib/peripheral_details/peripheral_details_state.dart @@ -1,12 +1,15 @@ import 'package:blemulator_example/model/ble_peripheral.dart'; +import 'package:blemulator_example/model/ble_service.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; class PeripheralDetailsState extends Equatable { final BlePeripheral peripheral; + final List bleServices; - const PeripheralDetailsState({@required this.peripheral}); + const PeripheralDetailsState( + {@required this.peripheral, this.bleServices = const []}); @override - List get props => [peripheral]; + List get props => [peripheral, bleServices]; } From 479f95df49b4e9b5dc24b334c84a7f0ca5dc3df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Thu, 2 Jan 2020 13:21:32 +0100 Subject: [PATCH 02/12] Service tile view updated --- .../lib/common/components/property_row.dart | 6 ++- .../generic_peripheral.dart | 52 +++++++++++++++---- .../components/peripheral_details_view.dart | 30 +++++++---- example/lib/styles/custom_text_style.dart | 6 +++ 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/example/lib/common/components/property_row.dart b/example/lib/common/components/property_row.dart index 173008ed..f3deb818 100644 --- a/example/lib/common/components/property_row.dart +++ b/example/lib/common/components/property_row.dart @@ -7,6 +7,7 @@ class PropertyRow extends StatelessWidget { final Widget titleIcon; final Color titleColor; final String valueCompanion; + final TextStyle valueTextStyle; final Widget rowAccessory; final Widget titleAccessory; final Widget valueAccessory; @@ -17,6 +18,7 @@ class PropertyRow extends StatelessWidget { this.titleIcon, this.titleColor, @required this.value, + this.valueTextStyle = CustomTextStyle.cardValue, this.valueCompanion, this.rowAccessory, this.titleAccessory, @@ -113,7 +115,7 @@ class PropertyRow extends StatelessWidget { child: Text( value ?? '', textWidthBasis: TextWidthBasis.longestLine, - style: CustomTextStyle.cardValue, + style: valueTextStyle, ), ), ), @@ -129,7 +131,7 @@ class PropertyRow extends StatelessWidget { return Expanded( child: Text( value ?? '', - style: CustomTextStyle.cardValue, + style: valueTextStyle, ), ); } diff --git a/example/lib/example_peripherals/generic_peripheral.dart b/example/lib/example_peripherals/generic_peripheral.dart index a62ccaba..91dc6bf3 100644 --- a/example/lib/example_peripherals/generic_peripheral.dart +++ b/example/lib/example_peripherals/generic_peripheral.dart @@ -14,15 +14,49 @@ class GenericPeripheral extends SimulatedPeripheral { advertisementInterval: Duration(milliseconds: milliseconds), services: [ SimulatedService( - uuid: 'F000AA00-0001-4000-B000-000000000000', - isAdvertised: true, - characteristics: [ - SimulatedCharacteristic( - uuid: 'F000AA10-0001-4000-B000-000000000000', - value: Uint8List.fromList([0]), - convenienceName: 'Generic characteristic'), - ], - convenienceName: 'Generic service'), + uuid: 'F000AA00-0001-4000-B000-000000000000', + isAdvertised: true, + characteristics: [ + SimulatedCharacteristic( + uuid: 'F000AA10-0001-4000-B000-000000000000', + value: Uint8List.fromList([0]), + convenienceName: 'Generic characteristic'), + ], + convenienceName: 'Generic service', + ), + SimulatedService( + uuid: 'F000AA01-0001-4000-B000-000000000000', + isAdvertised: true, + characteristics: [ + SimulatedCharacteristic( + uuid: 'F000AA10-0001-4000-B000-000000000000', + value: Uint8List.fromList([0]), + convenienceName: 'Generic characteristic'), + ], + convenienceName: 'Generic service', + ), + SimulatedService( + uuid: 'F000AA02-0001-4000-B000-000000000000', + isAdvertised: true, + characteristics: [ + SimulatedCharacteristic( + uuid: 'F000AA10-0001-4000-B000-000000000000', + value: Uint8List.fromList([0]), + convenienceName: 'Generic characteristic'), + ], + convenienceName: 'Generic service', + ), + SimulatedService( + uuid: 'F000AA03-0001-4000-B000-000000000000', + isAdvertised: true, + characteristics: [ + SimulatedCharacteristic( + uuid: 'F000AA10-0001-4000-B000-000000000000', + value: Uint8List.fromList([0]), + convenienceName: 'Generic characteristic'), + ], + convenienceName: 'Generic service', + ), ], ); diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 88c60126..fbda3f98 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -1,6 +1,9 @@ +import 'package:blemulator_example/model/ble_service.dart'; import 'package:blemulator_example/peripheral_details/bloc.dart'; import 'package:blemulator_example/common/components/property_row.dart'; +import 'package:blemulator_example/styles/custom_text_style.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class PeripheralDetailsView extends StatelessWidget { @@ -38,21 +41,30 @@ class PeripheralDetailsView extends StatelessWidget { Widget _createServiceView( BuildContext context, PeripheralDetailsState state) { - final PeripheralDetailsBloc bloc = BlocProvider.of(context); +// final PeripheralDetailsBloc bloc = BlocProvider.of(context); return Flexible( fit: FlexFit.loose, child: ListView.builder( shrinkWrap: true, itemCount: state.bleServices.length, - itemBuilder: (context, index) => InkWell( - onTap: () => print("Clicked $index"), //TODO - child: Row( - children: [ - Text("Id: ${state.bleServices[index].uuid}"), - ], - ), - ), + itemBuilder: (context, index) => + _createServiceTileView(context, state.bleServices[index]), + ), + ); + } + + Widget _createServiceTileView(BuildContext context, BleService service) { + return PropertyRow( + title: "Service UUID", + titleColor: Theme + .of(context) + .primaryColor, + value: service.uuid, + valueTextStyle: CustomTextStyle.serviceUuidStyle, + rowAccessory: IconButton( + icon: Icon(Icons.arrow_drop_down_circle), + onPressed: () => print(service.uuid), ), ); } diff --git a/example/lib/styles/custom_text_style.dart b/example/lib/styles/custom_text_style.dart index 0be0d72f..81c75f1a 100644 --- a/example/lib/styles/custom_text_style.dart +++ b/example/lib/styles/custom_text_style.dart @@ -22,4 +22,10 @@ abstract class CustomTextStyle { fontWeight: FontWeight.w500, color: Colors.grey, ); + + static const serviceUuidStyle = TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w500, + color: Colors.grey, + ); } From 5ebbb127c4b2ac0b9904fc92f742feec45708f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Thu, 2 Jan 2020 15:15:03 +0100 Subject: [PATCH 03/12] Update service expanded state on icon button clicked --- .../components/peripheral_details_view.dart | 32 ++++++++++++------- .../peripheral_details_bloc.dart | 19 ++++++++++- .../peripheral_details_event.dart | 11 +++++++ .../peripheral_details_state.dart | 16 ++++++++-- 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index fbda3f98..461724da 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -40,31 +40,39 @@ class PeripheralDetailsView extends StatelessWidget { } Widget _createServiceView( - BuildContext context, PeripheralDetailsState state) { -// final PeripheralDetailsBloc bloc = BlocProvider.of(context); - + BuildContext context, + PeripheralDetailsState state, + ) { return Flexible( fit: FlexFit.loose, child: ListView.builder( shrinkWrap: true, - itemCount: state.bleServices.length, + itemCount: state.bleServiceStates.length, itemBuilder: (context, index) => - _createServiceTileView(context, state.bleServices[index]), + _createServiceTileView(context, state.bleServiceStates[index]), ), ); } - Widget _createServiceTileView(BuildContext context, BleService service) { + Widget _createServiceTileView( + BuildContext context, + BleServiceState serviceState, + ) { + // ignore: close_sinks + final PeripheralDetailsBloc bloc = + BlocProvider.of(context); + return PropertyRow( title: "Service UUID", - titleColor: Theme - .of(context) - .primaryColor, - value: service.uuid, + titleColor: Theme.of(context).primaryColor, + value: serviceState.service.uuid, valueTextStyle: CustomTextStyle.serviceUuidStyle, rowAccessory: IconButton( - icon: Icon(Icons.arrow_drop_down_circle), - onPressed: () => print(service.uuid), + icon: Icon(serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), + onPressed: () => bloc.add(ServiceViewExpandedEvent( + serviceState, + !serviceState.expanded, + )), ), ); } diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index d3bcacd3..63ebee54 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -29,7 +29,24 @@ class PeripheralDetailsBloc ) async* { if (event is ServicesFetchedEvent) { yield PeripheralDetailsState( - peripheral: state.peripheral, bleServices: event.services); + peripheral: state.peripheral, + bleServiceStates: event.services + .map((service) => BleServiceState(service, false)) + .toList(), + ); + } else if (event is ServiceViewExpandedEvent) { + List newBleServiceStates = + List.from(state.bleServiceStates); + + int serviceIndex = + newBleServiceStates.indexOf(event.serviceStateToChange); + newBleServiceStates[serviceIndex] = + BleServiceState(event.serviceStateToChange.service, event.expanded); + + yield PeripheralDetailsState( + peripheral: state.peripheral, + bleServiceStates: newBleServiceStates, + ); } } } diff --git a/example/lib/peripheral_details/peripheral_details_event.dart b/example/lib/peripheral_details/peripheral_details_event.dart index 3dd19540..ab2c236d 100644 --- a/example/lib/peripheral_details/peripheral_details_event.dart +++ b/example/lib/peripheral_details/peripheral_details_event.dart @@ -1,4 +1,5 @@ import 'package:blemulator_example/model/ble_service.dart'; +import 'package:blemulator_example/peripheral_details/peripheral_details_state.dart'; import 'package:equatable/equatable.dart'; abstract class PeripheralDetailsEvent extends Equatable { @@ -13,3 +14,13 @@ class ServicesFetchedEvent extends PeripheralDetailsEvent { ServicesFetchedEvent(this.services); } + +class ServiceViewExpandedEvent extends PeripheralDetailsEvent { + @override + List get props => [serviceStateToChange, expanded]; + + final BleServiceState serviceStateToChange; + final bool expanded; + + ServiceViewExpandedEvent(this.serviceStateToChange, this.expanded); +} diff --git a/example/lib/peripheral_details/peripheral_details_state.dart b/example/lib/peripheral_details/peripheral_details_state.dart index fe7fea10..1ec92eb6 100644 --- a/example/lib/peripheral_details/peripheral_details_state.dart +++ b/example/lib/peripheral_details/peripheral_details_state.dart @@ -5,11 +5,21 @@ import 'package:flutter/cupertino.dart'; class PeripheralDetailsState extends Equatable { final BlePeripheral peripheral; - final List bleServices; + final List bleServiceStates; const PeripheralDetailsState( - {@required this.peripheral, this.bleServices = const []}); + {@required this.peripheral, this.bleServiceStates = const []}); @override - List get props => [peripheral, bleServices]; + List get props => [peripheral, bleServiceStates]; } + +class BleServiceState extends Equatable { + final BleService service; + final bool expanded; + + @override + List get props => [service, expanded]; + + BleServiceState(this.service, this.expanded); +} \ No newline at end of file From 8002cdd7b68c0649cff08841776a72f1002d1f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Thu, 2 Jan 2020 16:18:07 +0100 Subject: [PATCH 04/12] Display characteristics when service view is expanded --- .../components/peripheral_details_view.dart | 79 ++++++++++++++++--- example/lib/styles/custom_text_style.dart | 6 ++ 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 461724da..1d3245d2 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -62,18 +62,75 @@ class PeripheralDetailsView extends StatelessWidget { final PeripheralDetailsBloc bloc = BlocProvider.of(context); - return PropertyRow( - title: "Service UUID", - titleColor: Theme.of(context).primaryColor, - value: serviceState.service.uuid, - valueTextStyle: CustomTextStyle.serviceUuidStyle, - rowAccessory: IconButton( - icon: Icon(serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), - onPressed: () => bloc.add(ServiceViewExpandedEvent( - serviceState, - !serviceState.expanded, - )), + return Column( + children: [ + PropertyRow( + title: "Service UUID", + titleColor: Theme.of(context).primaryColor, + value: serviceState.service.uuid, + valueTextStyle: CustomTextStyle.serviceUuidStyle, + rowAccessory: IconButton( + icon: Icon( + serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), + onPressed: () => bloc.add(ServiceViewExpandedEvent( + serviceState, + !serviceState.expanded, + )), + ), + ), + serviceState.expanded + ? Padding( + padding: EdgeInsets.only(left: 16.0), + child: ListView.builder( + itemCount: serviceState.service.characteristics.length, + itemBuilder: (context, index) => _buildCharacteristicCard( + context, serviceState.service.characteristics[index]), + shrinkWrap: true, + ), + ) + : Column(), + ], + ); + } + + Widget _buildCharacteristicCard( + BuildContext context, + BleCharacteristic characteristic, + ) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "UUID: ${characteristic.uuid}", + style: CustomTextStyle.characteristicsStyle, + ), + Text( + "Properties: ${_getCharacteristicProperties(characteristic).toString()}", + style: CustomTextStyle.characteristicsStyle, + ), + ], + ), ), ); } } + +List _getCharacteristicProperties(BleCharacteristic characteristic) { + List properties = new List(); + + if (characteristic.isWritableWithoutResponse || + characteristic.isWritableWithoutResponse) { + properties.add("write"); + } + if (characteristic.isReadable) { + properties.add("read"); + } + if (characteristic.isIndicatable || characteristic.isNotifiable) { + properties.add("notify"); + } + + return properties; +} diff --git a/example/lib/styles/custom_text_style.dart b/example/lib/styles/custom_text_style.dart index 81c75f1a..12f169ca 100644 --- a/example/lib/styles/custom_text_style.dart +++ b/example/lib/styles/custom_text_style.dart @@ -28,4 +28,10 @@ abstract class CustomTextStyle { fontWeight: FontWeight.w500, color: Colors.grey, ); + + static const characteristicsStyle = TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w500, + color: Colors.black26, + ); } From dcb89f8d7c4b6d5c297c1f744712fd97db7743d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Wed, 8 Jan 2020 12:06:26 +0100 Subject: [PATCH 05/12] Test fixed --- .../peripheral_details/peripheral_details_bloc_test.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example/test/peripheral_details/peripheral_details_bloc_test.dart b/example/test/peripheral_details/peripheral_details_bloc_test.dart index e12f8415..9ab04757 100644 --- a/example/test/peripheral_details/peripheral_details_bloc_test.dart +++ b/example/test/peripheral_details/peripheral_details_bloc_test.dart @@ -1,6 +1,7 @@ import 'package:blemulator_example/model/ble_peripheral.dart'; import 'package:blemulator_example/peripheral_details/bloc.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; import '../mock/mocks.dart'; import '../mock/sample_ble_peripheral.dart'; @@ -13,9 +14,10 @@ void main() { setUp(() { bleAdapter = MockBleAdapter(); peripheral = SampleBlePeripheral(); + when(bleAdapter.discoverAndGetServicesCharacteristics(peripheral.id)) + .thenAnswer((_) => Future.value([])); - peripheralDetailsBloc = - PeripheralDetailsBloc(bleAdapter, peripheral); + peripheralDetailsBloc = PeripheralDetailsBloc(bleAdapter, peripheral); }); tearDown(() { From 10acaa66dabc9d747e137bc1f11b398267768022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Thu, 16 Jan 2020 12:50:53 +0100 Subject: [PATCH 06/12] Code review fixups --- example/lib/adapter/ble_adapter.dart | 20 ++++---- example/lib/model/ble_service.dart | 28 +++++------ .../peripheral_details_bloc.dart | 47 ++++++++++++------- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/example/lib/adapter/ble_adapter.dart b/example/lib/adapter/ble_adapter.dart index 83496100..35a1f059 100644 --- a/example/lib/adapter/ble_adapter.dart +++ b/example/lib/adapter/ble_adapter.dart @@ -31,7 +31,7 @@ class BleAdapter { Stream get blePeripherals => _blePeripheralsController.stream; - Map _peripherals = Map(); + Map _scannedPeripherals = Map(); factory BleAdapter(BleManager bleManager, Blemulator blemulator) { if (_instance == null) { @@ -61,7 +61,7 @@ class BleAdapter { Stream _startPeripheralScan() { return _bleManager.startPeripheralScan().map((scanResult) { - _peripherals.putIfAbsent( + _scannedPeripherals.putIfAbsent( scanResult.peripheral.identifier, () => scanResult.peripheral); return BlePeripheral( scanResult.peripheral.name ?? @@ -90,23 +90,25 @@ class BleAdapter { Future> discoverAndGetServicesCharacteristics( String peripheralId) async { - await _peripherals[peripheralId].connect(); - await _peripherals[peripheralId].discoverAllServicesAndCharacteristics(); + await _scannedPeripherals[peripheralId].connect(); + await _scannedPeripherals[peripheralId] + .discoverAllServicesAndCharacteristics(); List bleServices = []; - for (Service service in await _peripherals[peripheralId].services()) { + for (Service service + in await _scannedPeripherals[peripheralId].services()) { List serviceCharacteristics = - await service.characteristics(); + await service.characteristics(); List bleCharacteristics = serviceCharacteristics .map( (characteristic) => - BleCharacteristic.fromCharacteristic(characteristic), - ) + BleCharacteristic.fromCharacteristic(characteristic), + ) .toList(); bleServices.add(BleService(service.uuid, bleCharacteristics)); } - _peripherals[peripheralId].disconnectOrCancelConnection(); + _scannedPeripherals[peripheralId].disconnectOrCancelConnection(); return bleServices; } diff --git a/example/lib/model/ble_service.dart b/example/lib/model/ble_service.dart index fdc8e0e6..14c66505 100644 --- a/example/lib/model/ble_service.dart +++ b/example/lib/model/ble_service.dart @@ -4,27 +4,16 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_ble_lib/flutter_ble_lib.dart'; class BleService extends Equatable { - @override - List get props => [uuid, characteristics]; - final String uuid; final List characteristics; BleService(this.uuid, this.characteristics); -} -class BleCharacteristic extends Equatable { @override - List get props => [ - uuid, - value, - isReadable, - isWritableWithResponse, - isWritableWithoutResponse, - isNotifiable, - isIndicatable - ]; + List get props => [uuid, characteristics]; +} +class BleCharacteristic extends Equatable { final String uuid; final Uint8List value; final bool isReadable; @@ -43,6 +32,17 @@ class BleCharacteristic extends Equatable { this.isIndicatable, ); + @override + List get props => [ + uuid, + value, + isReadable, + isWritableWithResponse, + isWritableWithoutResponse, + isNotifiable, + isIndicatable + ]; + BleCharacteristic.fromCharacteristic(Characteristic characteristic) : uuid = characteristic.uuid, value = null, diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index 63ebee54..140fd30d 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -28,25 +28,36 @@ class PeripheralDetailsBloc PeripheralDetailsEvent event, ) async* { if (event is ServicesFetchedEvent) { - yield PeripheralDetailsState( - peripheral: state.peripheral, - bleServiceStates: event.services - .map((service) => BleServiceState(service, false)) - .toList(), - ); + yield mapServicesFetchedEventToState(event); } else if (event is ServiceViewExpandedEvent) { - List newBleServiceStates = - List.from(state.bleServiceStates); - - int serviceIndex = - newBleServiceStates.indexOf(event.serviceStateToChange); - newBleServiceStates[serviceIndex] = - BleServiceState(event.serviceStateToChange.service, event.expanded); - - yield PeripheralDetailsState( - peripheral: state.peripheral, - bleServiceStates: newBleServiceStates, - ); + yield mapServiceViewExpandedEventToState(event); } } + + PeripheralDetailsState mapServicesFetchedEventToState( + ServicesFetchedEvent event, + ) { + return PeripheralDetailsState( + peripheral: state.peripheral, + bleServiceStates: event.services + .map((service) => BleServiceState(service, false)) + .toList(), + ); + } + + PeripheralDetailsState mapServiceViewExpandedEventToState( + ServiceViewExpandedEvent event, + ) { + List newBleServiceStates = + List.from(state.bleServiceStates); + + int serviceIndex = newBleServiceStates.indexOf(event.serviceStateToChange); + newBleServiceStates[serviceIndex] = + BleServiceState(event.serviceStateToChange.service, event.expanded); + + return PeripheralDetailsState( + peripheral: state.peripheral, + bleServiceStates: newBleServiceStates, + ); + } } From 544e483dfa733053d6c7e4fe65cfa24e0adcfc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Fri, 17 Jan 2020 15:30:56 +0100 Subject: [PATCH 07/12] Tests added --- .../peripheral_details_bloc.dart | 8 ++-- example/test/mock/sample_ble_service.dart | 37 +++++++++++++++ .../peripheral_details_bloc_test.dart | 45 +++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 example/test/mock/sample_ble_service.dart diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index 140fd30d..ac0bc35a 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -28,13 +28,13 @@ class PeripheralDetailsBloc PeripheralDetailsEvent event, ) async* { if (event is ServicesFetchedEvent) { - yield mapServicesFetchedEventToState(event); + yield _mapServicesFetchedEventToState(event); } else if (event is ServiceViewExpandedEvent) { - yield mapServiceViewExpandedEventToState(event); + yield _mapServiceViewExpandedEventToState(event); } } - PeripheralDetailsState mapServicesFetchedEventToState( + PeripheralDetailsState _mapServicesFetchedEventToState( ServicesFetchedEvent event, ) { return PeripheralDetailsState( @@ -45,7 +45,7 @@ class PeripheralDetailsBloc ); } - PeripheralDetailsState mapServiceViewExpandedEventToState( + PeripheralDetailsState _mapServiceViewExpandedEventToState( ServiceViewExpandedEvent event, ) { List newBleServiceStates = diff --git a/example/test/mock/sample_ble_service.dart b/example/test/mock/sample_ble_service.dart new file mode 100644 index 00000000..4d65a8b3 --- /dev/null +++ b/example/test/mock/sample_ble_service.dart @@ -0,0 +1,37 @@ +import 'dart:typed_data'; + +import 'package:blemulator_example/model/ble_service.dart'; + +class SampleBleService extends BleService { + SampleBleService({ + String uuid = 'F000AA00-0001-4000-B000-000000000000', + List characteristics + }) : super(uuid, characteristics) { + if (characteristics == null) { + characteristics = [SampleBleCharacteristic()]; + } + } +} + +class SampleBleCharacteristic extends BleCharacteristic { + SampleBleCharacteristic({ + String uuid = 'F000AA00-0001-4000-B000-000000000000', + Uint8List value, + bool isReadable = true, + bool isWritableWithResponse = false, + bool isWritableWithoutResponse = false, + bool isNotifiable = false, + bool isIndicatable = false + }) : super( + uuid, + value, + isReadable, + isWritableWithResponse, + isWritableWithoutResponse, + isNotifiable, + isIndicatable) { + if (value == null) { + value = Uint8List(1); + } + } +} \ No newline at end of file diff --git a/example/test/peripheral_details/peripheral_details_bloc_test.dart b/example/test/peripheral_details/peripheral_details_bloc_test.dart index 9ab04757..745e0958 100644 --- a/example/test/peripheral_details/peripheral_details_bloc_test.dart +++ b/example/test/peripheral_details/peripheral_details_bloc_test.dart @@ -1,10 +1,14 @@ +import 'dart:math'; + import 'package:blemulator_example/model/ble_peripheral.dart'; +import 'package:blemulator_example/model/ble_service.dart'; import 'package:blemulator_example/peripheral_details/bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import '../mock/mocks.dart'; import '../mock/sample_ble_peripheral.dart'; +import '../mock/sample_ble_service.dart'; void main() { PeripheralDetailsBloc peripheralDetailsBloc; @@ -28,4 +32,45 @@ void main() { test('initial state contains peripheral provided in the constructor', () { expect(peripheralDetailsBloc.initialState.peripheral, peripheral); }); + + test('should map ServicesFetchedEvent to PeripheralDetailsState', () async { + // given + List bleServices = [SampleBleService()]; + ServicesFetchedEvent event = ServicesFetchedEvent(bleServices); + List states = + bleServices.map((service) => BleServiceState(service, false)).toList(); + + PeripheralDetailsState expectedState = PeripheralDetailsState( + peripheral: peripheral, bleServiceStates: states); + + // when + peripheralDetailsBloc.add(event); + + // then + expectLater( + peripheralDetailsBloc, + emitsThrough(equals(expectedState)), + ); + }); + + test('should map ServiceViewExpandedEvent to PeripheralDetailsState', () async { + // given + SampleBleService service = SampleBleService(); + BleServiceState bleServiceState = BleServiceState(service, false); + BleServiceState newBleServiceState = BleServiceState(service, true); + + peripheralDetailsBloc.add(ServicesFetchedEvent([service])); + + PeripheralDetailsState expectedState = PeripheralDetailsState( + peripheral: peripheral, bleServiceStates: [newBleServiceState]); + + // when + peripheralDetailsBloc.add(ServiceViewExpandedEvent(bleServiceState, true)); + + // then + await expectLater( + peripheralDetailsBloc, + emitsThrough(equals(expectedState)), + ); + }); } From 04b4b32daca91c6017f22e0c9f27010541a0d7f0 Mon Sep 17 00:00:00 2001 From: Tomasz Bogusz Date: Tue, 21 Jan 2020 17:20:05 +0100 Subject: [PATCH 08/12] CustomScrollView handling proposition --- .../components/peripheral_details_view.dart | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 1d3245d2..f91ce4ee 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -11,30 +11,31 @@ class PeripheralDetailsView extends StatelessWidget { Widget build(BuildContext context) { return CustomScrollView( slivers: [ - SliverSafeArea( - top: false, - sliver: SliverPadding( - padding: const EdgeInsets.all(8.0), - sliver: SliverToBoxAdapter( - child: BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - PropertyRow( - title: 'Identifier', - titleIcon: Icon(Icons.perm_device_information), - titleColor: Theme.of(context).primaryColor, - value: state.peripheral.id, - ), - _createServiceView(context, state) - ], - ); - }, - ), + SliverPadding( + padding: const EdgeInsets.all(8.0), + sliver: SliverToBoxAdapter( + child: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + PropertyRow( + title: 'Identifier', + titleIcon: Icon(Icons.perm_device_information), + titleColor: Theme.of(context).primaryColor, + value: state.peripheral.id, + ), + ], + ); + }, ), ), ), + BlocBuilder( + builder: (context, state) { + return _createServiceView(context, state); + }, + ) ], ); } @@ -43,13 +44,11 @@ class PeripheralDetailsView extends StatelessWidget { BuildContext context, PeripheralDetailsState state, ) { - return Flexible( - fit: FlexFit.loose, - child: ListView.builder( - shrinkWrap: true, - itemCount: state.bleServiceStates.length, - itemBuilder: (context, index) => + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _createServiceTileView(context, state.bleServiceStates[index]), + childCount: state.bleServiceStates.length, ), ); } From a8021d9cc1c6bdec9431929416f0409bd7e1f5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Tue, 21 Jan 2020 17:26:36 +0100 Subject: [PATCH 09/12] Code review fixups --- example/lib/adapter/ble_adapter.dart | 2 ++ .../generic_peripheral.dart | 22 ++++++------- .../components/peripheral_details_view.dart | 31 +++++++++---------- .../peripheral_details_bloc.dart | 5 ++- .../peripheral_details_event.dart | 8 ++--- .../peripheral_details_bloc_test.dart | 3 +- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/example/lib/adapter/ble_adapter.dart b/example/lib/adapter/ble_adapter.dart index 35a1f059..622a275b 100644 --- a/example/lib/adapter/ble_adapter.dart +++ b/example/lib/adapter/ble_adapter.dart @@ -90,6 +90,7 @@ class BleAdapter { Future> discoverAndGetServicesCharacteristics( String peripheralId) async { + // TODO remove connect() call when connectivity handling is implemented await _scannedPeripherals[peripheralId].connect(); await _scannedPeripherals[peripheralId] .discoverAllServicesAndCharacteristics(); @@ -108,6 +109,7 @@ class BleAdapter { bleServices.add(BleService(service.uuid, bleCharacteristics)); } + // TODO remove when connectivity handling is implemented _scannedPeripherals[peripheralId].disconnectOrCancelConnection(); return bleServices; diff --git a/example/lib/example_peripherals/generic_peripheral.dart b/example/lib/example_peripherals/generic_peripheral.dart index 91dc6bf3..28d96d8c 100644 --- a/example/lib/example_peripherals/generic_peripheral.dart +++ b/example/lib/example_peripherals/generic_peripheral.dart @@ -20,42 +20,42 @@ class GenericPeripheral extends SimulatedPeripheral { SimulatedCharacteristic( uuid: 'F000AA10-0001-4000-B000-000000000000', value: Uint8List.fromList([0]), - convenienceName: 'Generic characteristic'), + convenienceName: 'Generic characteristic 1'), ], - convenienceName: 'Generic service', + convenienceName: 'Generic service 1', ), SimulatedService( uuid: 'F000AA01-0001-4000-B000-000000000000', isAdvertised: true, characteristics: [ SimulatedCharacteristic( - uuid: 'F000AA10-0001-4000-B000-000000000000', + uuid: 'F000AA11-0001-4000-B000-000000000000', value: Uint8List.fromList([0]), - convenienceName: 'Generic characteristic'), + convenienceName: 'Generic characteristic 2'), ], - convenienceName: 'Generic service', + convenienceName: 'Generic service 2', ), SimulatedService( uuid: 'F000AA02-0001-4000-B000-000000000000', isAdvertised: true, characteristics: [ SimulatedCharacteristic( - uuid: 'F000AA10-0001-4000-B000-000000000000', + uuid: 'F000AA12-0001-4000-B000-000000000000', value: Uint8List.fromList([0]), - convenienceName: 'Generic characteristic'), + convenienceName: 'Generic characteristic 3'), ], - convenienceName: 'Generic service', + convenienceName: 'Generic service 3', ), SimulatedService( uuid: 'F000AA03-0001-4000-B000-000000000000', isAdvertised: true, characteristics: [ SimulatedCharacteristic( - uuid: 'F000AA10-0001-4000-B000-000000000000', + uuid: 'F000AA13-0001-4000-B000-000000000000', value: Uint8List.fromList([0]), - convenienceName: 'Generic characteristic'), + convenienceName: 'Generic characteristic 4'), ], - convenienceName: 'Generic service', + convenienceName: 'Generic service 4', ), ], ); diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 1d3245d2..f5ce50f4 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -48,8 +48,8 @@ class PeripheralDetailsView extends StatelessWidget { child: ListView.builder( shrinkWrap: true, itemCount: state.bleServiceStates.length, - itemBuilder: (context, index) => - _createServiceTileView(context, state.bleServiceStates[index]), + itemBuilder: (context, index) => _createServiceTileView( + context, state.bleServiceStates[index], index), ), ); } @@ -57,6 +57,7 @@ class PeripheralDetailsView extends StatelessWidget { Widget _createServiceTileView( BuildContext context, BleServiceState serviceState, + int index, ) { // ignore: close_sinks final PeripheralDetailsBloc bloc = @@ -73,22 +74,20 @@ class PeripheralDetailsView extends StatelessWidget { icon: Icon( serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), onPressed: () => bloc.add(ServiceViewExpandedEvent( - serviceState, - !serviceState.expanded, + index, )), ), ), - serviceState.expanded - ? Padding( - padding: EdgeInsets.only(left: 16.0), - child: ListView.builder( - itemCount: serviceState.service.characteristics.length, - itemBuilder: (context, index) => _buildCharacteristicCard( - context, serviceState.service.characteristics[index]), - shrinkWrap: true, - ), - ) - : Column(), + if (serviceState.expanded) + Padding( + padding: EdgeInsets.only(left: 16.0), + child: ListView.builder( + itemCount: serviceState.service.characteristics.length, + itemBuilder: (context, index) => _buildCharacteristicCard( + context, serviceState.service.characteristics[index]), + shrinkWrap: true, + ), + ), ], ); } @@ -121,7 +120,7 @@ class PeripheralDetailsView extends StatelessWidget { List _getCharacteristicProperties(BleCharacteristic characteristic) { List properties = new List(); - if (characteristic.isWritableWithoutResponse || + if (characteristic.isWritableWithResponse || characteristic.isWritableWithoutResponse) { properties.add("write"); } diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index ac0bc35a..0262ae24 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -51,9 +51,8 @@ class PeripheralDetailsBloc List newBleServiceStates = List.from(state.bleServiceStates); - int serviceIndex = newBleServiceStates.indexOf(event.serviceStateToChange); - newBleServiceStates[serviceIndex] = - BleServiceState(event.serviceStateToChange.service, event.expanded); + newBleServiceStates[event.index] = + BleServiceState(state.bleServiceStates[event.index].service, !state.bleServiceStates[event.index].expanded); return PeripheralDetailsState( peripheral: state.peripheral, diff --git a/example/lib/peripheral_details/peripheral_details_event.dart b/example/lib/peripheral_details/peripheral_details_event.dart index ab2c236d..28de3d0b 100644 --- a/example/lib/peripheral_details/peripheral_details_event.dart +++ b/example/lib/peripheral_details/peripheral_details_event.dart @@ -1,5 +1,4 @@ import 'package:blemulator_example/model/ble_service.dart'; -import 'package:blemulator_example/peripheral_details/peripheral_details_state.dart'; import 'package:equatable/equatable.dart'; abstract class PeripheralDetailsEvent extends Equatable { @@ -17,10 +16,9 @@ class ServicesFetchedEvent extends PeripheralDetailsEvent { class ServiceViewExpandedEvent extends PeripheralDetailsEvent { @override - List get props => [serviceStateToChange, expanded]; + List get props => [index]; - final BleServiceState serviceStateToChange; - final bool expanded; + final int index; - ServiceViewExpandedEvent(this.serviceStateToChange, this.expanded); + ServiceViewExpandedEvent(this.index); } diff --git a/example/test/peripheral_details/peripheral_details_bloc_test.dart b/example/test/peripheral_details/peripheral_details_bloc_test.dart index 745e0958..aea13de8 100644 --- a/example/test/peripheral_details/peripheral_details_bloc_test.dart +++ b/example/test/peripheral_details/peripheral_details_bloc_test.dart @@ -56,7 +56,6 @@ void main() { test('should map ServiceViewExpandedEvent to PeripheralDetailsState', () async { // given SampleBleService service = SampleBleService(); - BleServiceState bleServiceState = BleServiceState(service, false); BleServiceState newBleServiceState = BleServiceState(service, true); peripheralDetailsBloc.add(ServicesFetchedEvent([service])); @@ -65,7 +64,7 @@ void main() { peripheral: peripheral, bleServiceStates: [newBleServiceState]); // when - peripheralDetailsBloc.add(ServiceViewExpandedEvent(bleServiceState, true)); + peripheralDetailsBloc.add(ServiceViewExpandedEvent(0)); // then await expectLater( From 13f6c2b3b5c115e10efe1a97f0e7be65c8198b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Tue, 21 Jan 2020 18:35:45 +0100 Subject: [PATCH 10/12] Fixed scrolling characteristics' list --- .../peripheral_details/components/peripheral_details_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 5dbf91c7..34188548 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -85,6 +85,7 @@ class PeripheralDetailsView extends StatelessWidget { itemBuilder: (context, index) => _buildCharacteristicCard( context, serviceState.service.characteristics[index]), shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), ), ), ], From 634b561300a68b33f4171ddfc763baa54eba95dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Thu, 30 Jan 2020 13:56:13 +0100 Subject: [PATCH 11/12] Code review fixups --- .../peripheral_details_bloc.dart | 27 ++++++++++++------- .../peripheral_details_event.dart | 6 ++--- .../peripheral_details_state.dart | 2 +- .../peripheral_details_bloc_test.dart | 26 +++++++++++++++--- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/example/lib/peripheral_details/peripheral_details_bloc.dart b/example/lib/peripheral_details/peripheral_details_bloc.dart index 0262ae24..364f57d9 100644 --- a/example/lib/peripheral_details/peripheral_details_bloc.dart +++ b/example/lib/peripheral_details/peripheral_details_bloc.dart @@ -3,6 +3,7 @@ import 'package:blemulator_example/adapter/ble_adapter.dart'; import 'package:blemulator_example/model/ble_peripheral.dart'; import 'package:bloc/bloc.dart'; import './bloc.dart'; +import 'package:flutter_ble_lib/flutter_ble_lib.dart'; class PeripheralDetailsBloc extends Bloc { @@ -10,13 +11,19 @@ class PeripheralDetailsBloc final BlePeripheral _chosenPeripheral; PeripheralDetailsBloc(this._bleAdapter, this._chosenPeripheral) { - _bleAdapter - .discoverAndGetServicesCharacteristics(_chosenPeripheral.id) - .then( - (bleServices) { - add(ServicesFetchedEvent(bleServices)); - }, - ); + try { + //TODO check if device is connected + _bleAdapter + .discoverAndGetServicesCharacteristics(_chosenPeripheral.id) + .then( + (bleServices) { + add(ServicesFetchedEvent(bleServices)); + }, + ); + } on BleError catch (e) { + // TODO handle the error. To my knowledge only possible cause is either peripheral got disconnected or Bluetooth has been turned off, + // so it should be handled the same way as disconnection. + } } @override @@ -40,7 +47,7 @@ class PeripheralDetailsBloc return PeripheralDetailsState( peripheral: state.peripheral, bleServiceStates: event.services - .map((service) => BleServiceState(service, false)) + .map((service) => BleServiceState(service: service, expanded: false)) .toList(), ); } @@ -51,8 +58,8 @@ class PeripheralDetailsBloc List newBleServiceStates = List.from(state.bleServiceStates); - newBleServiceStates[event.index] = - BleServiceState(state.bleServiceStates[event.index].service, !state.bleServiceStates[event.index].expanded); + newBleServiceStates[event.expandedViewIndex] = + BleServiceState(service: state.bleServiceStates[event.expandedViewIndex].service, expanded: !state.bleServiceStates[event.expandedViewIndex].expanded); return PeripheralDetailsState( peripheral: state.peripheral, diff --git a/example/lib/peripheral_details/peripheral_details_event.dart b/example/lib/peripheral_details/peripheral_details_event.dart index 28de3d0b..08f3e57c 100644 --- a/example/lib/peripheral_details/peripheral_details_event.dart +++ b/example/lib/peripheral_details/peripheral_details_event.dart @@ -16,9 +16,9 @@ class ServicesFetchedEvent extends PeripheralDetailsEvent { class ServiceViewExpandedEvent extends PeripheralDetailsEvent { @override - List get props => [index]; + List get props => [expandedViewIndex]; - final int index; + final int expandedViewIndex; - ServiceViewExpandedEvent(this.index); + ServiceViewExpandedEvent(this.expandedViewIndex); } diff --git a/example/lib/peripheral_details/peripheral_details_state.dart b/example/lib/peripheral_details/peripheral_details_state.dart index 1ec92eb6..63de5afa 100644 --- a/example/lib/peripheral_details/peripheral_details_state.dart +++ b/example/lib/peripheral_details/peripheral_details_state.dart @@ -21,5 +21,5 @@ class BleServiceState extends Equatable { @override List get props => [service, expanded]; - BleServiceState(this.service, this.expanded); + BleServiceState({@required this.service, @required this.expanded}); } \ No newline at end of file diff --git a/example/test/peripheral_details/peripheral_details_bloc_test.dart b/example/test/peripheral_details/peripheral_details_bloc_test.dart index aea13de8..8642cec8 100644 --- a/example/test/peripheral_details/peripheral_details_bloc_test.dart +++ b/example/test/peripheral_details/peripheral_details_bloc_test.dart @@ -38,7 +38,7 @@ void main() { List bleServices = [SampleBleService()]; ServicesFetchedEvent event = ServicesFetchedEvent(bleServices); List states = - bleServices.map((service) => BleServiceState(service, false)).toList(); + bleServices.map((service) => BleServiceState(service: service, expanded: false)).toList(); PeripheralDetailsState expectedState = PeripheralDetailsState( peripheral: peripheral, bleServiceStates: states); @@ -53,10 +53,30 @@ void main() { ); }); - test('should map ServiceViewExpandedEvent to PeripheralDetailsState', () async { + test('should map ServiceViewExpandedEvent to PeripheralDetailsState when view expanded', () async { // given SampleBleService service = SampleBleService(); - BleServiceState newBleServiceState = BleServiceState(service, true); + BleServiceState newBleServiceState = BleServiceState(service: service, expanded: true); + + peripheralDetailsBloc.add(ServicesFetchedEvent([service])); + + PeripheralDetailsState expectedState = PeripheralDetailsState( + peripheral: peripheral, bleServiceStates: [newBleServiceState]); + + // when + peripheralDetailsBloc.add(ServiceViewExpandedEvent(0)); + + // then + await expectLater( + peripheralDetailsBloc, + emitsThrough(equals(expectedState)), + ); + }); + + test('should map ServiceViewExpandedEvent to PeripheralDetailsState when view collapsed', () async { + // given + SampleBleService service = SampleBleService(); + BleServiceState newBleServiceState = BleServiceState(service: service, expanded: false); peripheralDetailsBloc.add(ServicesFetchedEvent([service])); From a02d612c9e5e0845c89dfb3b06fbb68dc602040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marta=20Woldan=CC=81ska?= Date: Tue, 4 Feb 2020 13:03:40 +0100 Subject: [PATCH 12/12] Refactored services and characteristics view --- .../components/characteristics_view.dart | 50 +++++++++ .../components/peripheral_details_view.dart | 100 +----------------- .../components/services_sliver.dart | 65 ++++++++++++ 3 files changed, 118 insertions(+), 97 deletions(-) create mode 100644 example/lib/peripheral_details/components/characteristics_view.dart create mode 100644 example/lib/peripheral_details/components/services_sliver.dart diff --git a/example/lib/peripheral_details/components/characteristics_view.dart b/example/lib/peripheral_details/components/characteristics_view.dart new file mode 100644 index 00000000..91173faf --- /dev/null +++ b/example/lib/peripheral_details/components/characteristics_view.dart @@ -0,0 +1,50 @@ +import 'package:blemulator_example/model/ble_service.dart'; +import 'package:blemulator_example/styles/custom_text_style.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class CharacteristicsView extends StatelessWidget { + final BleCharacteristic _characteristic; + + CharacteristicsView(this._characteristic); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "UUID: ${_characteristic.uuid}", + style: CustomTextStyle.characteristicsStyle, + ), + Text( + "Properties: ${_getCharacteristicProperties(_characteristic).toString()}", + style: CustomTextStyle.characteristicsStyle, + ), + ], + ), + ), + ); + } + + static List _getCharacteristicProperties( + BleCharacteristic characteristic) { + List properties = new List(); + + if (characteristic.isWritableWithResponse || + characteristic.isWritableWithoutResponse) { + properties.add("write"); + } + if (characteristic.isReadable) { + properties.add("read"); + } + if (characteristic.isIndicatable || characteristic.isNotifiable) { + properties.add("notify"); + } + + return properties; + } +} diff --git a/example/lib/peripheral_details/components/peripheral_details_view.dart b/example/lib/peripheral_details/components/peripheral_details_view.dart index 34188548..9ffcc19d 100644 --- a/example/lib/peripheral_details/components/peripheral_details_view.dart +++ b/example/lib/peripheral_details/components/peripheral_details_view.dart @@ -1,7 +1,6 @@ -import 'package:blemulator_example/model/ble_service.dart'; -import 'package:blemulator_example/peripheral_details/bloc.dart'; import 'package:blemulator_example/common/components/property_row.dart'; -import 'package:blemulator_example/styles/custom_text_style.dart'; +import 'package:blemulator_example/peripheral_details/bloc.dart'; +import 'package:blemulator_example/peripheral_details/components/services_sliver.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -33,103 +32,10 @@ class PeripheralDetailsView extends StatelessWidget { ), BlocBuilder( builder: (context, state) { - return _createServiceView(context, state); + return ServicesSliver(state.bleServiceStates); }, ) ], ); } - - Widget _createServiceView( - BuildContext context, - PeripheralDetailsState state, - ) { - return SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => _createServiceTileView( - context, state.bleServiceStates[index], index), - childCount: state.bleServiceStates.length, - ), - ); - } - - Widget _createServiceTileView( - BuildContext context, - BleServiceState serviceState, - int index, - ) { - // ignore: close_sinks - final PeripheralDetailsBloc bloc = - BlocProvider.of(context); - - return Column( - children: [ - PropertyRow( - title: "Service UUID", - titleColor: Theme.of(context).primaryColor, - value: serviceState.service.uuid, - valueTextStyle: CustomTextStyle.serviceUuidStyle, - rowAccessory: IconButton( - icon: Icon( - serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), - onPressed: () => bloc.add(ServiceViewExpandedEvent( - index, - )), - ), - ), - if (serviceState.expanded) - Padding( - padding: EdgeInsets.only(left: 16.0), - child: ListView.builder( - itemCount: serviceState.service.characteristics.length, - itemBuilder: (context, index) => _buildCharacteristicCard( - context, serviceState.service.characteristics[index]), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - ), - ), - ], - ); - } - - Widget _buildCharacteristicCard( - BuildContext context, - BleCharacteristic characteristic, - ) { - return Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "UUID: ${characteristic.uuid}", - style: CustomTextStyle.characteristicsStyle, - ), - Text( - "Properties: ${_getCharacteristicProperties(characteristic).toString()}", - style: CustomTextStyle.characteristicsStyle, - ), - ], - ), - ), - ); - } -} - -List _getCharacteristicProperties(BleCharacteristic characteristic) { - List properties = new List(); - - if (characteristic.isWritableWithResponse || - characteristic.isWritableWithoutResponse) { - properties.add("write"); - } - if (characteristic.isReadable) { - properties.add("read"); - } - if (characteristic.isIndicatable || characteristic.isNotifiable) { - properties.add("notify"); - } - - return properties; } diff --git a/example/lib/peripheral_details/components/services_sliver.dart b/example/lib/peripheral_details/components/services_sliver.dart new file mode 100644 index 00000000..b56e967d --- /dev/null +++ b/example/lib/peripheral_details/components/services_sliver.dart @@ -0,0 +1,65 @@ +import 'package:blemulator_example/common/components/property_row.dart'; +import 'package:blemulator_example/peripheral_details/components/characteristics_view.dart'; +import 'package:blemulator_example/peripheral_details/peripheral_details_bloc.dart'; +import 'package:blemulator_example/peripheral_details/peripheral_details_event.dart'; +import 'package:blemulator_example/peripheral_details/peripheral_details_state.dart'; +import 'package:blemulator_example/styles/custom_text_style.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ServicesSliver extends StatelessWidget { + final List _bleServiceStates; + + ServicesSliver(this._bleServiceStates); + + @override + Widget build(BuildContext context) { + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => + _createServiceTileView(context, _bleServiceStates[index], index), + childCount: _bleServiceStates.length, + ), + ); + } + + Widget _createServiceTileView( + BuildContext context, + BleServiceState serviceState, + int index, + ) { + // ignore: close_sinks + final PeripheralDetailsBloc bloc = + BlocProvider.of(context); + + return Column( + children: [ + PropertyRow( + title: "Service UUID", + titleColor: Theme.of(context).primaryColor, + value: serviceState.service.uuid, + valueTextStyle: CustomTextStyle.serviceUuidStyle, + rowAccessory: IconButton( + icon: Icon( + serviceState.expanded ? Icons.unfold_less : Icons.unfold_more), + onPressed: () => bloc.add(ServiceViewExpandedEvent( + index, + )), + ), + ), + if (serviceState.expanded) + Padding( + padding: EdgeInsets.only(left: 16.0), + child: ListView.builder( + itemCount: serviceState.service.characteristics.length, + itemBuilder: (context, index) => CharacteristicsView( + serviceState.service.characteristics[index]), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + ), + ), + ], + ); + } +}