Files
bluetooth_low_energy/example/lib/views/home_view.dart
iAMD 6bc1a364fb Android 平台开发 (#1)
* 修复 UUID 创建失败的问题

* 移除 scanning 属性

* 临时提交

* CentralManager 开发 & 示例项目开发

* CentralManager 开发 & 示例项目开发

* android 插件生命周期监听

* 修改 API

* 示例程序开发

* 修改字体,添加 API,解决后台问题

* Central#connect API

* 蓝牙连接部分开发

* 蓝牙连接部分开发

* 解决一些问题

* 解决一些问题

* Connect API 优化

* 添加 API

* example 开发

* API 基本完成

* 消息重命名

* API 修改,Android 实现

* 删除多余代码

* 删除多余文件

* 解决 descriptor 自动生成报错的问题

* 还原 Kotlin 版本,广播处理代码迁移至 dart 端

* Kotlin 版本升至 1.5.20

* 解决特征值通知没有在主线程触发的问题,优化代码

* 引入哈希值,避免对象销毁后继续使用

* 使用下拉刷新代替搜索按钮

* 解决由于热重载和蓝牙关闭产生的问题

* 更新插件信息

* 更新 README 和 CHANGELOG

* 更新许可证

* 添加注释

* 添加注释,central 拆分
2021-07-01 17:35:54 +08:00

233 lines
6.3 KiB
Dart

import 'dart:async';
import 'package:bluetooth_low_energy/bluetooth_low_energy.dart';
import 'package:convert/convert.dart';
import 'package:flutter/material.dart';
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
final ValueNotifier<bool> discovering;
final ValueNotifier<Map<MAC, Discovery>> discoveries;
late StreamSubscription<bool> stateSubscription;
late StreamSubscription<Discovery> discoverySubscription;
_HomeViewState()
: discovering = ValueNotifier(false),
discoveries = ValueNotifier({});
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
stateSubscription = central.stateChanged.listen((state) {
if (state) {
startDiscovery();
} else {
discoveries.value = {};
discovering.value = false;
}
});
discoverySubscription = central.discovered.listen(
(discovery) {
discoveries.value[discovery.address] = discovery;
discoveries.value = {...discoveries.value};
},
);
startDiscovery();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
}
@override
void dispose() {
stopDiscovery();
stateSubscription.cancel();
discoverySubscription.cancel();
discoveries.dispose();
discovering.dispose();
WidgetsBinding.instance!.removeObserver(this);
print('dispose');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: bodyView,
);
}
void startDiscovery() async {
final state = await central.state;
if (!state) return;
await central.startDiscovery();
discovering.value = true;
}
void stopDiscovery() async {
final state = await central.state;
if (!state) return;
await central.stopDiscovery();
discovering.value = false;
}
void showAdvertisements(Discovery discovery) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
elevation: 0.0,
builder: (context) => buildAdvertisementsView(discovery),
);
}
void showGattView(Discovery discovery) async {
stopDiscovery();
await Navigator.of(context).pushNamed(
'gatt',
arguments: discovery.address,
);
startDiscovery();
}
}
extension on _HomeViewState {
Widget get bodyView {
return FutureBuilder<bool>(
future: central.state,
builder: (context, snapshot) => snapshot.hasData
? StreamBuilder<bool>(
stream: central.stateChanged,
initialData: snapshot.data,
builder: (context, snapshot) =>
snapshot.data! ? discoveriesView : closedView,
)
: closedView,
);
}
Widget get closedView {
return Center(
child: Text('蓝牙未开启'),
);
}
Widget get discoveriesView {
return RefreshIndicator(
onRefresh: () async => discoveries.value = {},
child: ValueListenableBuilder(
valueListenable: discoveries,
builder: (context, Map<MAC, Discovery> discoveries, child) {
return ListView.builder(
padding: EdgeInsets.all(6.0),
itemCount: discoveries.length,
itemBuilder: (context, i) {
final discovery = discoveries.values.elementAt(i);
return Card(
color: Colors.amber,
clipBehavior: Clip.antiAlias,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(12.0),
bottomLeft: Radius.circular(12.0)),
),
margin: EdgeInsets.all(6.0),
key: Key(discovery.address.name),
child: InkWell(
splashColor: Colors.purple,
onTap: () => showGattView(discovery),
onLongPress: () => showAdvertisements(discovery),
child: Container(
height: 100.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(discovery.name ?? 'NaN'),
Text(discovery.address.name),
],
),
Text(discovery.rssi.toString()),
],
),
),
),
);
},
);
},
),
);
}
Widget buildAdvertisementsView(Discovery discovery) {
final widgets = <Widget>[
Row(
children: [
Text('Type'),
Expanded(
child: Center(
child: Text('Value'),
),
),
],
),
Divider(),
];
for (final entry in discovery.advertisements.entries) {
final key = entry.key.toRadixString(16).padLeft(2, '0');
final value = hex.encode(entry.value);
final widget = Row(
children: [
Text('0x$key'),
Container(width: 12.0),
Expanded(
child: Text(
'$value',
softWrap: true,
),
),
],
);
widgets.add(widget);
if (entry.key != discovery.advertisements.entries.last.key) {
final divider = Divider();
widgets.add(divider);
}
}
return Container(
margin: const EdgeInsets.all(12.0),
child: Material(
elevation: 1.0,
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
bottomRight: Radius.circular(12.0),
),
),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: widgets,
),
),
),
);
}
}