26 Commits
Author SHA1 Message Date
会做饭的二哈andGitHub c4209ea813 Merge pull request #1 from EasyTier/Android_test
安卓端测试
2025-04-03 15:44:56 +08:00
会做饭的二哈andGitHub dbf4168e13 Update README.md 2025-03-20 15:51:58 +08:00
会做饭的二哈 c4c7d93ca8 11 2025-03-19 18:32:20 +08:00
会做饭的二哈 086c3a7818 refactor(widgets): 将 WindowControls 从 StatelessWidget 转换为 StatefulWidget
为了支持窗口最大化状态的管理,将 WindowControls 从 StatelessWidget 转换为 StatefulWidget。添加了窗口最大化状态监听和手动状态更新逻辑,以确保按钮图标和窗口状态同步。
2025-03-18 22:38:23 +08:00
会做饭的二哈 afcf43bcf7 Merge branch 'main' of https://github.com/ldoubil/astral 2025-03-18 21:50:49 +08:00
会做饭的二哈 f19853a49e chore: 更新版本号至1.0.6 2025-03-18 21:50:44 +08:00
会做饭的二哈andGitHub 92e9d0fc15 Update dart.yml 2025-03-18 21:49:56 +08:00
会做饭的二哈andGitHub 0d567ce686 Update dart.yml 2025-03-18 21:47:55 +08:00
会做饭的二哈 912dc263b0 refactor(settings): 将公共服务器列表从底部表单改为对话框
修改了公共服务器列表的显示方式,从底部表单改为对话框,简化了界面结构并提升了用户体验。同时,移除了不必要的约束和重复代码。
2025-03-18 21:40:05 +08:00
会做饭的二哈 38acea1bf5 feat: 添加窗口标题并优化Windows应用配置
- 在`windowconfiguration.dart`中添加窗口标题"Astral"并确保窗口管理器初始化
- 在`runner.exe.manifest`中添加UTF-8编码、长路径支持和SegmentHeap堆类型配置
- 在`Runner.rc`中更新应用描述、内部名称、原始文件名和产品名称为"Astral"
- 在`main.cpp`中实现单实例应用逻辑并更新窗口标题为"Astral"
2025-03-18 21:00:17 +08:00
会做饭的二哈 16d31c01fc fix: 将丢包率显示保留两位小数 2025-03-18 20:25:03 +08:00
会做饭的二哈andGitHub 48c1894cc4 Update dart.yml 2025-03-18 17:54:37 +08:00
会做饭的二哈andGitHub 5f66d65fe4 Update dart.yml 2025-03-18 17:53:08 +08:00
会做饭的二哈andGitHub ad875e0e8a Update dart.yml 2025-03-18 17:50:35 +08:00
会做饭的二哈andGitHub 1ff1067b98 Update dart.yml 2025-03-18 17:31:10 +08:00
会做饭的二哈andGitHub fca3d3b17f Update dart.yml 2025-03-18 17:27:41 +08:00
会做饭的二哈andGitHub 5646d550cb Update dart.yml 2025-03-18 17:27:30 +08:00
会做饭的二哈andGitHub fa8885bec3 Update dart.yml 2025-03-18 17:22:26 +08:00
会做饭的二哈andGitHub 27bd2b0c1b Update dart.yml 2025-03-18 17:17:54 +08:00
会做饭的二哈 0fef772a01 refactor(front.dart): 优化数据压缩算法的赋值逻辑
将 `dataCompressAlgo` 的赋值逻辑从简单的字符串映射改为数值映射,以提高代码的可读性和维护性。具体修改包括将 "Invalid" 映射为 0,"None" 映射为 1,"Zstd" 映射为 2,其他情况默认为 1。
2025-03-18 17:12:55 +08:00
会做饭的二哈andGitHub 87e6e6d70d Update dart.yml 2025-03-18 17:02:26 +08:00
会做饭的二哈andGitHub e6049383bb Update dart.yml 2025-03-18 16:40:16 +08:00
会做饭的二哈andGitHub d506555741 构建.yml 2025-03-18 16:37:12 +08:00
会做饭的二哈 01a4d1a81e 1. 将原有的Provider状态管理替换为Riverpod,提升状态管理效率
2. 重构AppConfig类,使用Hive进行持久化存储
3. 新增AdvancedConfig类,用于管理高级配置项
4. 优化服务器列表和节点状态的管理逻辑
5. 添加多个Provider来管理不同模块的状态
6. 更新依赖项,添加Riverpod及相关依赖
2025-03-18 16:28:59 +08:00
会做饭的二哈 ca49264ad6 自动验证配置文件 2025-03-17 23:19:31 +08:00
会做饭的二哈 99ed31f992 自动验证配置文件 2025-03-17 23:19:25 +08:00
49 changed files with 4331 additions and 736 deletions
+69
View File
@@ -0,0 +1,69 @@
name: Dart and Flutter Build
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v1.0.0)'
required: true
type: string
jobs:
build:
runs-on: windows-latest
# 调整 GITHUB_TOKEN 的权限
permissions:
contents: write # 允许写入仓库内容(上传文件)
steps:
- uses: actions/checkout@v4
# 安装 Flutter
- name: Install Flutter
run: |
echo "Cloning Flutter stable branch..."
git clone https://github.com/flutter/flutter.git -b stable
echo "Adding Flutter to PATH..."
echo "$env:GITHUB_WORKSPACE\flutter\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
echo "Flutter installation completed."
# 安装 Flutter 依赖
- name: Install Flutter dependencies
run: |
echo "Fetching Flutter dependencies..."
flutter pub get
echo "Flutter dependencies installed."
# 编译 Flutter Windows 应用
- name: Build Flutter Windows Release
run: |
echo "Building Flutter Windows Release..."
flutter build windows --release
echo "Flutter Windows Release build completed."
# 压缩 build\windows\x64\runner\Release 目录
- name: Compress Release Directory
run: |
echo "Compressing Release directory..."
$releaseDir = "$env:GITHUB_WORKSPACE\build\windows\x64\runner\Release"
$zipFile = "$env:GITHUB_WORKSPACE\release.zip"
Compress-Archive -Path $releaseDir -DestinationPath $zipFile
echo "Release directory compressed to $zipFile."
# 上传压缩包作为 Artifact
- name: Upload Release Artifact
uses: actions/upload-artifact@v4
with:
name: release
path: ${{ github.workspace }}/release.zip
# 创建 GitHub Release 并上传压缩包
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: Release ${{ inputs.version }}
draft: true
files: ${{ github.workspace }}/release.zip
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ inputs.version }}
+3
View File
@@ -15,6 +15,9 @@ migration:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
- platform: android
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# User provided section
+3
View File
@@ -1,5 +1,8 @@
# AstralET 游戏联机工具
[临时发布位置](https://github.com/ldoubil/astral)
[![GitHub](https://img.shields.io/badge/GitHub-AstralET-blue)](https://github.com/ldoubil/astral)
+14
View File
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
+56
View File
@@ -0,0 +1,56 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.astral"
compileSdk = flutter.compileSdkVersion
// ndkVersion = flutter.ndkVersion
ndkVersion = "27.2.12479018"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
// 启用 core library desugaring
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
// 添加 buildFeatures 配置
buildFeatures {
buildConfig = true
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.astral"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
// 添加 dependencies 块
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
}
flutter {
source = "../.."
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+45
View File
@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="astral"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package com.example.astral
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+21
View File
@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
+25
View File
@@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")
+3 -3
View File
@@ -4,7 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:system_tray/system_tray.dart';
import 'dart:io';
import 'screens/Home.dart';
import 'config/themeconfiguration.dart';
import 'config/themeconfiguration.dart' as theme_config;
import 'config/app_config.dart';
// 定义应用程序的主要StatefulWidget
@@ -114,7 +114,7 @@ class _MyAppState extends State<MyApp> {
],
// Insert this line
supportedLocales: const [Locale("zh", "CN"), Locale("en", "US")],
theme: ThemeConfig.getLightTheme(
theme: theme_config.ThemeConfig.getLightTheme(
useMaterial3: useMaterial3,
seedColor: _seedColor,
).copyWith(
@@ -124,7 +124,7 @@ class _MyAppState extends State<MyApp> {
primaryTextTheme: Typography.material2021().black.apply(
fontFamily: 'MiSans',
)),
darkTheme: ThemeConfig.getDarkTheme(
darkTheme: theme_config.ThemeConfig.getDarkTheme(
useMaterial3: useMaterial3,
seedColor: _seedColor,
).copyWith(
+821 -117
View File
@@ -1,177 +1,881 @@
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'cof.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
// 配置模型基类
abstract class ConfigModel {
Map<String, dynamic> toJson();
String get configKey; // 配置在存储中的键名
}
// 添加高级配置类
class AdvancedConfig implements ConfigModel {
final String defaultProtocol;
final String devName;
final bool enableEncryption;
final bool enableIpv6;
final int mtu;
final bool latencyFirst;
final bool enableExitNode;
final bool proxyForwardBySystem;
final bool noTun;
final bool useSmoltcp;
final String relayNetworkWhitelist;
final bool disableP2p;
final bool relayAllPeerRpc;
final bool disableUdpHolePunching;
final bool multiThread;
final String dataCompressAlgo;
final bool bindDevice;
final bool enableKcpProxy;
final bool disableKcpInput;
final bool disableRelayKcp;
@override
String get configKey => 'advanced';
AdvancedConfig({
this.defaultProtocol = "tcp",
this.devName = "",
this.enableEncryption = true,
this.enableIpv6 = true,
this.mtu = 1380,
this.latencyFirst = false,
this.enableExitNode = false,
this.proxyForwardBySystem = false,
this.noTun = false,
this.useSmoltcp = false,
this.relayNetworkWhitelist = "*",
this.disableP2p = false,
this.relayAllPeerRpc = false,
this.disableUdpHolePunching = false,
this.multiThread = true,
this.dataCompressAlgo = "None",
this.bindDevice = true,
this.enableKcpProxy = false,
this.disableKcpInput = false,
this.disableRelayKcp = true,
});
factory AdvancedConfig.fromJson(Map<String, dynamic> json) {
return AdvancedConfig(
defaultProtocol: json['defaultProtocol'] ?? "tcp",
devName: json['devName'] ?? "",
enableEncryption: json['enableEncryption'] ?? true,
enableIpv6: json['enableIpv6'] ?? true,
mtu: json['mtu'] ?? 1380,
latencyFirst: json['latencyFirst'] ?? false,
enableExitNode: json['enableExitNode'] ?? false,
proxyForwardBySystem: json['proxyForwardBySystem'] ?? false,
noTun: json['noTun'] ?? false,
useSmoltcp: json['useSmoltcp'] ?? false,
relayNetworkWhitelist: json['relayNetworkWhitelist'] ?? "*",
disableP2p: json['disableP2p'] ?? false,
relayAllPeerRpc: json['relayAllPeerRpc'] ?? false,
disableUdpHolePunching: json['disableUdpHolePunching'] ?? false,
multiThread: json['multiThread'] ?? true,
dataCompressAlgo: json['dataCompressAlgo'] ?? "None",
bindDevice: json['bindDevice'] ?? true,
enableKcpProxy: json['enableKcpProxy'] ?? false,
disableKcpInput: json['disableKcpInput'] ?? false,
disableRelayKcp: json['disableRelayKcp'] ?? true,
);
}
@override
Map<String, dynamic> toJson() => {
'defaultProtocol': defaultProtocol,
'devName': devName,
'enableEncryption': enableEncryption,
'enableIpv6': enableIpv6,
'mtu': mtu,
'latencyFirst': latencyFirst,
'enableExitNode': enableExitNode,
'proxyForwardBySystem': proxyForwardBySystem,
'noTun': noTun,
'useSmoltcp': useSmoltcp,
'relayNetworkWhitelist': relayNetworkWhitelist,
'disableP2p': disableP2p,
'relayAllPeerRpc': relayAllPeerRpc,
'disableUdpHolePunching': disableUdpHolePunching,
'multiThread': multiThread,
'dataCompressAlgo': dataCompressAlgo,
'bindDevice': bindDevice,
'enableKcpProxy': enableKcpProxy,
'disableKcpInput': disableKcpInput,
'disableRelayKcp': disableRelayKcp,
};
}
// 配置模型类 - 保持原有结构
class ThemeConfig implements ConfigModel {
final String mode;
final int seedColor;
@override
String get configKey => 'theme';
ThemeConfig({
this.mode = 'system',
this.seedColor = 0xFF2196F3, // Colors.blue.value
});
factory ThemeConfig.fromJson(Map<String, dynamic> json) {
return ThemeConfig(
mode: json['mode'] ?? 'system',
seedColor: json['seedColor'] ?? 0xFF2196F3,
);
}
@override
Map<String, dynamic> toJson() => {
'mode': mode,
'seedColor': seedColor,
};
}
class ServerConfig implements ConfigModel {
final String url;
final String name;
final bool selected;
final bool tcp;
final bool udp;
final bool ws;
final bool wss;
final bool quic;
@override
String get configKey => 'server';
ServerConfig({
required this.url,
required this.name,
this.selected = false,
this.tcp = true,
this.udp = true,
this.ws = false,
this.wss = false,
this.quic = false,
});
factory ServerConfig.fromJson(Map<String, dynamic> json) {
return ServerConfig(
url: json['url'] ?? '',
name: json['name'] ?? '',
selected: json['selected'] ?? false,
tcp: json['tcp'] ?? true,
udp: json['udp'] ?? true,
ws: json['ws'] ?? false,
wss: json['wss'] ?? false,
quic: json['quic'] ?? false,
);
}
Map<String, dynamic> toJson() => {
'url': url,
'name': name,
'selected': selected,
'tcp': tcp,
'udp': udp,
'ws': ws,
'wss': wss,
'quic': quic,
};
}
// 创建一个新的服务器列表配置类
class ServerListConfig implements ConfigModel {
final List<ServerConfig> servers;
@override
String get configKey => 'server';
ServerListConfig({
required this.servers,
});
factory ServerListConfig.fromJson(Map<String, dynamic> json) {
List<ServerConfig> serverList = [];
if (json['list'] is List) {
try {
serverList = (json['list'] as List).map((item) {
// 安全地将 Map<dynamic, dynamic> 转换为 Map<String, dynamic>
if (item is Map) {
Map<String, dynamic> serverMap = {};
item.forEach((key, value) {
if (key is String) {
serverMap[key] = value;
}
});
return ServerConfig.fromJson(serverMap);
}
// 如果不是 Map,返回默认服务器配置
return ServerConfig(
url: 'public.easytier.cn:11010',
name: '公共服务器',
selected: true,
tcp: true,
udp: true,
ws: false,
wss: false,
quic: false,
);
}).toList();
// 确保至少有一个服务器被选中
if (!serverList.any((server) => server.selected) &&
serverList.isNotEmpty) {
print('没有选中的服务器,将第一个服务器设为选中状态');
serverList[0] = ServerConfig(
url: serverList[0].url,
name: serverList[0].name,
selected: true,
tcp: serverList[0].tcp,
udp: serverList[0].udp,
ws: serverList[0].ws,
wss: serverList[0].wss,
quic: serverList[0].quic,
);
} else {
print('已有选中的服务器,保持原状');
}
} catch (e) {
print('解析服务器列表失败: $e');
// 解析失败时使用默认值
}
}
// 如果列表为空,添加默认服务器
if (serverList.isEmpty) {
print('服务器列表为空,添加默认服务器');
serverList = [
ServerConfig(
url: 'public.easytier.cn:11010',
name: '公共服务器',
selected: true,
tcp: true,
udp: true,
ws: false,
wss: false,
quic: false,
)
];
}
return ServerListConfig(servers: serverList);
}
@override
Map<String, dynamic> toJson() => {
'list': servers.map((server) => server.toJson()).toList(),
};
}
class RoomConfig implements ConfigModel {
final String name;
final String password;
@override
String get configKey => 'room';
RoomConfig({
this.name = 'kevin',
this.password = 'kevin',
});
factory RoomConfig.fromJson(Map<String, dynamic> json) {
return RoomConfig(
name: json['name'] ?? 'kevin',
password: json['password'] ?? 'kevin',
);
}
Map<String, dynamic> toJson() => {
'name': name,
'password': password,
};
}
class UserConfig implements ConfigModel {
final String name;
@override
String get configKey => 'user';
UserConfig({
required this.name,
});
factory UserConfig.fromJson(Map<String, dynamic> json) {
return UserConfig(
name: json['name'] ?? Platform.localHostname,
);
}
Map<String, dynamic> toJson() => {
'name': name,
};
}
class NetworkConfig implements ConfigModel {
final String virtualIP;
final bool dynamicIP;
@override
String get configKey => 'network';
NetworkConfig({
this.virtualIP = '',
this.dynamicIP = true,
});
factory NetworkConfig.fromJson(Map<String, dynamic> json) {
return NetworkConfig(
virtualIP: json['virtualIP'] ?? '',
dynamicIP: json['dynamicIP'] ?? true,
);
}
Map<String, dynamic> toJson() => {
'virtualIP': virtualIP,
'dynamicIP': dynamicIP,
};
}
class SystemConfig implements ConfigModel {
final bool closeToTray;
final bool enablePing;
@override
String get configKey => 'system';
SystemConfig({
this.closeToTray = true,
this.enablePing = true,
});
factory SystemConfig.fromJson(Map<String, dynamic> json) {
return SystemConfig(
closeToTray: json['closeToTray'] ?? true,
enablePing: json['enablePing'] ?? true,
);
}
Map<String, dynamic> toJson() => {
'closeToTray': closeToTray,
'enablePing': enablePing,
};
}
// 配置管理器
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
static late ConfigManager _configManager;
static late Box _configBox;
static late String _configDirectory;
static bool _initialized = false;
// 配置模型映射表
final Map<Type, ConfigModel> _configModels = {};
// 配置类型与工厂函数映射
final Map<Type, Function> _configFactories = {};
factory AppConfig() {
return _instance;
}
AppConfig._internal();
// 初始化配置
static Future<void> init() async {
// 获取可执行文件所在目录而不是当前工作目录
_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': [
{
'url': 'public.easytier.cn:11010',
'name': '公共服务器',
'selected': true,
}
],
},
'room': {
'name': 'kevin',
'password': 'kevin',
},
'user': {
'name': Platform.localHostname,
},
'network': {
'virtualIP': '',
'dynamicIP': true,
},
'system': {
'closeToTray': true,
'enablePing': true, // 添加全局ping开关默认配置
},
},
);
await _configManager.load();
// 在AppConfig类中添加静态方法setConfigDir
static void setConfigDir(String dirPath) {
_configDirectory = dirPath;
print('配置目录已设置为: $_configDirectory');
}
AppConfig._internal() {
// 注册所有配置类型
_registerConfig<ThemeConfig>(
(json) => ThemeConfig.fromJson(json),
ThemeConfig(),
);
_registerConfig<RoomConfig>(
(json) => RoomConfig.fromJson(json),
RoomConfig(),
);
_registerConfig<UserConfig>(
(json) => UserConfig.fromJson(json),
UserConfig(name: Platform.localHostname),
);
_registerConfig<NetworkConfig>(
(json) => NetworkConfig.fromJson(json),
NetworkConfig(),
);
_registerConfig<SystemConfig>(
(json) => SystemConfig.fromJson(json),
SystemConfig(),
);
// 注册服务器列表配置
_registerConfig<ServerListConfig>(
(json) => ServerListConfig.fromJson(json),
ServerListConfig(servers: [
ServerConfig(
url: 'public.easytier.cn:11010',
name: '公共服务器',
selected: true,
tcp: true,
udp: true,
ws: false,
wss: false,
quic: false,
)
]),
);
// 注册高级配置
_registerConfig<AdvancedConfig>(
(json) => AdvancedConfig.fromJson(json),
AdvancedConfig(),
);
}
// 注册配置类型
void _registerConfig<T extends ConfigModel>(
T Function(Map<String, dynamic>) fromJson,
T defaultValue,
) {
_configFactories[T] = fromJson;
_configModels[T] = defaultValue;
}
static Future<void> init() async {
if (_initialized) return;
try {
// 如果没有预先设置配置目录,则尝试获取可执行文件所在目录
if (!_configDirectory.isNotEmpty) {
_configDirectory = File(Platform.resolvedExecutable).parent.path;
}
print('配置目录: $_configDirectory');
// 初始化Hive
await Hive.initFlutter(_configDirectory);
print('Hive初始化完成');
// 打开配置Box
_configBox = await Hive.openBox('app_config');
print('配置Box打开成功,包含 ${_configBox.length} 个条目');
// 打印所有键值,用于调试
print('配置Box中的所有键: ${_configBox.keys.toList()}');
// 先创建实例并注册所有配置类型
// 由于AppConfig是单例模式,这里不需要显式创建实例
// 加载并验证配置
await _instance._loadAndValidateConfig();
print('配置加载和验证完成');
_initialized = true;
} catch (e) {
print('AppConfig初始化失败: $e');
// 尝试使用备用目录
try {
// 使用应用文档目录作为备用目录
final appDocDir = Directory(path.join(
Directory.current.path,
'config',
));
// 确保目录存在
if (!appDocDir.existsSync()) {
appDocDir.createSync(recursive: true);
}
_configDirectory = appDocDir.path;
print('尝试使用备用目录: $_configDirectory');
await Hive.initFlutter(_configDirectory);
_configBox = await Hive.openBox('app_config');
await _instance._loadAndValidateConfig();
_initialized = true;
print('使用备用目录初始化成功');
} catch (e2) {
print('备用初始化也失败: $e2');
rethrow;
}
}
}
// 加载并验证所有配置
Future<void> _loadAndValidateConfig() async {
print('开始加载配置...');
// 加载所有注册的配置
for (var entry in _configFactories.entries) {
final type = entry.key;
final fromJson = entry.value;
final defaultValue = _configModels[type]!;
final configKey = defaultValue.configKey;
print('加载配置: $configKey (${type.toString()})');
_configModels[type] = _loadConfig(
configKey,
fromJson as ConfigModel Function(Map<String, dynamic>),
defaultValue,
);
}
// 更新服务器列表缓存
_serverConfigs = (getModel<ServerListConfig>()).servers;
print('服务器列表缓存更新完成,共 ${_serverConfigs.length} 个服务器');
// 打印服务器列表,用于调试
for (var server in _serverConfigs) {
print(
'服务器: ${server.name} (${server.url}), 选中: ${server.selected}, 协议: TCP=${server.tcp}, UDP=${server.udp}, WS=${server.ws}, WSS=${server.wss}, QUIC=${server.quic}');
}
}
// 通用配置加载方法
T _loadConfig<T extends ConfigModel>(
String key,
T Function(Map<String, dynamic>) fromJson,
T defaultValue,
) {
final dynamic config = _configBox.get(key);
print('读取配置 $key: ${config != null ? '存在' : '不存在'}');
if (config != null) {
try {
print('配置内容: $config');
final result = fromJson(Map<String, dynamic>.from(config));
print('配置解析成功');
return result;
} catch (e) {
print('配置解析失败: $e');
// 如果解析失败,使用默认值
}
}
// 保存默认值
final defaultMap = defaultValue.toJson();
print('使用默认配置: $defaultMap');
_configBox.put(key, defaultMap);
return defaultValue;
}
// 获取配置
T getModel<T extends ConfigModel>() {
if (!_configModels.containsKey(T)) {
throw Exception('未注册的配置类型: $T');
}
return _configModels[T] as T;
}
// 更新配置
Future<void> updateModel<T extends ConfigModel>(T newConfig) async {
if (!_configModels.containsKey(T)) {
throw Exception('未注册的配置类型: $T');
}
_configModels[T] = newConfig;
await _configBox.put(newConfig.configKey, newConfig.toJson());
}
// 缓存的服务器配置列表
late List<ServerConfig> _serverConfigs;
// 保存服务器列表
Future<void> _saveServerList() async {
try {
// 打印调试信息
print('保存服务器列表: ${_serverConfigs.length} 个服务器');
// 确保配置模型已更新
await updateModel<ServerListConfig>(
ServerListConfig(servers: _serverConfigs));
// 强制刷新 Hive 存储
await _configBox.flush();
print('服务器列表保存完成');
} catch (e) {
print('保存服务器列表时出错: $e');
}
}
// 以下是为了保持原有API的getter和setter
// 主题设置
ThemeConfig get theme => getModel<ThemeConfig>();
ThemeMode get themeMode {
final String? value = _configManager.get<String>('theme.mode');
final String mode = theme.mode.toLowerCase();
return ThemeMode.values.firstWhere(
(mode) => mode.toString() == 'ThemeMode.$value',
(m) => m.toString().split('.').last.toLowerCase() == mode,
orElse: () => ThemeMode.system,
);
}
Future<void> setThemeMode(ThemeMode mode) async {
final modeString = mode.toString().split('.').last.toLowerCase();
_configManager.set('theme.mode', modeString);
await _configManager.save();
await updateModel<ThemeConfig>(ThemeConfig(
mode: modeString,
seedColor: theme.seedColor,
));
}
// 主题色设置
Color get seedColor {
final int? value = _configManager.get<int>('theme.seedColor');
return value != null ? Color(value) : Colors.blue;
}
Color get seedColor => Color(theme.seedColor);
Future<void> setSeedColor(Color color) async {
_configManager.set('theme.seedColor', color.value);
await _configManager.save();
await updateModel<ThemeConfig>(ThemeConfig(
mode: theme.mode,
seedColor: color.value,
));
}
// 服务器列表设置
List<Map<String, dynamic>> get serverList {
final dynamic value = _configManager.get('server.list');
if (value is List) {
return List<Map<String, dynamic>>.from(
value.map((item) => Map<String, dynamic>.from(item as Map)),
);
}
return [
{
'url': 'public.easytier.cn:11010',
'name': '公共服务器',
'selected': true,
}
];
return _serverConfigs.map((server) => server.toJson()).toList();
}
Future<void> setServerList(List<Map<String, dynamic>> servers) async {
_configManager.set('server.list', servers);
await _configManager.save();
try {
print('设置服务器列表: ${servers.length} 个服务器');
_serverConfigs = servers.map((server) {
// 确保所有必要的字段都存在
return ServerConfig(
url: server['url'] ?? '',
name: server['name'] ?? '',
selected: server['selected'] ?? false,
tcp: server['tcp'] ?? true,
udp: server['udp'] ?? true,
ws: server['ws'] ?? false,
wss: server['wss'] ?? false,
quic: server['quic'] ?? false,
);
}).toList();
await _saveServerList();
// 打印保存后的服务器列表,用于调试
print(
'服务器列表已更新: ${_serverConfigs.map((s) => '${s.name}(${s.url})').join(', ')}');
} catch (e) {
print('设置服务器列表时出错: $e');
}
}
// 房间名设
String get roomName {
return _configManager.get<String>('room.name') ?? 'kevin';
}
// 房间
RoomConfig get room => getModel<RoomConfig>();
String get roomName => room.name;
String get roomPassword => room.password;
Future<void> setRoomName(String name) async {
_configManager.set('room.name', name);
await _configManager.save();
}
// 房间密码设置
String get roomPassword {
return _configManager.get<String>('room.password') ?? 'kevin';
await updateModel<RoomConfig>(RoomConfig(
name: name,
password: room.password,
));
}
Future<void> setRoomPassword(String password) async {
_configManager.set('room.password', password);
await _configManager.save();
await updateModel<RoomConfig>(RoomConfig(
name: room.name,
password: password,
));
}
// 用户名设
String get username {
return _configManager.get<String>('user.name') ?? Platform.localHostname;
}
// 用户
UserConfig get user => getModel<UserConfig>();
String get username => user.name;
Future<void> setUsername(String name) async {
_configManager.set('user.name', name);
await _configManager.save();
await updateModel<UserConfig>(UserConfig(name: name));
}
// 虚拟IP设
String get virtualIP {
return _configManager.get<String>('network.virtualIP') ?? '';
}
// 网络配
NetworkConfig get network => getModel<NetworkConfig>();
String get virtualIP => network.virtualIP;
bool get dynamicIP => network.dynamicIP;
Future<void> setVirtualIP(String ip) async {
_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设置
bool get dynamicIP {
return _configManager.get<bool>('network.dynamicIP') ?? true;
await updateModel<NetworkConfig>(NetworkConfig(
virtualIP: ip,
dynamicIP: network.dynamicIP,
));
}
Future<void> setDynamicIP(bool enabled) async {
_configManager.set('network.dynamicIP', enabled);
await _configManager.save();
await updateModel<NetworkConfig>(NetworkConfig(
virtualIP: network.virtualIP,
dynamicIP: enabled,
));
}
// 全局ping开关设
bool get enablePing {
return _configManager.get<bool>('system.enablePing') ?? true;
// 系统配
SystemConfig get system => getModel<SystemConfig>();
bool get closeToTray => system.closeToTray;
bool get enablePing => system.enablePing;
Future<void> setCloseToTray(bool enabled) async {
await updateModel<SystemConfig>(SystemConfig(
closeToTray: enabled,
enablePing: system.enablePing,
));
}
Future<void> setEnablePing(bool enabled) async {
_configManager.set('system.enablePing', enabled);
await _configManager.save();
await updateModel<SystemConfig>(SystemConfig(
closeToTray: system.closeToTray,
enablePing: enabled,
));
}
// 通用配置获取方法
T? getConfig<T>(String key) {
return _configBox.get(key) as T?;
}
// 通用配置设置方法
Future<void> setConfig<T>(String key, T value) async {
await _configBox.put(key, value);
// 更新缓存的配置对象
await _loadAndValidateConfig();
}
// 高级配置
AdvancedConfig get advanced => getModel<AdvancedConfig>();
// 高级配置 getter
String get defaultProtocol => advanced.defaultProtocol;
String get devName => advanced.devName;
bool get enableEncryption => advanced.enableEncryption;
bool get enableIpv6 => advanced.enableIpv6;
int get mtu => advanced.mtu;
bool get latencyFirst => advanced.latencyFirst;
bool get enableExitNode => advanced.enableExitNode;
bool get proxyForwardBySystem => advanced.proxyForwardBySystem;
bool get noTun => advanced.noTun;
bool get useSmoltcp => advanced.useSmoltcp;
String get relayNetworkWhitelist => advanced.relayNetworkWhitelist;
bool get disableP2p => advanced.disableP2p;
bool get relayAllPeerRpc => advanced.relayAllPeerRpc;
bool get disableUdpHolePunching => advanced.disableUdpHolePunching;
bool get multiThread => advanced.multiThread;
String get dataCompressAlgo => advanced.dataCompressAlgo;
bool get bindDevice => advanced.bindDevice;
bool get enableKcpProxy => advanced.enableKcpProxy;
bool get disableKcpInput => advanced.disableKcpInput;
bool get disableRelayKcp => advanced.disableRelayKcp;
// 高级配置 setter 方法
Future<void> setDefaultProtocol(String value) async {
await updateModel<AdvancedConfig>(AdvancedConfig(
defaultProtocol: value,
devName: advanced.devName,
enableEncryption: advanced.enableEncryption,
enableIpv6: advanced.enableIpv6,
mtu: advanced.mtu,
latencyFirst: advanced.latencyFirst,
enableExitNode: advanced.enableExitNode,
proxyForwardBySystem: advanced.proxyForwardBySystem,
noTun: advanced.noTun,
useSmoltcp: advanced.useSmoltcp,
relayNetworkWhitelist: advanced.relayNetworkWhitelist,
disableP2p: advanced.disableP2p,
relayAllPeerRpc: advanced.relayAllPeerRpc,
disableUdpHolePunching: advanced.disableUdpHolePunching,
multiThread: advanced.multiThread,
dataCompressAlgo: advanced.dataCompressAlgo,
bindDevice: advanced.bindDevice,
enableKcpProxy: advanced.enableKcpProxy,
disableKcpInput: advanced.disableKcpInput,
disableRelayKcp: advanced.disableRelayKcp,
));
}
Future<void> setDevName(String value) async {
await updateModel<AdvancedConfig>(AdvancedConfig(
defaultProtocol: advanced.defaultProtocol,
devName: value,
enableEncryption: advanced.enableEncryption,
enableIpv6: advanced.enableIpv6,
mtu: advanced.mtu,
latencyFirst: advanced.latencyFirst,
enableExitNode: advanced.enableExitNode,
proxyForwardBySystem: advanced.proxyForwardBySystem,
noTun: advanced.noTun,
useSmoltcp: advanced.useSmoltcp,
relayNetworkWhitelist: advanced.relayNetworkWhitelist,
disableP2p: advanced.disableP2p,
relayAllPeerRpc: advanced.relayAllPeerRpc,
disableUdpHolePunching: advanced.disableUdpHolePunching,
multiThread: advanced.multiThread,
dataCompressAlgo: advanced.dataCompressAlgo,
bindDevice: advanced.bindDevice,
enableKcpProxy: advanced.enableKcpProxy,
disableKcpInput: advanced.disableKcpInput,
disableRelayKcp: advanced.disableRelayKcp,
));
}
// 更新高级配置的通用方法
Future<void> updateAdvancedConfig({
String? defaultProtocol,
String? devName,
bool? enableEncryption,
bool? enableIpv6,
int? mtu,
bool? latencyFirst,
bool? enableExitNode,
bool? proxyForwardBySystem,
bool? noTun,
bool? useSmoltcp,
String? relayNetworkWhitelist,
bool? disableP2p,
bool? relayAllPeerRpc,
bool? disableUdpHolePunching,
bool? multiThread,
String? dataCompressAlgo,
bool? bindDevice,
bool? enableKcpProxy,
bool? disableKcpInput,
bool? disableRelayKcp,
}) async {
await updateModel<AdvancedConfig>(AdvancedConfig(
defaultProtocol: defaultProtocol ?? advanced.defaultProtocol,
devName: devName ?? advanced.devName,
enableEncryption: enableEncryption ?? advanced.enableEncryption,
enableIpv6: enableIpv6 ?? advanced.enableIpv6,
mtu: mtu ?? advanced.mtu,
latencyFirst: latencyFirst ?? advanced.latencyFirst,
enableExitNode: enableExitNode ?? advanced.enableExitNode,
proxyForwardBySystem:
proxyForwardBySystem ?? advanced.proxyForwardBySystem,
noTun: noTun ?? advanced.noTun,
useSmoltcp: useSmoltcp ?? advanced.useSmoltcp,
relayNetworkWhitelist:
relayNetworkWhitelist ?? advanced.relayNetworkWhitelist,
disableP2p: disableP2p ?? advanced.disableP2p,
relayAllPeerRpc: relayAllPeerRpc ?? advanced.relayAllPeerRpc,
disableUdpHolePunching:
disableUdpHolePunching ?? advanced.disableUdpHolePunching,
multiThread: multiThread ?? advanced.multiThread,
dataCompressAlgo: dataCompressAlgo ?? advanced.dataCompressAlgo,
bindDevice: bindDevice ?? advanced.bindDevice,
enableKcpProxy: enableKcpProxy ?? advanced.enableKcpProxy,
disableKcpInput: disableKcpInput ?? advanced.disableKcpInput,
disableRelayKcp: disableRelayKcp ?? advanced.disableRelayKcp,
));
}
}
+26 -1
View File
@@ -146,7 +146,13 @@ class ConfigManager {
for (final item in list) {
buffer.write('$prefix- ');
if (item is Map) {
_writeMap(item.cast<String, dynamic>(), buffer, indent + 1);
if (item.isEmpty) {
buffer.writeln('{}');
} else {
buffer.writeln();
// 注意这里使用了正确的缩进级别
_writeMapInList(item.cast<String, dynamic>(), buffer, indent + 1);
}
} else if (item is List) {
_writeList(item, buffer, indent + 1);
} else {
@@ -155,6 +161,25 @@ class ConfigManager {
}
}
/// 专门用于处理列表中的Map项,确保正确缩进
void _writeMapInList(
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));
}
});
}
/// 格式值处理
String _formatValue(dynamic value) {
if (value is String) return '"$value"';
+2
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
Future<void> setupWindow() async {
await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions(
size: Size(850, 520),
minimumSize: Size(300, 300),
@@ -9,6 +10,7 @@ Future<void> setupWindow() async {
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden, // 隐藏标题栏
title: "Astral", // 添加窗口标题
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
+17 -6
View File
@@ -1,26 +1,37 @@
import 'package:flutter/material.dart';
import 'package:astral/src/rust/frb_generated.dart';
import 'package:window_manager/window_manager.dart';
import 'dart:io' show Platform, Directory;
import 'package:path_provider/path_provider.dart';
import 'app.dart';
import 'config/windowconfiguration.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'config/app_config.dart';
import 'utils/kv_state.dart';
import 'package:provider/provider.dart'; // 添加这一行
import 'package:astral/utils/app_info.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化应用信息
await AppInfoUtil.init();
await windowManager.ensureInitialized();
// 设置窗口属性
await setupWindow();
// 为Android平台设置配置目录
if (Platform.isAndroid) {
final appDir = await getApplicationDocumentsDirectory();
AppConfig.setConfigDir(appDir.path);
}
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
await windowManager.ensureInitialized();
// 设置窗口属性
await setupWindow();
}
await AppConfig.init();
// 初始化应用信息
await RustLib.init();
runApp(
ChangeNotifierProvider(
create: (context) => KM(),
ProviderScope(
child: const MyApp(),
),
);
+175 -99
View File
@@ -1,13 +1,17 @@
// 导入必要的包
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:astral/config/app_config.dart';
import 'package:astral/src/rust/api/simple.dart';
import 'package:astral/src/rust/frb_generated.dart';
import 'package:astral/utils/kv_state.dart';
import 'package:astral/utils/app_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'dart:async';
import 'package:provider/provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; // 替换 provider 导入
import '../widgets/card.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import '../utils/runin.dart';
@@ -31,7 +35,7 @@ String _intToIpv4String(int addr) {
/// 首页组件
/// 用于显示应用的主页面,包含主题切换和问候功能
class HomePage extends StatefulWidget {
class HomePage extends ConsumerStatefulWidget {
// 主题模式切换回调函数
final Function toggleThemeMode;
// 主题色更改回调函数
@@ -48,10 +52,10 @@ class HomePage extends StatefulWidget {
});
@override
State<HomePage> createState() => _HomePageState();
ConsumerState<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
class _HomePageState extends ConsumerState<HomePage> {
int connectionTimeoutCounter = 0;
// 当前连接状态
ConnectionState _connectionState = ConnectionState.notStarted;
@@ -74,6 +78,7 @@ class _HomePageState extends State<HomePage> {
bool _isAutoIP = true; // 只用于控制IP自动/手动模式
List<String> Serverip = [""];
List<ServerConfig> Serveripz = [];
int _uploadBytes = 0;
int _downloadBytes = 0;
int _lastUploadBytes = 0;
@@ -123,38 +128,39 @@ class _HomePageState extends State<HomePage> {
_buildServerListCard,
_buildVersionInfoCard,
];
// 启动内存监控
}
// 添加焦点变化监听方法
void _onVirtualIPFocusChange() {
if (!_virtualIPFocusNode.hasFocus && !_isAutoIP) {
// 当失去焦点且不是自动IP模式时更新值
Provider.of<KM>(context, listen: false).virtualIP =
_virtualIPController.text;
ref
.read(virtualIPProvider.notifier)
.setVirtualIP(_virtualIPController.text);
}
}
// 添加用户名焦点变化监听方法
void _onUsernameFocusChange() {
if (!_usernameControllerFocusNode.hasFocus) {
Provider.of<KM>(context, listen: false).username =
_usernameController.text;
ref.read(usernameProvider.notifier).setUsername(_usernameController.text);
}
}
// 添加房间名焦点变化监听方法
void _onRoomNameFocusChange() {
if (!_roomNameControllerFocusNode.hasFocus) {
Provider.of<KM>(context, listen: false).roomName =
_roomNameController.text;
ref.read(roomNameProvider.notifier).setRoomName(_roomNameController.text);
}
}
// 添加房间密码焦点变化监听方法
void _onRoomPasswordFocusChange() {
if (!_roomPasswordControllerFocusNode.hasFocus) {
Provider.of<KM>(context, listen: false).roomPassword =
_roomPasswordController.text;
ref
.read(roomPasswordProvider.notifier)
.setRoomPassword(_roomPasswordController.text);
}
}
@@ -186,19 +192,84 @@ class _HomePageState extends State<HomePage> {
}
void toggleRunning() {
final km = Provider.of<KM>(context, listen: false);
setState(() {
isRunning = !isRunning;
if (isRunning) {
// 切换到连接中状态
_connectionState = ConnectionState.connecting;
//利用 Serveripz 重组
List<String> ssServerip = [];
print(ssServerip);
for (var item in Serveripz) {
if (item.tcp) {
ssServerip.add("tcp://" + item.url);
}
if (item.udp) {
ssServerip.add("udp://" + item.url);
}
if (item.ws) {
ssServerip.add("ws://" + item.url);
}
if (item.wss) {
ssServerip.add("wss://" + item.url);
}
if (item.quic) {
ssServerip.add("quic://" + item.url);
}
}
// 复制
createServer(
username: username,
enableDhcp: _isAutoIP,
specifiedIp: publicIP,
roomName: roomName,
roomPassword: roomPassword,
severurl: Serverip);
severurl: ssServerip,
flag: FlagsC(
defaultProtocol:
ref.read(advancedConfigProvider)['defaultProtocol'] ??
"tcp",
devName: ref.read(advancedConfigProvider)['devName'] ?? "",
enableEncryption:
ref.read(advancedConfigProvider)['enableEncryption'] ??
true,
enableIpv6:
ref.read(advancedConfigProvider)['enableIpv6'] ?? true,
mtu: ref.read(advancedConfigProvider)['mtu'] ?? 1380,
multiThread:
ref.read(advancedConfigProvider)['multiThread'] ?? true,
latencyFirst:
ref.read(advancedConfigProvider)['latencyFirst'] ?? false,
enableExitNode:
ref.read(advancedConfigProvider)['enableExitNode'] ?? false,
noTun: ref.read(advancedConfigProvider)['noTun'] ?? false,
useSmoltcp:
ref.read(advancedConfigProvider)['useSmoltcp'] ?? false,
relayNetworkWhitelist:
ref.read(advancedConfigProvider)['relayNetworkWhitelist'] ??
"*",
disableP2P:
ref.read(advancedConfigProvider)['disableP2p'] ?? false,
relayAllPeerRpc:
ref.read(advancedConfigProvider)['relayAllPeerRpc'] ??
false,
disableUdpHolePunching:
ref.read(advancedConfigProvider)['disableUdpHolePunching'] ??
false,
dataCompressAlgo: ref.read(advancedConfigProvider)['dataCompressAlgo'] ==
"Invalid"
? 0
: ref.read(advancedConfigProvider)['dataCompressAlgo'] == "None"
? 1
: ref.read(advancedConfigProvider)['dataCompressAlgo'] == "Zstd"
? 2
: 1,
bindDevice: ref.read(advancedConfigProvider)['bindDevice'] ?? true,
enableKcpProxy: ref.read(advancedConfigProvider)['enableKcpProxy'] ?? false,
disableKcpInput: ref.read(advancedConfigProvider)['disableKcpInput'] ?? false,
disableRelayKcp: ref.read(advancedConfigProvider)['disableRelayKcp'] ?? true,
proxyForwardBySystem: ref.read(advancedConfigProvider)['proxyForwardBySystem'] ?? false));
// 不再使用固定延迟模拟连接成功,而是通过定时检查IP来确定连接状态
timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
@@ -213,8 +284,7 @@ class _HomePageState extends State<HomePage> {
// 获取网络状态
final networkStatus = await getNetworkStatus();
final km = Provider.of<KM>(context, listen: false);
km.nodes = networkStatus.nodes;
ref.read(nodesProvider.notifier).setNodes(networkStatus.nodes);
// 更新网络流量数据
_updateNetworkStats(networkStatus.nodes);
@@ -223,7 +293,7 @@ class _HomePageState extends State<HomePage> {
if (version != null) {
String ipStr = _intToIpv4String(version);
if (publicIP != ipStr) {
km.virtualIP = ipStr;
ref.read(virtualIPProvider.notifier).setVirtualIP(ipStr);
}
// 如果当前状态还是连接中,则更新为已连接
@@ -245,8 +315,8 @@ class _HomePageState extends State<HomePage> {
});
closeAllServer();
// 清空玩家列表数据
Provider.of<KM>(context, listen: false).nodes = [];
// 清空玩家列表数据
ref.read(nodesProvider.notifier).setNodes([]);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
@@ -269,13 +339,12 @@ class _HomePageState extends State<HomePage> {
runningTime += const Duration(seconds: 1);
});
});
// 移除原来的Future.delayed模拟连接成功的代码
} else {
// 停止时重置状态
_connectionState = ConnectionState.notStarted;
closeAllServer();
// 清空玩家列表数据
Provider.of<KM>(context, listen: false).nodes = [];
ref.read(nodesProvider.notifier).setNodes([]);
timer?.cancel();
runningTime = Duration.zero;
// 重置网络统计数据
@@ -285,7 +354,6 @@ class _HomePageState extends State<HomePage> {
_lastDownloadBytes = 0;
uploadSpeed = 0;
downloadSpeed = 0;
//connectionTimeoutCounter
}
});
}
@@ -296,7 +364,7 @@ class _HomePageState extends State<HomePage> {
int totalUploadBytes = 0;
int totalDownloadBytes = 0;
String myIP = Provider.of<KM>(context, listen: false).virtualIP;
String myIP = ref.read(virtualIPProvider);
// 查找本机节点
for (var node in nodes) {
@@ -343,22 +411,22 @@ class _HomePageState extends State<HomePage> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
final km = Provider.of<KM>(context);
// 使用 Riverpod 读取数据
_roomNameController.value = TextEditingValue(
text: km.roomName,
text: ref.read(roomNameProvider),
selection: _roomNameController.selection,
);
_roomPasswordController.value = TextEditingValue(
text: km.roomPassword,
text: ref.read(roomPasswordProvider),
selection: _roomPasswordController.selection,
);
_usernameController.value = TextEditingValue(
text: km.username,
text: ref.read(usernameProvider),
selection: _usernameController.selection,
);
// 添加虚拟IP控制器的值同步
_virtualIPController.value = TextEditingValue(
text: km.virtualIP,
text: ref.read(virtualIPProvider),
selection: _virtualIPController.selection,
);
}
@@ -366,15 +434,17 @@ class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
publicIP = Provider.of<KM>(context).virtualIP;
_isAutoIP = Provider.of<KM>(context).dynamicIP; // 更新自动IP状态
//我的房间
roomName = Provider.of<KM>(context).roomName;
//我的密码
roomPassword = Provider.of<KM>(context).roomPassword;
//我的用户名
username = Provider.of<KM>(context).username;
Serverip = Provider.of<KM>(context).serverIP;
// 使用 Riverpod 读取数据
publicIP = ref.watch(virtualIPProvider);
_isAutoIP = ref.watch(dynamicIPProvider);
roomName = ref.watch(roomNameProvider);
roomPassword = ref.watch(roomPasswordProvider);
username = ref.watch(usernameProvider);
Serverip = ref.watch(serverIPProvider);
// 从 selectedServerProvider 获取服务器配置列表
final serverConfigs =
ref.watch(selectedServerProvider) as List<ServerConfig>;
Serveripz = serverConfigs;
// 使用 LayoutBuilder 来处理布局变化,同时保留状态
return Scaffold(
@@ -396,10 +466,7 @@ class _HomePageState extends State<HomePage> {
childCount: _cardBuilders.length,
itemBuilder: (context, index) {
// 直接从列表中获取构建函数并调用
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 100),
child: _cardBuilders[index](colorScheme),
);
return _cardBuilders[index](colorScheme);
},
),
),
@@ -576,53 +643,57 @@ class _HomePageState extends State<HomePage> {
// 添加服务器列表卡片
Widget _buildServerListCard(ColorScheme colorScheme) {
final km = Provider.of<KM>(context);
// 直接使用 serverIP 列表,而不是尝试访问 serverList
final serverUrls = km.serverIP;
return Consumer(
builder: (context, ref, child) {
// 直接使用 serverIP 列表
final serverUrls = ref.watch(serverIPProvider);
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题栏
Row(
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.dns, color: colorScheme.primary, size: 22),
const SizedBox(width: 8),
const Text('当前服务器',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
// 标题栏
Row(
children: [
Icon(Icons.dns, color: colorScheme.primary, size: 22),
const SizedBox(width: 8),
const Text('当前服务器',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 16),
// 当前选中的服务器
Wrap(
spacing: 8,
runSpacing: 8,
children: serverUrls.isEmpty ||
(serverUrls.length == 1 && serverUrls[0].isEmpty)
? [
Chip(
avatar: Icon(Icons.info_outline,
size: 16, color: colorScheme.error),
label: const Text('未选择服务器'),
backgroundColor:
colorScheme.errorContainer.withOpacity(0.3),
)
]
: serverUrls
.map((url) => Chip(
avatar: Icon(Icons.dns,
size: 16, color: colorScheme.primary),
label: Text(url),
backgroundColor: colorScheme.surfaceVariant,
))
.toList(),
),
],
),
const SizedBox(height: 16),
// 当前选中的服务器
Wrap(
spacing: 8,
runSpacing: 8,
children: serverUrls.isEmpty ||
(serverUrls.length == 1 && serverUrls[0].isEmpty)
? [
Chip(
avatar: Icon(Icons.info_outline,
size: 16, color: colorScheme.error),
label: const Text('未选择服务器'),
backgroundColor:
colorScheme.errorContainer.withOpacity(0.3),
)
]
: serverUrls
.map((url) => Chip(
avatar: Icon(Icons.dns,
size: 16, color: colorScheme.primary),
label: Text(url),
backgroundColor: colorScheme.surfaceVariant,
))
.toList(),
),
],
),
);
},
);
}
@@ -716,8 +787,7 @@ class _HomePageState extends State<HomePage> {
// 优化用户信息卡片
Widget _buildUserInfoCard(ColorScheme colorScheme) {
final km = Provider.of<KM>(context, listen: false);
final isValidIP = _isAutoIP || _isValidIPv4(km.virtualIP);
final isValidIP = _isAutoIP || _isValidIPv4(ref.watch(virtualIPProvider));
return FloatingCard(
colorScheme: colorScheme,
@@ -762,8 +832,9 @@ class _HomePageState extends State<HomePage> {
enabled: _connectionState != ConnectionState.connected,
onEditingComplete: () {
// 改为完成编辑时更新
Provider.of<KM>(context, listen: false).username =
_usernameController.text;
ref
.read(usernameProvider.notifier)
.setUsername(_usernameController.text);
},
decoration: InputDecoration(
labelText: '用户名',
@@ -789,8 +860,9 @@ class _HomePageState extends State<HomePage> {
onEditingComplete: () {
// 添加完成编辑回调
if (!_isAutoIP) {
Provider.of<KM>(context, listen: false).virtualIP =
_virtualIPController.text;
ref
.read(virtualIPProvider.notifier)
.setVirtualIP(_virtualIPController.text);
}
},
decoration: InputDecoration(
@@ -812,10 +884,14 @@ class _HomePageState extends State<HomePage> {
setState(() {
_isAutoIP = value;
});
km.dynamicIP = value;
ref
.read(dynamicIPProvider.notifier)
.setDynamicIP(value);
// 切换模式时同步最新值
if (!value) {
km.virtualIP = _virtualIPController.text;
ref
.read(virtualIPProvider.notifier)
.setVirtualIP(_virtualIPController.text);
}
}
: null,
@@ -846,7 +922,6 @@ class _HomePageState extends State<HomePage> {
// 优化房间信息卡片
Widget _buildRoomInfoCard(ColorScheme colorScheme) {
final km = Provider.of<KM>(context, listen: false);
return FloatingCard(
colorScheme: colorScheme,
maxWidth: 600,
@@ -890,8 +965,9 @@ class _HomePageState extends State<HomePage> {
enabled: _connectionState != ConnectionState.connected,
onEditingComplete: () {
// 改为完成编辑时更新
Provider.of<KM>(context, listen: false).roomName =
_roomNameController.text;
ref
.read(roomNameProvider.notifier)
.setRoomName(_roomNameController.text);
},
decoration: InputDecoration(
labelText: '房间名称',
@@ -908,9 +984,9 @@ class _HomePageState extends State<HomePage> {
focusNode: _roomPasswordControllerFocusNode, // 添加焦点节点
enabled: _connectionState != ConnectionState.connected,
onEditingComplete: () {
// 改为完成编辑时更新
Provider.of<KM>(context, listen: false).roomPassword =
_roomPasswordController.text;
ref
.read(roomPasswordProvider.notifier)
.setRoomPassword(_roomPasswordController.text);
},
obscureText: true,
decoration: InputDecoration(
+17 -14
View File
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; // 添加这一行导入剪贴板服务
import 'package:astral/utils/kv_state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; // 替换 provider 导入
import '../widgets/card.dart';
/// 玩家信息模型类
@@ -37,14 +37,17 @@ class PlayerInfo {
/// 房间页面组件
/// 用于显示所有玩家的信息
class RoomPage extends StatefulWidget {
class RoomPage extends ConsumerStatefulWidget {
// 修改为 ConsumerStatefulWidget
const RoomPage({super.key});
@override
State<RoomPage> createState() => _RoomPageState();
ConsumerState<RoomPage> createState() =>
_RoomPageState(); // 修改为 ConsumerState
}
class _RoomPageState extends State<RoomPage> {
class _RoomPageState extends ConsumerState<RoomPage> {
// 修改为 ConsumerState
List<PlayerInfo> players = [];
List<PlayerInfo> filteredPlayers = []; // 添加过滤后的玩家列表
bool isLoading = true;
@@ -88,6 +91,7 @@ class _RoomPageState extends State<RoomPage> {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// 使用 Riverpod 监听节点数据
return Scaffold(
appBar: AppBar(
@@ -109,11 +113,10 @@ class _RoomPageState extends State<RoomPage> {
),
],
),
body: Consumer<KM>(
builder: (context, km, child) {
// 当 KM 更新时,这个 builder 会被重新调用
body: Builder(
builder: (context) {
// 异步处理数据
_processNodeData(km);
_processNodeData();
if (isLoading) {
return Center(
@@ -203,9 +206,9 @@ class _RoomPageState extends State<RoomPage> {
}
// 处理节点数据 - 合并了原来的两个相似方法
Future<void> _processNodeData(KM km) async {
Future<void> _processNodeData() async {
try {
final nodes = await km.nodes; // 获取最新的节点信息
final nodes = await ref.read(nodesProvider); // 获取最新的节点信息
// 将节点数据转换为PlayerInfo对象
List<PlayerInfo> nodePlayerInfos = [];
@@ -216,8 +219,8 @@ class _RoomPageState extends State<RoomPage> {
int downloadSpeed = 0;
int sentPackets = 0;
int receivedPackets = 0;
String connectionType =
_mapConnectionType(node.cost, node.ipv4, km.virtualIP);
String connectionType = _mapConnectionType(
node.cost, node.ipv4, ref.read(virtualIPProvider));
// 如果有连接信息,计算网络统计数据
if (node.connections.isNotEmpty) {
@@ -382,7 +385,7 @@ class _RoomPageState extends State<RoomPage> {
_buildInfoRow(
Icons.error_outline,
'丢包率',
'${player.packetLossRate}%',
'${player.packetLossRate.toStringAsFixed(2)}%', // 修改这里,保留2位小数
colorScheme,
valueColor: _getPacketLossColor(player.packetLossRate),
),
@@ -551,7 +554,7 @@ class _RoomPageState extends State<RoomPage> {
_buildInfoRow(
Icons.error_outline,
'丢包率',
'${player.packetLossRate}%',
'${player.packetLossRate.toStringAsFixed(2)}%', // 修改这里,保留2位小数
colorScheme,
valueColor: _getPacketLossColor(player.packetLossRate),
),
+1367 -203
View File
File diff suppressed because it is too large Load Diff
+101 -2
View File
@@ -37,14 +37,16 @@ Future<void> createServer(
required String specifiedIp,
required String roomName,
required String roomPassword,
required List<String> severurl}) =>
required List<String> severurl,
required FlagsC flag}) =>
RustLib.instance.api.crateApiSimpleCreateServer(
username: username,
enableDhcp: enableDhcp,
specifiedIp: specifiedIp,
roomName: roomName,
roomPassword: roomPassword,
severurl: severurl);
severurl: severurl,
flag: flag);
Future<void> closeAllServer() =>
RustLib.instance.api.crateApiSimpleCloseAllServer();
@@ -63,6 +65,103 @@ abstract class PeerRoutePair implements RustOpaqueInterface {}
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<Route>>
abstract class Route implements RustOpaqueInterface {}
class FlagsC {
final String defaultProtocol;
final String devName;
final bool enableEncryption;
final bool enableIpv6;
final int mtu;
final bool latencyFirst;
final bool enableExitNode;
final bool noTun;
final bool useSmoltcp;
final String relayNetworkWhitelist;
final bool disableP2P;
final bool relayAllPeerRpc;
final bool disableUdpHolePunching;
/// string ipv6_listener = 14; \[deprecated = true\]; use -l udp://\[::\]:12345 instead
final bool multiThread;
final int dataCompressAlgo;
final bool bindDevice;
final bool enableKcpProxy;
final bool disableKcpInput;
final bool disableRelayKcp;
final bool proxyForwardBySystem;
const FlagsC({
required this.defaultProtocol,
required this.devName,
required this.enableEncryption,
required this.enableIpv6,
required this.mtu,
required this.latencyFirst,
required this.enableExitNode,
required this.noTun,
required this.useSmoltcp,
required this.relayNetworkWhitelist,
required this.disableP2P,
required this.relayAllPeerRpc,
required this.disableUdpHolePunching,
required this.multiThread,
required this.dataCompressAlgo,
required this.bindDevice,
required this.enableKcpProxy,
required this.disableKcpInput,
required this.disableRelayKcp,
required this.proxyForwardBySystem,
});
@override
int get hashCode =>
defaultProtocol.hashCode ^
devName.hashCode ^
enableEncryption.hashCode ^
enableIpv6.hashCode ^
mtu.hashCode ^
latencyFirst.hashCode ^
enableExitNode.hashCode ^
noTun.hashCode ^
useSmoltcp.hashCode ^
relayNetworkWhitelist.hashCode ^
disableP2P.hashCode ^
relayAllPeerRpc.hashCode ^
disableUdpHolePunching.hashCode ^
multiThread.hashCode ^
dataCompressAlgo.hashCode ^
bindDevice.hashCode ^
enableKcpProxy.hashCode ^
disableKcpInput.hashCode ^
disableRelayKcp.hashCode ^
proxyForwardBySystem.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FlagsC &&
runtimeType == other.runtimeType &&
defaultProtocol == other.defaultProtocol &&
devName == other.devName &&
enableEncryption == other.enableEncryption &&
enableIpv6 == other.enableIpv6 &&
mtu == other.mtu &&
latencyFirst == other.latencyFirst &&
enableExitNode == other.enableExitNode &&
noTun == other.noTun &&
useSmoltcp == other.useSmoltcp &&
relayNetworkWhitelist == other.relayNetworkWhitelist &&
disableP2P == other.disableP2P &&
relayAllPeerRpc == other.relayAllPeerRpc &&
disableUdpHolePunching == other.disableUdpHolePunching &&
multiThread == other.multiThread &&
dataCompressAlgo == other.dataCompressAlgo &&
bindDevice == other.bindDevice &&
enableKcpProxy == other.enableKcpProxy &&
disableKcpInput == other.disableKcpInput &&
disableRelayKcp == other.disableRelayKcp &&
proxyForwardBySystem == other.proxyForwardBySystem;
}
class KVNetworkStatus {
final BigInt totalNodes;
final List<KVNodeInfo> nodes;
+146 -4
View File
@@ -85,7 +85,8 @@ abstract class RustLibApi extends BaseApi {
required String specifiedIp,
required String roomName,
required String roomPassword,
required List<String> severurl});
required List<String> severurl,
required FlagsC flag});
Future<String> crateApiSimpleEasytierVersion();
@@ -174,7 +175,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required String specifiedIp,
required String roomName,
required String roomPassword,
required List<String> severurl}) {
required List<String> severurl,
required FlagsC flag}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
@@ -184,6 +186,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(roomName, serializer);
sse_encode_String(roomPassword, serializer);
sse_encode_list_String(severurl, serializer);
sse_encode_box_autoadd_flags_c(flag, serializer);
pdeCallFfi(generalizedFrbRustBinding, serializer,
funcId: 2, port: port_);
},
@@ -198,7 +201,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
specifiedIp,
roomName,
roomPassword,
severurl
severurl,
flag
],
apiImpl: this,
));
@@ -212,7 +216,8 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
"specifiedIp",
"roomName",
"roomPassword",
"severurl"
"severurl",
"flag"
],
);
@@ -515,6 +520,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as bool;
}
@protected
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_flags_c(raw);
}
@protected
double dco_decode_f_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -527,6 +538,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as double;
}
@protected
FlagsC dco_decode_flags_c(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 20)
throw Exception('unexpected arr length: expect 20 but see ${arr.length}');
return FlagsC(
defaultProtocol: dco_decode_String(arr[0]),
devName: dco_decode_String(arr[1]),
enableEncryption: dco_decode_bool(arr[2]),
enableIpv6: dco_decode_bool(arr[3]),
mtu: dco_decode_u_32(arr[4]),
latencyFirst: dco_decode_bool(arr[5]),
enableExitNode: dco_decode_bool(arr[6]),
noTun: dco_decode_bool(arr[7]),
useSmoltcp: dco_decode_bool(arr[8]),
relayNetworkWhitelist: dco_decode_String(arr[9]),
disableP2P: dco_decode_bool(arr[10]),
relayAllPeerRpc: dco_decode_bool(arr[11]),
disableUdpHolePunching: dco_decode_bool(arr[12]),
multiThread: dco_decode_bool(arr[13]),
dataCompressAlgo: dco_decode_i_32(arr[14]),
bindDevice: dco_decode_bool(arr[15]),
enableKcpProxy: dco_decode_bool(arr[16]),
disableKcpInput: dco_decode_bool(arr[17]),
disableRelayKcp: dco_decode_bool(arr[18]),
proxyForwardBySystem: dco_decode_bool(arr[19]),
);
}
@protected
int dco_decode_i_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -657,6 +698,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
@protected
int dco_decode_u_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as int;
}
@protected
BigInt dco_decode_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@@ -766,6 +813,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getUint8() != 0;
}
@protected
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_flags_c(deserializer));
}
@protected
double sse_decode_f_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -778,6 +831,52 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getFloat64();
}
@protected
FlagsC sse_decode_flags_c(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_defaultProtocol = sse_decode_String(deserializer);
var var_devName = sse_decode_String(deserializer);
var var_enableEncryption = sse_decode_bool(deserializer);
var var_enableIpv6 = sse_decode_bool(deserializer);
var var_mtu = sse_decode_u_32(deserializer);
var var_latencyFirst = sse_decode_bool(deserializer);
var var_enableExitNode = sse_decode_bool(deserializer);
var var_noTun = sse_decode_bool(deserializer);
var var_useSmoltcp = sse_decode_bool(deserializer);
var var_relayNetworkWhitelist = sse_decode_String(deserializer);
var var_disableP2P = sse_decode_bool(deserializer);
var var_relayAllPeerRpc = sse_decode_bool(deserializer);
var var_disableUdpHolePunching = sse_decode_bool(deserializer);
var var_multiThread = sse_decode_bool(deserializer);
var var_dataCompressAlgo = sse_decode_i_32(deserializer);
var var_bindDevice = sse_decode_bool(deserializer);
var var_enableKcpProxy = sse_decode_bool(deserializer);
var var_disableKcpInput = sse_decode_bool(deserializer);
var var_disableRelayKcp = sse_decode_bool(deserializer);
var var_proxyForwardBySystem = sse_decode_bool(deserializer);
return FlagsC(
defaultProtocol: var_defaultProtocol,
devName: var_devName,
enableEncryption: var_enableEncryption,
enableIpv6: var_enableIpv6,
mtu: var_mtu,
latencyFirst: var_latencyFirst,
enableExitNode: var_enableExitNode,
noTun: var_noTun,
useSmoltcp: var_useSmoltcp,
relayNetworkWhitelist: var_relayNetworkWhitelist,
disableP2P: var_disableP2P,
relayAllPeerRpc: var_relayAllPeerRpc,
disableUdpHolePunching: var_disableUdpHolePunching,
multiThread: var_multiThread,
dataCompressAlgo: var_dataCompressAlgo,
bindDevice: var_bindDevice,
enableKcpProxy: var_enableKcpProxy,
disableKcpInput: var_disableKcpInput,
disableRelayKcp: var_disableRelayKcp,
proxyForwardBySystem: var_proxyForwardBySystem);
}
@protected
int sse_decode_i_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -940,6 +1039,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (var_field0, var_field1);
}
@protected
int sse_decode_u_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getUint32();
}
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1049,6 +1154,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putUint8(self ? 1 : 0);
}
@protected
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_flags_c(self, serializer);
}
@protected
void sse_encode_f_32(double self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1061,6 +1172,31 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putFloat64(self);
}
@protected
void sse_encode_flags_c(FlagsC self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(self.defaultProtocol, serializer);
sse_encode_String(self.devName, serializer);
sse_encode_bool(self.enableEncryption, serializer);
sse_encode_bool(self.enableIpv6, serializer);
sse_encode_u_32(self.mtu, serializer);
sse_encode_bool(self.latencyFirst, serializer);
sse_encode_bool(self.enableExitNode, serializer);
sse_encode_bool(self.noTun, serializer);
sse_encode_bool(self.useSmoltcp, serializer);
sse_encode_String(self.relayNetworkWhitelist, serializer);
sse_encode_bool(self.disableP2P, serializer);
sse_encode_bool(self.relayAllPeerRpc, serializer);
sse_encode_bool(self.disableUdpHolePunching, serializer);
sse_encode_bool(self.multiThread, serializer);
sse_encode_i_32(self.dataCompressAlgo, serializer);
sse_encode_bool(self.bindDevice, serializer);
sse_encode_bool(self.enableKcpProxy, serializer);
sse_encode_bool(self.disableKcpInput, serializer);
sse_encode_bool(self.disableRelayKcp, serializer);
sse_encode_bool(self.proxyForwardBySystem, serializer);
}
@protected
void sse_encode_i_32(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@@ -1183,6 +1319,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
self.$2, serializer);
}
@protected
void sse_encode_u_32(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putUint32(self);
}
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
+27
View File
@@ -77,12 +77,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool dco_decode_bool(dynamic raw);
@protected
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw);
@protected
double dco_decode_f_32(dynamic raw);
@protected
double dco_decode_f_64(dynamic raw);
@protected
FlagsC dco_decode_flags_c(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@@ -130,6 +136,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
) dco_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
BigInt dco_decode_u_64(dynamic raw);
@@ -188,12 +197,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer);
@protected
double sse_decode_f_32(SseDeserializer deserializer);
@protected
double sse_decode_f_64(SseDeserializer deserializer);
@protected
FlagsC sse_decode_flags_c(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@@ -242,6 +257,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
) sse_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer);
@@ -300,12 +318,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer);
@protected
void sse_encode_f_32(double self, SseSerializer serializer);
@protected
void sse_encode_f_64(double self, SseSerializer serializer);
@protected
void sse_encode_flags_c(FlagsC self, SseSerializer serializer);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@@ -355,6 +379,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
sse_encode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
(List<PeerInfo>, List<Route>) self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer);
+27
View File
@@ -79,12 +79,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool dco_decode_bool(dynamic raw);
@protected
FlagsC dco_decode_box_autoadd_flags_c(dynamic raw);
@protected
double dco_decode_f_32(dynamic raw);
@protected
double dco_decode_f_64(dynamic raw);
@protected
FlagsC dco_decode_flags_c(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@@ -132,6 +138,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
) dco_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
BigInt dco_decode_u_64(dynamic raw);
@@ -190,12 +199,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
FlagsC sse_decode_box_autoadd_flags_c(SseDeserializer deserializer);
@protected
double sse_decode_f_32(SseDeserializer deserializer);
@protected
double sse_decode_f_64(SseDeserializer deserializer);
@protected
FlagsC sse_decode_flags_c(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@@ -244,6 +259,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
) sse_decode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer);
@@ -302,12 +320,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_flags_c(FlagsC self, SseSerializer serializer);
@protected
void sse_encode_f_32(double self, SseSerializer serializer);
@protected
void sse_encode_f_64(double self, SseSerializer serializer);
@protected
void sse_encode_flags_c(FlagsC self, SseSerializer serializer);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@@ -357,6 +381,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
sse_encode_record_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_peer_info_list_auto_owned_rust_opaque_flutter_rust_bridgefor_generated_rust_auto_opaque_inner_route(
(List<PeerInfo>, List<Route>) self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer);
+401 -68
View File
@@ -1,103 +1,436 @@
import 'package:astral/src/rust/api/simple.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../config/app_config.dart';
class KM extends ChangeNotifier {
final _config = AppConfig();
int _count = 0;
int get count => _count;
// 全局配置实例
final appConfigProvider = Provider<AppConfig>((ref) => AppConfig());
void increment() {
_count++;
notifyListeners(); // 通知监听器重建UI
}
// 计数器相关
class CountNotifier extends StateNotifier<int> {
CountNotifier() : super(0);
// 房间名
String get roomName => _config.roomName;
set roomName(String value) {
void increment() => state++;
}
final countProvider =
StateNotifierProvider<CountNotifier, int>((ref) => CountNotifier());
// 房间名相关
class RoomNameNotifier extends StateNotifier<String> {
final AppConfig _config;
RoomNameNotifier(this._config) : super(_config.roomName);
void setRoomName(String value) {
_config.setRoomName(value);
notifyListeners(); // 通知监听器重建UI
state = value;
}
}
//房间密码设置
String get roomPassword => _config.roomPassword;
set roomPassword(String value) {
final roomNameProvider = StateNotifierProvider<RoomNameNotifier, String>((ref) {
return RoomNameNotifier(ref.watch(appConfigProvider));
});
// 房间密码相关
class RoomPasswordNotifier extends StateNotifier<String> {
final AppConfig _config;
RoomPasswordNotifier(this._config) : super(_config.roomPassword);
void setRoomPassword(String value) {
_config.setRoomPassword(value);
notifyListeners();
state = value;
}
}
//用户名设置
String get username => _config.username;
set username(String value) {
final roomPasswordProvider =
StateNotifierProvider<RoomPasswordNotifier, String>((ref) {
return RoomPasswordNotifier(ref.watch(appConfigProvider));
});
// 用户名相关
class UsernameNotifier extends StateNotifier<String> {
final AppConfig _config;
UsernameNotifier(this._config) : super(_config.username);
void setUsername(String value) {
_config.setUsername(value);
notifyListeners();
state = value;
}
}
//虚拟IP设置
String get virtualIP => _config.virtualIP;
set virtualIP(String value) {
final usernameProvider = StateNotifierProvider<UsernameNotifier, String>((ref) {
return UsernameNotifier(ref.watch(appConfigProvider));
});
// 虚拟IP相关
class VirtualIPNotifier extends StateNotifier<String> {
final AppConfig _config;
VirtualIPNotifier(this._config) : super(_config.virtualIP);
void setVirtualIP(String value) {
_config.setVirtualIP(value);
notifyListeners();
state = value;
}
}
//动态获取IP设置
bool get dynamicIP => _config.dynamicIP;
set dynamicIP(bool value) {
final virtualIPProvider =
StateNotifierProvider<VirtualIPNotifier, String>((ref) {
return VirtualIPNotifier(ref.watch(appConfigProvider));
});
// 动态获取IP设置相关
class DynamicIPNotifier extends StateNotifier<bool> {
final AppConfig _config;
DynamicIPNotifier(this._config) : super(_config.dynamicIP);
void setDynamicIP(bool value) {
_config.setDynamicIP(value);
notifyListeners();
state = value;
}
}
//服务器列表
List<Map<String, dynamic>> get serverList => _config.serverList;
set serverList(List<Map<String, dynamic>> value) {
final dynamicIPProvider = StateNotifierProvider<DynamicIPNotifier, bool>((ref) {
return DynamicIPNotifier(ref.watch(appConfigProvider));
});
// 服务器列表相关
class ServerListNotifier extends StateNotifier<List<Map<String, dynamic>>> {
final AppConfig _config;
ServerListNotifier(this._config) : super(_config.serverList);
void setServerList(List<Map<String, dynamic>> value) {
List<Map<String, dynamic>> convertedList = value.map((item) {
return Map<String, dynamic>.from(item);
}).toList();
_config.setServerList(convertedList);
notifyListeners();
state = convertedList;
}
//获取选中的服务器
//获取选中的服务器
List<String> get serverIP {
try {
final selected =
serverList.where((server) => server['selected'] == true).toList();
if (selected.isEmpty && serverList.isNotEmpty) {
final firstServer = serverList.first;
if (firstServer['url'] is String) {
return [firstServer['url'] as String];
}
return [];
}
return selected
.where((server) => server['url'] is String)
.map((server) => server['url'] as String)
.toList();
} catch (e) {
debugPrint('获取服务器IP时出错: $e');
return [];
}
}
//设置服务器选中状态
void setServerSelected(String url, bool selected) {
final servers = serverList;
final servers = [...state];
for (var i = 0; i < servers.length; i++) {
if (servers[i]['url'] == url) {
servers[i]['selected'] = selected;
}
}
serverList = servers;
}
// 节点列表
List<KVNodeInfo> _nodes = [];
List<KVNodeInfo> get nodes => _nodes;
set nodes(List<KVNodeInfo> value) {
_nodes = value;
notifyListeners();
setServerList(servers);
}
}
final serverListProvider =
StateNotifierProvider<ServerListNotifier, List<Map<String, dynamic>>>(
(ref) {
return ServerListNotifier(ref.watch(appConfigProvider));
});
// 选中的服务器IP
final serverIPProvider = Provider<List<String>>((ref) {
final serverList = ref.watch(serverListProvider);
try {
final selected =
serverList.where((server) => server['selected'] == true).toList();
if (selected.isEmpty && serverList.isNotEmpty) {
final firstServer = serverList.first;
if (firstServer['url'] is String) {
return [firstServer['url'] as String];
}
return [];
}
return selected
.where((server) => server['url'] is String)
.map((server) => server['url'] as String)
.toList();
} catch (e) {
debugPrint('获取服务器IP时出错: $e');
return [];
}
});
//返回选中的服务器
final selectedServerProvider = Provider<List<ServerConfig>>((ref) {
final serverList = ref.watch(serverListProvider);
final selected =
serverList.where((server) => server['selected'] == true).toList();
if (selected.isEmpty) {
return [];
}
// 将选中的服务器转换为ServerConfig对象列表
return selected
.map((server) => ServerConfig(
url: server['url'] ?? '',
name: server['name'] ?? '',
selected: true,
tcp: server['tcp'] ?? true,
udp: server['udp'] ?? true,
ws: server['ws'] ?? false,
wss: server['wss'] ?? false,
quic: server['quic'] ?? false,
))
.toList();
});
// 节点列表相关
class NodesNotifier extends StateNotifier<List<KVNodeInfo>> {
NodesNotifier() : super([]);
void setNodes(List<KVNodeInfo> value) {
state = value;
}
}
final nodesProvider = StateNotifierProvider<NodesNotifier, List<KVNodeInfo>>(
(ref) => NodesNotifier());
// 高级配置相关
class AdvancedConfigNotifier extends StateNotifier<Map<String, dynamic>> {
final AppConfig _config;
AdvancedConfigNotifier(this._config)
: super({
'defaultProtocol': _config.advanced.defaultProtocol,
'devName': _config.advanced.devName,
'enableEncryption': _config.advanced.enableEncryption,
'enableIpv6': _config.advanced.enableIpv6,
'mtu': _config.advanced.mtu,
'latencyFirst': _config.advanced.latencyFirst,
'enableExitNode': _config.advanced.enableExitNode,
'proxyForwardBySystem': _config.advanced.proxyForwardBySystem,
'noTun': _config.advanced.noTun,
'useSmoltcp': _config.advanced.useSmoltcp,
'relayNetworkWhitelist': _config.advanced.relayNetworkWhitelist,
'disableP2p': _config.advanced.disableP2p,
'relayAllPeerRpc': _config.advanced.relayAllPeerRpc,
'disableUdpHolePunching': _config.advanced.disableUdpHolePunching,
'multiThread': _config.advanced.multiThread,
'dataCompressAlgo': _config.advanced.dataCompressAlgo,
'bindDevice': _config.advanced.bindDevice,
'enableKcpProxy': _config.advanced.enableKcpProxy,
'disableKcpInput': _config.advanced.disableKcpInput,
'disableRelayKcp': _config.advanced.disableRelayKcp,
});
// 更新单个配置项
Future<void> updateConfig(String key, dynamic value) async {
if (state.containsKey(key) && state[key] != value) {
final newState = {...state, key: value};
state = newState;
// 使用通用方法更新配置
await _config.updateAdvancedConfig(
defaultProtocol: key == 'defaultProtocol' ? value : null,
devName: key == 'devName' ? value : null,
enableEncryption: key == 'enableEncryption' ? value : null,
enableIpv6: key == 'enableIpv6' ? value : null,
mtu: key == 'mtu' ? value : null,
latencyFirst: key == 'latencyFirst' ? value : null,
enableExitNode: key == 'enableExitNode' ? value : null,
proxyForwardBySystem: key == 'proxyForwardBySystem' ? value : null,
noTun: key == 'noTun' ? value : null,
useSmoltcp: key == 'useSmoltcp' ? value : null,
relayNetworkWhitelist: key == 'relayNetworkWhitelist' ? value : null,
disableP2p: key == 'disableP2p' ? value : null,
relayAllPeerRpc: key == 'relayAllPeerRpc' ? value : null,
disableUdpHolePunching: key == 'disableUdpHolePunching' ? value : null,
multiThread: key == 'multiThread' ? value : null,
dataCompressAlgo: key == 'dataCompressAlgo' ? value : null,
bindDevice: key == 'bindDevice' ? value : null,
enableKcpProxy: key == 'enableKcpProxy' ? value : null,
disableKcpInput: key == 'disableKcpInput' ? value : null,
disableRelayKcp: key == 'disableRelayKcp' ? value : null,
);
}
}
// 批量更新配置
Future<void> updateMultipleConfigs(Map<String, dynamic> updates) async {
final newState = {...state, ...updates};
state = newState;
// 使用通用方法更新配置
await _config.updateAdvancedConfig(
defaultProtocol: updates.containsKey('defaultProtocol')
? updates['defaultProtocol']
: null,
devName: updates.containsKey('devName') ? updates['devName'] : null,
enableEncryption: updates.containsKey('enableEncryption')
? updates['enableEncryption']
: null,
enableIpv6:
updates.containsKey('enableIpv6') ? updates['enableIpv6'] : null,
mtu: updates.containsKey('mtu') ? updates['mtu'] : null,
latencyFirst:
updates.containsKey('latencyFirst') ? updates['latencyFirst'] : null,
enableExitNode: updates.containsKey('enableExitNode')
? updates['enableExitNode']
: null,
proxyForwardBySystem: updates.containsKey('proxyForwardBySystem')
? updates['proxyForwardBySystem']
: null,
noTun: updates.containsKey('noTun') ? updates['noTun'] : null,
useSmoltcp:
updates.containsKey('useSmoltcp') ? updates['useSmoltcp'] : null,
relayNetworkWhitelist: updates.containsKey('relayNetworkWhitelist')
? updates['relayNetworkWhitelist']
: null,
disableP2p:
updates.containsKey('disableP2p') ? updates['disableP2p'] : null,
relayAllPeerRpc: updates.containsKey('relayAllPeerRpc')
? updates['relayAllPeerRpc']
: null,
disableUdpHolePunching: updates.containsKey('disableUdpHolePunching')
? updates['disableUdpHolePunching']
: null,
multiThread:
updates.containsKey('multiThread') ? updates['multiThread'] : null,
dataCompressAlgo: updates.containsKey('dataCompressAlgo')
? updates['dataCompressAlgo']
: null,
bindDevice:
updates.containsKey('bindDevice') ? updates['bindDevice'] : null,
enableKcpProxy: updates.containsKey('enableKcpProxy')
? updates['enableKcpProxy']
: null,
disableKcpInput: updates.containsKey('disableKcpInput')
? updates['disableKcpInput']
: null,
disableRelayKcp: updates.containsKey('disableRelayKcp')
? updates['disableRelayKcp']
: null,
);
}
// 为每个配置项提供单独的更新方法
Future<void> setDefaultProtocol(String value) async =>
updateConfig('defaultProtocol', value);
Future<void> setDevName(String value) async => updateConfig('devName', value);
Future<void> setEnableEncryption(bool value) async =>
updateConfig('enableEncryption', value);
Future<void> setEnableIpv6(bool value) async =>
updateConfig('enableIpv6', value);
Future<void> setMtu(int value) async => updateConfig('mtu', value);
Future<void> setLatencyFirst(bool value) async =>
updateConfig('latencyFirst', value);
Future<void> setEnableExitNode(bool value) async =>
updateConfig('enableExitNode', value);
Future<void> setProxyForwardBySystem(bool value) async =>
updateConfig('proxyForwardBySystem', value);
Future<void> setNoTun(bool value) async => updateConfig('noTun', value);
Future<void> setUseSmoltcp(bool value) async =>
updateConfig('useSmoltcp', value);
Future<void> setRelayNetworkWhitelist(String value) async =>
updateConfig('relayNetworkWhitelist', value);
Future<void> setDisableP2p(bool value) async =>
updateConfig('disableP2p', value);
Future<void> setRelayAllPeerRpc(bool value) async =>
updateConfig('relayAllPeerRpc', value);
Future<void> setDisableUdpHolePunching(bool value) async =>
updateConfig('disableUdpHolePunching', value);
Future<void> setMultiThread(bool value) async =>
updateConfig('multiThread', value);
Future<void> setDataCompressAlgo(String value) async =>
updateConfig('dataCompressAlgo', value);
Future<void> setBindDevice(bool value) async =>
updateConfig('bindDevice', value);
Future<void> setEnableKcpProxy(bool value) async =>
updateConfig('enableKcpProxy', value);
Future<void> setDisableKcpInput(bool value) async =>
updateConfig('disableKcpInput', value);
Future<void> setDisableRelayKcp(bool value) async =>
updateConfig('disableRelayKcp', value);
}
final advancedConfigProvider =
StateNotifierProvider<AdvancedConfigNotifier, Map<String, dynamic>>((ref) {
return AdvancedConfigNotifier(ref.watch(appConfigProvider));
});
// 为每个高级配置项创建单独的Provider,方便在UI中使用
final defaultProtocolProvider = Provider<String>((ref) {
return ref.watch(advancedConfigProvider)['defaultProtocol'];
});
final devNameProvider = Provider<String>((ref) {
return ref.watch(advancedConfigProvider)['devName'];
});
final enableEncryptionProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['enableEncryption'];
});
final enableIpv6Provider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['enableIpv6'];
});
final mtuProvider = Provider<int>((ref) {
return ref.watch(advancedConfigProvider)['mtu'];
});
final latencyFirstProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['latencyFirst'];
});
final enableExitNodeProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['enableExitNode'];
});
final proxyForwardBySystemProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['proxyForwardBySystem'];
});
final noTunProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['noTun'];
});
final useSmoltcpProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['useSmoltcp'];
});
final relayNetworkWhitelistProvider = Provider<String>((ref) {
return ref.watch(advancedConfigProvider)['relayNetworkWhitelist'];
});
final disableP2pProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['disableP2p'];
});
final relayAllPeerRpcProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['relayAllPeerRpc'];
});
final disableUdpHolePunchingProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['disableUdpHolePunching'];
});
final multiThreadProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['multiThread'];
});
final dataCompressAlgoProvider = Provider<String>((ref) {
return ref.watch(advancedConfigProvider)['dataCompressAlgo'];
});
final bindDeviceProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['bindDevice'];
});
final enableKcpProxyProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['enableKcpProxy'];
});
final disableKcpInputProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['disableKcpInput'];
});
final disableRelayKcpProvider = Provider<bool>((ref) {
return ref.watch(advancedConfigProvider)['disableRelayKcp'];
});
+198
View File
@@ -0,0 +1,198 @@
import 'dart:convert';
// 主入口类
class StatusPageData {
final Config config;
final Incident incident;
final List<PublicGroup> publicGroupList;
StatusPageData({
required this.config,
required this.incident,
required this.publicGroupList,
});
factory StatusPageData.fromJson(Map<String, dynamic> json) => StatusPageData(
config: Config.fromJson(json['config']),
incident: Incident.fromJson(json['incident']),
publicGroupList: List<PublicGroup>.from(
json['publicGroupList'].map((x) => PublicGroup.fromJson(x))),
);
}
// 配置信息
class Config {
final String slug;
final String title;
final String description;
final String icon;
final String theme;
final bool published;
final bool showTags;
final String customCSS;
final String footerText;
final bool showPoweredBy;
final dynamic googleAnalyticsId;
final bool showCertificateExpiry;
Config({
required this.slug,
required this.title,
required this.description,
required this.icon,
required this.theme,
required this.published,
required this.showTags,
required this.customCSS,
required this.footerText,
required this.showPoweredBy,
this.googleAnalyticsId,
required this.showCertificateExpiry,
});
factory Config.fromJson(Map<String, dynamic> json) => Config(
slug: json['slug'],
title: json['title'],
description: json['description'],
icon: json['icon'],
theme: json['theme'],
published: json['published'],
showTags: json['showTags'],
customCSS: json['customCSS'],
footerText: json['footerText'],
showPoweredBy: json['showPoweredBy'],
googleAnalyticsId: json['googleAnalyticsId'],
showCertificateExpiry: json['showCertificateExpiry'],
);
}
// 事件信息
class Incident {
final int id;
final String style;
final String title;
final String content;
final int pin;
final DateTime createdDate;
final DateTime lastUpdatedDate;
Incident({
required this.id,
required this.style,
required this.title,
required this.content,
required this.pin,
required this.createdDate,
required this.lastUpdatedDate,
});
factory Incident.fromJson(Map<String, dynamic> json) => Incident(
id: json['id'],
style: json['style'],
title: json['title'],
content: json['content'],
pin: json['pin'],
createdDate: DateTime.parse(json['createdDate']),
lastUpdatedDate: DateTime.parse(json['lastUpdatedDate']),
);
}
// 公共服务器组
class PublicGroup {
final int id;
final String name;
final int weight;
final List<Monitor> monitorList;
PublicGroup({
required this.id,
required this.name,
required this.weight,
required this.monitorList,
});
factory PublicGroup.fromJson(Map<String, dynamic> json) => PublicGroup(
id: json['id'],
name: json['name'],
weight: json['weight'],
monitorList: List<Monitor>.from(
json['monitorList'].map((x) => Monitor.fromJson(x))),
);
}
// 监控项
class Monitor {
final int id;
final String name;
final int sendUrl;
final String type;
final List<Tag> tags;
final int? certExpiryDaysRemaining;
final bool? validCert;
Monitor({
required this.id,
required this.name,
required this.sendUrl,
required this.type,
required this.tags,
this.certExpiryDaysRemaining,
this.validCert,
});
factory Monitor.fromJson(Map<String, dynamic> json) => Monitor(
id: json['id'],
name: json['name'],
sendUrl: json['sendUrl'],
type: json['type'],
tags: List<Tag>.from(json['tags'].map((x) => Tag.fromJson(x))),
certExpiryDaysRemaining: json['certExpiryDaysRemaining'],
validCert: json['validCert'],
);
}
// 标签
class Tag {
final int id;
final int monitorId;
final int tagId;
final String value;
final String name;
final String color;
Tag({
required this.id,
required this.monitorId,
required this.tagId,
required this.value,
required this.name,
required this.color,
});
factory Tag.fromJson(Map<String, dynamic> json) => Tag(
id: json['id'],
monitorId: json['monitor_id'],
tagId: json['tag_id'],
value: json['value'],
name: json['name'],
color: json['color'],
);
}
// // 使用示例
// void main() {
// final jsonString = '''{/* 你的原始JSON数据 */}''';
// final data = StatusPageData.fromJson(jsonDecode(jsonString));
// // 示例:获取第一个服务器组的名称
// print('第一组名称: ${data.publicGroupList.first.name}');
// // 示例:列出所有可中转的服务器
// final transferableServers = data.publicGroupList
// .expand((group) => group.monitorList)
// .where((monitor) => monitor.tags.any((tag) => tag.name == '可中转'))
// .toList();
// print('可中转服务器数量: ${transferableServers.length}');
// }
+1
View File
@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:astral/utils/app_info.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:fl_chart/fl_chart.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
+1
View File
@@ -39,6 +39,7 @@ class _FloatingCardState extends State<FloatingCard> {
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: widget.maxWidth ?? double.infinity,
minHeight: widget.height ?? 74, // 默认最小高度为74
),
child: AnimatedContainer(
duration: widget.duration,
+64 -5
View File
@@ -16,11 +16,63 @@ NotificationMessage message = NotificationMessage.fromPluginTemplate(
// image: file_path
);
class WindowControls extends StatelessWidget {
class WindowControls extends StatefulWidget {
const WindowControls({super.key});
@override
State<WindowControls> createState() => _WindowControlsState();
}
class _WindowControlsState extends State<WindowControls> with WindowListener {
bool _isMaximized = false;
@override
void initState() {
super.initState();
windowManager.addListener(this);
_init();
}
@override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}
void _init() async {
final isMaximized = await windowManager.isMaximized();
if (mounted) {
setState(() {
_isMaximized = isMaximized;
});
}
}
@override
void onWindowMaximize() {
if (mounted) {
setState(() {
_isMaximized = true;
});
print("窗口已最大化: $_isMaximized"); // 添加调试信息
}
}
@override
void onWindowUnmaximize() {
if (mounted) {
setState(() {
_isMaximized = false;
});
print("窗口已还原: $_isMaximized"); // 添加调试信息
}
}
@override
Widget build(BuildContext context) {
// 添加调试信息
print("构建窗口控制按钮,当前最大化状态: $_isMaximized");
return Row(
children: [
IconButton(
@@ -29,12 +81,19 @@ class WindowControls extends StatelessWidget {
tooltip: '最小化',
),
IconButton(
icon: const Icon(Icons.crop_square),
icon: Icon(_isMaximized ? Icons.filter_none : Icons.crop_square),
onPressed: () async {
if (await windowManager.isMaximized()) {
windowManager.unmaximize();
if (_isMaximized) {
await windowManager.unmaximize();
} else {
windowManager.maximize();
await windowManager.maximize();
}
// 手动更新状态,以防事件监听不工作
if (mounted) {
final isMaximized = await windowManager.isMaximized();
setState(() {
_isMaximized = isMaximized;
});
}
},
tooltip: '最大化/还原',
+329 -140
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -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.4
version: 1.0.6
environment:
sdk: ^3.5.4
@@ -52,11 +52,19 @@ dependencies:
flutter_local_notifications: ^19.0.0
windows_notification: ^1.3.0
fl_chart: ^0.70.2
sqflite: ^2.4.2
path: ^1.9.1
flutter_riverpod: ^2.6.1
floor_generator: ^1.5.0
build_runner: ^2.4.15
hive: ^2.2.3
hive_flutter: ^1.1.0
http: ^1.1.0
path_provider: ^2.1.5
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.6
json_serializable: ^6.7.1
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
@@ -64,6 +72,8 @@ dev_dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
hive_generator: ^2.0.0
build_runner: ^2.3.3
integration_test:
sdk: flutter
+1
View File
@@ -21,3 +21,4 @@ easytier = { path = "./easytier" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
+2 -2
View File
@@ -30,6 +30,6 @@ pub const WIN_SERVICE_WORK_DIR_REG_KEY: &str = "SOFTWARE\\EasyTier\\Service\\Wor
pub const EASYTIER_VERSION: &str = git_version::git_version!(
args = ["--abbrev=8", "--always", "--dirty=~"],
prefix = concat!(env!("CARGO_PKG_VERSION"), "-"),
suffix = "",
fallback = "astral"
suffix = "-astral",
fallback = concat!(env!("CARGO_PKG_VERSION"), "-astral")
);
+48 -5
View File
@@ -2,7 +2,7 @@ pub use std::collections::BTreeMap;
//use BTreeMap
use dashmap::DashMap;
pub use easytier::common::global_ctx::{EventBusSubscriber, GlobalCtxEvent};
use easytier::common::scoped_task::ScopedTask;
use easytier::common::{config::Flags, scoped_task::ScopedTask};
pub use easytier::{
common::config::{ConfigLoader, TomlConfigLoader},
launcher::NetworkInstance,
@@ -158,6 +158,7 @@ fn create_config() -> TomlConfigLoader {
cfg.set_listeners(vec![
"tcp://0.0.0.0:11010".to_string().parse().unwrap(),
"udp://0.0.0.0:11010".to_string().parse().unwrap(),
"tcp://[::]:11010".to_string().parse().unwrap(),
]);
// cfg.set_inst_name("default".to_string());
// cfg.set_inst_name(name);
@@ -566,6 +567,30 @@ pub fn get_running_info() -> String {
.unwrap_or_else(|| "{}".to_string())
}
pub struct FlagsC {
pub default_protocol: String,
pub dev_name: String,
pub enable_encryption: bool,
pub enable_ipv6: bool,
pub mtu: u32,
pub latency_first: bool,
pub enable_exit_node: bool,
pub no_tun: bool,
pub use_smoltcp: bool,
pub relay_network_whitelist: String,
pub disable_p2p: bool,
pub relay_all_peer_rpc: bool,
pub disable_udp_hole_punching: bool,
/// string ipv6_listener = 14; \[deprecated = true\]; use -l udp://\[::\]:12345 instead
pub multi_thread: bool,
pub data_compress_algo: i32,
pub bind_device: bool,
pub enable_kcp_proxy: bool,
pub disable_kcp_input: bool,
pub disable_relay_kcp: bool,
pub proxy_forward_by_system: bool,
}
// 创建服务器
pub fn create_server(
username: String,
@@ -574,6 +599,7 @@ pub fn create_server(
room_name: String,
room_password: String,
severurl: Vec<String>,
flag:FlagsC,
) {
RT.spawn(async move {
// 创建一个示例配置
@@ -581,6 +607,26 @@ pub fn create_server(
cfg.set_hostname(Option::from(username));
cfg.set_dhcp(enable_dhcp);
let mut flags = cfg.get_flags();
flags.default_protocol = flag.default_protocol;
flags.dev_name = flag.dev_name;
flags.enable_encryption = flag.enable_encryption;
flags.enable_ipv6 = flag.enable_ipv6;
flags.mtu = flag.mtu;
flags.latency_first = flag.latency_first;
flags.enable_exit_node = flag.enable_exit_node;
flags.no_tun = flag.no_tun;
flags.use_smoltcp = flag.use_smoltcp;
flags.relay_network_whitelist = flag.relay_network_whitelist;
flags.disable_p2p = flag.disable_p2p;
flags.relay_all_peer_rpc = flag.relay_all_peer_rpc;
flags.disable_udp_hole_punching = flag.disable_udp_hole_punching;
flags.multi_thread = flag.multi_thread;
flags.data_compress_algo = flag.data_compress_algo;
flags.bind_device = flag.bind_device;
flags.enable_kcp_proxy = flag.enable_kcp_proxy;
flags.disable_kcp_input = flag.disable_kcp_input;
flags.disable_relay_kcp = flag.disable_relay_kcp;
flags.proxy_forward_by_system = flag.proxy_forward_by_system;
// flags.dev_name = "astral".to_string();
cfg.set_flags(flags);
// 创建TCP和UDP连接配置列表
@@ -588,10 +634,7 @@ pub fn create_server(
// 为每个服务器URL创建TCP和UDP配置
for url in severurl {
peer_configs.push(PeerConfig {
uri: format!("tcp://{}", url).parse().unwrap(),
});
peer_configs.push(PeerConfig {
uri: format!("udp://{}", url).parse().unwrap(),
uri: format!("{}", url).parse().unwrap(),
});
}
+124
View File
@@ -108,6 +108,7 @@ fn wire__crate__api__simple__create_server_impl(
let api_room_name = <String>::sse_decode(&mut deserializer);
let api_room_password = <String>::sse_decode(&mut deserializer);
let api_severurl = <Vec<String>>::sse_decode(&mut deserializer);
let api_flag = <crate::api::simple::FlagsC>::sse_decode(&mut deserializer);
deserializer.end();
move |context| {
transform_result_sse::<_, ()>((move || {
@@ -119,6 +120,7 @@ fn wire__crate__api__simple__create_server_impl(
api_room_name,
api_room_password,
api_severurl,
api_flag,
);
})?;
Ok(output_ok)
@@ -510,6 +512,54 @@ impl SseDecode for f64 {
}
}
impl SseDecode for crate::api::simple::FlagsC {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_defaultProtocol = <String>::sse_decode(deserializer);
let mut var_devName = <String>::sse_decode(deserializer);
let mut var_enableEncryption = <bool>::sse_decode(deserializer);
let mut var_enableIpv6 = <bool>::sse_decode(deserializer);
let mut var_mtu = <u32>::sse_decode(deserializer);
let mut var_latencyFirst = <bool>::sse_decode(deserializer);
let mut var_enableExitNode = <bool>::sse_decode(deserializer);
let mut var_noTun = <bool>::sse_decode(deserializer);
let mut var_useSmoltcp = <bool>::sse_decode(deserializer);
let mut var_relayNetworkWhitelist = <String>::sse_decode(deserializer);
let mut var_disableP2P = <bool>::sse_decode(deserializer);
let mut var_relayAllPeerRpc = <bool>::sse_decode(deserializer);
let mut var_disableUdpHolePunching = <bool>::sse_decode(deserializer);
let mut var_multiThread = <bool>::sse_decode(deserializer);
let mut var_dataCompressAlgo = <i32>::sse_decode(deserializer);
let mut var_bindDevice = <bool>::sse_decode(deserializer);
let mut var_enableKcpProxy = <bool>::sse_decode(deserializer);
let mut var_disableKcpInput = <bool>::sse_decode(deserializer);
let mut var_disableRelayKcp = <bool>::sse_decode(deserializer);
let mut var_proxyForwardBySystem = <bool>::sse_decode(deserializer);
return crate::api::simple::FlagsC {
default_protocol: var_defaultProtocol,
dev_name: var_devName,
enable_encryption: var_enableEncryption,
enable_ipv6: var_enableIpv6,
mtu: var_mtu,
latency_first: var_latencyFirst,
enable_exit_node: var_enableExitNode,
no_tun: var_noTun,
use_smoltcp: var_useSmoltcp,
relay_network_whitelist: var_relayNetworkWhitelist,
disable_p2p: var_disableP2P,
relay_all_peer_rpc: var_relayAllPeerRpc,
disable_udp_hole_punching: var_disableUdpHolePunching,
multi_thread: var_multiThread,
data_compress_algo: var_dataCompressAlgo,
bind_device: var_bindDevice,
enable_kcp_proxy: var_enableKcpProxy,
disable_kcp_input: var_disableKcpInput,
disable_relay_kcp: var_disableRelayKcp,
proxy_forward_by_system: var_proxyForwardBySystem,
};
}
}
impl SseDecode for i32 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -667,6 +717,13 @@ impl SseDecode for (Vec<PeerInfo>, Vec<Route>) {
}
}
impl SseDecode for u32 {
// 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_u32::<NativeEndian>().unwrap()
}
}
impl SseDecode for u64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -790,6 +847,40 @@ impl flutter_rust_bridge::IntoIntoDart<FrbWrapper<Route>> for Route {
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::api::simple::FlagsC {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.default_protocol.into_into_dart().into_dart(),
self.dev_name.into_into_dart().into_dart(),
self.enable_encryption.into_into_dart().into_dart(),
self.enable_ipv6.into_into_dart().into_dart(),
self.mtu.into_into_dart().into_dart(),
self.latency_first.into_into_dart().into_dart(),
self.enable_exit_node.into_into_dart().into_dart(),
self.no_tun.into_into_dart().into_dart(),
self.use_smoltcp.into_into_dart().into_dart(),
self.relay_network_whitelist.into_into_dart().into_dart(),
self.disable_p2p.into_into_dart().into_dart(),
self.relay_all_peer_rpc.into_into_dart().into_dart(),
self.disable_udp_hole_punching.into_into_dart().into_dart(),
self.multi_thread.into_into_dart().into_dart(),
self.data_compress_algo.into_into_dart().into_dart(),
self.bind_device.into_into_dart().into_dart(),
self.enable_kcp_proxy.into_into_dart().into_dart(),
self.disable_kcp_input.into_into_dart().into_dart(),
self.disable_relay_kcp.into_into_dart().into_dart(),
self.proxy_forward_by_system.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::simple::FlagsC {}
impl flutter_rust_bridge::IntoIntoDart<crate::api::simple::FlagsC> for crate::api::simple::FlagsC {
fn into_into_dart(self) -> crate::api::simple::FlagsC {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::api::simple::KVNetworkStatus {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
@@ -964,6 +1055,32 @@ impl SseEncode for f64 {
}
}
impl SseEncode for crate::api::simple::FlagsC {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<String>::sse_encode(self.default_protocol, serializer);
<String>::sse_encode(self.dev_name, serializer);
<bool>::sse_encode(self.enable_encryption, serializer);
<bool>::sse_encode(self.enable_ipv6, serializer);
<u32>::sse_encode(self.mtu, serializer);
<bool>::sse_encode(self.latency_first, serializer);
<bool>::sse_encode(self.enable_exit_node, serializer);
<bool>::sse_encode(self.no_tun, serializer);
<bool>::sse_encode(self.use_smoltcp, serializer);
<String>::sse_encode(self.relay_network_whitelist, serializer);
<bool>::sse_encode(self.disable_p2p, serializer);
<bool>::sse_encode(self.relay_all_peer_rpc, serializer);
<bool>::sse_encode(self.disable_udp_hole_punching, serializer);
<bool>::sse_encode(self.multi_thread, serializer);
<i32>::sse_encode(self.data_compress_algo, serializer);
<bool>::sse_encode(self.bind_device, serializer);
<bool>::sse_encode(self.enable_kcp_proxy, serializer);
<bool>::sse_encode(self.disable_kcp_input, serializer);
<bool>::sse_encode(self.disable_relay_kcp, serializer);
<bool>::sse_encode(self.proxy_forward_by_system, serializer);
}
}
impl SseEncode for i32 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -1082,6 +1199,13 @@ impl SseEncode for (Vec<PeerInfo>, Vec<Route>) {
}
}
impl SseEncode for u32 {
// 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_u32::<NativeEndian>(self).unwrap();
}
}
impl SseEncode for u64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
+58 -58
View File
@@ -6,7 +6,7 @@ packages:
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "67.0.0"
adaptive_number:
@@ -14,7 +14,7 @@ packages:
description:
name: adaptive_number
sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
analyzer:
@@ -22,7 +22,7 @@ packages:
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
args:
@@ -30,7 +30,7 @@ packages:
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
async:
@@ -38,7 +38,7 @@ packages:
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
@@ -46,7 +46,7 @@ packages:
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
collection:
@@ -54,7 +54,7 @@ packages:
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
@@ -62,7 +62,7 @@ packages:
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
coverage:
@@ -70,7 +70,7 @@ packages:
description:
name: coverage
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
crypto:
@@ -78,7 +78,7 @@ packages:
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
ed25519_edwards:
@@ -86,7 +86,7 @@ packages:
description:
name: ed25519_edwards
sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
file:
@@ -94,7 +94,7 @@ packages:
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
@@ -102,7 +102,7 @@ packages:
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
frontend_server_client:
@@ -110,7 +110,7 @@ packages:
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
github:
@@ -118,7 +118,7 @@ packages:
description:
name: github
sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "9.17.0"
glob:
@@ -126,7 +126,7 @@ packages:
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
hex:
@@ -134,7 +134,7 @@ packages:
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
http:
@@ -142,7 +142,7 @@ packages:
description:
name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_multi_server:
@@ -150,7 +150,7 @@ packages:
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
http_parser:
@@ -158,7 +158,7 @@ packages:
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
io:
@@ -166,7 +166,7 @@ packages:
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
@@ -174,7 +174,7 @@ packages:
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation:
@@ -182,7 +182,7 @@ packages:
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
lints:
@@ -190,7 +190,7 @@ packages:
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
logging:
@@ -198,7 +198,7 @@ packages:
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher:
@@ -206,7 +206,7 @@ packages:
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.12.17"
meta:
@@ -214,7 +214,7 @@ packages:
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
@@ -222,7 +222,7 @@ packages:
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
node_preamble:
@@ -230,7 +230,7 @@ packages:
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config:
@@ -238,7 +238,7 @@ packages:
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
@@ -246,7 +246,7 @@ packages:
description:
name: path
sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
petitparser:
@@ -254,7 +254,7 @@ packages:
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "5.4.0"
pool:
@@ -262,7 +262,7 @@ packages:
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
@@ -270,7 +270,7 @@ packages:
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shelf:
@@ -278,7 +278,7 @@ packages:
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_packages_handler:
@@ -286,7 +286,7 @@ packages:
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
@@ -294,7 +294,7 @@ packages:
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_web_socket:
@@ -302,7 +302,7 @@ packages:
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
source_map_stack_trace:
@@ -310,7 +310,7 @@ packages:
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
source_maps:
@@ -318,7 +318,7 @@ packages:
description:
name: source_maps
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.10.13"
source_span:
@@ -326,7 +326,7 @@ packages:
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
@@ -334,7 +334,7 @@ packages:
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
@@ -342,7 +342,7 @@ packages:
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
@@ -350,7 +350,7 @@ packages:
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
@@ -358,7 +358,7 @@ packages:
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test:
@@ -366,7 +366,7 @@ packages:
description:
name: test
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.25.15"
test_api:
@@ -374,7 +374,7 @@ packages:
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
test_core:
@@ -382,7 +382,7 @@ packages:
description:
name: test_core
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.6.8"
toml:
@@ -390,7 +390,7 @@ packages:
description:
name: toml
sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.14.0"
typed_data:
@@ -398,7 +398,7 @@ packages:
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version:
@@ -406,7 +406,7 @@ packages:
description:
name: version
sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
vm_service:
@@ -414,7 +414,7 @@ packages:
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "15.0.0"
watcher:
@@ -422,7 +422,7 @@ packages:
description:
name: watcher
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web:
@@ -430,7 +430,7 @@ packages:
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
@@ -438,7 +438,7 @@ packages:
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
@@ -446,7 +446,7 @@ packages:
description:
name: web_socket_channel
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
webkit_inspection_protocol:
@@ -454,7 +454,7 @@ packages:
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
yaml:
@@ -462,7 +462,7 @@ packages:
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
+4 -4
View File
@@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "fltier" "\0"
VALUE "FileDescription", "Astral" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "fltier" "\0"
VALUE "InternalName", "Astral" "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "fltier.exe" "\0"
VALUE "ProductName", "fltier" "\0"
VALUE "OriginalFilename", "Astral.exe" "\0"
VALUE "ProductName", "Astral" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
+33 -3
View File
@@ -1,12 +1,42 @@
#include <flutter/dart_project.h>
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include <string>
#include "flutter_window.h"
#include "utils.h"
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
const int bufferSize = 256;
wchar_t windowTitle[bufferSize];
if (GetWindowTextW(hwnd, windowTitle, bufferSize)) {
if (_wcsicmp(windowTitle, L"Astral") == 0) {
*((HWND*)lParam) = hwnd;
return FALSE;
}
}
return TRUE;
}
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
HANDLE hMutex = CreateMutexW(NULL, TRUE, L"AstralAppSingleInstanceMutex");
if (GetLastError() == ERROR_ALREADY_EXISTS) {
HWND hWnd = NULL;
EnumWindows(EnumWindowsProc, (LPARAM)&hWnd);
if (hWnd != NULL) {
if (IsIconic(hWnd)) {
ShowWindow(hWnd, SW_RESTORE);
}
SetForegroundWindow(hWnd);
}
CloseHandle(hMutex);
return 0;
}
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
@@ -27,7 +57,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"fltier", origin, size)) {
if (!window.Create(L"Astral", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
@@ -38,6 +68,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
::DispatchMessage(&msg);
}
::CoUninitialize();
CloseHandle(hMutex);
return EXIT_SUCCESS;
}
+3
View File
@@ -4,6 +4,9 @@
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">