mirror of
https://github.com/EasyTier/astral.git
synced 2025-05-19 10:30:24 +00:00
修复内存泄漏
This commit is contained in:
@@ -15,9 +15,6 @@ migration:
|
|||||||
- platform: root
|
- platform: root
|
||||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||||
- platform: web
|
|
||||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
|
||||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
Vendored
+6
@@ -4,6 +4,12 @@
|
|||||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Flutter",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "profile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Flutter Run",
|
"name": "Flutter Run",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'dart:io';
|
|||||||
import 'screens/Home.dart';
|
import 'screens/Home.dart';
|
||||||
import 'config/themeconfiguration.dart';
|
import 'config/themeconfiguration.dart';
|
||||||
import 'config/app_config.dart';
|
import 'config/app_config.dart';
|
||||||
import 'package:astral/utils/up.dart'; // 添加这行导入
|
|
||||||
|
|
||||||
// 定义应用程序的主要StatefulWidget
|
// 定义应用程序的主要StatefulWidget
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatefulWidget {
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ class NavigationConfig {
|
|||||||
currentThemeMode: currentThemeMode,
|
currentThemeMode: currentThemeMode,
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
NavItem(
|
// NavItem(
|
||||||
label: '房间',
|
// label: '房间',
|
||||||
icon: Icons.room_outlined,
|
// icon: Icons.room_outlined,
|
||||||
selectedIcon: Icons.room,
|
// selectedIcon: Icons.room,
|
||||||
pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
|
// pageBuilder: () => _getOrCreatePage(1, () => const RoomPage()),
|
||||||
),
|
// ),
|
||||||
NavItem(
|
NavItem(
|
||||||
label: '设置',
|
label: '设置',
|
||||||
icon: Icons.settings_outlined,
|
icon: Icons.settings_outlined,
|
||||||
|
|||||||
+118
-277
@@ -2,35 +2,11 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'package:astral/utils/app_info.dart';
|
import 'package:astral/utils/app_info.dart';
|
||||||
|
|
||||||
class InfoPage extends StatefulWidget {
|
class InfoPage extends StatelessWidget {
|
||||||
const InfoPage({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
@@ -40,156 +16,95 @@ class _InfoPageState extends State<InfoPage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// 旋转的图标效果
|
// 静态图标
|
||||||
AnimatedBuilder(
|
Container(
|
||||||
animation: _controller,
|
height: 110,
|
||||||
builder: (_, child) {
|
width: 110,
|
||||||
return Transform.rotate(
|
decoration: BoxDecoration(
|
||||||
angle: _controller.value * 2 * math.pi,
|
shape: BoxShape.circle,
|
||||||
child: Container(
|
color: Theme.of(context).colorScheme.primary,
|
||||||
height: 110,
|
boxShadow: [
|
||||||
width: 110,
|
BoxShadow(
|
||||||
decoration: BoxDecoration(
|
color: Theme.of(context)
|
||||||
shape: BoxShape.circle,
|
.colorScheme
|
||||||
gradient: SweepGradient(
|
.primary
|
||||||
colors: [
|
.withOpacity(0.5),
|
||||||
Theme.of(context).colorScheme.primary,
|
blurRadius: 15,
|
||||||
Theme.of(context).colorScheme.secondary,
|
spreadRadius: 1,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.games,
|
||||||
|
size: 50,
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// 应用名称添加渐变效果
|
// 应用名称
|
||||||
ShaderMask(
|
Text(
|
||||||
shaderCallback: (bounds) => LinearGradient(
|
'ASTRAL',
|
||||||
colors: [
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
Theme.of(context).colorScheme.primary,
|
fontWeight: FontWeight.bold,
|
||||||
Theme.of(context).colorScheme.tertiary,
|
letterSpacing: 2.0,
|
||||||
],
|
color: Theme.of(context).colorScheme.primary,
|
||||||
).createShader(bounds),
|
),
|
||||||
child: Text(
|
|
||||||
'ASTRAL',
|
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 2.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// 版本号添加动画效果
|
// 版本号
|
||||||
TweenAnimationBuilder<double>(
|
Text(
|
||||||
tween: Tween<double>(begin: 0, end: 1),
|
AppInfoUtil.getVersion(),
|
||||||
duration: const Duration(milliseconds: 800),
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// 卡片添加动画和阴影效果
|
// 静态卡片
|
||||||
_buildAnimatedCard(
|
_buildCard(
|
||||||
context,
|
context,
|
||||||
'特别鸣谢',
|
'特别鸣谢',
|
||||||
'特别感谢EasyTier作者所做的工作和帮助,为本项目提供了重要的技术支持。如果您有功能需求或遇到bug,欢迎加入我们的QQ群获取帮助和了解最新动态。',
|
'特别感谢EasyTier作者所做的工作和帮助,为本项目提供了重要的技术支持。如果您有功能需求或遇到bug,欢迎加入我们的QQ群获取帮助和了解最新动态。',
|
||||||
Icons.favorite,
|
Icons.favorite,
|
||||||
Colors.red,
|
|
||||||
delay: 200,
|
|
||||||
),
|
),
|
||||||
// 合并后的卡片
|
// 合并后的卡片
|
||||||
|
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
// 按钮添加动画效果
|
// 静态按钮
|
||||||
TweenAnimationBuilder<double>(
|
ElevatedButton.icon(
|
||||||
tween: Tween<double>(begin: 0, end: 1),
|
icon: const Icon(Icons.group_add),
|
||||||
duration: const Duration(milliseconds: 1000),
|
label: const Text('加入QQ群'),
|
||||||
curve: Curves.elasticOut,
|
style: ElevatedButton.styleFrom(
|
||||||
builder: (context, value, child) {
|
padding:
|
||||||
return Transform.scale(
|
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
scale: value,
|
shape: RoundedRectangleBorder(
|
||||||
child: child,
|
borderRadius: BorderRadius.circular(30),
|
||||||
);
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
elevation: 5,
|
||||||
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群链接')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
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),
|
const SizedBox(height: 20),
|
||||||
// 添加版权信息
|
// 版权信息
|
||||||
TweenAnimationBuilder<double>(
|
Text(
|
||||||
tween: Tween<double>(begin: 0, end: 1),
|
'© ${DateTime.now().year} ASTRAL Team',
|
||||||
duration: const Duration(milliseconds: 1200),
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
builder: (context, value, child) {
|
color: Theme.of(context)
|
||||||
return Opacity(
|
.colorScheme
|
||||||
opacity: value,
|
.onSurface
|
||||||
child: child,
|
.withOpacity(0.6),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
child: 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,
|
Widget _buildCard(
|
||||||
IconData icon, Color iconColor,
|
BuildContext context,
|
||||||
{int delay = 0}) {
|
String title,
|
||||||
// 根据当前主题调整颜色
|
String content,
|
||||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
IconData icon,
|
||||||
final cardColor = Theme.of(context).cardColor;
|
) {
|
||||||
final textColor = Theme.of(context).textTheme.bodyMedium?.color;
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
return TweenAnimationBuilder<double>(
|
shape: RoundedRectangleBorder(
|
||||||
tween: Tween<double>(begin: 0, end: 1),
|
borderRadius: BorderRadius.circular(16),
|
||||||
duration: Duration(milliseconds: 800 + delay),
|
),
|
||||||
curve: Curves.easeOutBack,
|
child: Padding(
|
||||||
builder: (context, value, child) {
|
padding: const EdgeInsets.all(16.0),
|
||||||
// 确保 opacity 值在有效范围内 (0.0 到 1.0)
|
child: Row(
|
||||||
final safeOpacity = value.clamp(0.0, 1.0);
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
return Opacity(
|
children: [
|
||||||
opacity: safeOpacity,
|
Container(
|
||||||
child: Transform.translate(
|
padding: const EdgeInsets.all(12),
|
||||||
offset: Offset(100 * (1 - value), 0),
|
decoration: BoxDecoration(
|
||||||
child: child,
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
),
|
shape: BoxShape.circle,
|
||||||
);
|
),
|
||||||
},
|
child: Icon(
|
||||||
child: Card(
|
icon,
|
||||||
elevation: 8,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
shadowColor: iconColor.withOpacity(isDarkMode ? 0.3 : 0.4),
|
size: 30,
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
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
@@ -52,8 +52,7 @@ class HomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
// 定义状态枚举
|
int connectionTimeoutCounter = 0;
|
||||||
|
|
||||||
// 当前连接状态
|
// 当前连接状态
|
||||||
ConnectionState _connectionState = ConnectionState.notStarted;
|
ConnectionState _connectionState = ConnectionState.notStarted;
|
||||||
|
|
||||||
@@ -160,11 +159,19 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// 释放资源
|
// 取消所有计时器
|
||||||
|
timer?.cancel();
|
||||||
|
timer = null;
|
||||||
|
|
||||||
|
// 释放所有控制器和焦点节点
|
||||||
|
_roomNameController.dispose();
|
||||||
|
_roomPasswordController.dispose();
|
||||||
|
_usernameController.dispose();
|
||||||
|
_virtualIPController.dispose();
|
||||||
|
|
||||||
_virtualIPFocusNode.removeListener(_onVirtualIPFocusChange);
|
_virtualIPFocusNode.removeListener(_onVirtualIPFocusChange);
|
||||||
_virtualIPFocusNode.dispose();
|
_virtualIPFocusNode.dispose();
|
||||||
|
|
||||||
// 释放新增的FocusNode资源
|
|
||||||
_usernameControllerFocusNode.removeListener(_onUsernameFocusChange);
|
_usernameControllerFocusNode.removeListener(_onUsernameFocusChange);
|
||||||
_usernameControllerFocusNode.dispose();
|
_usernameControllerFocusNode.dispose();
|
||||||
|
|
||||||
@@ -191,8 +198,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
roomName: roomName,
|
roomName: roomName,
|
||||||
roomPassword: roomPassword,
|
roomPassword: roomPassword,
|
||||||
severurl: Serverip);
|
severurl: Serverip);
|
||||||
// 添加连接超时计数器
|
|
||||||
int connectionTimeoutCounter = 0;
|
|
||||||
|
|
||||||
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
|
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
|
||||||
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
|
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;
|
final int? version = runin.myNodeInfo?.virtualIpv4?.address?.addr;
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
String ipStr = _intToIpv4String(version);
|
String ipStr = _intToIpv4String(version);
|
||||||
// 检查IP不为0.0.0.0时认为连接成功
|
if (publicIP != ipStr) {
|
||||||
if (ipStr != "0.0.0.0") {
|
km.virtualIP = ipStr;
|
||||||
if (publicIP != ipStr) {
|
}
|
||||||
km.virtualIP = ipStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前状态还是连接中,则更新为已连接
|
// 如果当前状态还是连接中,则更新为已连接
|
||||||
if (_connectionState == ConnectionState.connecting) {
|
if (_connectionState == ConnectionState.connecting) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_connectionState = ConnectionState.connected;
|
_connectionState = ConnectionState.connected;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
connectionTimeoutCounter = 0;
|
||||||
// 重置超时计数器
|
} else if (_connectionState == ConnectionState.connecting) {
|
||||||
|
connectionTimeoutCounter++;
|
||||||
|
if (connectionTimeoutCounter >= 10) {
|
||||||
connectionTimeoutCounter = 0;
|
connectionTimeoutCounter = 0;
|
||||||
} else if (_connectionState == ConnectionState.connecting) {
|
timer.cancel();
|
||||||
// 只在连接状态下增加超时计数
|
setState(() {
|
||||||
connectionTimeoutCounter++;
|
isRunning = false;
|
||||||
|
_connectionState = ConnectionState.notStarted;
|
||||||
|
runningTime = Duration.zero;
|
||||||
|
});
|
||||||
|
|
||||||
// 如果连续10秒都是0.0.0.0,判断为连接失败
|
closeAllServer();
|
||||||
if (connectionTimeoutCounter >= 10) {
|
// 清空玩家列表数据
|
||||||
// 停止连接并显示失败消息
|
Provider.of<KM>(context, listen: false).nodes = [];
|
||||||
timer.cancel();
|
if (mounted) {
|
||||||
setState(() {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
isRunning = false;
|
const SnackBar(
|
||||||
_connectionState = ConnectionState.notStarted;
|
content: Text('连接失败,未能获取节点信息'),
|
||||||
runningTime = Duration.zero;
|
duration: Duration(seconds: 3),
|
||||||
});
|
),
|
||||||
|
);
|
||||||
// 关闭服务器连接
|
|
||||||
closeAllServer();
|
|
||||||
|
|
||||||
// 显示连接失败提示
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('连接失败,请检查网络或房间信息后重试'),
|
|
||||||
duration: Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +273,8 @@ class _HomePageState extends State<HomePage> {
|
|||||||
// 停止时重置状态
|
// 停止时重置状态
|
||||||
_connectionState = ConnectionState.notStarted;
|
_connectionState = ConnectionState.notStarted;
|
||||||
closeAllServer();
|
closeAllServer();
|
||||||
|
// 清空玩家列表数据
|
||||||
|
Provider.of<KM>(context, listen: false).nodes = [];
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
runningTime = Duration.zero;
|
runningTime = Duration.zero;
|
||||||
// 重置网络统计数据
|
// 重置网络统计数据
|
||||||
@@ -286,6 +284,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
_lastDownloadBytes = 0;
|
_lastDownloadBytes = 0;
|
||||||
uploadSpeed = 0;
|
uploadSpeed = 0;
|
||||||
downloadSpeed = 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(
|
Widget _buildTrafficInfo(
|
||||||
String label, String value, IconData icon, Color color) {
|
String label, String value, IconData icon, Color color) {
|
||||||
return Column(
|
return Column(
|
||||||
|
|||||||
+59
-65
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:astral/utils/app_info.dart';
|
import 'package:astral/utils/app_info.dart';
|
||||||
import 'package:astral/utils/up.dart';
|
import 'package:astral/utils/up.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -25,11 +27,11 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
late String _currentServer;
|
late String _currentServer;
|
||||||
final _appConfig = AppConfig();
|
final _appConfig = AppConfig();
|
||||||
bool _closeToTray = false; // 添加关闭进入托盘变量
|
bool _closeToTray = false; // 添加关闭进入托盘变量
|
||||||
|
bool _pingEnabled = true; // 添加全局ping开关
|
||||||
|
|
||||||
String serverIP = "";
|
String serverIP = "";
|
||||||
// 添加 ping 相关状态
|
// 添加 ping 相关状态
|
||||||
Map<String, int?> pingResults = {};
|
Map<String, int?> pingResults = {};
|
||||||
Map<String, bool> isPinging = {};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -42,55 +44,67 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
// 初始化 ping 状态
|
// 初始化 ping 状态
|
||||||
for (var server in _serverList) {
|
for (var server in _serverList) {
|
||||||
pingResults[server] = null;
|
pingResults[server] = null;
|
||||||
isPinging[server] = true; // 默认所有服务器都开启ping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始 ping 所有服务器
|
// 开始 ping 所有服务器
|
||||||
for (var server in _serverList) {
|
_startPingAllServers();
|
||||||
_pingServer(server);
|
}
|
||||||
}
|
|
||||||
|
// 添加一个计时器变量来控制 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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// 停止所有 ping
|
// 取消计时器
|
||||||
for (var server in _serverList) {
|
_pingTimer?.cancel();
|
||||||
isPinging[server] = false;
|
_pingTimer = null;
|
||||||
}
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简化 ping 方法,移除强制持续 ping 参数
|
// 执行单次 ping 操作
|
||||||
void _startPingServer(String server) {
|
Future<void> _pingServerOnce(String server) async {
|
||||||
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;
|
|
||||||
|
|
||||||
final pingResult = await PingUtil.ping(server);
|
final pingResult = await PingUtil.ping(server);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pingResults[server] = pingResult;
|
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 {
|
Future<void> _showAddServerDialog() async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
@@ -123,14 +137,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
_serverList.add(result);
|
_serverList.add(result);
|
||||||
_appConfig.setServerList(_serverList);
|
_appConfig.setServerList(_serverList);
|
||||||
|
|
||||||
// 初始化新服务器的 ping 状态并立即开始 ping
|
// 初始化新服务器的 ping 状态
|
||||||
pingResults[result] = null;
|
pingResults[result] = null;
|
||||||
isPinging[result] = true;
|
|
||||||
_pingServer(result);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑服务器对话框
|
// 编辑服务器对话框
|
||||||
Future<void> _showEditServerDialog(int index) async {
|
Future<void> _showEditServerDialog(int index) async {
|
||||||
final controller = TextEditingController(text: _serverList[index]);
|
final controller = TextEditingController(text: _serverList[index]);
|
||||||
@@ -189,21 +201,16 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
if (confirm == true) {
|
if (confirm == true) {
|
||||||
final server = _serverList[index];
|
final server = _serverList[index];
|
||||||
|
|
||||||
// 停止 ping
|
|
||||||
_stopPingServer(server);
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_serverList.removeAt(index);
|
_serverList.removeAt(index);
|
||||||
_appConfig.setServerList(_serverList);
|
_appConfig.setServerList(_serverList);
|
||||||
|
|
||||||
// 移除 ping 状态
|
// 移除 ping 状态
|
||||||
pingResults.remove(server);
|
pingResults.remove(server);
|
||||||
isPinging.remove(server);
|
|
||||||
|
|
||||||
if (_currentServer == server && _serverList.isNotEmpty) {
|
if (_currentServer == server && _serverList.isNotEmpty) {
|
||||||
_currentServer = _serverList[0];
|
_currentServer = _serverList[0];
|
||||||
_appConfig.setCurrentServer(_currentServer);
|
_appConfig.setCurrentServer(_currentServer);
|
||||||
_startPingServer(_currentServer);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -212,7 +219,9 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
// 添加构建 ping 显示组件的方法
|
// 添加构建 ping 显示组件的方法
|
||||||
Widget _buildPingWidget(String server) {
|
Widget _buildPingWidget(String server) {
|
||||||
final pingResult = pingResults[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));
|
return const Text('测试中...', style: TextStyle(color: Colors.grey));
|
||||||
} else {
|
} else {
|
||||||
return Text(
|
return Text(
|
||||||
@@ -250,9 +259,6 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
ExpansionTile(
|
ExpansionTile(
|
||||||
leading: const Icon(Icons.list),
|
leading: const Icon(Icons.list),
|
||||||
title: const Text('服务器列表'),
|
title: const Text('服务器列表'),
|
||||||
onExpansionChanged: (expanded) {
|
|
||||||
// 不再需要处理展开折叠时的 ping 状态,所有服务器都持续 ping
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
// 在服务器列表前添加当前服务器的 ping 状态显示
|
// 在服务器列表前添加当前服务器的 ping 状态显示
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
@@ -261,22 +267,6 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
itemCount: _serverList.length,
|
itemCount: _serverList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final server = _serverList[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(
|
return ListTile(
|
||||||
leading: const Icon(Icons.computer),
|
leading: const Icon(Icons.computer),
|
||||||
@@ -284,14 +274,13 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text('服务器 ${index + 1}'),
|
Text('服务器 ${index + 1}'),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
pingWidget,
|
_buildPingWidget(server),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Text(server),
|
subtitle: Text(server),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// 移除 ping 按钮,只保留编辑和删除按钮
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
onPressed: () => _showEditServerDialog(index),
|
onPressed: () => _showEditServerDialog(index),
|
||||||
@@ -305,9 +294,8 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentServer = server;
|
_currentServer = server;
|
||||||
Provider.of<KM>(context, listen: false).serverIP =
|
Provider.of<KM>(context, listen: false).serverIP = server;
|
||||||
server;
|
_appConfig.setCurrentServer(_currentServer);
|
||||||
// 不再需要特别开始 ping 新选择的服务器,因为所有服务器都在 ping
|
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('已切换到服务器: $server')),
|
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
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
class FloatingCard extends StatefulWidget {
|
class FloatingCard extends StatefulWidget {
|
||||||
final ColorScheme colorScheme;
|
final ColorScheme colorScheme;
|
||||||
@@ -8,14 +7,8 @@ class FloatingCard extends StatefulWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
final double hoverElevation;
|
final double hoverElevation;
|
||||||
final double? maxWidth; // 添加最大宽度参数
|
final double? maxWidth;
|
||||||
final double? height; // 添加最大宽度参数
|
final double? height;
|
||||||
final bool enable3DEffect; // 是否启用3D效果
|
|
||||||
final double maxRotationDegree; // 最大旋转角度
|
|
||||||
final bool enableTranslateEffect; // 是否启用偏移效果
|
|
||||||
final double maxTranslateDistance; // 最大偏移距离
|
|
||||||
final double zTranslation; // Z轴偏移距离
|
|
||||||
final bool riseOnHover; // 控制悬浮时是升起还是降下
|
|
||||||
|
|
||||||
const FloatingCard({
|
const FloatingCard({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -27,12 +20,6 @@ class FloatingCard extends StatefulWidget {
|
|||||||
this.hoverElevation = 8,
|
this.hoverElevation = 8,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.height,
|
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
|
@override
|
||||||
@@ -41,67 +28,12 @@ class FloatingCard extends StatefulWidget {
|
|||||||
|
|
||||||
class _FloatingCardState extends State<FloatingCard> {
|
class _FloatingCardState extends State<FloatingCard> {
|
||||||
bool isHovered = false;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return MouseRegion(
|
||||||
onEnter: (_) => setState(() => isHovered = true),
|
onEnter: (_) => setState(() => isHovered = true),
|
||||||
onExit: (_) => setState(() => isHovered = false),
|
onExit: (_) => setState(() => isHovered = false),
|
||||||
onHover: (event) {
|
|
||||||
if (widget.enable3DEffect || widget.enableTranslateEffect) {
|
|
||||||
setState(() {
|
|
||||||
mousePosition = event.localPosition;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
@@ -109,15 +41,7 @@ class _FloatingCardState extends State<FloatingCard> {
|
|||||||
maxWidth: widget.maxWidth ?? double.infinity,
|
maxWidth: widget.maxWidth ?? double.infinity,
|
||||||
),
|
),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
key: _cardKey,
|
|
||||||
duration: widget.duration,
|
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,
|
transformAlignment: Alignment.center,
|
||||||
child: Card(
|
child: Card(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|||||||
+188
-175
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -42,7 +42,6 @@ dependencies:
|
|||||||
flutter_rust_bridge: 2.9.0
|
flutter_rust_bridge: 2.9.0
|
||||||
flutter_colorpicker: ^1.1.0
|
flutter_colorpicker: ^1.1.0
|
||||||
window_manager: ^0.4.3
|
window_manager: ^0.4.3
|
||||||
fl_chart: ^0.70.2
|
|
||||||
flutter_staggered_grid_view: ^0.7.0
|
flutter_staggered_grid_view: ^0.7.0
|
||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.3.2
|
||||||
@@ -52,6 +51,7 @@ dependencies:
|
|||||||
yaml: ^3.1.2
|
yaml: ^3.1.2
|
||||||
flutter_local_notifications: ^19.0.0
|
flutter_local_notifications: ^19.0.0
|
||||||
windows_notification: ^1.3.0
|
windows_notification: ^1.3.0
|
||||||
|
fl_chart: ^0.70.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
+12
-8
@@ -581,15 +581,19 @@ pub fn create_server(
|
|||||||
cfg.set_hostname(Option::from(username));
|
cfg.set_hostname(Option::from(username));
|
||||||
cfg.set_dhcp(enable_dhcp);
|
cfg.set_dhcp(enable_dhcp);
|
||||||
let mut flags = cfg.get_flags();
|
let mut flags = cfg.get_flags();
|
||||||
flags.dev_name = "Astral".to_string();
|
// flags.dev_name = "astral".to_string();
|
||||||
cfg.set_flags(flags);
|
cfg.set_flags(flags);
|
||||||
let peer_config = PeerConfig {
|
// 创建TCP和UDP连接配置列表
|
||||||
uri: ("tcp://".to_string() + &severurl).parse().unwrap(),
|
let peer_configs = vec![
|
||||||
};
|
PeerConfig {
|
||||||
let peer_config2 = PeerConfig {
|
uri: format!("tcp://{}", severurl).parse().unwrap(),
|
||||||
uri: ("udp://".to_string() + &severurl).parse().unwrap(),
|
},
|
||||||
};
|
PeerConfig {
|
||||||
cfg.set_peers(vec![peer_config, peer_config2]);
|
uri: format!("udp://{}", severurl).parse().unwrap(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
cfg.set_peers(peer_configs);
|
||||||
if enable_dhcp == false {
|
if enable_dhcp == false {
|
||||||
// 使用完整路径引用 cidr 模块的 Ipv4Inet
|
// 使用完整路径引用 cidr 模块的 Ipv4Inet
|
||||||
// 解析IP地址和子网掩码
|
// 解析IP地址和子网掩码
|
||||||
|
|||||||
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 |
@@ -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>
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -75,10 +75,6 @@ set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
|
|||||||
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
COMPONENT Runtime)
|
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}"
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
COMPONENT Runtime)
|
COMPONENT Runtime)
|
||||||
|
|||||||
@@ -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_MINOR=${FLUTTER_VERSION_MINOR}")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
|
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.
|
# Disable Windows macros that collide with C++ standard library functions.
|
||||||
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
<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">
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<windowsSettings>
|
<windowsSettings>
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
@@ -18,4 +12,14 @@
|
|||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
</application>
|
</application>
|
||||||
</compatibility>
|
</compatibility>
|
||||||
|
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel
|
||||||
|
level="requireAdministrator"
|
||||||
|
uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|||||||
Reference in New Issue
Block a user