修复内存泄漏

This commit is contained in:
会做饭的二哈
2025-03-17 18:15:21 +08:00
parent 2f6195ecd9
commit b1e48999c3
21 changed files with 448 additions and 826 deletions
-3
View File
@@ -15,9 +15,6 @@ migration:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: web
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
+6
View File
@@ -4,6 +4,12 @@
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "Flutter Run",
"request": "launch",
-1
View File
@@ -6,7 +6,6 @@ import 'dart:io';
import 'screens/Home.dart';
import 'config/themeconfiguration.dart';
import 'config/app_config.dart';
import 'package:astral/utils/up.dart'; // 添加这行导入
// 定义应用程序的主要StatefulWidget
class MyApp extends StatefulWidget {
+6 -6
View File
@@ -40,12 +40,12 @@ class NavigationConfig {
currentThemeMode: currentThemeMode,
)),
),
NavItem(
label: '房间',
icon: Icons.room_outlined,
selectedIcon: Icons.room,
pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
),
// NavItem(
// label: '房间',
// icon: Icons.room_outlined,
// selectedIcon: Icons.room,
// pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
// ),
NavItem(
label: '设置',
icon: Icons.settings_outlined,
+118 -277
View File
@@ -2,35 +2,11 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:math' as math;
import 'package:astral/utils/app_info.dart';
class InfoPage extends StatefulWidget {
class InfoPage extends StatelessWidget {
const InfoPage({super.key});
@override
State<InfoPage> createState() => _InfoPageState();
}
class _InfoPageState extends State<InfoPage>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
@@ -40,156 +16,95 @@ class _InfoPageState extends State<InfoPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 旋转的图标效果
AnimatedBuilder(
animation: _controller,
builder: (_, child) {
return Transform.rotate(
angle: _controller.value * 2 * math.pi,
child: Container(
height: 110,
width: 110,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: SweepGradient(
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.secondary,
Theme.of(context).colorScheme.tertiary,
Theme.of(context).colorScheme.primary,
],
stops: const [0.0, 0.3, 0.6, 1.0],
transform:
GradientRotation(_controller.value * 2 * math.pi),
),
boxShadow: [
BoxShadow(
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.5),
blurRadius: 15,
spreadRadius: 1,
),
],
),
child: Center(
child: Icon(
Icons.games,
size: 50,
color: Theme.of(context).colorScheme.onPrimary,
),
),
// 静态图标
Container(
height: 110,
width: 110,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.primary,
boxShadow: [
BoxShadow(
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.5),
blurRadius: 15,
spreadRadius: 1,
),
);
},
],
),
child: Center(
child: Icon(
Icons.games,
size: 50,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
const SizedBox(height: 20),
// 应用名称添加渐变效果
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.tertiary,
],
).createShader(bounds),
child: Text(
'ASTRAL',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
),
),
// 应用名称
Text(
'ASTRAL',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 2.0,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
// 版本号添加动画效果
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(milliseconds: 800),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: child,
),
);
},
child: Text(
AppInfoUtil.getVersion(),
style: Theme.of(context).textTheme.bodyLarge,
),
// 版本号
Text(
AppInfoUtil.getVersion(),
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 20),
// 卡片添加动画和阴影效果
_buildAnimatedCard(
// 静态卡片
_buildCard(
context,
'特别鸣谢',
'特别感谢EasyTier作者所做的工作和帮助,为本项目提供了重要的技术支持。如果您有功能需求或遇到bug,欢迎加入我们的QQ群获取帮助和了解最新动态。',
Icons.favorite,
Colors.red,
delay: 200,
),
// 合并后的卡片
const SizedBox(height: 30),
// 按钮添加动画效果
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(milliseconds: 1000),
curve: Curves.elasticOut,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: child,
);
},
child: ElevatedButton.icon(
icon: const Icon(Icons.group_add),
label: const Text('加入QQ群'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
elevation: 5,
// 静态按钮
ElevatedButton.icon(
icon: const Icon(Icons.group_add),
label: const Text('加入QQ群'),
style: ElevatedButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
onPressed: () async {
final url = 'https://qm.qq.com/q/ErscyNPTzO';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
// 无法打开链接时显示提示
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法打开QQ群链接')),
);
}
}
},
elevation: 5,
),
onPressed: () async {
final url = 'https://qm.qq.com/q/ErscyNPTzO';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
// 无法打开链接时显示提示
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法打开QQ群链接')),
);
}
}
},
),
const SizedBox(height: 20),
// 添加版权信息
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(milliseconds: 1200),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: child,
);
},
child: Text(
'© ${DateTime.now().year} ASTRAL Team',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.6),
),
),
// 版权信息
Text(
'© ${DateTime.now().year} ASTRAL Team',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.6),
),
),
],
),
@@ -198,129 +113,55 @@ class _InfoPageState extends State<InfoPage>
);
}
Widget _buildAnimatedCard(BuildContext context, String title, String content,
IconData icon, Color iconColor,
{int delay = 0}) {
// 根据当前主题调整颜色
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final cardColor = Theme.of(context).cardColor;
final textColor = Theme.of(context).textTheme.bodyMedium?.color;
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: Duration(milliseconds: 800 + delay),
curve: Curves.easeOutBack,
builder: (context, value, child) {
// 确保 opacity 值在有效范围内 (0.0 到 1.0)
final safeOpacity = value.clamp(0.0, 1.0);
return Opacity(
opacity: safeOpacity,
child: Transform.translate(
offset: Offset(100 * (1 - value), 0),
child: child,
),
);
},
child: Card(
elevation: 8,
shadowColor: iconColor.withOpacity(isDarkMode ? 0.3 : 0.4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
cardColor,
isDarkMode
? cardColor.withOpacity(0.9).withBlue(cardColor.blue + 5)
: iconColor.withOpacity(0.05),
cardColor,
],
stops: const [0.0, 0.5, 1.0],
),
boxShadow: [
BoxShadow(
color: iconColor.withOpacity(isDarkMode ? 0.05 : 0.1),
blurRadius: 10,
spreadRadius: -5,
offset: const Offset(0, 5),
Widget _buildCard(
BuildContext context,
String title,
String content,
IconData icon,
) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: Theme.of(context).colorScheme.primary,
size: 30,
),
],
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 左侧图标 - 适配深色模式
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: iconColor.withOpacity(isDarkMode ? 0.2 : 0.15),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: iconColor.withOpacity(isDarkMode ? 0.15 : 0.2),
blurRadius: isDarkMode ? 6 : 8,
spreadRadius: isDarkMode ? 0 : 1,
),
],
gradient: RadialGradient(
colors: [
iconColor.withOpacity(isDarkMode ? 0.8 : 0.7),
iconColor.withOpacity(isDarkMode ? 0.2 : 0.1),
],
stops: const [0.0, 1.0],
radius: 0.8,
),
),
child: Icon(
icon,
color: Colors.white,
size: 30,
),
),
const SizedBox(width: 16),
// 右侧内容 - 适配深色模式
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
color:
iconColor.withOpacity(isDarkMode ? 0.9 : 0.8),
),
),
const SizedBox(height: 8),
Divider(
color: iconColor.withOpacity(isDarkMode ? 0.4 : 0.3),
thickness: 1.5,
endIndent: 60,
),
const SizedBox(height: 8),
Text(
content,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
height: 1.5,
letterSpacing: 0.5,
color: textColor
?.withOpacity(isDarkMode ? 0.9 : 1.0),
),
),
],
),
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
Text(
content,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
],
),
),
);
+44 -127
View File
@@ -52,8 +52,7 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage> {
// 定义状态枚举
int connectionTimeoutCounter = 0;
// 当前连接状态
ConnectionState _connectionState = ConnectionState.notStarted;
@@ -160,11 +159,19 @@ class _HomePageState extends State<HomePage> {
@override
void dispose() {
// 释放资源
// 取消所有计时器
timer?.cancel();
timer = null;
// 释放所有控制器和焦点节点
_roomNameController.dispose();
_roomPasswordController.dispose();
_usernameController.dispose();
_virtualIPController.dispose();
_virtualIPFocusNode.removeListener(_onVirtualIPFocusChange);
_virtualIPFocusNode.dispose();
// 释放新增的FocusNode资源
_usernameControllerFocusNode.removeListener(_onUsernameFocusChange);
_usernameControllerFocusNode.dispose();
@@ -191,8 +198,6 @@ class _HomePageState extends State<HomePage> {
roomName: roomName,
roomPassword: roomPassword,
severurl: Serverip);
// 添加连接超时计数器
int connectionTimeoutCounter = 0;
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
@@ -216,49 +221,40 @@ class _HomePageState extends State<HomePage> {
final int? version = runin.myNodeInfo?.virtualIpv4?.address?.addr;
if (version != null) {
String ipStr = _intToIpv4String(version);
// 检查IP不为0.0.0.0时认为连接成功
if (ipStr != "0.0.0.0") {
if (publicIP != ipStr) {
km.virtualIP = ipStr;
}
if (publicIP != ipStr) {
km.virtualIP = ipStr;
}
// 如果当前状态还是连接中,则更新为已连接
if (_connectionState == ConnectionState.connecting) {
setState(() {
_connectionState = ConnectionState.connected;
});
}
// 重置超时计数器
// 如果当前状态还是连接中,则更新为已连接
if (_connectionState == ConnectionState.connecting) {
setState(() {
_connectionState = ConnectionState.connected;
});
}
connectionTimeoutCounter = 0;
} else if (_connectionState == ConnectionState.connecting) {
connectionTimeoutCounter++;
if (connectionTimeoutCounter >= 10) {
connectionTimeoutCounter = 0;
} else if (_connectionState == ConnectionState.connecting) {
// 只在连接状态下增加超时计数
connectionTimeoutCounter++;
timer.cancel();
setState(() {
isRunning = false;
_connectionState = ConnectionState.notStarted;
runningTime = Duration.zero;
});
// 如果连续10秒都是0.0.0.0,判断为连接失败
if (connectionTimeoutCounter >= 10) {
// 停止连接并显示失败消息
timer.cancel();
setState(() {
isRunning = false;
_connectionState = ConnectionState.notStarted;
runningTime = Duration.zero;
});
// 关闭服务器连接
closeAllServer();
// 显示连接失败提示
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('连接失败,请检查网络或房间信息后重试'),
duration: Duration(seconds: 3),
),
);
}
return;
closeAllServer();
// 清空玩家列表数据
Provider.of<KM>(context, listen: false).nodes = [];
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('连接失败,未能获取节点信息'),
duration: Duration(seconds: 3),
),
);
}
return;
}
}
@@ -277,6 +273,8 @@ class _HomePageState extends State<HomePage> {
// 停止时重置状态
_connectionState = ConnectionState.notStarted;
closeAllServer();
// 清空玩家列表数据
Provider.of<KM>(context, listen: false).nodes = [];
timer?.cancel();
runningTime = Duration.zero;
// 重置网络统计数据
@@ -286,6 +284,7 @@ class _HomePageState extends State<HomePage> {
_lastDownloadBytes = 0;
uploadSpeed = 0;
downloadSpeed = 0;
//connectionTimeoutCounter
}
});
}
@@ -549,88 +548,6 @@ class _HomePageState extends State<HomePage> {
}
}
// 修改卡片构建方法,移除多余的内边距
Widget _buildDashboardCard(ColorScheme colorScheme) {
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600, // 设置最大宽度
height: 200,
child: SizedBox(
child: PieChart(
PieChartData(
sections: [
PieChartSectionData(
value: uploadSpeed,
title: '上传',
color: colorScheme.primary,
),
PieChartSectionData(
value: downloadSpeed,
title: '下载',
color: colorScheme.secondary,
),
],
),
),
));
}
// 修改流量统计卡片,移除多余的内边距
Widget _buildTrafficCard(ColorScheme colorScheme) {
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 修改标题为图标+文字组合
Row(
children: [
Icon(Icons.data_usage, color: colorScheme.primary, size: 22),
const SizedBox(width: 8),
const Text('流量统计',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTrafficInfo('上传速度', '$uploadSpeed MB/s', Icons.upload,
colorScheme.primary),
_buildTrafficInfo('下载速度', '$downloadSpeed MB/s', Icons.download,
colorScheme.secondary),
],
),
],
));
}
// 修改IP地址卡片,移除多余的内边距
Widget _buildIPCard(ColorScheme colorScheme) {
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 修改标题为图标+文字组合
Row(
children: [
Icon(Icons.wifi, color: colorScheme.primary, size: 22),
const SizedBox(width: 8),
const Text('网络信息',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 16),
_buildIPInfo('虚拟 IP', publicIP, Icons.public, colorScheme),
],
),
);
}
Widget _buildTrafficInfo(
String label, String value, IconData icon, Color color) {
return Column(
+58 -64
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:astral/utils/app_info.dart';
import 'package:astral/utils/up.dart';
import 'package:flutter/material.dart';
@@ -25,11 +27,11 @@ class _SettingsPageState extends State<SettingsPage> {
late String _currentServer;
final _appConfig = AppConfig();
bool _closeToTray = false; // 添加关闭进入托盘变量
bool _pingEnabled = true; // 添加全局ping开关
String serverIP = "";
// 添加 ping 相关状态
Map<String, int?> pingResults = {};
Map<String, bool> isPinging = {};
@override
void initState() {
@@ -42,55 +44,67 @@ class _SettingsPageState extends State<SettingsPage> {
// 初始化 ping 状态
for (var server in _serverList) {
pingResults[server] = null;
isPinging[server] = true; // 默认所有服务器都开启ping
}
// 开始 ping 所有服务器
for (var server in _serverList) {
_pingServer(server);
}
_startPingAllServers();
}
// 添加一个计时器变量来控制 ping 操作
Timer? _pingTimer;
// 新增方法:开始 ping 所有服务器
void _startPingAllServers() {
// 取消之前的计时器(如果存在)
_pingTimer?.cancel();
// 创建新的计时器,每秒执行一次 ping
_pingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) {
timer.cancel();
return;
}
if (_pingEnabled) {
for (var server in _serverList) {
_pingServerOnce(server);
}
}
});
}
@override
void dispose() {
// 停止所有 ping
for (var server in _serverList) {
isPinging[server] = false;
}
// 取消计时器
_pingTimer?.cancel();
_pingTimer = null;
super.dispose();
}
// 简化 ping 方法,移除强制持续 ping 参数
void _startPingServer(String server) {
if (isPinging[server] == true) return;
isPinging[server] = true;
_pingServer(server);
}
// 修改停止 ping 方法 - 实际上不再需要,但保留以防将来需要
void _stopPingServer(String server) {
isPinging[server] = false;
}
// 执行 ping 操作
Future<void> _pingServer(String server) async {
if (isPinging[server] != true) return;
// 执行单次 ping 操作
Future<void> _pingServerOnce(String server) async {
final pingResult = await PingUtil.ping(server);
if (mounted) {
setState(() {
pingResults[server] = pingResult;
});
// 1秒后再次 ping
Future.delayed(const Duration(seconds: 1), () {
_pingServer(server);
});
}
}
// 切换全局ping状态
void _togglePingStatus(bool value) {
setState(() {
_pingEnabled = value;
if (!_pingEnabled) {
// 如果关闭ping,清空所有结果
for (var server in _serverList) {
pingResults[server] = null;
}
}
});
}
// 添加服务器对话框
Future<void> _showAddServerDialog() async {
final controller = TextEditingController();
@@ -123,10 +137,8 @@ class _SettingsPageState extends State<SettingsPage> {
_serverList.add(result);
_appConfig.setServerList(_serverList);
// 初始化新服务器的 ping 状态并立即开始 ping
// 初始化新服务器的 ping 状态
pingResults[result] = null;
isPinging[result] = true;
_pingServer(result);
});
}
}
@@ -189,21 +201,16 @@ class _SettingsPageState extends State<SettingsPage> {
if (confirm == true) {
final server = _serverList[index];
// 停止 ping
_stopPingServer(server);
setState(() {
_serverList.removeAt(index);
_appConfig.setServerList(_serverList);
// 移除 ping 状态
pingResults.remove(server);
isPinging.remove(server);
if (_currentServer == server && _serverList.isNotEmpty) {
_currentServer = _serverList[0];
_appConfig.setCurrentServer(_currentServer);
_startPingServer(_currentServer);
}
});
}
@@ -212,7 +219,9 @@ class _SettingsPageState extends State<SettingsPage> {
// 添加构建 ping 显示组件的方法
Widget _buildPingWidget(String server) {
final pingResult = pingResults[server];
if (pingResult == null) {
if (!_pingEnabled) {
return const Text('Ping已关闭', style: TextStyle(color: Colors.grey));
} else if (pingResult == null) {
return const Text('测试中...', style: TextStyle(color: Colors.grey));
} else {
return Text(
@@ -250,9 +259,6 @@ class _SettingsPageState extends State<SettingsPage> {
ExpansionTile(
leading: const Icon(Icons.list),
title: const Text('服务器列表'),
onExpansionChanged: (expanded) {
// 不再需要处理展开折叠时的 ping 状态,所有服务器都持续 ping
},
children: [
// 在服务器列表前添加当前服务器的 ping 状态显示
ListView.builder(
@@ -261,22 +267,6 @@ class _SettingsPageState extends State<SettingsPage> {
itemCount: _serverList.length,
itemBuilder: (context, index) {
final server = _serverList[index];
final pingResult = pingResults[server];
// 构建延迟显示组件
Widget pingWidget;
if (pingResult == null) {
pingWidget = const Text('测试中...',
style: TextStyle(color: Colors.grey));
} else {
pingWidget = Text('${pingResult}ms',
style: TextStyle(
color: pingResult < 100
? Colors.green
: (pingResult < 300
? Colors.orange
: Colors.red)));
}
return ListTile(
leading: const Icon(Icons.computer),
@@ -284,14 +274,13 @@ class _SettingsPageState extends State<SettingsPage> {
children: [
Text('服务器 ${index + 1}'),
const SizedBox(width: 8),
pingWidget,
_buildPingWidget(server),
],
),
subtitle: Text(server),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 移除 ping 按钮,只保留编辑和删除按钮
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _showEditServerDialog(index),
@@ -305,9 +294,8 @@ class _SettingsPageState extends State<SettingsPage> {
onTap: () {
setState(() {
_currentServer = server;
Provider.of<KM>(context, listen: false).serverIP =
server;
// 不再需要特别开始 ping 新选择的服务器,因为所有服务器都在 ping
Provider.of<KM>(context, listen: false).serverIP = server;
_appConfig.setCurrentServer(_currentServer);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已切换到服务器: $server')),
@@ -347,6 +335,12 @@ class _SettingsPageState extends State<SettingsPage> {
});
},
),
SwitchListTile(
title: const Text('启用服务器Ping测试'),
subtitle: const Text('定期测试所有服务器的网络延迟'),
value: _pingEnabled,
onChanged: _togglePingStatus,
),
],
),
),
+2 -78
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'dart:math' as math;
class FloatingCard extends StatefulWidget {
final ColorScheme colorScheme;
@@ -8,14 +7,8 @@ class FloatingCard extends StatefulWidget {
final EdgeInsetsGeometry padding;
final Duration duration;
final double hoverElevation;
final double? maxWidth; // 添加最大宽度参数
final double? height; // 添加最大宽度参数
final bool enable3DEffect; // 是否启用3D效果
final double maxRotationDegree; // 最大旋转角度
final bool enableTranslateEffect; // 是否启用偏移效果
final double maxTranslateDistance; // 最大偏移距离
final double zTranslation; // Z轴偏移距离
final bool riseOnHover; // 控制悬浮时是升起还是降下
final double? maxWidth;
final double? height;
const FloatingCard({
super.key,
@@ -27,12 +20,6 @@ class FloatingCard extends StatefulWidget {
this.hoverElevation = 8,
this.maxWidth,
this.height,
this.enable3DEffect = true, // 默认启用3D效果
this.maxRotationDegree = 10, // 默认最大旋转角度为10度
this.enableTranslateEffect = true, // 默认启用偏移效果
this.maxTranslateDistance = 5, // 默认最大偏移距离为5
this.zTranslation = 10, // 默认Z轴偏移距离为20
this.riseOnHover = true, // 默认悬浮时升起
});
@override
@@ -41,67 +28,12 @@ class FloatingCard extends StatefulWidget {
class _FloatingCardState extends State<FloatingCard> {
bool isHovered = false;
Offset mousePosition = Offset.zero;
final GlobalKey _cardKey = GlobalKey();
// 获取卡片的尺寸和位置
Rect? _getCardRect() {
final RenderBox? renderBox =
_cardKey.currentContext?.findRenderObject() as RenderBox?;
if (renderBox == null) return null;
final position = renderBox.localToGlobal(Offset.zero);
return Rect.fromLTWH(
position.dx, position.dy, renderBox.size.width, renderBox.size.height);
}
// 计算旋转角度
(double, double) _calculateRotation() {
final rect = _getCardRect();
if (rect == null) return (0, 0);
// 计算鼠标相对于卡片中心的位置
final centerX = rect.width / 2;
final centerY = rect.height / 2;
final deltaX = (mousePosition.dx - centerX) / centerX;
final deltaY = (mousePosition.dy - centerY) / centerY;
// 计算旋转角度,鼠标在右侧时向左倾斜(Y轴正向旋转),鼠标在下方时向上倾斜(X轴负向旋转)
final rotateY = deltaX * widget.maxRotationDegree;
final rotateX = -deltaY * widget.maxRotationDegree;
return (rotateX, rotateY);
}
@override
Widget build(BuildContext context) {
// 根据是否启用3D效果计算旋转角度
final (rotateX, rotateY) =
isHovered && widget.enable3DEffect ? _calculateRotation() : (0.0, 0.0);
// 根据是否启用偏移效果计算偏移距离
final translateX = isHovered && widget.enableTranslateEffect
? rotateY * widget.maxTranslateDistance
: 0.0;
final translateY = isHovered && widget.enableTranslateEffect
? rotateX * widget.maxTranslateDistance
: 0.0;
// 根据riseOnHover决定Z轴偏移方向
final zDirection = widget.riseOnHover ? 1.0 : -1.0;
final translateZ = isHovered && widget.enableTranslateEffect
? widget.zTranslation * zDirection
: 0.0;
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
onHover: (event) {
if (widget.enable3DEffect || widget.enableTranslateEffect) {
setState(() {
mousePosition = event.localPosition;
});
}
},
cursor: SystemMouseCursors.click,
child: Center(
child: ConstrainedBox(
@@ -109,15 +41,7 @@ class _FloatingCardState extends State<FloatingCard> {
maxWidth: widget.maxWidth ?? double.infinity,
),
child: AnimatedContainer(
key: _cardKey,
duration: widget.duration,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001) // 透视效果
// 仅当启用3D效果时应用旋转
..rotateX(widget.enable3DEffect ? rotateX * math.pi / 180 : 0)
..rotateY(widget.enable3DEffect ? rotateY * math.pi / 180 : 0)
// 仅当启用偏移效果时应用偏移
..translate(translateX, translateY, translateZ),
transformAlignment: Alignment.center,
child: Card(
shape: RoundedRectangleBorder(
+188 -175
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -42,7 +42,6 @@ dependencies:
flutter_rust_bridge: 2.9.0
flutter_colorpicker: ^1.1.0
window_manager: ^0.4.3
fl_chart: ^0.70.2
flutter_staggered_grid_view: ^0.7.0
json_annotation: ^4.8.1
tray_manager: ^0.3.2
@@ -52,6 +51,7 @@ dependencies:
yaml: ^3.1.2
flutter_local_notifications: ^19.0.0
windows_notification: ^1.3.0
fl_chart: ^0.70.2
dev_dependencies:
flutter_test:
+12 -8
View File
@@ -581,15 +581,19 @@ pub fn create_server(
cfg.set_hostname(Option::from(username));
cfg.set_dhcp(enable_dhcp);
let mut flags = cfg.get_flags();
flags.dev_name = "Astral".to_string();
// flags.dev_name = "astral".to_string();
cfg.set_flags(flags);
let peer_config = PeerConfig {
uri: ("tcp://".to_string() + &severurl).parse().unwrap(),
};
let peer_config2 = PeerConfig {
uri: ("udp://".to_string() + &severurl).parse().unwrap(),
};
cfg.set_peers(vec![peer_config, peer_config2]);
// 创建TCP和UDP连接配置列表
let peer_configs = vec![
PeerConfig {
uri: format!("tcp://{}", severurl).parse().unwrap(),
},
PeerConfig {
uri: format!("udp://{}", severurl).parse().unwrap(),
}
];
cfg.set_peers(peer_configs);
if enable_dhcp == false {
// 使用完整路径引用 cidr 模块的 Ipv4Inet
// 解析IP地址和子网掩码
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

-38
View File
@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="astral">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>astral</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
-35
View File
@@ -1,35 +0,0 @@
{
"name": "astral",
"short_name": "astral",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
-4
View File
@@ -75,10 +75,6 @@ set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
# 添加管理员权限要求(修正路径格式)
target_link_options(${BINARY_NAME} PRIVATE
"LINKER:/MANIFESTUAC:level='requireAdministrator' uiAccess='false'"
$<$<C_COMPILER_ID:MSVC>:"LINKER:/MANIFESTINPUT:${CMAKE_CURRENT_SOURCE_DIR}/runner/runner.exe.manifest">)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
+1 -1
View File
@@ -26,7 +26,7 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTT
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
SET_TARGET_PROPERTIES(${BINARY_NAME} PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:WINDOWS")
# Disable Windows macros that collide with C++ standard library functions.
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+11 -7
View File
@@ -1,12 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
@@ -18,4 +12,14 @@
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="requireAdministrator"
uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>