feat(app): 初始化家庭网络监控Flutter应用基本结构

- 添加Flutter项目基础文件与配置,包括.gitignore和analysis_options.yaml
- 配置Android相关文件及Gradle构建脚本支持
- 新增设备状态与网络状态数据模型及相关枚举
- 实现网络检测及网速测试服务
- 创建监控状态管理Provider,实现设备状态管理和自动刷新机制
- 编写主界面HomeScreen,包括设备列表、网速仪表盘及基础交互
- 添加资源文件支持应用图标和启动画面
- 配置项目元数据文件,支持Flutter迁移及版本控制
- 新增项目README,提供入门指引和相关资源链接
This commit is contained in:
2025-12-08 09:01:47 +08:00
parent 360cb1a991
commit 6a0d84f063
76 changed files with 4153 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import '../models/models.dart';
/// 设备列表项组件
class DeviceTile extends StatelessWidget {
final DeviceStatus device;
final VoidCallback? onTap;
final VoidCallback? onRefresh;
const DeviceTile({
super.key,
required this.device,
this.onTap,
this.onRefresh,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final statusColor = _getStatusColor();
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
onTap: onTap,
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
_getDeviceIcon(),
color: statusColor,
),
),
title: Text(
device.name,
style: theme.textTheme.titleMedium,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${device.ip}:${device.port}',
style: theme.textTheme.bodySmall,
),
const SizedBox(height: 4),
Row(
children: [
_StatusBadge(
status: _getStatusText(),
color: statusColor,
),
if (device.latency != null) ...[
const SizedBox(width: 8),
Text(
'${device.latency}ms',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
],
),
],
),
trailing: IconButton(
icon: const Icon(Icons.refresh),
onPressed: onRefresh,
tooltip: '刷新',
),
isThreeLine: true,
),
);
}
IconData _getDeviceIcon() {
switch (device.type) {
case DeviceType.gateway:
return Icons.router;
case DeviceType.bypass:
return Icons.alt_route;
case DeviceType.nas:
return Icons.storage;
case DeviceType.server:
return Icons.dns;
case DeviceType.camera:
return Icons.videocam;
case DeviceType.printer:
return Icons.print;
case DeviceType.other:
return Icons.devices;
}
}
Color _getStatusColor() {
switch (device.state) {
case DeviceState.online:
return Colors.green;
case DeviceState.offline:
return Colors.red;
case DeviceState.warning:
return Colors.orange;
case DeviceState.unknown:
return Colors.grey;
}
}
String _getStatusText() {
switch (device.state) {
case DeviceState.online:
return '在线';
case DeviceState.offline:
return '离线';
case DeviceState.warning:
return '警告';
case DeviceState.unknown:
return '未知';
}
}
}
/// 状态徽章
class _StatusBadge extends StatelessWidget {
final String status;
final Color color;
const _StatusBadge({
required this.status,
required this.color,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: color.withValues(alpha: 0.3)),
),
child: Text(
status,
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
);
}
}

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../models/models.dart';
/// 网速仪表盘组件
class SpeedGauge extends StatelessWidget {
final NetworkStatus networkStatus;
final bool isLoading;
const SpeedGauge({
super.key,
required this.networkStatus,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
'网络状态',
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 20),
if (isLoading)
const SizedBox(
height: 120,
child: Center(
child: CircularProgressIndicator(),
),
)
else
_buildSpeedDisplay(context),
const SizedBox(height: 16),
_buildConnectionInfo(context),
],
),
),
);
}
Widget _buildSpeedDisplay(BuildContext context) {
final theme = Theme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 下载速度
_SpeedIndicator(
label: '下载',
value: networkStatus.downloadSpeedText,
icon: Icons.download,
color: Colors.blue,
),
// 延迟
_SpeedIndicator(
label: '延迟',
value: networkStatus.latency >= 0
? '${networkStatus.latency}ms'
: '--',
icon: Icons.speed,
color: _getLatencyColor(networkStatus.latency),
),
// 上传速度
_SpeedIndicator(
label: '上传',
value: networkStatus.uploadSpeedText,
icon: Icons.upload,
color: Colors.green,
),
],
);
}
Widget _buildConnectionInfo(BuildContext context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_InfoItem(
label: '状态',
value: networkStatus.isConnected ? '已连接' : '未连接',
valueColor: networkStatus.isConnected ? Colors.green : Colors.red,
),
_InfoItem(
label: '本地IP',
value: networkStatus.localIp.isEmpty
? '--'
: networkStatus.localIp,
),
],
),
);
}
Color _getLatencyColor(int latency) {
if (latency < 0) return Colors.grey;
if (latency > 200) return Colors.red;
if (latency > 100) return Colors.orange;
return Colors.green;
}
}
/// 速度指示器
class _SpeedIndicator extends StatelessWidget {
final String label;
final String value;
final IconData icon;
final Color color;
const _SpeedIndicator({
required this.label,
required this.value,
required this.icon,
required this.color,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(height: 8),
Text(
value,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
);
}
}
/// 信息项
class _InfoItem extends StatelessWidget {
final String label;
final String value;
final Color? valueColor;
const _InfoItem({
required this.label,
required this.value,
this.valueColor,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
Text(
label,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 4),
Text(
value,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: valueColor,
),
),
],
);
}
}

View File

@@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
/// 状态卡片组件
class StatusCard extends StatelessWidget {
final String title;
final String value;
final IconData icon;
final Color? iconColor;
final Color? backgroundColor;
final String? subtitle;
final VoidCallback? onTap;
const StatusCard({
super.key,
required this.title,
required this.value,
required this.icon,
this.iconColor,
this.backgroundColor,
this.subtitle,
this.onTap,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
color: backgroundColor ?? theme.cardColor,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(
icon,
color: iconColor ?? theme.colorScheme.primary,
size: 24,
),
const SizedBox(width: 8),
Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
const SizedBox(height: 12),
Text(
value,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
],
),
),
),
);
}
}
/// 网络状态指示器
class NetworkStatusIndicator extends StatelessWidget {
final bool isConnected;
final int latency;
const NetworkStatusIndicator({
super.key,
required this.isConnected,
required this.latency,
});
@override
Widget build(BuildContext context) {
final color = _getStatusColor();
final text = _getStatusText();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withValues(alpha: 0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(
text,
style: TextStyle(
color: color,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
Color _getStatusColor() {
if (!isConnected) return Colors.red;
if (latency < 0) return Colors.grey;
if (latency > 200) return Colors.orange;
if (latency > 100) return Colors.yellow.shade700;
return Colors.green;
}
String _getStatusText() {
if (!isConnected) return '离线';
if (latency < 0) return '检测中';
return '${latency}ms';
}
}

View File

@@ -0,0 +1,4 @@
// Widgets导出文件
export 'status_card.dart';
export 'device_tile.dart';
export 'speed_gauge.dart';