Implement scan filter in example app (#61)

* Implement scan filter in example app

* Remove Fixed FlutterVersion from Workflow and improve ScanFilterWidget

* Update example/lib/home/widgets/scan_filter_widget.dart

* Update example/lib/home/widgets/scan_filter_widget.dart

* Update example/lib/peripheral_details/peripheral_detail_page.dart

* Improve text

---------

Co-authored-by: Foti Dim <foti@navideck.com>
Co-authored-by: Foti Dim <fdimanidis@gmail.com>
This commit is contained in:
Rohit Sangwan
2024-07-12 20:54:00 +05:30
committed by GitHub
parent dffbbd4ba6
commit 44412508e7
9 changed files with 208 additions and 91 deletions
-1
View File
@@ -14,7 +14,6 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
channel: 'stable'
- run: flutter --version
+1
View File
@@ -42,3 +42,4 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
.metadata
-30
View File
@@ -1,30 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "ead455963c12b453cdb2358cad34969c76daf180"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
- platform: web
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+30 -23
View File
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/data/capabilities.dart';
import 'package:universal_ble_example/data/mock_universal_ble.dart';
import 'package:universal_ble_example/home/widgets/scan_filter_widget.dart';
import 'package:universal_ble_example/home/widgets/scanned_devices_placeholder_widget.dart';
import 'package:universal_ble_example/home/widgets/scanned_item_widget.dart';
import 'package:universal_ble_example/data/permission_handler.dart';
@@ -23,17 +24,12 @@ class _MyAppState extends State<MyApp> {
final _bleDevices = <BleDevice>[];
bool _isScanning = false;
QueueType _queueType = QueueType.global;
TextEditingController servicesFilterController = TextEditingController();
TextEditingController namePrefixController = TextEditingController();
TextEditingController manufacturerDataController = TextEditingController();
AvailabilityState? bleAvailabilityState;
final List<String> _services = [
"00001800-0000-1000-8000-00805f9b34fb",
"0000180f-0000-1000-8000-00805f9b34fb",
"00002a00-0000-1000-8000-00805f9b34fb",
"00002a01-0000-1000-8000-00805f9b34fb",
"00002a19-0000-1000-8000-00805f9b34fb",
"8000cc00-cc00-ffff-ffff-ffffffffffff",
"8000dd00-dd00-ffff-ffff-ffffffffffff",
];
ScanFilter? scanFilter;
@override
void initState() {
@@ -76,17 +72,26 @@ class _MyAppState extends State<MyApp> {
Future<void> startScan() async {
await UniversalBle.startScan(
scanFilter: ScanFilter(
withServices: kIsWeb ? _services : [],
withManufacturerData: [
// ManufacturerDataFilter(
// companyIdentifier: 0x012D,
// data: Uint8List.fromList(
// [0x03, 0x00, 0x64, 0x00],
// ),
// ),
],
),
scanFilter: scanFilter,
);
}
void _showScanFilterBottomSheet() {
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return ScanFilterWidget(
servicesFilterController: servicesFilterController,
namePrefixController: namePrefixController,
manufacturerDataController: manufacturerDataController,
onScanFilter: (ScanFilter? filter) {
setState(() {
scanFilter = filter;
});
},
);
},
);
}
@@ -172,9 +177,7 @@ class _MyAppState extends State<MyApp> {
text: 'Connected Devices',
onPressed: () async {
List<BleDevice> devices =
await UniversalBle.getSystemDevices(
withServices: _services,
);
await UniversalBle.getSystemDevices();
if (devices.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
@@ -201,6 +204,10 @@ class _MyAppState extends State<MyApp> {
});
},
),
PlatformButton(
text: 'Scan Filters',
onPressed: _showScanFilterBottomSheet,
),
if (_bleDevices.isNotEmpty)
PlatformButton(
text: 'Clear List',
@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/widgets/platform_button.dart';
class ScanFilterWidget extends StatefulWidget {
final Function(ScanFilter? filter) onScanFilter;
final TextEditingController servicesFilterController;
final TextEditingController namePrefixController;
final TextEditingController manufacturerDataController;
const ScanFilterWidget({
super.key,
required this.onScanFilter,
required this.servicesFilterController,
required this.namePrefixController,
required this.manufacturerDataController,
});
@override
State<ScanFilterWidget> createState() => _ScanFilterWidgetState();
}
class _ScanFilterWidgetState extends State<ScanFilterWidget> {
String? error;
void applyFilter() {
setState(() {
error = null;
});
try {
List<String> serviceUUids = [];
List<String> namePrefixes = [];
List<ManufacturerDataFilter> manufacturerDataFilters = [];
// Parse Services
if (widget.servicesFilterController.text.isNotEmpty) {
List<String> services = widget.servicesFilterController.text.split(',');
for (String service in services) {
try {
serviceUUids.add(BleUuid.parse(service.trim()));
} on FormatException catch (_) {
throw Exception("Invalid Service UUID $service");
}
}
}
// Parse Name Prefix
String namePrefix = widget.namePrefixController.text;
if (namePrefix.isNotEmpty) {
namePrefixes = namePrefix.split(',').map((e) => e.trim()).toList();
}
// Parse Manufacturer Data
String manufacturerDataText = widget.manufacturerDataController.text;
if (manufacturerDataText.isNotEmpty) {
List<String> manufacturerData = manufacturerDataText.split(',');
for (String manufacturer in manufacturerData) {
int? companyIdentifier = int.tryParse(manufacturer);
if (companyIdentifier == null) {
throw Exception("Invalid Manufacturer Data $manufacturer");
}
manufacturerDataFilters.add(
ManufacturerDataFilter(companyIdentifier: companyIdentifier),
);
}
}
if (serviceUUids.isEmpty &&
namePrefixes.isEmpty &&
manufacturerDataFilters.isEmpty) {
widget.onScanFilter(null);
} else {
widget.onScanFilter(
ScanFilter(
withServices: serviceUUids,
withNamePrefix: namePrefixes,
),
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Filters Applied")),
);
}
Navigator.pop(context);
} catch (e) {
setState(() {
error = e.toString();
});
}
}
void clearFilter() {
widget.servicesFilterController.clear();
widget.namePrefixController.clear();
widget.manufacturerDataController.clear();
widget.onScanFilter(null);
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets.copyWith(left: 20, right: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 20),
Center(
child: Text(
"Scan Filters",
style: Theme.of(context).textTheme.titleLarge,
),
),
const Text("Use comma to add multiple values"),
const Divider(),
const SizedBox(height: 10),
TextFormField(
controller: widget.servicesFilterController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Services",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
TextFormField(
controller: widget.namePrefixController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Name Prefixes",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
TextFormField(
controller: widget.manufacturerDataController,
maxLines: 2,
decoration: const InputDecoration(
labelText: "Manufacturer Data Company IDs",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: PlatformButton(
text: 'Apply',
onPressed: applyFilter,
),
),
const SizedBox(width: 10),
Expanded(
child: PlatformButton(
text: 'Clear',
onPressed: clearFilter,
),
),
],
),
const SizedBox(height: 10),
if (error != null)
Text(
error!,
style: const TextStyle(color: Colors.red),
),
const SizedBox(height: 10),
],
),
);
}
}
@@ -3,7 +3,6 @@
import 'dart:async';
import 'package:convert/convert.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_ble/universal_ble.dart';
@@ -30,7 +29,6 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
bool isConnected = false;
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
List<BleService> discoveredServices = [];
ExpandableController expandableController = ExpandableController();
final List<String> _logs = [];
final binaryCode = TextEditingController();
@@ -104,7 +102,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
if (kIsWeb) {
_addLog("DiscoverServices",
'${services.length} services discovered,\nNote: Only services added in WebRequestOptionsBuilder will be discoverd');
'${services.length} services discovered,\nNote: Only services added in ScanFilter will be discovered');
}
}
+2 -10
View File
@@ -134,14 +134,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_adaptive_scaffold:
dependency: "direct main"
description:
name: flutter_adaptive_scaffold
sha256: "794791e6fc0cc23e375d07ea987e76cf7f0eb7cd753532e45dc35b7e90575e2d"
url: "https://pub.dev"
source: hosted
version: "0.1.11"
flutter_driver:
dependency: transitive
description: flutter
@@ -410,7 +402,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.10.0"
version: "0.11.0"
vector_math:
dependency: transitive
description:
@@ -461,4 +453,4 @@ packages:
version: "6.5.0"
sdks:
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.19.0"
flutter: ">=3.18.0-18.0.pre.54"
+1 -2
View File
@@ -9,12 +9,11 @@ environment:
dependencies:
flutter:
sdk: flutter
convert: ^3.1.1
expandable: ^5.0.1
cupertino_icons: ^1.0.2
permission_handler: ^10.4.3
device_info_plus: ^9.0.2
flutter_adaptive_scaffold: ^0.1.7+2
convert: ^3.1.1
universal_ble:
path: ../
+1 -22
View File
@@ -31,29 +31,8 @@
<title>example</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>