mirror of
https://github.com/EasyTier/astral.git
synced 2025-05-19 10:30:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c72617aaf0 | ||
|
|
b1e48999c3 | ||
|
|
2f6195ecd9 | ||
|
|
4fb5387f5b |
@@ -15,9 +15,6 @@ migration:
|
||||
- platform: root
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
- platform: web
|
||||
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
Vendored
+6
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
@@ -39,17 +38,6 @@ class _MyAppState extends State<MyApp> {
|
||||
|
||||
// 初始化系统托盘
|
||||
initSystemTray();
|
||||
|
||||
// 添加版本检查(在下一帧执行确保context可用)
|
||||
Future.microtask(() {
|
||||
final updateChecker = UpdateChecker(
|
||||
owner: 'ldoubil',
|
||||
repo: 'astral',
|
||||
);
|
||||
if (mounted) {
|
||||
updateChecker.scheckForUpdates(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化系统托盘
|
||||
|
||||
@@ -44,6 +44,7 @@ class AppConfig {
|
||||
},
|
||||
'system': {
|
||||
'closeToTray': true,
|
||||
'enablePing': true, // 添加全局ping开关默认配置
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -158,4 +159,14 @@ class AppConfig {
|
||||
_configManager.set('network.dynamicIP', enabled);
|
||||
await _configManager.save();
|
||||
}
|
||||
|
||||
// 全局ping开关设置
|
||||
bool get enablePing {
|
||||
return _configManager.get<bool>('system.enablePing') ?? true;
|
||||
}
|
||||
|
||||
Future<void> setEnablePing(bool enabled) async {
|
||||
_configManager.set('system.enablePing', enabled);
|
||||
await _configManager.save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+14
-1
@@ -1,3 +1,4 @@
|
||||
import 'package:astral/utils/up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import '../widgets/window_control_buttons.dart';
|
||||
@@ -46,6 +47,17 @@ class _MainScreenState extends State<MainScreen>
|
||||
duration: const Duration(seconds: 3),
|
||||
vsync: this,
|
||||
)..repeat();
|
||||
|
||||
// 添加异步更新检查(推荐方式)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final updateChecker = UpdateChecker(
|
||||
owner: 'ldoubil',
|
||||
repo: 'astral',
|
||||
);
|
||||
if (mounted) {
|
||||
updateChecker.scheckForUpdates(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -229,7 +241,8 @@ class PageKeepAlive extends StatefulWidget {
|
||||
State<PageKeepAlive> createState() => _PageKeepAliveState();
|
||||
}
|
||||
|
||||
class _PageKeepAliveState extends State<PageKeepAlive> with AutomaticKeepAliveClientMixin {
|
||||
class _PageKeepAliveState extends State<PageKeepAlive>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
|
||||
+118
-277
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+146
-140
@@ -52,8 +52,7 @@ class HomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
// 定义状态枚举
|
||||
|
||||
int connectionTimeoutCounter = 0;
|
||||
// 当前连接状态
|
||||
ConnectionState _connectionState = ConnectionState.notStarted;
|
||||
|
||||
@@ -87,25 +86,104 @@ class _HomePageState extends State<HomePage> {
|
||||
late final TextEditingController _roomPasswordController;
|
||||
late final TextEditingController _usernameController;
|
||||
late final TextEditingController _virtualIPController;
|
||||
// 添加FocusNode来监听焦点变化
|
||||
late final FocusNode _virtualIPFocusNode;
|
||||
late final FocusNode _usernameControllerFocusNode;
|
||||
late final FocusNode _roomNameControllerFocusNode;
|
||||
late final FocusNode _roomPasswordControllerFocusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 初始化 TextEditingController
|
||||
// 初始化所有TextEditingController
|
||||
_roomNameController = TextEditingController(text: roomName);
|
||||
_roomPasswordController = TextEditingController(text: roomPassword);
|
||||
_usernameController = TextEditingController(text: username);
|
||||
_virtualIPController = TextEditingController(text: publicIP);
|
||||
_virtualIPController = TextEditingController(text: publicIP); // 添加缺失的初始化
|
||||
|
||||
// 初始化FocusNode并添加监听器
|
||||
_virtualIPFocusNode = FocusNode();
|
||||
_virtualIPFocusNode.addListener(_onVirtualIPFocusChange);
|
||||
|
||||
// 初始化用户名、房间名和密码的FocusNode
|
||||
_usernameControllerFocusNode = FocusNode();
|
||||
_usernameControllerFocusNode.addListener(_onUsernameFocusChange);
|
||||
|
||||
_roomNameControllerFocusNode = FocusNode();
|
||||
_roomNameControllerFocusNode.addListener(_onRoomNameFocusChange);
|
||||
|
||||
_roomPasswordControllerFocusNode = FocusNode();
|
||||
_roomPasswordControllerFocusNode.addListener(_onRoomPasswordFocusChange);
|
||||
|
||||
// 修改卡片构建器列表,添加版本信息卡片
|
||||
_cardBuilders = [
|
||||
_buildNetworkStatusCard, // 网络状态卡片
|
||||
_buildUserInfoCard, // 用户信息卡片
|
||||
_buildRoomInfoCard, // 房间信息卡片
|
||||
_buildVersionInfoCard, // 新增版本信息卡片
|
||||
_buildNetworkStatusCard,
|
||||
_buildUserInfoCard,
|
||||
_buildRoomInfoCard,
|
||||
_buildVersionInfoCard,
|
||||
];
|
||||
}
|
||||
|
||||
// 添加焦点变化监听方法
|
||||
void _onVirtualIPFocusChange() {
|
||||
if (!_virtualIPFocusNode.hasFocus && !_isAutoIP) {
|
||||
// 当失去焦点且不是自动IP模式时更新值
|
||||
Provider.of<KM>(context, listen: false).virtualIP =
|
||||
_virtualIPController.text;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户名焦点变化监听方法
|
||||
void _onUsernameFocusChange() {
|
||||
if (!_usernameControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).username =
|
||||
_usernameController.text;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加房间名焦点变化监听方法
|
||||
void _onRoomNameFocusChange() {
|
||||
if (!_roomNameControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).roomName =
|
||||
_roomNameController.text;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加房间密码焦点变化监听方法
|
||||
void _onRoomPasswordFocusChange() {
|
||||
if (!_roomPasswordControllerFocusNode.hasFocus) {
|
||||
Provider.of<KM>(context, listen: false).roomPassword =
|
||||
_roomPasswordController.text;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// 取消所有计时器
|
||||
timer?.cancel();
|
||||
timer = null;
|
||||
|
||||
// 释放所有控制器和焦点节点
|
||||
_roomNameController.dispose();
|
||||
_roomPasswordController.dispose();
|
||||
_usernameController.dispose();
|
||||
_virtualIPController.dispose();
|
||||
|
||||
_virtualIPFocusNode.removeListener(_onVirtualIPFocusChange);
|
||||
_virtualIPFocusNode.dispose();
|
||||
|
||||
_usernameControllerFocusNode.removeListener(_onUsernameFocusChange);
|
||||
_usernameControllerFocusNode.dispose();
|
||||
|
||||
_roomNameControllerFocusNode.removeListener(_onRoomNameFocusChange);
|
||||
_roomNameControllerFocusNode.dispose();
|
||||
|
||||
_roomPasswordControllerFocusNode.removeListener(_onRoomPasswordFocusChange);
|
||||
_roomPasswordControllerFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void toggleRunning() {
|
||||
final km = Provider.of<KM>(context, listen: false);
|
||||
setState(() {
|
||||
@@ -121,9 +199,6 @@ class _HomePageState extends State<HomePage> {
|
||||
roomPassword: roomPassword,
|
||||
severurl: Serverip);
|
||||
|
||||
// 添加连接超时计数器
|
||||
int connectionTimeoutCounter = 0;
|
||||
|
||||
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
|
||||
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
|
||||
// 检查组件是否仍然挂载
|
||||
@@ -146,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +273,8 @@ class _HomePageState extends State<HomePage> {
|
||||
// 停止时重置状态
|
||||
_connectionState = ConnectionState.notStarted;
|
||||
closeAllServer();
|
||||
// 清空玩家列表数据
|
||||
Provider.of<KM>(context, listen: false).nodes = [];
|
||||
timer?.cancel();
|
||||
runningTime = Duration.zero;
|
||||
// 重置网络统计数据
|
||||
@@ -216,6 +284,7 @@ class _HomePageState extends State<HomePage> {
|
||||
_lastDownloadBytes = 0;
|
||||
uploadSpeed = 0;
|
||||
downloadSpeed = 0;
|
||||
//connectionTimeoutCounter
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -286,6 +355,7 @@ class _HomePageState extends State<HomePage> {
|
||||
text: km.username,
|
||||
selection: _usernameController.selection,
|
||||
);
|
||||
// 添加虚拟IP控制器的值同步
|
||||
_virtualIPController.value = TextEditingValue(
|
||||
text: km.virtualIP,
|
||||
selection: _virtualIPController.selection,
|
||||
@@ -478,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(
|
||||
@@ -743,9 +731,12 @@ class _HomePageState extends State<HomePage> {
|
||||
// 用户名输入框
|
||||
TextField(
|
||||
controller: _usernameController,
|
||||
focusNode: _usernameControllerFocusNode, // 添加焦点节点
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onChanged: (value) {
|
||||
km.username = value;
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).username =
|
||||
_usernameController.text;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: '用户名',
|
||||
@@ -762,12 +753,17 @@ class _HomePageState extends State<HomePage> {
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _virtualIPController,
|
||||
focusNode: _virtualIPFocusNode, // 添加焦点节点
|
||||
enabled: !_isAutoIP &&
|
||||
_connectionState != ConnectionState.connected,
|
||||
onChanged: (value) {
|
||||
// 保留空回调以避免实时更新
|
||||
},
|
||||
onEditingComplete: () {
|
||||
// 添加完成编辑回调
|
||||
if (!_isAutoIP) {
|
||||
setState(() {});
|
||||
km.virtualIP = value;
|
||||
Provider.of<KM>(context, listen: false).virtualIP =
|
||||
_virtualIPController.text;
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
@@ -790,6 +786,10 @@ class _HomePageState extends State<HomePage> {
|
||||
_isAutoIP = value;
|
||||
});
|
||||
km.dynamicIP = value;
|
||||
// 切换模式时同步最新值
|
||||
if (!value) {
|
||||
km.virtualIP = _virtualIPController.text;
|
||||
}
|
||||
}
|
||||
: null,
|
||||
),
|
||||
@@ -859,9 +859,12 @@ class _HomePageState extends State<HomePage> {
|
||||
// 房间名称输入框
|
||||
TextField(
|
||||
controller: _roomNameController,
|
||||
focusNode: _roomNameControllerFocusNode, // 添加焦点节点
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onChanged: (value) {
|
||||
km.roomName = value;
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).roomName =
|
||||
_roomNameController.text;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: '房间名称',
|
||||
@@ -875,9 +878,12 @@ class _HomePageState extends State<HomePage> {
|
||||
// 房间密码输入框
|
||||
TextField(
|
||||
controller: _roomPasswordController,
|
||||
focusNode: _roomPasswordControllerFocusNode, // 添加焦点节点
|
||||
enabled: _connectionState != ConnectionState.connected,
|
||||
onChanged: (value) {
|
||||
km.roomPassword = value;
|
||||
onEditingComplete: () {
|
||||
// 改为完成编辑时更新
|
||||
Provider.of<KM>(context, listen: false).roomPassword =
|
||||
_roomPasswordController.text;
|
||||
},
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -230,13 +230,7 @@ class _RoomPageState extends State<RoomPage> {
|
||||
}
|
||||
|
||||
// 计算丢包率 (简单估算)
|
||||
double packetLossRate = 0.0;
|
||||
if (sentPackets > 0 && receivedPackets > 0) {
|
||||
packetLossRate = (1.0 - (receivedPackets / sentPackets)).abs() * 100;
|
||||
if (packetLossRate > 100) packetLossRate = 100.0;
|
||||
packetLossRate = double.parse(packetLossRate.toStringAsFixed(1));
|
||||
}
|
||||
|
||||
double packetLossRate = node.lossRate;
|
||||
// 获取NAT类型
|
||||
String natType = _mapNatType(node.nat);
|
||||
|
||||
|
||||
+59
-64
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:astral/utils/app_info.dart';
|
||||
import 'package:astral/utils/up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,17 +20,15 @@ final updateChecker = UpdateChecker(
|
||||
);
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
bool _notificationsEnabled = true;
|
||||
double _fontSize = 16.0;
|
||||
late List<String> _serverList;
|
||||
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() {
|
||||
@@ -38,59 +37,74 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
_currentServer = _appConfig.currentServer;
|
||||
serverIP = _appConfig.currentServer;
|
||||
_closeToTray = _appConfig.closeToTray; // 初始化托盘设置
|
||||
// 初始化全局ping开关
|
||||
_pingEnabled = _appConfig.enablePing;
|
||||
|
||||
// 初始化 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();
|
||||
|
||||
// 创建新的计时器,使用设置的间隔时间
|
||||
_pingTimer = Timer.periodic(Duration(milliseconds: 1000), (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;
|
||||
_appConfig.setEnablePing(_pingEnabled);
|
||||
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),
|
||||
@@ -307,7 +296,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
_currentServer = server;
|
||||
Provider.of<KM>(context, listen: false).serverIP =
|
||||
server;
|
||||
// 不再需要特别开始 ping 新选择的服务器,因为所有服务器都在 ping
|
||||
_appConfig.setCurrentServer(_currentServer);
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('已切换到服务器: $server')),
|
||||
@@ -347,6 +336,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
});
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('启用服务器Ping测试'),
|
||||
subtitle: const Text('定期测试所有服务器的网络延迟'),
|
||||
value: _pingEnabled,
|
||||
onChanged: _togglePingStatus,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -124,6 +124,7 @@ class KVNodeInfo {
|
||||
final String ipv4;
|
||||
final double latencyMs;
|
||||
final String nat;
|
||||
final double lossRate;
|
||||
final List<KVNodeConnectionStats> connections;
|
||||
final String version;
|
||||
final int cost;
|
||||
@@ -133,6 +134,7 @@ class KVNodeInfo {
|
||||
required this.ipv4,
|
||||
required this.latencyMs,
|
||||
required this.nat,
|
||||
required this.lossRate,
|
||||
required this.connections,
|
||||
required this.version,
|
||||
required this.cost,
|
||||
@@ -144,6 +146,7 @@ class KVNodeInfo {
|
||||
ipv4.hashCode ^
|
||||
latencyMs.hashCode ^
|
||||
nat.hashCode ^
|
||||
lossRate.hashCode ^
|
||||
connections.hashCode ^
|
||||
version.hashCode ^
|
||||
cost.hashCode;
|
||||
@@ -157,6 +160,7 @@ class KVNodeInfo {
|
||||
ipv4 == other.ipv4 &&
|
||||
latencyMs == other.latencyMs &&
|
||||
nat == other.nat &&
|
||||
lossRate == other.lossRate &&
|
||||
connections == other.connections &&
|
||||
version == other.version &&
|
||||
cost == other.cost;
|
||||
|
||||
@@ -515,6 +515,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return raw as bool;
|
||||
}
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as double;
|
||||
}
|
||||
|
||||
@protected
|
||||
double dco_decode_f_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
@@ -558,16 +564,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
KVNodeInfo dco_decode_kv_node_info(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 7)
|
||||
throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
|
||||
if (arr.length != 8)
|
||||
throw Exception('unexpected arr length: expect 8 but see ${arr.length}');
|
||||
return KVNodeInfo(
|
||||
hostname: dco_decode_String(arr[0]),
|
||||
ipv4: dco_decode_String(arr[1]),
|
||||
latencyMs: dco_decode_f_64(arr[2]),
|
||||
nat: dco_decode_String(arr[3]),
|
||||
connections: dco_decode_list_kv_node_connection_stats(arr[4]),
|
||||
version: dco_decode_String(arr[5]),
|
||||
cost: dco_decode_i_32(arr[6]),
|
||||
lossRate: dco_decode_f_32(arr[4]),
|
||||
connections: dco_decode_list_kv_node_connection_stats(arr[5]),
|
||||
version: dco_decode_String(arr[6]),
|
||||
cost: dco_decode_i_32(arr[7]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -753,6 +760,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
return deserializer.buffer.getUint8() != 0;
|
||||
}
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getFloat32();
|
||||
}
|
||||
|
||||
@protected
|
||||
double sse_decode_f_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -797,6 +810,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
var var_ipv4 = sse_decode_String(deserializer);
|
||||
var var_latencyMs = sse_decode_f_64(deserializer);
|
||||
var var_nat = sse_decode_String(deserializer);
|
||||
var var_lossRate = sse_decode_f_32(deserializer);
|
||||
var var_connections =
|
||||
sse_decode_list_kv_node_connection_stats(deserializer);
|
||||
var var_version = sse_decode_String(deserializer);
|
||||
@@ -806,6 +820,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
ipv4: var_ipv4,
|
||||
latencyMs: var_latencyMs,
|
||||
nat: var_nat,
|
||||
lossRate: var_lossRate,
|
||||
connections: var_connections,
|
||||
version: var_version,
|
||||
cost: var_cost);
|
||||
@@ -1016,6 +1031,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
serializer.buffer.putUint8(self ? 1 : 0);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putFloat32(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_f_64(double self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
@@ -1054,6 +1075,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
sse_encode_String(self.ipv4, serializer);
|
||||
sse_encode_f_64(self.latencyMs, serializer);
|
||||
sse_encode_String(self.nat, serializer);
|
||||
sse_encode_f_32(self.lossRate, serializer);
|
||||
sse_encode_list_kv_node_connection_stats(self.connections, serializer);
|
||||
sse_encode_String(self.version, serializer);
|
||||
sse_encode_i_32(self.cost, serializer);
|
||||
|
||||
@@ -77,6 +77,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_64(dynamic raw);
|
||||
|
||||
@@ -182,6 +185,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -288,6 +294,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_64(double self, SseSerializer serializer);
|
||||
|
||||
|
||||
@@ -79,6 +79,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_64(dynamic raw);
|
||||
|
||||
@@ -184,6 +187,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_64(SseDeserializer deserializer);
|
||||
|
||||
@@ -290,6 +296,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_64(double self, SseSerializer serializer);
|
||||
|
||||
|
||||
@@ -3,25 +3,28 @@ import 'dart:async';
|
||||
|
||||
class PingUtil {
|
||||
static Future<int?> ping(String host) async {
|
||||
Socket? socket;
|
||||
try {
|
||||
// 从 host:port 格式中提取主机名
|
||||
final hostname = host.split(':')[0];
|
||||
final post = host.split(':')[1];
|
||||
final port = host.split(':')[1]; // 修正变量名 post -> port
|
||||
|
||||
// 使用 Socket 连接来测量实际网络延迟
|
||||
final startTime = DateTime.now();
|
||||
final socket = await Socket.connect(hostname, int.parse(post),
|
||||
socket = await Socket.connect(hostname, int.parse(port),
|
||||
timeout: const Duration(seconds: 2));
|
||||
final endTime = DateTime.now();
|
||||
|
||||
// 关闭连接
|
||||
await socket.close();
|
||||
|
||||
// 计算延迟时间
|
||||
return endTime.difference(startTime).inMilliseconds;
|
||||
} on SocketException {
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
} finally {
|
||||
// 确保在所有情况下都关闭 socket 连接
|
||||
socket?.destroy();
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-78
@@ -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 = false, // 默认启用3D效果
|
||||
this.maxRotationDegree = 10, // 默认最大旋转角度为10度
|
||||
this.enableTranslateEffect = false, // 默认启用偏移效果
|
||||
this.maxTranslateDistance = 0.1, // 默认最大偏移距离为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
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.1
|
||||
version: 1.0.3
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
@@ -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:
|
||||
|
||||
+24
-8
@@ -292,6 +292,7 @@ pub struct KVNodeInfo {
|
||||
pub ipv4: String,
|
||||
pub latency_ms: f64,
|
||||
pub nat: String, // NAT类型
|
||||
pub loss_rate: f32,
|
||||
pub connections: Vec<KVNodeConnectionStats>,
|
||||
pub version: String,
|
||||
pub cost: i32,
|
||||
@@ -330,7 +331,6 @@ pub fn get_network_status() -> KVNetworkStatus {
|
||||
hostname: route.hostname.clone(),
|
||||
ipv4,
|
||||
latency_ms: if let Some(peer) = &pair.peer {
|
||||
// 类似cli.rs中的get_latency_ms方法
|
||||
let mut min_latency = u64::MAX;
|
||||
for conn in &peer.conns {
|
||||
if let Some(stats) = &conn.stats {
|
||||
@@ -348,6 +348,15 @@ pub fn get_network_status() -> KVNetworkStatus {
|
||||
// 如果没有peer信息,则使用路由路径延迟
|
||||
f64::from(route.path_latency.max(0)) / 1000.0
|
||||
},
|
||||
loss_rate: if let Some(peer) = &pair.peer {
|
||||
let mut total_loss_rate = 0.0;
|
||||
for conn in &peer.conns {
|
||||
total_loss_rate += conn.loss_rate;
|
||||
}
|
||||
total_loss_rate
|
||||
} else {
|
||||
0.0 // 如果没有连接信息,默认为0
|
||||
},
|
||||
nat: route.stun_info.as_ref().map_or_else(
|
||||
|| "Unknown".to_string(),
|
||||
|stun| {
|
||||
@@ -571,13 +580,20 @@ pub fn create_server(
|
||||
let cfg = create_config();
|
||||
cfg.set_hostname(Option::from(username));
|
||||
cfg.set_dhcp(enable_dhcp);
|
||||
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]);
|
||||
let mut flags = cfg.get_flags();
|
||||
// flags.dev_name = "astral".to_string();
|
||||
cfg.set_flags(flags);
|
||||
// 创建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地址和子网掩码
|
||||
|
||||
@@ -496,6 +496,13 @@ impl SseDecode for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for f32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_f32::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for f64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
@@ -547,6 +554,7 @@ impl SseDecode for crate::api::simple::KVNodeInfo {
|
||||
let mut var_ipv4 = <String>::sse_decode(deserializer);
|
||||
let mut var_latencyMs = <f64>::sse_decode(deserializer);
|
||||
let mut var_nat = <String>::sse_decode(deserializer);
|
||||
let mut var_lossRate = <f32>::sse_decode(deserializer);
|
||||
let mut var_connections =
|
||||
<Vec<crate::api::simple::KVNodeConnectionStats>>::sse_decode(deserializer);
|
||||
let mut var_version = <String>::sse_decode(deserializer);
|
||||
@@ -556,6 +564,7 @@ impl SseDecode for crate::api::simple::KVNodeInfo {
|
||||
ipv4: var_ipv4,
|
||||
latency_ms: var_latencyMs,
|
||||
nat: var_nat,
|
||||
loss_rate: var_lossRate,
|
||||
connections: var_connections,
|
||||
version: var_version,
|
||||
cost: var_cost,
|
||||
@@ -822,6 +831,7 @@ impl flutter_rust_bridge::IntoDart for crate::api::simple::KVNodeInfo {
|
||||
self.ipv4.into_into_dart().into_dart(),
|
||||
self.latency_ms.into_into_dart().into_dart(),
|
||||
self.nat.into_into_dart().into_dart(),
|
||||
self.loss_rate.into_into_dart().into_dart(),
|
||||
self.connections.into_into_dart().into_dart(),
|
||||
self.version.into_into_dart().into_dart(),
|
||||
self.cost.into_into_dart().into_dart(),
|
||||
@@ -928,6 +938,13 @@ impl SseEncode for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for f32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_f32::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for f64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
@@ -968,6 +985,7 @@ impl SseEncode for crate::api::simple::KVNodeInfo {
|
||||
<String>::sse_encode(self.ipv4, serializer);
|
||||
<f64>::sse_encode(self.latency_ms, serializer);
|
||||
<String>::sse_encode(self.nat, serializer);
|
||||
<f32>::sse_encode(self.loss_rate, serializer);
|
||||
<Vec<crate::api::simple::KVNodeConnectionStats>>::sse_encode(self.connections, serializer);
|
||||
<String>::sse_encode(self.version, serializer);
|
||||
<i32>::sse_encode(self.cost, serializer);
|
||||
|
||||
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}"
|
||||
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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user