|
@@ -1,17 +1,46 @@
|
|
|
import 'dart:async';
|
|
|
import 'dart:io';
|
|
|
+import 'dart:math';
|
|
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
|
|
|
+import 'package:nordic_dfu/nordic_dfu.dart';
|
|
|
import 'package:nrf/app_subscription_state.dart';
|
|
|
import 'package:nrf/connector.dart';
|
|
|
import 'package:nrf/dfu_list.dart';
|
|
|
import 'package:nrf/find.dart';
|
|
|
+import 'package:path_provider/path_provider.dart';
|
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
+import 'package:wakelock/wakelock.dart';
|
|
|
|
|
|
const SH_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
|
|
|
|
|
|
+Future clearCache() async {
|
|
|
+ await delDir(await getTemporaryDirectory());
|
|
|
+ if (Platform.isAndroid) {
|
|
|
+ var dir = await getExternalStorageDirectory();
|
|
|
+ if (dir != null) await delDir(dir);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+///递归方式删除目录
|
|
|
+Future<Null> delDir(FileSystemEntity file) async {
|
|
|
+ print(("del path $file"));
|
|
|
+ if (file is Directory) {
|
|
|
+ try {
|
|
|
+ final List<FileSystemEntity> children = file.listSync();
|
|
|
+ for (final FileSystemEntity child in children) {
|
|
|
+ await delDir(child);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ await file.delete();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
class Scanner extends StatefulWidget {
|
|
|
const Scanner({Key? key}) : super(key: key);
|
|
|
|
|
@@ -23,7 +52,7 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
final flutterReactiveBle = FlutterReactiveBle();
|
|
|
|
|
|
bool _selectAll = false;
|
|
|
- double _filterRssi = 70;
|
|
|
+ double _filterRssi = 35;
|
|
|
bool _filterNameEquals = true;
|
|
|
bool _filterSofewareEquals = true;
|
|
|
bool _filterHardwareEquals = true;
|
|
@@ -35,6 +64,13 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
List<DiscoveredDevice> scanResults = <DiscoveredDevice>[];
|
|
|
Set<DiscoveredDevice> selectedResults = {};
|
|
|
|
|
|
+ bool _dfuAll = false;
|
|
|
+ DiscoveredDevice? _targetDevice;
|
|
|
+ int _countAll = 0;
|
|
|
+ int _countSucc = 0;
|
|
|
+ int _countFail = 0;
|
|
|
+ final ValueNotifier<String> _messageNotifier = ValueNotifier<String>("");
|
|
|
+
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
@@ -47,6 +83,8 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
}));
|
|
|
|
|
|
_grant();
|
|
|
+ clearCache();
|
|
|
+ Wakelock.enable();
|
|
|
}
|
|
|
|
|
|
_grant() async {
|
|
@@ -73,6 +111,7 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
|
|
|
scanForDevices = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)]).listen((e) {
|
|
|
if (e.name.isEmpty) return;
|
|
|
+ // print("111111111111111111111111 ${e}");
|
|
|
final knownDeviceIndex = scanResults.indexWhere((d) => d.id == e.id);
|
|
|
if (knownDeviceIndex >= 0) {
|
|
|
scanResults[knownDeviceIndex] = e;
|
|
@@ -88,6 +127,153 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ List<DiscoveredDevice> updateResults = <DiscoveredDevice>[];
|
|
|
+ Map<DiscoveredDevice, int> failedResults = {};
|
|
|
+
|
|
|
+ _dfu(String path, int type) async {
|
|
|
+ updateResults.clear();
|
|
|
+ failedResults.clear();
|
|
|
+
|
|
|
+ while (mounted) {
|
|
|
+ try {
|
|
|
+ _messageNotifier.value = "开始搜索...(M$type)";
|
|
|
+ DiscoveredDevice device;
|
|
|
+ if (type == 1) {
|
|
|
+ List<DiscoveredDevice> scanResults = <DiscoveredDevice>[];
|
|
|
+ Completer<bool> completer = Completer();
|
|
|
+ int times = 0;
|
|
|
+ Timer timeout = Timer.periodic(const Duration(seconds: 5), (t) {
|
|
|
+ if(scanResults.isNotEmpty) {
|
|
|
+ completer.complete(true);
|
|
|
+ }else{
|
|
|
+ _messageNotifier.value = "开始搜索...(M$type .... ${times++})";
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ var stream = flutterReactiveBle.scanForDevices(withServices: [Uuid.parse(SH_UUID)], scanMode: ScanMode.lowPower);
|
|
|
+ var listen = stream.listen((event) {
|
|
|
+ if (scanResults.where((element) => element.id == event.id).isEmpty && (failedResults[event] ?? 0) < 3) {
|
|
|
+ List<DiscoveredDevice> results = [event];
|
|
|
+ scanResults.addAll(results
|
|
|
+ .where((element) => updateResults.where((event) => element.id == event.id).isEmpty)
|
|
|
+ .where((element) => element.rssi.abs() < _filterRssi).where((element) => element.name.contains(_filterName ?? "") == _filterNameEquals).where((element) {
|
|
|
+ if (_filterHardware?.isNotEmpty == true) {
|
|
|
+ if (element.manufacturerData.length >= 6 && (_filterHardware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[5]}") == _filterHardwareEquals) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }).where((element) {
|
|
|
+ if (_filterSofeware?.isNotEmpty == true) {
|
|
|
+ if (element.manufacturerData.length >= 6 && (_filterSofeware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[0]}") == _filterSofewareEquals) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ await completer.future;
|
|
|
+ listen.cancel();
|
|
|
+ timeout.cancel();
|
|
|
+
|
|
|
+ _messageNotifier.value = "搜索到 ${scanResults.map((e) => e.name).join(", ")}";
|
|
|
+
|
|
|
+ if (scanResults.isEmpty) {
|
|
|
+ await Future.delayed(const Duration(seconds: 2));
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ device = scanResults[Random().nextInt(scanResults.length)];
|
|
|
+ } else {
|
|
|
+ device = await flutterReactiveBle
|
|
|
+ .scanForDevices(withServices: [Uuid.parse(SH_UUID)])
|
|
|
+ .where((element) => updateResults.where((event) => element.id == event.id).isEmpty)
|
|
|
+ .where((element) => failedResults.keys.where((event) => element.id == event.id).isEmpty)
|
|
|
+ .where((element) => element.rssi.abs() < _filterRssi)
|
|
|
+ .where((element) => element.name.contains(_filterName ?? "") == _filterNameEquals)
|
|
|
+ .where((element) {
|
|
|
+ if (_filterHardware?.isNotEmpty == true) {
|
|
|
+ if (element.manufacturerData.length >= 6 && (_filterHardware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[5]}") == _filterHardwareEquals) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ })
|
|
|
+ .firstWhere((element) {
|
|
|
+ if (_filterSofeware?.isNotEmpty == true) {
|
|
|
+ if (element.manufacturerData.length >= 6 && (_filterSofeware == "${element.manufacturerData[3]}.${element.manufacturerData[4]}.${element.manufacturerData[0]}") == _filterSofewareEquals) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ setState(() {
|
|
|
+ _countAll++;
|
|
|
+ _targetDevice = device;
|
|
|
+ });
|
|
|
+ await Future.delayed(const Duration(seconds: 2));
|
|
|
+
|
|
|
+ DateTime start = DateTime.now();
|
|
|
+ try {
|
|
|
+ await NordicDfu().startDfu(device.id, path, fileInAsset: false, onEnablingDfuMode: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "EnablingDfuMode";
|
|
|
+ }, onDfuCompleted: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "DfuCompleted use:${DateTime.now().difference(start).inSeconds}s";
|
|
|
+ _countSucc++;
|
|
|
+ updateResults.add(device);
|
|
|
+ }, onDfuProcessStarted: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "DfuProcessStarted";
|
|
|
+ }, onDfuProcessStarting: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "DfuProcessStarting";
|
|
|
+ }, onDeviceConnecting: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "DeviceConnecting";
|
|
|
+ }, onDeviceConnected: (deviceAddress) {
|
|
|
+ _messageNotifier.value = "DeviceConnected";
|
|
|
+ }, onProgressChanged: (
|
|
|
+ deviceAddress,
|
|
|
+ percent,
|
|
|
+ speed,
|
|
|
+ avgSpeed,
|
|
|
+ currentPart,
|
|
|
+ partsTotal,
|
|
|
+ ) {
|
|
|
+ _messageNotifier.value = "$percent% speed:${avgSpeed.toStringAsFixed(2)}kb/s";
|
|
|
+ }, onError: (
|
|
|
+ String? deviceAddress,
|
|
|
+ int? error,
|
|
|
+ int? errorType,
|
|
|
+ String? message,
|
|
|
+ ) {
|
|
|
+ _messageNotifier.value = "Error $error $errorType $message";
|
|
|
+ _countFail++;
|
|
|
+ failedResults[device] = (failedResults[device] ?? 0)+1;
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ _messageNotifier.value = "升级异常: $e";
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ print(e);
|
|
|
+ _messageNotifier.value = "异常: $e";
|
|
|
+ }
|
|
|
+ setState(() {
|
|
|
+ _targetDevice = null;
|
|
|
+ });
|
|
|
+ await Future.delayed(const Duration(seconds: 2));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
List<DiscoveredDevice> items = <DiscoveredDevice>[];
|
|
@@ -114,155 +300,137 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
return a.rssi.abs() > b.rssi.abs() ? 1 : -1;
|
|
|
});
|
|
|
return Scaffold(
|
|
|
- appBar: AppBar(
|
|
|
- title: const Text("发现设备"),
|
|
|
- actions: [
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.all(8.0),
|
|
|
- child: ElevatedButton(
|
|
|
- onPressed: () {
|
|
|
- setState(() {
|
|
|
- scanForDevices?.cancel();
|
|
|
- scanForDevices = null;
|
|
|
- _selectAll = !_selectAll;
|
|
|
- });
|
|
|
- },
|
|
|
- child: _selectAll ? const Text("取消多选") : const Text("多选")),
|
|
|
- ),
|
|
|
- scanForDevices == null
|
|
|
- ? IconButton(
|
|
|
- onPressed: () {
|
|
|
- setState(() {
|
|
|
- _search();
|
|
|
- });
|
|
|
- },
|
|
|
- icon: const Icon(Icons.search))
|
|
|
- : IconButton(
|
|
|
- onPressed: () {
|
|
|
- setState(() {
|
|
|
- scanForDevices?.cancel();
|
|
|
- scanForDevices = null;
|
|
|
- });
|
|
|
- },
|
|
|
- icon: const Icon(Icons.stop))
|
|
|
- ],
|
|
|
- ),
|
|
|
- body: Column(
|
|
|
- children: [
|
|
|
- Container(
|
|
|
- padding: const EdgeInsets.all(12.0),
|
|
|
- child: Column(
|
|
|
- children: [
|
|
|
- Row(
|
|
|
- children: [
|
|
|
- const Text("rssi"),
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
- child: Text("< ${_filterRssi.toInt()}"),
|
|
|
- ),
|
|
|
- Expanded(
|
|
|
- child: Slider(
|
|
|
- min: 10,
|
|
|
- max: 100,
|
|
|
- value: _filterRssi,
|
|
|
- onChanged: (v) {
|
|
|
- setState(() {
|
|
|
- _filterRssi = v;
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
- )
|
|
|
- ],
|
|
|
- ),
|
|
|
- Row(
|
|
|
- children: [
|
|
|
- const Text("关键字"),
|
|
|
- Checkbox(
|
|
|
- value: _filterNameEquals,
|
|
|
- onChanged: (bool? value) {
|
|
|
- setState(() {
|
|
|
- _filterNameEquals = value ?? false;
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
- Expanded(
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
- child: TextField(
|
|
|
- controller: TextEditingController.fromValue(TextEditingValue(
|
|
|
- text: _filterName ?? "",
|
|
|
- selection: TextSelection.fromPosition(TextPosition(
|
|
|
- affinity: TextAffinity.downstream,
|
|
|
- offset: _filterName == null ? 0 : _filterName?.length ?? 0,
|
|
|
- )))),
|
|
|
- onChanged: (v) {
|
|
|
- setState(() {
|
|
|
- _filterName = v;
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
- ),
|
|
|
- ),
|
|
|
- ElevatedButton(
|
|
|
+ appBar: _dfuAll
|
|
|
+ ? null
|
|
|
+ : AppBar(
|
|
|
+ title: const Text("发现设备"),
|
|
|
+ actions: [
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.all(8.0),
|
|
|
+ child: ElevatedButton(
|
|
|
onPressed: () {
|
|
|
setState(() {
|
|
|
- _filterName = "FUN_";
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ scanForDevices = null;
|
|
|
+ _selectAll = !_selectAll;
|
|
|
});
|
|
|
},
|
|
|
- child: const Text("FUN"),
|
|
|
+ child: _selectAll ? const Text("取消多选") : const Text("多选")),
|
|
|
+ ),
|
|
|
+ scanForDevices == null
|
|
|
+ ? IconButton(
|
|
|
+ onPressed: () {
|
|
|
+ setState(() {
|
|
|
+ _search();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ icon: const Icon(Icons.search))
|
|
|
+ : IconButton(
|
|
|
+ onPressed: () {
|
|
|
+ setState(() {
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ scanForDevices = null;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ icon: const Icon(Icons.stop))
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ body: _dfuAll
|
|
|
+ ? Container(
|
|
|
+ width: double.infinity,
|
|
|
+ height: double.infinity,
|
|
|
+ color: Colors.white,
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ const SizedBox(
|
|
|
+ height: 50,
|
|
|
+ ),
|
|
|
+ Text("升级固件(总数:$_countAll/成功:$_countSucc/失败:$_countFail)"),
|
|
|
+ const SizedBox(
|
|
|
+ height: 25,
|
|
|
+ ),
|
|
|
+ ConstrainedBox(
|
|
|
+ constraints: const BoxConstraints(maxWidth: 140.0),
|
|
|
+ child: Text(
|
|
|
+ "${_targetDevice?.name}",
|
|
|
+ style: const TextStyle(fontSize: 14, color: Color(0xff333333)),
|
|
|
+ softWrap: true,
|
|
|
),
|
|
|
- const SizedBox(
|
|
|
- width: 10,
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ height: 25,
|
|
|
+ ),
|
|
|
+ ConstrainedBox(
|
|
|
+ constraints: const BoxConstraints(maxWidth: 140.0),
|
|
|
+ child: Text(
|
|
|
+ "${_targetDevice?.id}",
|
|
|
+ style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
|
|
|
),
|
|
|
- ElevatedButton(
|
|
|
- onPressed: () {
|
|
|
- setState(() {
|
|
|
- _filterName = "";
|
|
|
- });
|
|
|
- },
|
|
|
- child: const Text("CLEAR"),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ height: 25,
|
|
|
+ ),
|
|
|
+ Text(
|
|
|
+ "${_targetDevice?..manufacturerData}",
|
|
|
+ style: const TextStyle(fontSize: 12, color: Color(0xff999999)),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ height: 25,
|
|
|
+ ),
|
|
|
+ ValueListenableBuilder(
|
|
|
+ valueListenable: _messageNotifier,
|
|
|
+ builder: (BuildContext context, String value, Widget? child) => Text(
|
|
|
+ value,
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
- Row(
|
|
|
- children: [
|
|
|
- Expanded(
|
|
|
- child: Row(
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ height: 50,
|
|
|
+ ),
|
|
|
+ Container(
|
|
|
+ padding: const EdgeInsets.all(12.0),
|
|
|
+ height: 200,
|
|
|
+ child: SingleChildScrollView(
|
|
|
+ child: Text("升级列表 ... ${updateResults.map((e) => e.name).join(", ")}"),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : Column(
|
|
|
+ children: [
|
|
|
+ Container(
|
|
|
+ padding: const EdgeInsets.all(12.0),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ Row(
|
|
|
children: [
|
|
|
- const Text("固件"),
|
|
|
- Checkbox(
|
|
|
- value: _filterSofewareEquals,
|
|
|
- onChanged: (bool? value) {
|
|
|
- setState(() {
|
|
|
- _filterSofewareEquals = value ?? false;
|
|
|
- });
|
|
|
- },
|
|
|
+ const Text("rssi"),
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
+ child: Text("< ${_filterRssi.toInt()}"),
|
|
|
),
|
|
|
Expanded(
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
- child: TextField(
|
|
|
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
- onChanged: (v) {
|
|
|
- setState(() {
|
|
|
- _filterSofeware = v;
|
|
|
- });
|
|
|
- },
|
|
|
- ),
|
|
|
+ child: Slider(
|
|
|
+ min: 20,
|
|
|
+ max: 100,
|
|
|
+ value: _filterRssi,
|
|
|
+ onChanged: (v) {
|
|
|
+ setState(() {
|
|
|
+ _filterRssi = v;
|
|
|
+ });
|
|
|
+ },
|
|
|
),
|
|
|
- ),
|
|
|
+ )
|
|
|
],
|
|
|
),
|
|
|
- ),
|
|
|
- Expanded(
|
|
|
- child: Row(
|
|
|
+ Row(
|
|
|
children: [
|
|
|
- const Text("硬件"),
|
|
|
+ const Text("关键字"),
|
|
|
Checkbox(
|
|
|
- value: _filterHardwareEquals,
|
|
|
+ value: _filterNameEquals,
|
|
|
onChanged: (bool? value) {
|
|
|
setState(() {
|
|
|
- _filterHardwareEquals = value ?? false;
|
|
|
+ _filterNameEquals = value ?? false;
|
|
|
});
|
|
|
},
|
|
|
),
|
|
@@ -270,200 +438,347 @@ class _State extends State<Scanner> with SubscriptionState {
|
|
|
child: Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
child: TextField(
|
|
|
- keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
+ controller: TextEditingController.fromValue(TextEditingValue(
|
|
|
+ text: _filterName ?? "",
|
|
|
+ selection: TextSelection.fromPosition(TextPosition(
|
|
|
+ affinity: TextAffinity.downstream,
|
|
|
+ offset: _filterName == null ? 0 : _filterName?.length ?? 0,
|
|
|
+ )))),
|
|
|
onChanged: (v) {
|
|
|
setState(() {
|
|
|
- _filterHardware = v;
|
|
|
+ _filterName = v;
|
|
|
});
|
|
|
},
|
|
|
),
|
|
|
),
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
- if (scanForDevices == null && items.isEmpty)
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.all(40.0),
|
|
|
- child: ElevatedButton(
|
|
|
- onPressed: () {
|
|
|
- _search();
|
|
|
- },
|
|
|
- child: Text("搜索设备"),
|
|
|
- ),
|
|
|
- ),
|
|
|
- Expanded(
|
|
|
- child: ListView.builder(
|
|
|
- itemBuilder: (context, index) {
|
|
|
- var e = items[index];
|
|
|
- return GestureDetector(
|
|
|
- child: Card(
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.all(12.0),
|
|
|
- child: Row(
|
|
|
- children: [
|
|
|
- Expanded(
|
|
|
- child: Column(
|
|
|
- children: [
|
|
|
- Text(
|
|
|
- e.name,
|
|
|
- style: Theme.of(context).textTheme.headline6,
|
|
|
- ),
|
|
|
- ConstrainedBox(constraints: const BoxConstraints(maxWidth: 140.0), child: Text(e.id)),
|
|
|
- Text("${e.manufacturerData}"),
|
|
|
- Row(
|
|
|
- children: [
|
|
|
- Text("${e.rssi} dBm"),
|
|
|
- const SizedBox(
|
|
|
- width: 20,
|
|
|
- ),
|
|
|
- Text("#${index + 1}"),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ],
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
- ),
|
|
|
- ),
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
- child: ElevatedButton(
|
|
|
- onPressed: () async {
|
|
|
- await showDialog(
|
|
|
- context: context,
|
|
|
- builder: (context) => Find(device: e),
|
|
|
- );
|
|
|
- },
|
|
|
- child: const Text("找鞋"),
|
|
|
- ),
|
|
|
- ),
|
|
|
- if (!_selectAll)
|
|
|
ElevatedButton(
|
|
|
onPressed: () {
|
|
|
setState(() {
|
|
|
- scanForDevices?.cancel();
|
|
|
- scanForDevices = null;
|
|
|
+ _filterName = "FUN_";
|
|
|
});
|
|
|
-
|
|
|
- Navigator.of(context).push(MaterialPageRoute(builder: (context) {
|
|
|
- return Connector(
|
|
|
- device: e,
|
|
|
- );
|
|
|
- }));
|
|
|
},
|
|
|
- child: const Text("CONNECT"),
|
|
|
+ child: const Text("FUN"),
|
|
|
),
|
|
|
- if (_selectAll)
|
|
|
- Checkbox(
|
|
|
- value: selectedResults.contains(e),
|
|
|
- onChanged: (bool? value) {
|
|
|
+ const SizedBox(
|
|
|
+ width: 10,
|
|
|
+ ),
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () {
|
|
|
setState(() {
|
|
|
- if (value == true) {
|
|
|
- selectedResults.add(e);
|
|
|
- } else {
|
|
|
- selectedResults.remove(e);
|
|
|
- }
|
|
|
+ _filterName = "";
|
|
|
});
|
|
|
},
|
|
|
+ child: const Text("CLEAR"),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ const Text("固件"),
|
|
|
+ Checkbox(
|
|
|
+ value: _filterSofewareEquals,
|
|
|
+ onChanged: (bool? value) {
|
|
|
+ setState(() {
|
|
|
+ _filterSofewareEquals = value ?? false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
+ child: TextField(
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
+ onChanged: (v) {
|
|
|
+ setState(() {
|
|
|
+ _filterSofeware = v;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
),
|
|
|
+ Expanded(
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ const Text("硬件"),
|
|
|
+ Checkbox(
|
|
|
+ value: _filterHardwareEquals,
|
|
|
+ onChanged: (bool? value) {
|
|
|
+ setState(() {
|
|
|
+ _filterHardwareEquals = value ?? false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
+ child: TextField(
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
|
+ onChanged: (v) {
|
|
|
+ setState(() {
|
|
|
+ _filterHardware = v;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (scanForDevices == null && items.isEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.all(40.0),
|
|
|
+ child: ElevatedButton(
|
|
|
+ onPressed: () {
|
|
|
+ _search();
|
|
|
+ },
|
|
|
+ child: const Text("搜索设备"),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (scanForDevices == null)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ const Padding(
|
|
|
+ padding: EdgeInsets.all(16.0),
|
|
|
+ child: Text("升级设备"),
|
|
|
+ ),
|
|
|
+ Row(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ setState(() {
|
|
|
+ selectedResults.clear();
|
|
|
+ selectedResults.addAll(items);
|
|
|
+ _dfuAll = true;
|
|
|
+ });
|
|
|
+
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ clearCache();
|
|
|
+ FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
|
+
|
|
|
+ String? path = result?.files.single.path ?? "";
|
|
|
+ if (path.isNotEmpty == true) {
|
|
|
+ _dfu(path, 0);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ child: const Text("模式 1,选取最优"),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ width: 20,
|
|
|
+ ),
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ setState(() {
|
|
|
+ selectedResults.clear();
|
|
|
+ selectedResults.addAll(items);
|
|
|
+ _dfuAll = true;
|
|
|
+ });
|
|
|
+
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ clearCache();
|
|
|
+ FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
|
+
|
|
|
+ String? path = result?.files.single.path ?? "";
|
|
|
+ if (path.isNotEmpty == true) {
|
|
|
+ _dfu(path, 1);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ child: const Text("模式 2,列表随机"),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
- ),
|
|
|
- );
|
|
|
- },
|
|
|
- itemCount: items.length,
|
|
|
- )),
|
|
|
- if (_selectAll)
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.all(24.0),
|
|
|
- child: Row(
|
|
|
- children: [
|
|
|
- ElevatedButton(
|
|
|
- onPressed: () async {
|
|
|
- scanForDevices?.cancel();
|
|
|
-
|
|
|
- if (await showDialog(
|
|
|
- context: context,
|
|
|
- builder: (context) {
|
|
|
- return SimpleDialog(
|
|
|
- title: const Text("操作确认"),
|
|
|
+ Expanded(
|
|
|
+ child: ListView.builder(
|
|
|
+ itemBuilder: (context, index) {
|
|
|
+ var e = items[index];
|
|
|
+ return GestureDetector(
|
|
|
+ child: Card(
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.all(12.0),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: Column(
|
|
|
children: [
|
|
|
- if ((_filterHardware?.length ?? 0) == 0)
|
|
|
- const Padding(
|
|
|
- padding: EdgeInsets.all(20.0),
|
|
|
- child: Text("请确保所选设备是同一个硬件版本,排除升级后出现不兼容的情况"),
|
|
|
- ),
|
|
|
- if ((_filterHardware?.length ?? 0) > 0)
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.all(20.0),
|
|
|
- child: Text("升级对应的硬件版本 v${_filterHardware}"),
|
|
|
- ),
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.all(20.0),
|
|
|
- child: ElevatedButton(
|
|
|
- onPressed: () {
|
|
|
- Navigator.pop(context, true);
|
|
|
- },
|
|
|
- child: const Text("知道了"),
|
|
|
- ),
|
|
|
+ Text(
|
|
|
+ e.name,
|
|
|
+ style: Theme.of(context).textTheme.headline6,
|
|
|
+ ),
|
|
|
+ ConstrainedBox(constraints: const BoxConstraints(maxWidth: 140.0), child: Text(e.id)),
|
|
|
+ Text("${e.manufacturerData}"),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Text("${e.rssi} dBm"),
|
|
|
+ const SizedBox(
|
|
|
+ width: 20,
|
|
|
+ ),
|
|
|
+ Text("#${index + 1}"),
|
|
|
+ ],
|
|
|
),
|
|
|
],
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
|
+ child: ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ await showDialog(
|
|
|
+ context: context,
|
|
|
+ builder: (context) => Find(device: e),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ child: const Text("找鞋"),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (!_selectAll)
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () {
|
|
|
+ setState(() {
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ scanForDevices = null;
|
|
|
+ });
|
|
|
+
|
|
|
+ Navigator.of(context).push(MaterialPageRoute(builder: (context) {
|
|
|
+ return Connector(
|
|
|
+ device: e,
|
|
|
+ );
|
|
|
+ }));
|
|
|
+ },
|
|
|
+ child: const Text("CONNECT"),
|
|
|
+ ),
|
|
|
+ if (_selectAll)
|
|
|
+ Checkbox(
|
|
|
+ value: selectedResults.contains(e),
|
|
|
+ onChanged: (bool? value) {
|
|
|
+ setState(() {
|
|
|
+ if (value == true) {
|
|
|
+ selectedResults.add(e);
|
|
|
+ } else {
|
|
|
+ selectedResults.remove(e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ itemCount: items.length,
|
|
|
+ )),
|
|
|
+ if (_selectAll)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.all(24.0),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ scanForDevices?.cancel();
|
|
|
+ clearCache();
|
|
|
+
|
|
|
+ if (await showDialog(
|
|
|
+ context: context,
|
|
|
+ builder: (context) {
|
|
|
+ return SimpleDialog(
|
|
|
+ title: const Text("操作确认"),
|
|
|
+ children: [
|
|
|
+ if ((_filterHardware?.length ?? 0) == 0)
|
|
|
+ const Padding(
|
|
|
+ padding: EdgeInsets.all(20.0),
|
|
|
+ child: Text("请确保所选设备是同一个硬件版本,排除升级后出现不兼容的情况"),
|
|
|
+ ),
|
|
|
+ if ((_filterHardware?.length ?? 0) > 0)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.all(20.0),
|
|
|
+ child: Text("升级对应的硬件版本 v${_filterHardware}"),
|
|
|
+ ),
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.all(20.0),
|
|
|
+ child: ElevatedButton(
|
|
|
+ onPressed: () {
|
|
|
+ Navigator.pop(context, true);
|
|
|
+ },
|
|
|
+ child: const Text("知道了"),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }) !=
|
|
|
+ true) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedResults.isEmpty) {
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
|
|
+ content: Text('请选择需要升级的设备'),
|
|
|
+ ));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
|
+
|
|
|
+ String? path = result?.files.single.path ?? "";
|
|
|
+ if (path.isNotEmpty == true) {
|
|
|
+ File file = File(path);
|
|
|
+ Navigator.of(context).push(MaterialPageRoute(builder: (context) {
|
|
|
+ return DFUList(
|
|
|
+ selectedResults: selectedResults,
|
|
|
+ file: file,
|
|
|
);
|
|
|
- }) !=
|
|
|
- true) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- FilePickerResult? result = await FilePicker.platform.pickFiles();
|
|
|
-
|
|
|
- String? path = result?.files.single.path ?? "";
|
|
|
- if (path.isNotEmpty == true) {
|
|
|
- File file = File(path);
|
|
|
- Navigator.of(context).push(MaterialPageRoute(builder: (context) {
|
|
|
- return DFUList(
|
|
|
- selectedResults: selectedResults,
|
|
|
- file: file,
|
|
|
- );
|
|
|
- }));
|
|
|
- } else {
|
|
|
- // User canceled the picker
|
|
|
- }
|
|
|
- },
|
|
|
- child: Text("批量DFU(${selectedResults.length})"),
|
|
|
- ),
|
|
|
- const SizedBox(
|
|
|
- width: 12,
|
|
|
- ),
|
|
|
- ElevatedButton(
|
|
|
- onPressed: () async {
|
|
|
- setState(() {
|
|
|
- selectedResults.clear();
|
|
|
- selectedResults.addAll(items);
|
|
|
- });
|
|
|
- },
|
|
|
- child: Text("全选"),
|
|
|
- ),
|
|
|
- const SizedBox(
|
|
|
- width: 12,
|
|
|
- ),
|
|
|
- ElevatedButton(
|
|
|
- onPressed: () async {
|
|
|
- setState(() {
|
|
|
- selectedResults.clear();
|
|
|
- });
|
|
|
- },
|
|
|
- child: Text("全不选"),
|
|
|
+ }));
|
|
|
+ } else {
|
|
|
+ // User canceled the picker
|
|
|
+ }
|
|
|
+ },
|
|
|
+ child: Text("批量DFU(${selectedResults.length})"),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ width: 12,
|
|
|
+ ),
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ setState(() {
|
|
|
+ selectedResults.clear();
|
|
|
+ selectedResults.addAll(items);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ child: Text("全选"),
|
|
|
+ ),
|
|
|
+ const SizedBox(
|
|
|
+ width: 12,
|
|
|
+ ),
|
|
|
+ ElevatedButton(
|
|
|
+ onPressed: () async {
|
|
|
+ setState(() {
|
|
|
+ selectedResults.clear();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ child: Text("全不选"),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
+ ],
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
);
|
|
|
}
|
|
|
}
|