This commit is contained in:
会做饭的二哈
2025-03-16 17:53:06 +08:00
parent 769dab91f0
commit 45b6d939da
29 changed files with 949 additions and 366 deletions
+6 -6
View File
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: windows - platform: web
create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section # User provided section
+79 -43
View File
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'dart:io';
import 'dart:io'; // 添加 dart:io 导入以使用 Platform 类 import 'package:path/path.dart' as path;
import 'cof.dart';
class AppConfig { class AppConfig {
static final AppConfig _instance = AppConfig._internal(); static final AppConfig _instance = AppConfig._internal();
static late SharedPreferences _prefs; static late ConfigManager _configManager;
static late String _configDirectory;
factory AppConfig() { factory AppConfig() {
return _instance; return _instance;
@@ -14,112 +16,146 @@ class AppConfig {
// 初始化配置 // 初始化配置
static Future<void> init() async { static Future<void> init() async {
_prefs = await SharedPreferences.getInstance(); // 获取可执行文件所在目录而不是当前工作目录
_configDirectory = File(Platform.resolvedExecutable).parent.path;
final configPath = path.join(_configDirectory, 'config.yaml');
_configManager = ConfigManager(
filePath: configPath,
defaultConfig: {
'theme': {
'mode': 'system',
'seedColor': Colors.blue.value,
},
'server': {
'list': ['public.easytier.cn:11010'],
'current': 'public.easytier.cn:11010',
},
'room': {
'name': 'kevin',
'password': 'kevin',
},
'user': {
'name': Platform.localHostname,
},
'network': {
'virtualIP': '',
'dynamicIP': true,
},
'system': {
'closeToTray': true,
},
},
);
await _configManager.load();
} }
// 主题设置 // 主题设置
static const String _keyThemeMode = 'themeMode';
ThemeMode get themeMode { ThemeMode get themeMode {
final String? value = _prefs.getString(_keyThemeMode); final String? value = _configManager.get<String>('theme.mode');
return ThemeMode.values.firstWhere( return ThemeMode.values.firstWhere(
(mode) => mode.toString() == value, (mode) => mode.toString() == 'ThemeMode.$value',
orElse: () => ThemeMode.system, orElse: () => ThemeMode.system,
); );
} }
Future<void> setThemeMode(ThemeMode mode) async { Future<void> setThemeMode(ThemeMode mode) async {
await _prefs.setString(_keyThemeMode, mode.toString()); final modeString = mode.toString().split('.').last.toLowerCase();
_configManager.set('theme.mode', modeString);
await _configManager.save();
} }
// 主题色设置 // 主题色设置
static const String _keySeedColor = 'seedColor';
Color get seedColor { Color get seedColor {
final int? value = _prefs.getInt(_keySeedColor); final int? value = _configManager.get<int>('theme.seedColor');
return value != null ? Color(value) : Colors.blue; return value != null ? Color(value) : Colors.blue;
} }
Future<void> setSeedColor(Color color) async { Future<void> setSeedColor(Color color) async {
await _prefs.setInt(_keySeedColor, color.value); _configManager.set('theme.seedColor', color.value);
await _configManager.save();
} }
// 服务器列表设置 // 服务器列表设置
static const String _keyServerList = 'serverList';
List<String> get serverList { List<String> get serverList {
final List<String>? value = _prefs.getStringList(_keyServerList); final List? value = _configManager.get<List>('server.list');
return value?.isNotEmpty == true ? value! : ['public.easytier.net:11010']; return value?.cast<String>() ?? ['public.easytier.cn:11010'];
} }
Future<void> setServerList(List<String> servers) async { Future<void> setServerList(List<String> servers) async {
await _prefs.setStringList(_keyServerList, servers); _configManager.set('server.list', servers);
await _configManager.save();
} }
// 当前选中的服务器设置 // 当前选中的服务器设置
static const String _keyCurrentServer = 'currentServer';
String get currentServer { String get currentServer {
return _prefs.getString(_keyCurrentServer) ?? 'public.easytier.net:11010'; return _configManager.get<String>('server.current') ??
'public.easytier.cn:11010';
} }
Future<void> setCurrentServer(String server) async { Future<void> setCurrentServer(String server) async {
await _prefs.setString(_keyCurrentServer, server); _configManager.set('server.current', server);
await _configManager.save();
} }
// 房间名设置 // 房间名设置
static const String _keyRoomName = 'roomName';
String get roomName { String get roomName {
return _prefs.getString(_keyRoomName) ?? 'kevin'; return _configManager.get<String>('room.name') ?? 'kevin';
} }
Future<void> setRoomName(String name) async { Future<void> setRoomName(String name) async {
await _prefs.setString(_keyRoomName, name); _configManager.set('room.name', name);
await _configManager.save();
} }
// 房间密码设置 // 房间密码设置
static const String _keyRoomPassword = 'roomPassword';
String get roomPassword { String get roomPassword {
return _prefs.getString(_keyRoomPassword) ?? 'kevin'; return _configManager.get<String>('room.password') ?? 'kevin';
} }
Future<void> setRoomPassword(String password) async { Future<void> setRoomPassword(String password) async {
await _prefs.setString(_keyRoomPassword, password); _configManager.set('room.password', password);
await _configManager.save();
} }
// 用户名设置 // 用户名设置
static const String _keyUsername = 'username';
String get username { String get username {
return _prefs.getString(_keyUsername) ?? Platform.localHostname; return _configManager.get<String>('user.name') ?? Platform.localHostname;
} }
Future<void> setUsername(String name) async { Future<void> setUsername(String name) async {
await _prefs.setString(_keyUsername, name); _configManager.set('user.name', name);
await _configManager.save();
} }
// 虚拟IP设置 // 虚拟IP设置
static const String _keyVirtualIP = 'virtualIP';
String get virtualIP { String get virtualIP {
return _prefs.getString(_keyVirtualIP) ?? ''; return _configManager.get<String>('network.virtualIP') ?? '';
}
// 关闭按钮进入托盘
static const String _keyCloseToTray = 'closeToTray';
bool get closeToTray {
return _prefs.getBool(_keyCloseToTray) ?? true;
}
Future<void> setCloseToTray(bool enabled) async {
await _prefs.setBool(_keyCloseToTray, enabled);
} }
Future<void> setVirtualIP(String ip) async { Future<void> setVirtualIP(String ip) async {
await _prefs.setString(_keyVirtualIP, ip); _configManager.set('network.virtualIP', ip);
await _configManager.save();
}
// 关闭按钮进入托盘
bool get closeToTray {
return _configManager.get<bool>('system.closeToTray') ?? true;
}
Future<void> setCloseToTray(bool enabled) async {
_configManager.set('system.closeToTray', enabled);
await _configManager.save();
} }
// 动态获取IP设置 // 动态获取IP设置
static const String _keyDynamicIP = 'dynamicIP';
bool get dynamicIP { bool get dynamicIP {
return _prefs.getBool(_keyDynamicIP) ?? true; return _configManager.get<bool>('network.dynamicIP') ?? true;
} }
Future<void> setDynamicIP(bool enabled) async { Future<void> setDynamicIP(bool enabled) async {
await _prefs.setBool(_keyDynamicIP, enabled); _configManager.set('network.dynamicIP', enabled);
await _configManager.save();
} }
} }
+159
View File
@@ -0,0 +1,159 @@
import 'dart:io';
import 'package:yaml/yaml.dart';
class ConfigManager {
final String filePath;
final Map<String, dynamic> defaultConfig;
late Map<String, dynamic> _config;
ConfigManager({
required this.filePath,
required this.defaultConfig,
}) : _config = Map.from(defaultConfig);
/// 加载配置文件(如果不存在则创建默认配置)
Future<void> load() async {
final file = File(filePath);
if (!await file.exists()) {
await _createDefaultConfig();
return;
}
try {
final content = await file.readAsString();
final yamlMap = loadYaml(content);
_config = _mergeConfigs(_convertYaml(yamlMap));
} catch (e) {
throw Exception('配置文件解析失败: $e');
}
}
/// 保存当前配置到文件
Future<void> save() async {
final yamlString = _generateYaml();
await File(filePath).writeAsString(yamlString);
}
/// 获取配置值(支持点分隔符访问嵌套字段)
T? get<T>(String keyPath) {
final keys = keyPath.split('.');
dynamic value = _config;
for (final key in keys) {
if (value is Map && value.containsKey(key)) {
value = value[key];
} else {
return null;
}
}
return value is T ? value : null;
}
/// 设置配置值(支持点分隔符访问嵌套字段)
void set<T>(String keyPath, T value) {
final keys = keyPath.split('.');
dynamic current = _config;
for (int i = 0; i < keys.length - 1; i++) {
final key = keys[i];
current = current.putIfAbsent(key, () => <String, dynamic>{});
}
current[keys.last] = value;
}
/// 合并用户配置与默认配置
Map<String, dynamic> _mergeConfigs(Map<String, dynamic> userConfig) {
return _deepMerge(defaultConfig, userConfig);
}
/// 深度合并两个Map
Map<String, dynamic> _deepMerge(
Map<String, dynamic> base, Map<String, dynamic> override) {
final result = Map<String, dynamic>.from(base);
override.forEach((key, value) {
if (value is Map<String, dynamic> &&
result[key] is Map<String, dynamic>) {
result[key] = _deepMerge(result[key], value);
} else {
result[key] = value;
}
});
return result;
}
/// 转换YAML结构为Dart Map
dynamic _convertYaml(dynamic yaml) {
if (yaml is YamlMap) {
return yaml.map((k, v) => MapEntry(k.toString(), _convertYaml(v)));
}
if (yaml is YamlList) {
return yaml.map((e) => _convertYaml(e)).toList();
}
return yaml;
}
/// 生成YAML字符串
String _generateYaml() {
final buffer = StringBuffer();
_writeMap(_config, buffer, 0);
return buffer.toString();
}
/// 递归写入Map结构
void _writeMap(Map<String, dynamic> map, StringBuffer buffer, int indent) {
final prefix = ' ' * indent;
map.forEach((key, value) {
buffer.write('$prefix$key: ');
if (value is Map<String, dynamic>) {
buffer.writeln();
_writeMap(value, buffer, indent + 1);
} else if (value is List) {
_writeList(value, buffer, indent + 1);
} else {
buffer.writeln(_formatValue(value));
}
});
}
/// 处理列表值
void _writeList(List list, StringBuffer buffer, int indent) {
final prefix = ' ' * indent;
if (list.isEmpty) {
buffer.writeln('[]');
return;
}
buffer.writeln();
for (final item in list) {
buffer.write('$prefix- ');
if (item is Map) {
_writeMap(item.cast<String, dynamic>(), buffer, indent + 1);
} else if (item is List) {
_writeList(item, buffer, indent + 1);
} else {
buffer.writeln(_formatValue(item));
}
}
}
/// 格式值处理
String _formatValue(dynamic value) {
if (value is String) return '"$value"';
if (value is bool) return value.toString();
return value.toString();
}
/// 创建默认配置文件
Future<void> _createDefaultConfig() async {
_config = Map.from(defaultConfig);
await save();
}
}
+2 -3
View File
@@ -1,13 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ASTRAL/src/rust/frb_generated.dart'; import 'package:astral/src/rust/frb_generated.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'app.dart'; import 'app.dart';
import 'config/windowconfiguration.dart'; import 'config/windowconfiguration.dart';
import 'config/app_config.dart'; import 'config/app_config.dart';
import 'utils/kv_state.dart'; import 'utils/kv_state.dart';
import 'package:provider/provider.dart'; // 添加这一行 import 'package:provider/provider.dart'; // 添加这一行
import 'package:tray_manager/tray_manager.dart'; import 'package:astral/utils/app_info.dart';
import 'package:ASTRAL/utils/app_info.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
+35 -3
View File
@@ -33,6 +33,8 @@ class _MainScreenState extends State<MainScreen>
late List<Widget> _pages; late List<Widget> _pages;
late List<NavItem> _navItems; late List<NavItem> _navItems;
late AnimationController _titleAnimationController; late AnimationController _titleAnimationController;
// 添加一个key用于IndexedStack
final GlobalKey _indexedStackKey = GlobalKey();
@override @override
void initState() { void initState() {
@@ -70,8 +72,16 @@ class _MainScreenState extends State<MainScreen>
currentThemeMode: widget.currentThemeMode, currentThemeMode: widget.currentThemeMode,
); );
// 预先创建所有页面 // 预先创建所有页面,并包装在AutomaticKeepAlive中
_pages = _navItems.map((item) => item.pageBuilder()).toList(); _pages = _navItems.map((item) {
final page = item.pageBuilder();
// 如果页面已经实现了AutomaticKeepAliveClientMixin,则不需要包装
if (page is AutomaticKeepAliveClientMixin) {
return page;
}
// 否则包装在PageKeepAlive中
return PageKeepAlive(child: page);
}).toList();
} }
// 将侧边栏构建方法整合到MainScreen类中 // 将侧边栏构建方法整合到MainScreen类中
@@ -100,8 +110,9 @@ class _MainScreenState extends State<MainScreen>
// 设置一个阈值,当宽度大于此值时使用侧边栏 // 设置一个阈值,当宽度大于此值时使用侧边栏
final bool useSidebar = screenWidth > 600; final bool useSidebar = screenWidth > 600;
// 创建一个共享的 IndexedStack 实例 // 创建一个共享的 IndexedStack 实例,使用相同的key
final indexedStack = IndexedStack( final indexedStack = IndexedStack(
key: _indexedStackKey,
index: widget.currentIndex, index: widget.currentIndex,
children: _pages, children: _pages,
); );
@@ -207,3 +218,24 @@ class _MainScreenState extends State<MainScreen>
); );
} }
} }
// 添加一个包装类来保持页面状态
class PageKeepAlive extends StatefulWidget {
final Widget child;
const PageKeepAlive({Key? key, required this.child}) : super(key: key);
@override
State<PageKeepAlive> createState() => _PageKeepAliveState();
}
class _PageKeepAliveState extends State<PageKeepAlive> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
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 '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 StatefulWidget {
const InfoPage({super.key}); const InfoPage({super.key});
+72 -50
View File
@@ -1,9 +1,9 @@
// 导入必要的包 // 导入必要的包
import 'dart:convert'; import 'dart:convert';
import 'package:ASTRAL/src/rust/api/simple.dart'; import 'package:astral/src/rust/api/simple.dart';
import 'package:ASTRAL/utils/kv_state.dart'; import 'package:astral/utils/kv_state.dart';
import 'package:ASTRAL/utils/app_info.dart'; import 'package:astral/utils/app_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'dart:async'; import 'dart:async';
@@ -122,6 +122,9 @@ class _HomePageState extends State<HomePage> {
severurl: Serverip); severurl: Serverip);
// 模拟连接过程,2秒后连接成功 // 模拟连接过程,2秒后连接成功
Future.delayed(const Duration(seconds: 2), () { Future.delayed(const Duration(seconds: 2), () {
// 检查组件是否仍然挂载
if (!mounted) return;
if (isRunning) { if (isRunning) {
// 确保用户没有在连接过程中取消 // 确保用户没有在连接过程中取消
setState(() { setState(() {
@@ -129,6 +132,12 @@ class _HomePageState extends State<HomePage> {
// 连接成功后开始计时 // 连接成功后开始计时
timer = Timer.periodic(const Duration(seconds: 1), (timer) async { timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
// 检查组件是否仍然挂载
if (!mounted) {
timer.cancel();
return;
}
final info = await getRunningInfo(); final info = await getRunningInfo();
// 打印运行信息的详细内容 // 打印运行信息的详细内容
// print("运行信息详情:${info}"); // print("运行信息详情:${info}");
@@ -153,10 +162,13 @@ class _HomePageState extends State<HomePage> {
km.virtualIP = ipStr; km.virtualIP = ipStr;
} }
} }
// print("- 用户名: ${info?.myNodeInfo?.hostname}");
// print("- 虚拟IPv4: ${info?.myNodeInfo?.virtualIpv4?.address}"); // 再次检查组件是否仍然挂载
// print("- version: ${info?.myNodeInfo?.version}"); if (!mounted) {
// print("- 本地IP: ${info.myNodeInfo.}"); timer.cancel();
return;
}
setState(() { setState(() {
runningTime += const Duration(seconds: 1); runningTime += const Duration(seconds: 1);
}); });
@@ -203,6 +215,9 @@ class _HomePageState extends State<HomePage> {
} }
} }
// 再次检查挂载状态,确保在setState前组件仍然挂载
if (!mounted) return;
// 计算速度 (字节/秒 转换为 MB/秒) // 计算速度 (字节/秒 转换为 MB/秒)
setState(() { setState(() {
_uploadBytes = totalUploadBytes; _uploadBytes = totalUploadBytes;
@@ -228,26 +243,6 @@ class _HomePageState extends State<HomePage> {
}); });
} }
// 根据屏幕宽度计算列数
int _getColumnCount(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return 1; // 手机屏幕显示1列
} else if (width < 900) {
return 2; // 平板或小屏幕显示2列
} else {
return 3; // 大屏幕显示3列
}
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
String hours = twoDigits(duration.inHours);
String minutes = twoDigits(duration.inMinutes.remainder(60));
String seconds = twoDigits(duration.inSeconds.remainder(60));
return '$hours:$minutes:$seconds';
}
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
@@ -282,31 +277,37 @@ class _HomePageState extends State<HomePage> {
//我的用户名 //我的用户名
username = Provider.of<KM>(context).username; username = Provider.of<KM>(context).username;
Serverip = Provider.of<KM>(context).serverIP; Serverip = Provider.of<KM>(context).serverIP;
// 使用 SliverPadding 包裹 SliverList
// 使用 LayoutBuilder 来处理布局变化,同时保留状态
return Scaffold( return Scaffold(
body: CustomScrollView( body: LayoutBuilder(builder: (context, constraints) {
// 添加这个属性来控制滚动行为 // 根据约束计算列数
physics: const AlwaysScrollableScrollPhysics(), final columnCount = _getColumnCount(constraints.maxWidth);
slivers: [
// 替换原有的 SliverList 为 SliverPadding + SliverGrid return CustomScrollView(
SliverPadding( // 添加这个属性来控制滚动行为
padding: const EdgeInsets.all(16.0), physics: const AlwaysScrollableScrollPhysics(),
sliver: SliverMasonryGrid.count( slivers: [
crossAxisCount: _getColumnCount(context), // 根据屏幕宽度动态设置列数 // 替换原有的 SliverList 为 SliverPadding + SliverGrid
mainAxisSpacing: 16, // 主轴间距 SliverPadding(
crossAxisSpacing: 16, // 交叉轴间距 padding: const EdgeInsets.all(16.0),
childCount: _cardBuilders.length, sliver: SliverMasonryGrid.count(
itemBuilder: (context, index) { crossAxisCount: columnCount, // 使用计算出的列数
// 直接从列表中获取构建函数并调用 mainAxisSpacing: 16, // 主轴间距
return ConstrainedBox( crossAxisSpacing: 16, // 交叉轴间距
constraints: const BoxConstraints(minHeight: 100), childCount: _cardBuilders.length,
child: _cardBuilders[index](colorScheme), itemBuilder: (context, index) {
); // 直接从列表中获取构建函数并调用
}, return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 100),
child: _cardBuilders[index](colorScheme),
);
},
),
), ),
), ],
], );
), }),
floatingActionButton: AnimatedContainer( floatingActionButton: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic, curve: Curves.easeOutCubic,
@@ -351,6 +352,25 @@ class _HomePageState extends State<HomePage> {
); );
} }
// 修改为接受宽度参数,而不是使用 MediaQuery
int _getColumnCount(double width) {
if (width < 600) {
return 1; // 手机屏幕显示1列
} else if (width < 900) {
return 2; // 平板或小屏幕显示2列
} else {
return 3; // 大屏幕显示3列
}
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
String hours = twoDigits(duration.inHours);
String minutes = twoDigits(duration.inMinutes.remainder(60));
String seconds = twoDigits(duration.inSeconds.remainder(60));
return '$hours:$minutes:$seconds';
}
// 根据连接状态获取按钮图标 // 根据连接状态获取按钮图标
Widget _getButtonIcon(ConnectionState state) { Widget _getButtonIcon(ConnectionState state) {
switch (state) { switch (state) {
@@ -920,3 +940,5 @@ Widget _buildVersionItem(
], ],
); );
} }
+347 -141
View File
@@ -1,13 +1,10 @@
// 导入必要的包 // 导入必要的包
import 'dart:convert';
import 'package:flutter/services.dart'; // 添加这一行导入剪贴板服务 import 'package:flutter/services.dart'; // 添加这一行导入剪贴板服务
import 'package:ASTRAL/src/rust/api/simple.dart'; import 'package:astral/utils/kv_state.dart';
import 'package:ASTRAL/utils/kv_state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../widgets/card.dart'; import '../widgets/card.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
/// 玩家信息模型类 /// 玩家信息模型类
class PlayerInfo { class PlayerInfo {
@@ -48,8 +45,7 @@ class RoomPage extends StatefulWidget {
class _RoomPageState extends State<RoomPage> { class _RoomPageState extends State<RoomPage> {
List<PlayerInfo> players = []; List<PlayerInfo> players = [];
bool isLoading = true; bool isLoading = true;
// 添加布局类型状态变量 // 移除布局类型状态变量
bool isGridLayout = true; // 默认使用网格布局
@override @override
void initState() { void initState() {
@@ -64,18 +60,7 @@ class _RoomPageState extends State<RoomPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('房间成员'), title: const Text('房间成员'),
// 添加布局切换按钮 // 移除布局切换按钮
actions: [
IconButton(
icon: Icon(isGridLayout ? Icons.view_list : Icons.grid_view),
tooltip: isGridLayout ? '切换到列表视图' : '切换到网格视图',
onPressed: () {
setState(() {
isGridLayout = !isGridLayout;
});
},
),
],
), ),
body: Consumer<KM>( body: Consumer<KM>(
builder: (context, km, child) { builder: (context, km, child) {
@@ -138,29 +123,18 @@ class _RoomPageState extends State<RoomPage> {
slivers: [ slivers: [
SliverPadding( SliverPadding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
sliver: isGridLayout sliver: SliverList(
? SliverMasonryGrid.count( delegate: SliverChildBuilderDelegate(
crossAxisCount: _getColumnCount(context), (context, index) {
mainAxisSpacing: 16, return Padding(
crossAxisSpacing: 16, padding: const EdgeInsets.only(bottom: 16.0),
childCount: players.length, child:
itemBuilder: (context, index) { _buildPlayerListItem(players[index], colorScheme),
return _buildPlayerCard( );
players[index], colorScheme); },
}, childCount: players.length,
) ),
: SliverList( ),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: _buildPlayerListItem(
players[index], colorScheme),
);
},
childCount: players.length,
),
),
), ),
], ],
); );
@@ -201,8 +175,8 @@ class _RoomPageState extends State<RoomPage> {
// 如果有连接信息,计算网络统计数据 // 如果有连接信息,计算网络统计数据
if (node.connections.isNotEmpty) { if (node.connections.isNotEmpty) {
for (var conn in node.connections) { for (var conn in node.connections) {
uploadSpeed += (conn.txBytes as BigInt).toInt() ~/ 1024; // 转换为KB uploadSpeed += conn.txBytes.toInt() ~/ 1024; // 转换为KB
downloadSpeed += (conn.rxBytes as BigInt).toInt() ~/ 1024; // 转换为KB downloadSpeed += conn.rxBytes.toInt() ~/ 1024; // 转换为KB
sentPackets += conn.txPackets.toInt(); sentPackets += conn.txPackets.toInt();
receivedPackets += conn.rxPackets.toInt(); receivedPackets += conn.rxPackets.toInt();
} }
@@ -221,7 +195,7 @@ class _RoomPageState extends State<RoomPage> {
PlayerInfo( PlayerInfo(
name: node.hostname, name: node.hostname,
ip: node.ipv4, // 临时IP,实际应从节点信息中获取 ip: node.ipv4, // 临时IP,实际应从节点信息中获取
latency: (node.latencyMs * 1000).toInt(), // 转换为毫秒 latency: (node.latencyMs).toInt(), // 转换为毫秒
connectionType: connectionType, connectionType: connectionType,
uploadSpeed: uploadSpeed, uploadSpeed: uploadSpeed,
downloadSpeed: downloadSpeed, downloadSpeed: downloadSpeed,
@@ -419,127 +393,359 @@ class _RoomPageState extends State<RoomPage> {
); );
} }
// 构建列表项视图 // 构建列表项视图
Widget _buildPlayerListItem(PlayerInfo player, ColorScheme colorScheme) { Widget _buildPlayerListItem(PlayerInfo player, ColorScheme colorScheme) {
// 根据延迟值确定颜色 // 根据延迟值确定颜色
Color latencyColor = _getLatencyColor(player.latency); Color latencyColor = _getLatencyColor(player.latency);
// 根据连接类型选择图标 // 根据连接类型选择图标
IconData connectionIcon = _getConnectionIcon(player.connectionType); IconData connectionIcon = _getConnectionIcon(player.connectionType);
// 检测是否为小屏幕设备
final isSmallScreen = MediaQuery.of(context).size.width < 600;
return FloatingCard( return FloatingCard(
colorScheme: colorScheme, colorScheme: colorScheme,
maxWidth: double.infinity, maxWidth: double.infinity,
child: Row( child: isSmallScreen
crossAxisAlignment: CrossAxisAlignment.center, ? _buildMobilePlayerListItem(
children: [ player, colorScheme, latencyColor, connectionIcon)
// 左侧玩家信息 : _buildDesktopPlayerListItem(
Expanded( player, colorScheme, latencyColor, connectionIcon),
flex: 3, );
child: Column( }
crossAxisAlignment: CrossAxisAlignment.start,
children: [ // 为移动设备优化的列表项布局
// 玩家名称和连接类型 Widget _buildMobilePlayerListItem(PlayerInfo player, ColorScheme colorScheme,
Row( Color latencyColor, IconData connectionIcon) {
children: [ return Column(
Icon(Icons.person, color: colorScheme.primary, size: 22), crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(width: 8), children: [
Expanded( // 玩家名称和连接类型
child: Text( Row(
player.name, children: [
style: const TextStyle( Icon(Icons.person, color: colorScheme.primary, size: 22),
fontSize: 18, fontWeight: FontWeight.bold), const SizedBox(width: 8),
overflow: TextOverflow.ellipsis, Expanded(
), child: Text(
player.name,
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color:
_getConnectionTypeColor(player.connectionType, colorScheme),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
connectionIcon,
size: 14,
color: Colors.white,
),
const SizedBox(width: 4),
Text(
player.connectionType,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
), ),
], ),
), ],
const SizedBox(height: 8), ),
),
],
),
const SizedBox(height: 12),
// IP地址
_buildInfoRow(
Icons.lan,
'IP地址',
player.ip,
colorScheme,
showCopyButton: true,
),
const SizedBox(height: 8),
// 延迟信息
_buildInfoRow(
Icons.speed,
'延迟',
'${player.latency} ms',
colorScheme,
valueColor: latencyColor,
),
const SizedBox(height: 8),
// ET版本
_buildInfoRow(
Icons.memory,
'ET版本',
player.etVersion,
colorScheme,
),
const SizedBox(height: 8),
// 丢包率信息
_buildInfoRow(
Icons.error_outline,
'丢包率',
'${player.packetLossRate}%',
colorScheme,
valueColor: _getPacketLossColor(player.packetLossRate),
),
// IP地址 // 网络数据部分
_buildInfoRow( const Divider(height: 16),
Icons.lan,
'IP地址',
player.ip,
colorScheme,
showCopyButton: true,
),
const SizedBox(height: 8),
// ET版本 // 网络数据信息 - 移动设备上使用紧凑布局
_buildInfoRow( Padding(
Icons.memory, padding: const EdgeInsets.symmetric(vertical: 8.0),
'ET版本', child: Column(
player.etVersion, children: [
colorScheme, Row(
), children: [
], Expanded(
), child: _buildNetworkDataItem(
'上传',
'${player.uploadSpeed} KB/s',
Icons.upload,
colorScheme.primary,
),
),
Expanded(
child: _buildNetworkDataItem(
'下载',
'${player.downloadSpeed} KB/s',
Icons.download,
colorScheme.secondary,
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: _buildNetworkDataItem(
'发送包',
'${player.sentPackets}',
Icons.send,
colorScheme.primary,
),
),
Expanded(
child: _buildNetworkDataItem(
'接收包',
'${player.receivedPackets}',
Icons.call_received,
colorScheme.secondary,
),
),
],
),
],
), ),
),
],
);
}
// 右侧网络状态信息 // 为桌面设备优化的列表项布局
Expanded( Widget _buildDesktopPlayerListItem(PlayerInfo player, ColorScheme colorScheme,
flex: 2, Color latencyColor, IconData connectionIcon) {
child: Column( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 连接类型标签 // 左侧玩家基本信息
Container( Expanded(
padding: flex: 3,
const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Column(
decoration: BoxDecoration( crossAxisAlignment: CrossAxisAlignment.start,
color: _getConnectionTypeColor( children: [
player.connectionType, colorScheme), // 玩家名称和连接类型
borderRadius: BorderRadius.circular(12), Row(
children: [
Icon(Icons.person, color: colorScheme.primary, size: 22),
const SizedBox(width: 8),
Expanded(
child: Text(
player.name,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
),
), ),
child: Row( Container(
mainAxisSize: MainAxisSize.min, padding:
children: [ const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
Icon( decoration: BoxDecoration(
connectionIcon, color: _getConnectionTypeColor(
size: 14, player.connectionType, colorScheme),
color: Colors.white, borderRadius: BorderRadius.circular(12),
), ),
const SizedBox(width: 4), child: Row(
Text( mainAxisSize: MainAxisSize.min,
player.connectionType, children: [
style: const TextStyle( Icon(
connectionIcon,
size: 14,
color: Colors.white, color: Colors.white,
fontSize: 12, ),
fontWeight: FontWeight.bold, const SizedBox(width: 4),
Text(
player.connectionType,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
), ),
), ],
], ),
),
],
),
const SizedBox(height: 12),
// IP地址
_buildInfoRow(
Icons.lan,
'IP地址',
player.ip,
colorScheme,
showCopyButton: true,
),
const SizedBox(height: 8),
// ET版本
_buildInfoRow(
Icons.memory,
'ET版本',
player.etVersion,
colorScheme,
),
],
),
),
// 中间网络状态信息
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 延迟信息
_buildInfoRow(
Icons.speed,
'延迟',
'${player.latency} ms',
colorScheme,
valueColor: latencyColor,
),
const SizedBox(height: 8),
// 丢包率信息
_buildInfoRow(
Icons.error_outline,
'丢包率',
'${player.packetLossRate}%',
colorScheme,
valueColor: _getPacketLossColor(player.packetLossRate),
),
const SizedBox(height: 8),
// 上传下载速度
Row(
children: [
Expanded(
child: _buildNetworkDataItem(
'上传',
'${player.uploadSpeed} KB/s',
Icons.upload,
colorScheme.primary,
),
),
Expanded(
child: _buildNetworkDataItem(
'下载',
'${player.downloadSpeed} KB/s',
Icons.download,
colorScheme.secondary,
),
), ),
), ],
const SizedBox(height: 8), ),
],
),
),
// 延迟信息 // 右侧包数据信息
_buildInfoRow( Expanded(
Icons.speed, flex: 1,
'延迟', child: Column(
'${player.latency} ms', crossAxisAlignment: CrossAxisAlignment.start,
colorScheme, children: [
valueColor: latencyColor, _buildNetworkDataItem(
), '发送包',
const SizedBox(height: 8), '${player.sentPackets}',
Icons.send,
colorScheme.primary,
),
const SizedBox(height: 8),
_buildNetworkDataItem(
'接收包',
'${player.receivedPackets}',
Icons.call_received,
colorScheme.secondary,
),
],
),
),
],
);
}
// 丢包率信息 // 更紧凑的网络数据项
_buildInfoRow( Widget _buildNetworkDataItem(
Icons.error_outline, String label,
'丢包率', String value,
'${player.packetLossRate}%', IconData icon,
colorScheme, Color color,
valueColor: _getPacketLossColor(player.packetLossRate), ) {
), return Row(
], mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18, color: color),
const SizedBox(width: 4),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontSize: 12),
), ),
), Text(
], value,
), style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
fontSize: 13,
),
),
],
),
],
); );
} }
// 构建信息行
// 构建信息行 // 构建信息行
Widget _buildInfoRow( Widget _buildInfoRow(
IconData icon, IconData icon,
+39 -84
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../config/app_config.dart'; import '../config/app_config.dart';
import '../utils/ping_util.dart'; import '../utils/ping_util.dart';
import 'package:ASTRAL/utils/kv_state.dart'; import 'package:astral/utils/kv_state.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class SettingsPage extends StatefulWidget { class SettingsPage extends StatefulWidget {
@@ -35,37 +35,34 @@ 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] = false; isPinging[server] = true; // 默认所有服务器都开启ping
} }
// 开始 ping 当前服务器,并设置为持续 ping // 开始 ping 所有服务器
_startPingServer(_currentServer, forceContinuous: true); for (var server in _serverList) {
_pingServer(server);
}
} }
@override @override
void dispose() { void dispose() {
// 停止所有 ping // 停止所有 ping
for (var server in _serverList) { for (var server in _serverList) {
_stopPingServer(server); isPinging[server] = false;
} }
super.dispose(); super.dispose();
} }
// 修改开始 ping 方法,添加强制持续 ping 参数 // 简化 ping 方法,移除强制持续 ping 参数
void _startPingServer(String server, {bool forceContinuous = false}) { void _startPingServer(String server) {
if (isPinging[server] == true) return; if (isPinging[server] == true) return;
isPinging[server] = true; isPinging[server] = true;
if (forceContinuous) {
isPinging[server] = true; // 设置为持续 ping 状态
}
_pingServer(server); _pingServer(server);
} }
// 修改停止 ping 方法 // 修改停止 ping 方法 - 实际上不再需要,但保留以防将来需要
void _stopPingServer(String server) { void _stopPingServer(String server) {
// 如果是当前服务器,不允许停止
if (server == _currentServer) return;
isPinging[server] = false; isPinging[server] = false;
} }
@@ -119,9 +116,10 @@ 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] = false; isPinging[result] = true;
_pingServer(result);
}); });
} }
} }
@@ -207,21 +205,18 @@ class _SettingsPageState extends State<SettingsPage> {
// 添加构建 ping 显示组件的方法 // 添加构建 ping 显示组件的方法
Widget _buildPingWidget(String server) { Widget _buildPingWidget(String server) {
final pingResult = pingResults[server]; final pingResult = pingResults[server];
if (server == _currentServer || isPinging[server] == true) { if (pingResult == null) {
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( '${pingResult}ms',
'${pingResult}ms', style: TextStyle(
style: TextStyle( color: pingResult < 100
color: pingResult < 100 ? Colors.green
? Colors.green : (pingResult < 300 ? Colors.orange : Colors.red),
: (pingResult < 300 ? Colors.orange : Colors.red), ),
), );
);
}
} }
return const Text('点击测试', style: TextStyle(color: Colors.grey));
} }
@override @override
@@ -248,22 +243,7 @@ class _SettingsPageState extends State<SettingsPage> {
leading: const Icon(Icons.list), leading: const Icon(Icons.list),
title: const Text('服务器列表'), title: const Text('服务器列表'),
onExpansionChanged: (expanded) { onExpansionChanged: (expanded) {
// 展开/折叠时处理其他服务器的 ping 状态 // 不再需要处理展开折叠时的 ping 状态,所有服务器都持续 ping
setState(() {
for (var server in _serverList) {
if (server != _currentServer) {
if (expanded) {
// 如果之前是手动开启的,则恢复 ping
if (isPinging[server] == true) {
_startPingServer(server);
}
} else {
// 折叠时暂停所有非当前服务器的 ping
_stopPingServer(server);
}
}
}
});
}, },
children: [ children: [
// 在服务器列表前添加当前服务器的 ping 状态显示 // 在服务器列表前添加当前服务器的 ping 状态显示
@@ -277,22 +257,17 @@ class _SettingsPageState extends State<SettingsPage> {
// 构建延迟显示组件 // 构建延迟显示组件
Widget pingWidget; Widget pingWidget;
if (isPinging[server] == true) { if (pingResult == null) {
if (pingResult == null) { pingWidget = const Text('测试中...',
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)));
}
} else {
pingWidget = const Text('点击测试',
style: TextStyle(color: Colors.grey)); 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(
@@ -308,26 +283,7 @@ class _SettingsPageState extends State<SettingsPage> {
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// 添加 ping 按钮 // 移除 ping 按钮,只保留编辑和删除按钮
IconButton(
icon: Icon(
isPinging[server] == true
? Icons.pause
: Icons.play_arrow,
color: isPinging[server] == true
? Colors.blue
: null,
),
onPressed: () {
setState(() {
if (isPinging[server] == true) {
_stopPingServer(server);
} else {
_startPingServer(server);
}
});
},
),
IconButton( IconButton(
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: () => _showEditServerDialog(index), onPressed: () => _showEditServerDialog(index),
@@ -343,8 +299,7 @@ class _SettingsPageState extends State<SettingsPage> {
_currentServer = server; _currentServer = server;
Provider.of<KM>(context, listen: false).serverIP = Provider.of<KM>(context, listen: false).serverIP =
server; server;
// 开始 ping 新选择的服务器 // 不再需要特别开始 ping 新选择的服务器,因为所有服务器都在 ping
_startPingServer(server);
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已切换到服务器: $server')), SnackBar(content: Text('已切换到服务器: $server')),
@@ -364,7 +319,7 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// 添加应用设置卡片 // 添加应用设置卡片
Card( Card(
child: Column( child: Column(
@@ -387,7 +342,7 @@ class _SettingsPageState extends State<SettingsPage> {
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Card( Card(
child: Column( child: Column(
+4
View File
@@ -123,6 +123,7 @@ class KVNodeInfo {
final String hostname; final String hostname;
final String ipv4; final String ipv4;
final double latencyMs; final double latencyMs;
final String nat;
final List<KVNodeConnectionStats> connections; final List<KVNodeConnectionStats> connections;
final String version; final String version;
final int cost; final int cost;
@@ -131,6 +132,7 @@ class KVNodeInfo {
required this.hostname, required this.hostname,
required this.ipv4, required this.ipv4,
required this.latencyMs, required this.latencyMs,
required this.nat,
required this.connections, required this.connections,
required this.version, required this.version,
required this.cost, required this.cost,
@@ -141,6 +143,7 @@ class KVNodeInfo {
hostname.hashCode ^ hostname.hashCode ^
ipv4.hashCode ^ ipv4.hashCode ^
latencyMs.hashCode ^ latencyMs.hashCode ^
nat.hashCode ^
connections.hashCode ^ connections.hashCode ^
version.hashCode ^ version.hashCode ^
cost.hashCode; cost.hashCode;
@@ -153,6 +156,7 @@ class KVNodeInfo {
hostname == other.hostname && hostname == other.hostname &&
ipv4 == other.ipv4 && ipv4 == other.ipv4 &&
latencyMs == other.latencyMs && latencyMs == other.latencyMs &&
nat == other.nat &&
connections == other.connections && connections == other.connections &&
version == other.version && version == other.version &&
cost == other.cost; cost == other.cost;
+9 -5
View File
@@ -558,15 +558,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
KVNodeInfo dco_decode_kv_node_info(dynamic raw) { KVNodeInfo dco_decode_kv_node_info(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>; final arr = raw as List<dynamic>;
if (arr.length != 6) if (arr.length != 7)
throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
return KVNodeInfo( return KVNodeInfo(
hostname: dco_decode_String(arr[0]), hostname: dco_decode_String(arr[0]),
ipv4: dco_decode_String(arr[1]), ipv4: dco_decode_String(arr[1]),
latencyMs: dco_decode_f_64(arr[2]), latencyMs: dco_decode_f_64(arr[2]),
connections: dco_decode_list_kv_node_connection_stats(arr[3]), nat: dco_decode_String(arr[3]),
version: dco_decode_String(arr[4]), connections: dco_decode_list_kv_node_connection_stats(arr[4]),
cost: dco_decode_i_32(arr[5]), version: dco_decode_String(arr[5]),
cost: dco_decode_i_32(arr[6]),
); );
} }
@@ -795,6 +796,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_hostname = sse_decode_String(deserializer); var var_hostname = sse_decode_String(deserializer);
var var_ipv4 = sse_decode_String(deserializer); var var_ipv4 = sse_decode_String(deserializer);
var var_latencyMs = sse_decode_f_64(deserializer); var var_latencyMs = sse_decode_f_64(deserializer);
var var_nat = sse_decode_String(deserializer);
var var_connections = var var_connections =
sse_decode_list_kv_node_connection_stats(deserializer); sse_decode_list_kv_node_connection_stats(deserializer);
var var_version = sse_decode_String(deserializer); var var_version = sse_decode_String(deserializer);
@@ -803,6 +805,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
hostname: var_hostname, hostname: var_hostname,
ipv4: var_ipv4, ipv4: var_ipv4,
latencyMs: var_latencyMs, latencyMs: var_latencyMs,
nat: var_nat,
connections: var_connections, connections: var_connections,
version: var_version, version: var_version,
cost: var_cost); cost: var_cost);
@@ -1050,6 +1053,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.hostname, serializer); sse_encode_String(self.hostname, serializer);
sse_encode_String(self.ipv4, serializer); sse_encode_String(self.ipv4, serializer);
sse_encode_f_64(self.latencyMs, serializer); sse_encode_f_64(self.latencyMs, serializer);
sse_encode_String(self.nat, serializer);
sse_encode_list_kv_node_connection_stats(self.connections, serializer); sse_encode_list_kv_node_connection_stats(self.connections, serializer);
sse_encode_String(self.version, serializer); sse_encode_String(self.version, serializer);
sse_encode_i_32(self.cost, serializer); sse_encode_i_32(self.cost, serializer);
+8 -8
View File
@@ -375,7 +375,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo'); 'frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo');
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo =
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -391,7 +391,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo'); 'frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo');
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo =
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfoPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -407,7 +407,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo'); 'frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo');
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo =
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -423,7 +423,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo'); 'frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo');
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo =
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfoPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -439,7 +439,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair'); 'frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair');
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair =
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -455,7 +455,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair'); 'frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair');
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair =
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePairPtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -471,7 +471,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute'); 'frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute');
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute = late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute =
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
@@ -487,7 +487,7 @@ class RustLibWire implements BaseWire {
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
'frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute'); 'frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute');
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute = late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute =
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoutePtr
.asFunction<void Function(ffi.Pointer<ffi.Void>)>(); .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
+1 -1
View File
@@ -1,4 +1,4 @@
import 'package:ASTRAL/src/rust/api/simple.dart'; import 'package:astral/src/rust/api/simple.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../config/app_config.dart'; import '../config/app_config.dart';
+11
View File
@@ -0,0 +1,11 @@
[ ] 更换为官方服务器
[ ] 添加服务器多选
[ ] 移除模拟延迟(对其实不用等待2秒的那个是模拟的我给忘了)->改为检测是否成功连接
[ ] 修复延迟计算:从peer连接获取最小延迟(μs->ms),无效则用路由延迟
[x] 管理员问题没权限
[ ] 配置信息改为运行目录
[ ] 看nat类型
[x] 缩放尺寸让导航栏变为底部会导致页面状态丢失
[x] 服务器始终检测延迟,去除暂停和开始反正也不怎么影响性能多此一举还增加复杂度😜
[x] 增加自动更新检测 和自动更新
[ ] 那就打包两个版本 一个便携版(配置文件随软件) 一个安装版(配置文件随软件)
+7 -7
View File
@@ -229,10 +229,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: fl_chart name: fl_chart
sha256: c1e26c7e48496be85104c16c040950b0436674cdf0737f3f6e95511b2529b592 sha256: "5276944c6ffc975ae796569a826c38a62d2abcf264e26b88fa6f482e107f4237"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.63.0" version: "0.70.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -255,10 +255,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.0.0" version: "5.0.0"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -416,10 +416,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.0.0" version: "5.1.1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -985,7 +985,7 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
yaml: yaml:
dependency: transitive dependency: "direct main"
description: description:
name: yaml name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+5 -4
View File
@@ -1,4 +1,4 @@
name: ASTRAL name: astral
description: "ASTRAL" description: "ASTRAL"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
@@ -42,13 +42,14 @@ 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.63.0 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
system_tray: ^2.0.3 system_tray: ^2.0.3
url_launcher: ^6.3.1 url_launcher: ^6.3.1
package_info_plus: ^8.3.0 # 添加这一行 package_info_plus: ^8.3.0 # 添加这一行
yaml: ^3.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -60,7 +61,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^4.0.0 flutter_lints: ^5.0.0
integration_test: integration_test:
sdk: flutter sdk: flutter
@@ -70,7 +71,7 @@ dev_dependencies:
# The following section is specific to Flutter packages. # The following section is specific to Flutter packages.
flutter: flutter:
assets: assets:
- assets/dlls/ # - assets/dlls/
- assets/icon.ico - assets/icon.ico
fonts: fonts:
+29 -2
View File
@@ -280,7 +280,7 @@ pub fn is_easytier_running() -> bool {
// 定义节点连接统计信息结构体 // 定义节点连接统计信息结构体
pub struct KVNodeConnectionStats { pub struct KVNodeConnectionStats {
pub conn_type: String, pub conn_type: String, // 连接类型
pub rx_bytes: u64, pub rx_bytes: u64,
pub tx_bytes: u64, pub tx_bytes: u64,
pub rx_packets: u64, pub rx_packets: u64,
@@ -291,6 +291,7 @@ pub struct KVNodeInfo {
pub hostname: String, pub hostname: String,
pub ipv4: String, pub ipv4: String,
pub latency_ms: f64, pub latency_ms: f64,
pub nat: String, // NAT类型
pub connections: Vec<KVNodeConnectionStats>, pub connections: Vec<KVNodeConnectionStats>,
pub version: String, pub version: String,
pub cost: i32, pub cost: i32,
@@ -328,7 +329,33 @@ pub fn get_network_status() -> KVNetworkStatus {
let mut node_info = KVNodeInfo { let mut node_info = KVNodeInfo {
hostname: route.hostname.clone(), hostname: route.hostname.clone(),
ipv4, ipv4,
latency_ms: f64::from(route.path_latency.max(0)) / 1000.0, 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 {
min_latency = min_latency.min(stats.latency_us);
}
}
if min_latency == u64::MAX {
// 如果没有找到有效的连接延迟,则使用路由路径延迟
f64::from(route.path_latency.max(0)) / 1000.0
} else {
// 将微秒转换为毫秒
f64::from(min_latency as u32) / 1000.0
}
} else {
// 如果没有peer信息,则使用路由路径延迟
f64::from(route.path_latency.max(0)) / 1000.0
},
nat: route.stun_info.as_ref().map_or_else(
|| "Unknown".to_string(),
|stun| {
// 使用NatType枚举替代直接匹配数字
let nat_type = NatType::try_from(stun.udp_nat_type).unwrap_or(NatType::Unknown);
format!("{:?}", nat_type)
}
),
connections: Vec::new(), connections: Vec::new(),
version: route.version.clone(), version: route.version.clone(),
cost, cost,
+12 -8
View File
@@ -546,6 +546,7 @@ impl SseDecode for crate::api::simple::KVNodeInfo {
let mut var_hostname = <String>::sse_decode(deserializer); let mut var_hostname = <String>::sse_decode(deserializer);
let mut var_ipv4 = <String>::sse_decode(deserializer); let mut var_ipv4 = <String>::sse_decode(deserializer);
let mut var_latencyMs = <f64>::sse_decode(deserializer); let mut var_latencyMs = <f64>::sse_decode(deserializer);
let mut var_nat = <String>::sse_decode(deserializer);
let mut var_connections = let mut var_connections =
<Vec<crate::api::simple::KVNodeConnectionStats>>::sse_decode(deserializer); <Vec<crate::api::simple::KVNodeConnectionStats>>::sse_decode(deserializer);
let mut var_version = <String>::sse_decode(deserializer); let mut var_version = <String>::sse_decode(deserializer);
@@ -554,6 +555,7 @@ impl SseDecode for crate::api::simple::KVNodeInfo {
hostname: var_hostname, hostname: var_hostname,
ipv4: var_ipv4, ipv4: var_ipv4,
latency_ms: var_latencyMs, latency_ms: var_latencyMs,
nat: var_nat,
connections: var_connections, connections: var_connections,
version: var_version, version: var_version,
cost: var_cost, cost: var_cost,
@@ -819,6 +821,7 @@ impl flutter_rust_bridge::IntoDart for crate::api::simple::KVNodeInfo {
self.hostname.into_into_dart().into_dart(), self.hostname.into_into_dart().into_dart(),
self.ipv4.into_into_dart().into_dart(), self.ipv4.into_into_dart().into_dart(),
self.latency_ms.into_into_dart().into_dart(), self.latency_ms.into_into_dart().into_dart(),
self.nat.into_into_dart().into_dart(),
self.connections.into_into_dart().into_dart(), self.connections.into_into_dart().into_dart(),
self.version.into_into_dart().into_dart(), self.version.into_into_dart().into_dart(),
self.cost.into_into_dart().into_dart(), self.cost.into_into_dart().into_dart(),
@@ -964,6 +967,7 @@ impl SseEncode for crate::api::simple::KVNodeInfo {
<String>::sse_encode(self.hostname, serializer); <String>::sse_encode(self.hostname, serializer);
<String>::sse_encode(self.ipv4, serializer); <String>::sse_encode(self.ipv4, serializer);
<f64>::sse_encode(self.latency_ms, serializer); <f64>::sse_encode(self.latency_ms, serializer);
<String>::sse_encode(self.nat, serializer);
<Vec<crate::api::simple::KVNodeConnectionStats>>::sse_encode(self.connections, serializer); <Vec<crate::api::simple::KVNodeConnectionStats>>::sse_encode(self.connections, serializer);
<String>::sse_encode(self.version, serializer); <String>::sse_encode(self.version, serializer);
<i32>::sse_encode(self.cost, serializer); <i32>::sse_encode(self.cost, serializer);
@@ -1087,56 +1091,56 @@ mod io {
flutter_rust_bridge::frb_generated_boilerplate_io!(); flutter_rust_bridge::frb_generated_boilerplate_io!();
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo( pub extern "C" fn frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<MyNodeInfo>>::increment_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<MyNodeInfo>>::increment_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo( pub extern "C" fn frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMyNodeInfo(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<MyNodeInfo>>::decrement_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<MyNodeInfo>>::decrement_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo( pub extern "C" fn frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerInfo>>::increment_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerInfo>>::increment_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo( pub extern "C" fn frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerInfo(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerInfo>>::decrement_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerInfo>>::decrement_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair( pub extern "C" fn frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerRoutePair>>::increment_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerRoutePair>>::increment_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair( pub extern "C" fn frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerPeerRoutePair(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerRoutePair>>::decrement_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<PeerRoutePair>>::decrement_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute( pub extern "C" fn frbgen_astral_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>::increment_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>::increment_strong_count(ptr as _);
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn frbgen_ASTRAL_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute( pub extern "C" fn frbgen_astral_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRoute(
ptr: *const std::ffi::c_void, ptr: *const std::ffi::c_void,
) { ) {
MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>::decrement_strong_count(ptr as _); MoiArc::<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>::decrement_strong_count(ptr as _);
+30
View File
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:astral/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="astral">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>astral</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
{
"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"
}
]
}
+5
View File
@@ -75,6 +75,11 @@ 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)
+8
View File
@@ -33,6 +33,14 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
# Add dependency libraries and include directories. Add any application-specific # Add dependency libraries and include directories. Add any application-specific
# dependencies here. # dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
# 添加资源复制指令到可执行目标所在目录
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_CURRENT_SOURCE_DIR}/../../assets/dlls"
"${CMAKE_INSTALL_PREFIX}/"
COMMENT "Copying DLL files to output directory"
VERBATIM
)
target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+7
View File
@@ -1,5 +1,12 @@
<?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>