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:
@@ -14,7 +14,6 @@ jobs:
|
|||||||
- name: Setup Flutter
|
- name: Setup Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: '3.16.0'
|
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
- run: flutter --version
|
- run: flutter --version
|
||||||
|
|
||||||
|
|||||||
@@ -42,3 +42,4 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
.metadata
|
||||||
@@ -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
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:universal_ble/universal_ble.dart';
|
import 'package:universal_ble/universal_ble.dart';
|
||||||
import 'package:universal_ble_example/data/capabilities.dart';
|
import 'package:universal_ble_example/data/capabilities.dart';
|
||||||
import 'package:universal_ble_example/data/mock_universal_ble.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_devices_placeholder_widget.dart';
|
||||||
import 'package:universal_ble_example/home/widgets/scanned_item_widget.dart';
|
import 'package:universal_ble_example/home/widgets/scanned_item_widget.dart';
|
||||||
import 'package:universal_ble_example/data/permission_handler.dart';
|
import 'package:universal_ble_example/data/permission_handler.dart';
|
||||||
@@ -23,17 +24,12 @@ class _MyAppState extends State<MyApp> {
|
|||||||
final _bleDevices = <BleDevice>[];
|
final _bleDevices = <BleDevice>[];
|
||||||
bool _isScanning = false;
|
bool _isScanning = false;
|
||||||
QueueType _queueType = QueueType.global;
|
QueueType _queueType = QueueType.global;
|
||||||
|
TextEditingController servicesFilterController = TextEditingController();
|
||||||
|
TextEditingController namePrefixController = TextEditingController();
|
||||||
|
TextEditingController manufacturerDataController = TextEditingController();
|
||||||
|
|
||||||
AvailabilityState? bleAvailabilityState;
|
AvailabilityState? bleAvailabilityState;
|
||||||
final List<String> _services = [
|
ScanFilter? scanFilter;
|
||||||
"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",
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -76,17 +72,26 @@ class _MyAppState extends State<MyApp> {
|
|||||||
|
|
||||||
Future<void> startScan() async {
|
Future<void> startScan() async {
|
||||||
await UniversalBle.startScan(
|
await UniversalBle.startScan(
|
||||||
scanFilter: ScanFilter(
|
scanFilter: scanFilter,
|
||||||
withServices: kIsWeb ? _services : [],
|
);
|
||||||
withManufacturerData: [
|
}
|
||||||
// ManufacturerDataFilter(
|
|
||||||
// companyIdentifier: 0x012D,
|
void _showScanFilterBottomSheet() {
|
||||||
// data: Uint8List.fromList(
|
showModalBottomSheet(
|
||||||
// [0x03, 0x00, 0x64, 0x00],
|
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',
|
text: 'Connected Devices',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
List<BleDevice> devices =
|
List<BleDevice> devices =
|
||||||
await UniversalBle.getSystemDevices(
|
await UniversalBle.getSystemDevices();
|
||||||
withServices: _services,
|
|
||||||
);
|
|
||||||
if (devices.isEmpty) {
|
if (devices.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
@@ -201,6 +204,10 @@ class _MyAppState extends State<MyApp> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
PlatformButton(
|
||||||
|
text: 'Scan Filters',
|
||||||
|
onPressed: _showScanFilterBottomSheet,
|
||||||
|
),
|
||||||
if (_bleDevices.isNotEmpty)
|
if (_bleDevices.isNotEmpty)
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: 'Clear List',
|
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 'dart:async';
|
||||||
|
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:universal_ble/universal_ble.dart';
|
import 'package:universal_ble/universal_ble.dart';
|
||||||
@@ -30,7 +29,6 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
|||||||
bool isConnected = false;
|
bool isConnected = false;
|
||||||
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
|
GlobalKey<FormState> valueFormKey = GlobalKey<FormState>();
|
||||||
List<BleService> discoveredServices = [];
|
List<BleService> discoveredServices = [];
|
||||||
ExpandableController expandableController = ExpandableController();
|
|
||||||
final List<String> _logs = [];
|
final List<String> _logs = [];
|
||||||
final binaryCode = TextEditingController();
|
final binaryCode = TextEditingController();
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
|||||||
|
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
_addLog("DiscoverServices",
|
_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
@@ -134,14 +134,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
flutter_driver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -410,7 +402,7 @@ packages:
|
|||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.10.0"
|
version: "0.11.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -461,4 +453,4 @@ packages:
|
|||||||
version: "6.5.0"
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
convert: ^3.1.1
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
permission_handler: ^10.4.3
|
permission_handler: ^10.4.3
|
||||||
device_info_plus: ^9.0.2
|
device_info_plus: ^9.0.2
|
||||||
flutter_adaptive_scaffold: ^0.1.7+2
|
|
||||||
convert: ^3.1.1
|
|
||||||
universal_ble:
|
universal_ble:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
|
|||||||
+1
-22
@@ -31,29 +31,8 @@
|
|||||||
|
|
||||||
<title>example</title>
|
<title>example</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script src="flutter_bootstrap.js" async></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>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user