M1.1 收尾: - 24项 P0/P1/P2 bug 修复 (Rust 107 tests + Flutter 15 tests) - Flutter App v0.3: cupertino_icons 修复, 单元测试, 调试面板, APK 52.6MB - 示例插件完善: manifest.json + 请求/响应示范 + 7个测试 - API 文档重写 (以 routes.rs 为唯一权威) - MILESTONES.md 更新至 100% M1.2 启动: - P0: 插件管理 API 闭环 (handle_manager_message Custom 分支 + broadcast_plugin_states) - ServiceManager 集成测试 8/8 (tests/m1_2_service_manager.rs) - M1.2 测试计划 (docs/M1.2_TEST_PLAN.md, 18个E2E场景) - 动态插件系统: auto_rollback + version_manager GC + 路径穿越防护 总计: Rust 115/115 测试, Flutter 15/15 测试, 零 warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
303 lines
11 KiB
Dart
303 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../providers/ble_provider.dart';
|
|
import '../providers/device_provider.dart';
|
|
import '../providers/wifi_provider.dart';
|
|
import '../theme/app_colors.dart';
|
|
import '../widgets/control_button.dart';
|
|
import '../widgets/status_card.dart';
|
|
import '../widgets/wifi_list_tile.dart';
|
|
|
|
class NetworkScreen extends StatefulWidget {
|
|
const NetworkScreen({super.key});
|
|
|
|
@override
|
|
State<NetworkScreen> createState() => _NetworkScreenState();
|
|
}
|
|
|
|
class _NetworkScreenState extends State<NetworkScreen> {
|
|
final TextEditingController _ssidController = TextEditingController();
|
|
final TextEditingController _passwordController = TextEditingController();
|
|
final TextEditingController _apSsidController = TextEditingController(text: 'showen');
|
|
final TextEditingController _apPasswordController = TextEditingController(text: '12345678');
|
|
|
|
@override
|
|
void dispose() {
|
|
_ssidController.dispose();
|
|
_passwordController.dispose();
|
|
_apSsidController.dispose();
|
|
_apPasswordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bleProvider = context.watch<BleProvider>();
|
|
final wifiProvider = context.watch<WifiProvider>();
|
|
final deviceProvider = context.watch<DeviceProvider>();
|
|
final wifiStatus = wifiProvider.status;
|
|
final bleStatus = deviceProvider.status.bleStatus;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('网络设置')),
|
|
body: RefreshIndicator(
|
|
onRefresh: _handleRefresh,
|
|
child: ListView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
children: [
|
|
StatusCard(
|
|
title: 'WiFi 状态',
|
|
value: wifiStatus.connected ? (wifiStatus.ssid ?? '已连接') : '未连接',
|
|
subtitle: wifiStatus.ip ?? '尚未获取 IP 地址',
|
|
icon: Icons.router_rounded,
|
|
accentColor: wifiStatus.connected ? AppColors.success : AppColors.warning,
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('连接 WiFi', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: AppSpacing.md),
|
|
TextField(
|
|
controller: _ssidController,
|
|
decoration: const InputDecoration(labelText: 'WiFi 名称'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
TextField(
|
|
controller: _passwordController,
|
|
obscureText: true,
|
|
decoration: const InputDecoration(labelText: 'WiFi 密码'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.icon(
|
|
onPressed: wifiProvider.isLoading ? null : _handleConnectWifi,
|
|
icon: const Icon(Icons.wifi_password_rounded),
|
|
label: const Text('连接当前 WiFi'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: '扫描 WiFi',
|
|
icon: Icons.wifi_find_rounded,
|
|
onPressed: wifiProvider.isLoading
|
|
? null
|
|
: () => context.read<WifiProvider>().scanNetworks(),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: bleStatus?.running == true ? 'BLE 已就绪' : '启动 BLE',
|
|
icon: Icons.bluetooth_rounded,
|
|
isFilled: false,
|
|
onPressed: deviceProvider.isLoading
|
|
? null
|
|
: () => context.read<DeviceProvider>().startBle(deviceName: 'showen'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (bleProvider.isConnected) ...[
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('BLE 控制', style: Theme.of(context).textTheme.titleMedium),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(
|
|
bleProvider.selectedDevice?.name ?? '已连接 BLE 设备',
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: '播放',
|
|
icon: Icons.play_arrow_rounded,
|
|
onPressed: bleProvider.isSendingCommand
|
|
? null
|
|
: () => _sendBleCommand('play'),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: '暂停',
|
|
icon: Icons.pause_rounded,
|
|
onPressed: bleProvider.isSendingCommand
|
|
? null
|
|
: () => _sendBleCommand('pause'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: '上一个',
|
|
icon: Icons.skip_previous_rounded,
|
|
isFilled: false,
|
|
onPressed: bleProvider.isSendingCommand
|
|
? null
|
|
: () => _sendBleCommand('prev'),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: ControlButton(
|
|
label: '下一个',
|
|
icon: Icons.skip_next_rounded,
|
|
isFilled: false,
|
|
onPressed: bleProvider.isSendingCommand
|
|
? null
|
|
: () => _sendBleCommand('next'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Text('扫描结果', style: Theme.of(context).textTheme.headlineSmall),
|
|
const SizedBox(height: AppSpacing.md),
|
|
if (wifiProvider.networks.isEmpty)
|
|
const Card(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(AppSpacing.md),
|
|
child: Text('暂无扫描结果'),
|
|
),
|
|
),
|
|
...wifiProvider.networks.map(
|
|
(network) => Padding(
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
|
child: WifiListTile(
|
|
network: network,
|
|
onTap: () {
|
|
_ssidController.text = network.ssid;
|
|
setState(() {});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text('热点开关', style: Theme.of(context).textTheme.titleMedium),
|
|
),
|
|
Switch(
|
|
value: wifiProvider.hotspotEnabled,
|
|
onChanged: (enabled) {
|
|
if (enabled) {
|
|
context.read<WifiProvider>().startHotspot(
|
|
ssid: _apSsidController.text.trim(),
|
|
password: _apPasswordController.text,
|
|
);
|
|
return;
|
|
}
|
|
context.read<WifiProvider>().stopHotspot();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
TextField(
|
|
controller: _apSsidController,
|
|
decoration: const InputDecoration(labelText: '热点 SSID'),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
TextField(
|
|
controller: _apPasswordController,
|
|
obscureText: true,
|
|
decoration: const InputDecoration(labelText: '热点密码'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.tonal(
|
|
onPressed: () => context.push('/network/ble-provision'),
|
|
child: const Text('进入 BLE 配网页面'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _handleRefresh() async {
|
|
await Future.wait<void>([
|
|
context.read<WifiProvider>().bootstrap(),
|
|
context.read<DeviceProvider>().refresh(),
|
|
]);
|
|
}
|
|
|
|
void _handleConnectWifi() {
|
|
final ssid = _ssidController.text.trim();
|
|
if (ssid.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('请输入 WiFi 名称')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
context.read<WifiProvider>().connect(
|
|
ssid: ssid,
|
|
password: _passwordController.text,
|
|
);
|
|
}
|
|
|
|
Future<void> _sendBleCommand(String command) async {
|
|
try {
|
|
await context.read<BleProvider>().sendCommand(command);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('已发送 BLE 指令: $command')),
|
|
);
|
|
} catch (error) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('BLE 指令发送失败: $error')),
|
|
);
|
|
}
|
|
}
|
|
}
|