alroyso 1 年間 前
コミット
e82ce34060

+ 25 - 0
.vscode/launch.json

@@ -0,0 +1,25 @@
+{
+    // 使用 IntelliSense 了解相关属性。 
+    // 悬停以查看现有属性的描述。
+    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "speedsafe",
+            "request": "launch",
+            "type": "dart"
+        },
+        {
+            "name": "speedsafe (profile mode)",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "profile"
+        },
+        {
+            "name": "speedsafe (release mode)",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "release"
+        }
+    ]
+}

+ 44 - 0
ios/Podfile

@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+  target 'RunnerTests' do
+    inherit! :search_paths
+  end
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_ios_build_settings(target)
+  end
+end

+ 209 - 0
lib/app/config/app_config.dart

@@ -0,0 +1,209 @@
+import 'package:json_annotation/json_annotation.dart';
+part 'app_config.g.dart';
+enum UserAgent { chrome, firefox, safari, edge, none }
+
+// ignore: constant_identifier_names
+enum DomainStrategy { AsIs, IPIfNonMatch, IPOnDemand }
+
+enum DomainMatcher { hybrid, linear }
+
+enum LogLevel { none, warning, debug, error, info }
+
+enum RoutingProvider { sing, xray }
+
+enum VmessProvider { sing, xray }
+
+enum VlessProvider { sing, xray }
+
+enum ShadowsocksProvider { sing, xray, ssrust }
+
+enum TrojanProvider { sing, xray }
+
+enum HysteriaProvider { sing, hysteria }
+
+enum TunProvider { sing }
+
+enum TunStack { system, gvisor }
+
+@JsonSerializable(includeIfNull: false)
+class AppConfig {
+  /* app */
+  bool startOnBoot;
+  bool autoRunServer;
+  bool darkMode;
+  int themeColor;
+  bool showAddress;
+  bool enableStatistics;
+  bool enableSpeedChart;
+  int updateSubscribeInterval;
+  bool updateThroughProxy;
+  int userAgent;
+  /* Proxy */
+  bool autoGetIp;
+  bool autoConfigureSystemProxy;
+  bool enableTun;
+  int socksPort;
+  int httpPort;
+  int mixedPort;
+  String listen;
+  bool enableUdp;
+  bool authentication;
+  String user;
+  String password;
+
+  /* Core */
+  int coreApiPort;
+  bool enableSniffing;
+  bool configureDns;
+  String remoteDns;
+  String directDns;
+  int domainStrategy;
+  int domainMatcher;
+  bool enableCoreLog;
+  int logLevel;
+  int maxLogCount;
+  bool saveCoreLog;
+
+  /* Provider */
+  int routingProvider;
+  int vmessProvider;
+  int vlessProvider;
+  int shadowsocksProvider;
+  int trojanProvider;
+  int hysteriaProvider;
+  int additionalSocksPort;
+
+  /* Tun */
+  int tunProvider;
+  bool enableIpv4;
+  String ipv4Address;
+  bool enableIpv6;
+  String ipv6Address;
+  int mtu;
+  int stack;
+  bool autoRoute;
+  bool strictRoute;
+
+  AppConfig({
+    required this.startOnBoot,
+    required this.autoRunServer,
+    required this.darkMode,
+    required this.themeColor,
+    required this.showAddress,
+    required this.enableStatistics,
+    required this.enableSpeedChart,
+    required this.updateSubscribeInterval,
+    required this.updateThroughProxy,
+    required this.userAgent,
+    required this.autoGetIp,
+    required this.autoConfigureSystemProxy,
+    required this.enableTun,
+    required this.socksPort,
+    required this.httpPort,
+    required this.mixedPort,
+    required this.listen,
+    required this.enableUdp,
+    required this.authentication,
+    required this.user,
+    required this.password,
+    required this.coreApiPort,
+    required this.enableSniffing,
+    required this.configureDns,
+    required this.remoteDns,
+    required this.directDns,
+    required this.domainStrategy,
+    required this.domainMatcher,
+    required this.enableCoreLog,
+    required this.logLevel,
+    required this.maxLogCount,
+    required this.saveCoreLog,
+    required this.routingProvider,
+    required this.vmessProvider,
+    required this.vlessProvider,
+    required this.shadowsocksProvider,
+    required this.trojanProvider,
+    required this.hysteriaProvider,
+    required this.additionalSocksPort,
+    required this.tunProvider,
+    required this.enableIpv4,
+    required this.ipv4Address,
+    required this.enableIpv6,
+    required this.ipv6Address,
+    required this.mtu,
+    required this.stack,
+    required this.autoRoute,
+    required this.strictRoute,
+  });
+factory AppConfig.defaults() {
+    return AppConfig(
+      startOnBoot: false,
+      autoRunServer: false,
+      darkMode: false,
+      themeColor: 4278430196,
+      showAddress: false,
+      enableStatistics: false,
+      enableSpeedChart: false,
+      updateSubscribeInterval: -1,
+      updateThroughProxy: false,
+      userAgent: UserAgent.chrome.index,
+      autoGetIp: false,
+      autoConfigureSystemProxy: false,
+      enableTun: false,
+      socksPort: 11111,
+      httpPort: 11112,
+      mixedPort: 11113,
+      listen: '127.0.0.1',
+      enableUdp: true,
+      authentication: false,
+      user: 'admin',
+      password: 'fffffffff222222',
+      coreApiPort: 11110,
+      enableSniffing: true,
+      configureDns: true,
+      remoteDns: 'https://8.8.8.8/dns-query',
+      directDns: '223.5.5.5',
+      domainStrategy: DomainStrategy.IPIfNonMatch.index,
+      domainMatcher: DomainMatcher.hybrid.index,
+      enableCoreLog: true,
+      logLevel: LogLevel.warning.index,
+      maxLogCount: 64,
+      saveCoreLog: false,
+      routingProvider: RoutingProvider.sing.index,
+      vmessProvider: VmessProvider.sing.index,
+      vlessProvider: VlessProvider.sing.index,
+      shadowsocksProvider: ShadowsocksProvider.sing.index,
+      trojanProvider: TrojanProvider.sing.index,
+      hysteriaProvider: HysteriaProvider.sing.index,
+      additionalSocksPort: 11114,
+      tunProvider: TunProvider.sing.index,
+      enableIpv4: true,
+      ipv4Address: '172.19.0.1/30',
+      enableIpv6: false,
+      ipv6Address: 'fdfe:dcba:9876::1/126',
+      mtu: 9000,
+      stack: TunStack.system.index,
+      autoRoute: true,
+      strictRoute: false,
+    );
+  }
+
+ factory AppConfig.fromJson(Map<String, dynamic> json) =>
+      _$AppConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$AppConfigToJson(this);
+}
+
+
+
+
+const Map<String, String> userAgents = {
+  'chrome':
+      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
+  'firefox':
+      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0',
+  'safari':
+      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
+  'edge':
+      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.70',
+  'none': ''
+};

+ 109 - 0
lib/app/config/app_config.g.dart

@@ -0,0 +1,109 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'app_config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+AppConfig _$AppConfigFromJson(Map<String, dynamic> json) => AppConfig(
+      startOnBoot: json['startOnBoot'] as bool,
+      autoRunServer: json['autoRunServer'] as bool,
+      darkMode: json['darkMode'] as bool,
+      themeColor: json['themeColor'] as int,
+      showAddress: json['showAddress'] as bool,
+      enableStatistics: json['enableStatistics'] as bool,
+      enableSpeedChart: json['enableSpeedChart'] as bool,
+      updateSubscribeInterval: json['updateSubscribeInterval'] as int,
+      updateThroughProxy: json['updateThroughProxy'] as bool,
+      userAgent: json['userAgent'] as int,
+      autoGetIp: json['autoGetIp'] as bool,
+      autoConfigureSystemProxy: json['autoConfigureSystemProxy'] as bool,
+      enableTun: json['enableTun'] as bool,
+      socksPort: json['socksPort'] as int,
+      httpPort: json['httpPort'] as int,
+      mixedPort: json['mixedPort'] as int,
+      listen: json['listen'] as String,
+      enableUdp: json['enableUdp'] as bool,
+      authentication: json['authentication'] as bool,
+      user: json['user'] as String,
+      password: json['password'] as String,
+      coreApiPort: json['coreApiPort'] as int,
+      enableSniffing: json['enableSniffing'] as bool,
+      configureDns: json['configureDns'] as bool,
+      remoteDns: json['remoteDns'] as String,
+      directDns: json['directDns'] as String,
+      domainStrategy: json['domainStrategy'] as int,
+      domainMatcher: json['domainMatcher'] as int,
+      enableCoreLog: json['enableCoreLog'] as bool,
+      logLevel: json['logLevel'] as int,
+      maxLogCount: json['maxLogCount'] as int,
+      saveCoreLog: json['saveCoreLog'] as bool,
+      routingProvider: json['routingProvider'] as int,
+      vmessProvider: json['vmessProvider'] as int,
+      vlessProvider: json['vlessProvider'] as int,
+      shadowsocksProvider: json['shadowsocksProvider'] as int,
+      trojanProvider: json['trojanProvider'] as int,
+      hysteriaProvider: json['hysteriaProvider'] as int,
+      additionalSocksPort: json['additionalSocksPort'] as int,
+      tunProvider: json['tunProvider'] as int,
+      enableIpv4: json['enableIpv4'] as bool,
+      ipv4Address: json['ipv4Address'] as String,
+      enableIpv6: json['enableIpv6'] as bool,
+      ipv6Address: json['ipv6Address'] as String,
+      mtu: json['mtu'] as int,
+      stack: json['stack'] as int,
+      autoRoute: json['autoRoute'] as bool,
+      strictRoute: json['strictRoute'] as bool,
+    );
+
+Map<String, dynamic> _$AppConfigToJson(AppConfig instance) => <String, dynamic>{
+      'startOnBoot': instance.startOnBoot,
+      'autoRunServer': instance.autoRunServer,
+      'darkMode': instance.darkMode,
+      'themeColor': instance.themeColor,
+      'showAddress': instance.showAddress,
+      'enableStatistics': instance.enableStatistics,
+      'enableSpeedChart': instance.enableSpeedChart,
+      'updateSubscribeInterval': instance.updateSubscribeInterval,
+      'updateThroughProxy': instance.updateThroughProxy,
+      'userAgent': instance.userAgent,
+      'autoGetIp': instance.autoGetIp,
+      'autoConfigureSystemProxy': instance.autoConfigureSystemProxy,
+      'enableTun': instance.enableTun,
+      'socksPort': instance.socksPort,
+      'httpPort': instance.httpPort,
+      'mixedPort': instance.mixedPort,
+      'listen': instance.listen,
+      'enableUdp': instance.enableUdp,
+      'authentication': instance.authentication,
+      'user': instance.user,
+      'password': instance.password,
+      'coreApiPort': instance.coreApiPort,
+      'enableSniffing': instance.enableSniffing,
+      'configureDns': instance.configureDns,
+      'remoteDns': instance.remoteDns,
+      'directDns': instance.directDns,
+      'domainStrategy': instance.domainStrategy,
+      'domainMatcher': instance.domainMatcher,
+      'enableCoreLog': instance.enableCoreLog,
+      'logLevel': instance.logLevel,
+      'maxLogCount': instance.maxLogCount,
+      'saveCoreLog': instance.saveCoreLog,
+      'routingProvider': instance.routingProvider,
+      'vmessProvider': instance.vmessProvider,
+      'vlessProvider': instance.vlessProvider,
+      'shadowsocksProvider': instance.shadowsocksProvider,
+      'trojanProvider': instance.trojanProvider,
+      'hysteriaProvider': instance.hysteriaProvider,
+      'additionalSocksPort': instance.additionalSocksPort,
+      'tunProvider': instance.tunProvider,
+      'enableIpv4': instance.enableIpv4,
+      'ipv4Address': instance.ipv4Address,
+      'enableIpv6': instance.enableIpv6,
+      'ipv6Address': instance.ipv6Address,
+      'mtu': instance.mtu,
+      'stack': instance.stack,
+      'autoRoute': instance.autoRoute,
+      'strictRoute': instance.strictRoute,
+    };

+ 33 - 0
lib/app/controllers/config.dart

@@ -0,0 +1,33 @@
+import 'dart:convert';
+import 'dart:io';
+import 'package:path/path.dart' as p;
+import 'package:get/get.dart';
+import 'package:speed_safe/app/config/app_config.dart';
+import 'package:speed_safe/app/util/system.dart';
+
+class ConfigController extends GetxController {
+  AppConfig appConfig = AppConfig.defaults();
+  String configFileName = "config.json";
+  late File configFile = File(p.join(configPath, configFileName));
+
+  Future<void> init() async {
+    if(SystemUtil.fileExists(configFileName)){
+      final jsonString = jsonEncode(appConfig.toJson());
+      writeConfig(jsonString);
+    }
+  }
+
+  //保存并更新配置文件
+  Future<void>  saveConfig(AppConfig config) async {
+    appConfig = config;
+
+    final jsonString = jsonEncode(appConfig.toJson());
+    writeConfig(jsonString);
+  }
+
+  Future<void> writeConfig(String jsonString) async {
+    SystemUtil.deleteFileIfExists(
+        configFile.path, 'Deleting config file: $configFileName');
+    await configFile.writeAsString(jsonString);
+  }
+}

+ 13 - 0
lib/app/controllers/controllers.dart

@@ -0,0 +1,13 @@
+// ignore: file_names
+import 'package:get/get.dart';
+import 'package:speed_safe/app/controllers/config.dart';
+
+class Controllers {
+  late final ConfigController config;
+
+  void init() {
+    config = Get.find();
+  }
+}
+
+final controllers = Controllers();

+ 105 - 0
lib/app/server/core_base.dart

@@ -0,0 +1,105 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:speed_safe/app/server/server_base.dart';
+import 'package:speed_safe/app/util/log.dart';
+import 'package:path/path.dart' as p;
+import 'package:speed_safe/app/util/system.dart';
+abstract class CoreBase {
+  String coreName;
+  List<String> coreArgs;
+  late String configFileName;
+  late File configFile = File(p.join(tempPath, configFileName));
+  Process? coreProcess;
+  bool isPreLog = true;
+  final List<String> preLogList = [];
+  final logStreamController = StreamController<String>.broadcast();
+  late final String runningServer;
+
+  Stream<String> get logStream => logStreamController.stream;
+
+  CoreBase(this.coreName, this.coreArgs, this.configFileName);
+
+  Future<void> start(String server) async {
+    late final ServerBase serverBase;
+
+    runningServer = server;
+    serverBase = ServerBase.fromJson(jsonDecode(server));
+
+    await configure(serverBase);
+
+    logger.i('Starting core: $coreName');
+    try {
+      coreProcess = await Process.start(
+        p.join(binPath, SystemUtil.getCoreFileName(coreName)),
+        coreArgs,
+        runInShell: true,
+      );
+    } on ProcessException catch (e) {
+      logger.e('Failed to start $coreName: ${e.message}');
+      throw Exception('Failed to start $coreName: ${e.message}');
+    }
+
+    if (coreProcess == null) {
+      throw Exception('Core Process is null');
+    }
+
+    listenToProcessStream(coreProcess!.stdout);
+    listenToProcessStream(coreProcess!.stderr);
+
+    try {
+      if (await coreProcess?.exitCode
+          .timeout(const Duration(milliseconds: 500)) !=
+          0) {
+        throw Exception('\n${preLogList.join('\n')}');
+      }
+    } on TimeoutException catch (_) {
+      isPreLog = false;
+    }
+  }
+
+  Future<void> stop() async {
+    if (coreProcess != null) {
+      logger.i('Stopping core: $coreName');
+      coreProcess?.kill(ProcessSignal.sigterm);
+      await coreProcess?.exitCode.timeout(const Duration(milliseconds: 500),
+          onTimeout: () {
+            coreProcess?.kill(ProcessSignal.sigkill);
+            return Future.error(
+                'Failed to stop $coreName, force killed the process.');
+          });
+      coreProcess = null;
+    }
+    SystemUtil.deleteFileIfExists(
+        configFile.path, 'Deleting config file: $configFileName');
+    if (coreName == 'sing-box') {
+      SystemUtil.deleteFileIfExists(
+          p.join(tempPath, 'cache.db'), 'Deleting cache file: cache.db');
+    }
+    if (!logStreamController.isClosed) {
+      await logStreamController.close();
+    }
+  }
+
+  void listenToProcessStream(Stream<List<int>> stream) {
+    stream.transform(utf8.decoder).listen((data) {
+      if (data.trim().isNotEmpty) {
+        logStreamController.add(data);
+        if (isPreLog) {
+          preLogList.add(data);
+        }
+      }
+    });
+  }
+
+  Future<void> configure(ServerBase server);
+
+  Future<String> generateConfig(ServerBase server);
+
+  Future<void> writeConfig(String jsonString) async {
+    SystemUtil.deleteFileIfExists(
+        configFile.path, 'Deleting config file: $configFileName');
+    await configFile.writeAsString(jsonString);
+  }
+}

+ 68 - 0
lib/app/server/hysteria/config.dart

@@ -0,0 +1,68 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class HysteriaConfig {
+  String server;
+  String protocol;
+  String? obfs;
+  String? alpn;
+  String? auth;
+  @JsonKey(name: 'auth_str')
+  String? authStr;
+  @JsonKey(name: 'server_name')
+  String? serverName;
+  bool insecure;
+  @JsonKey(name: 'up_mbps')
+  int upMbps;
+  @JsonKey(name: 'down_mbps')
+  int downMbps;
+  @JsonKey(name: 'recv_window_conn')
+  int? recvWindowConn;
+  @JsonKey(name: 'recv_window')
+  int? recvWindow;
+  @JsonKey(name: 'disable_mtu_discovery')
+  bool disableMtuDiscovery;
+  Socks5 socks5;
+
+  HysteriaConfig({
+    required this.server,
+    required this.protocol,
+    this.obfs,
+    this.alpn,
+    this.auth,
+    this.authStr,
+    this.serverName,
+    required this.insecure,
+    required this.upMbps,
+    required this.downMbps,
+    this.recvWindowConn,
+    this.recvWindow,
+    required this.disableMtuDiscovery,
+    required this.socks5,
+  });
+
+  factory HysteriaConfig.fromJson(Map<String, dynamic> json) =>
+      _$HysteriaConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$HysteriaConfigToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Socks5 {
+  String listen;
+  int? timeout;
+  @JsonKey(name: 'disable_udp')
+  bool disableUdp;
+
+  Socks5({
+    required this.listen,
+    this.timeout,
+    required this.disableUdp,
+  });
+
+  factory Socks5.fromJson(Map<String, dynamic> json) => _$Socks5FromJson(json);
+
+  Map<String, dynamic> toJson() => _$Socks5ToJson(this);
+}

+ 74 - 0
lib/app/server/hysteria/config.g.dart

@@ -0,0 +1,74 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+HysteriaConfig _$HysteriaConfigFromJson(Map<String, dynamic> json) =>
+    HysteriaConfig(
+      server: json['server'] as String,
+      protocol: json['protocol'] as String,
+      obfs: json['obfs'] as String?,
+      alpn: json['alpn'] as String?,
+      auth: json['auth'] as String?,
+      authStr: json['auth_str'] as String?,
+      serverName: json['server_name'] as String?,
+      insecure: json['insecure'] as bool,
+      upMbps: json['up_mbps'] as int,
+      downMbps: json['down_mbps'] as int,
+      recvWindowConn: json['recv_window_conn'] as int?,
+      recvWindow: json['recv_window'] as int?,
+      disableMtuDiscovery: json['disable_mtu_discovery'] as bool,
+      socks5: Socks5.fromJson(json['socks5'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$HysteriaConfigToJson(HysteriaConfig instance) {
+  final val = <String, dynamic>{
+    'server': instance.server,
+    'protocol': instance.protocol,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('obfs', instance.obfs);
+  writeNotNull('alpn', instance.alpn);
+  writeNotNull('auth', instance.auth);
+  writeNotNull('auth_str', instance.authStr);
+  writeNotNull('server_name', instance.serverName);
+  val['insecure'] = instance.insecure;
+  val['up_mbps'] = instance.upMbps;
+  val['down_mbps'] = instance.downMbps;
+  writeNotNull('recv_window_conn', instance.recvWindowConn);
+  writeNotNull('recv_window', instance.recvWindow);
+  val['disable_mtu_discovery'] = instance.disableMtuDiscovery;
+  val['socks5'] = instance.socks5;
+  return val;
+}
+
+Socks5 _$Socks5FromJson(Map<String, dynamic> json) => Socks5(
+      listen: json['listen'] as String,
+      timeout: json['timeout'] as int?,
+      disableUdp: json['disable_udp'] as bool,
+    );
+
+Map<String, dynamic> _$Socks5ToJson(Socks5 instance) {
+  final val = <String, dynamic>{
+    'listen': instance.listen,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('timeout', instance.timeout);
+  val['disable_udp'] = instance.disableUdp;
+  return val;
+}

+ 72 - 0
lib/app/server/hysteria/server.dart

@@ -0,0 +1,72 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:speed_safe/app/server/server_base.dart';
+
+
+part 'server.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class HysteriaServer extends ServerBase {
+  String hysteriaProtocol;
+  String? obfs;
+  String? alpn;
+  String authType;
+  String? authPayload;
+  @JsonKey(name: 'server_name')
+  String? serverName;
+  bool insecure;
+  @JsonKey(name: 'up_mbps')
+  int upMbps;
+  @JsonKey(name: 'down_mbps')
+  int downMbps;
+  @JsonKey(name: 'recv_window_conn')
+  int? recvWindowConn;
+  @JsonKey(name: 'recv_window')
+  int? recvWindow;
+  @JsonKey(name: 'disable_mtu_discovery')
+  bool disableMtuDiscovery;
+
+  HysteriaServer({
+    required String protocol,
+    required String address,
+    required int port,
+    required String remark,
+    required this.hysteriaProtocol,
+    this.obfs,
+    this.alpn,
+    required this.authType,
+    this.authPayload,
+    this.serverName,
+    required this.insecure,
+    required this.upMbps,
+    required this.downMbps,
+    this.recvWindowConn,
+    this.recvWindow,
+    required this.disableMtuDiscovery,
+  }) : super(
+    protocol: protocol,
+    address: address,
+    port: port,
+    remark: remark,
+  );
+
+  factory HysteriaServer.defaults() => HysteriaServer(
+    protocol: 'hysteria',
+    address: '',
+    port: 0,
+    remark: '',
+    hysteriaProtocol: 'udp',
+    authType: 'none',
+    insecure: false,
+    upMbps: 10,
+    downMbps: 50,
+    recvWindowConn: 15728640,
+    recvWindow: 67108864,
+    disableMtuDiscovery: false,
+  );
+
+  factory HysteriaServer.fromJson(Map<String, dynamic> json) =>
+      _$HysteriaServerFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$HysteriaServerToJson(this);
+}

+ 60 - 0
lib/app/server/hysteria/server.g.dart

@@ -0,0 +1,60 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'server.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+HysteriaServer _$HysteriaServerFromJson(Map<String, dynamic> json) =>
+    HysteriaServer(
+      protocol: json['protocol'] as String,
+      address: json['address'] as String,
+      port: json['port'] as int,
+      remark: json['remark'] as String,
+      hysteriaProtocol: json['hysteriaProtocol'] as String,
+      obfs: json['obfs'] as String?,
+      alpn: json['alpn'] as String?,
+      authType: json['authType'] as String,
+      authPayload: json['authPayload'] as String?,
+      serverName: json['server_name'] as String?,
+      insecure: json['insecure'] as bool,
+      upMbps: json['up_mbps'] as int,
+      downMbps: json['down_mbps'] as int,
+      recvWindowConn: json['recv_window_conn'] as int?,
+      recvWindow: json['recv_window'] as int?,
+      disableMtuDiscovery: json['disable_mtu_discovery'] as bool,
+    )
+      ..uplink = json['uplink'] as int?
+      ..downlink = json['downlink'] as int?;
+
+Map<String, dynamic> _$HysteriaServerToJson(HysteriaServer instance) {
+  final val = <String, dynamic>{
+    'protocol': instance.protocol,
+    'address': instance.address,
+    'port': instance.port,
+    'remark': instance.remark,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('uplink', instance.uplink);
+  writeNotNull('downlink', instance.downlink);
+  val['hysteriaProtocol'] = instance.hysteriaProtocol;
+  writeNotNull('obfs', instance.obfs);
+  writeNotNull('alpn', instance.alpn);
+  val['authType'] = instance.authType;
+  writeNotNull('authPayload', instance.authPayload);
+  writeNotNull('server_name', instance.serverName);
+  val['insecure'] = instance.insecure;
+  val['up_mbps'] = instance.upMbps;
+  val['down_mbps'] = instance.downMbps;
+  writeNotNull('recv_window_conn', instance.recvWindowConn);
+  writeNotNull('recv_window', instance.recvWindow);
+  val['disable_mtu_discovery'] = instance.disableMtuDiscovery;
+  return val;
+}

+ 43 - 0
lib/app/server/shadowsocks/server.dart

@@ -0,0 +1,43 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:speed_safe/app/server/server_base.dart';
+
+part 'server.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class ShadowsocksServer extends ServerBase {
+  String password;
+  String encryption;
+  String? plugin;
+  String? pluginOpts;
+
+  ShadowsocksServer({
+    required String protocol,
+    required String address,
+    required int port,
+    required String remark,
+    required this.password,
+    required this.encryption,
+    this.plugin,
+    this.pluginOpts,
+  }) : super(
+    protocol: protocol,
+    address: address,
+    port: port,
+    remark: remark,
+  );
+
+  factory ShadowsocksServer.defaults() => ShadowsocksServer(
+    protocol: 'shadowsocks',
+    address: '',
+    port: 0,
+    remark: '',
+    password: '',
+    encryption: 'aes-128-gcm',
+  );
+
+  factory ShadowsocksServer.fromJson(Map<String, dynamic> json) =>
+      _$ShadowsocksServerFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$ShadowsocksServerToJson(this);
+}

+ 44 - 0
lib/app/server/shadowsocks/server.g.dart

@@ -0,0 +1,44 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'server.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+ShadowsocksServer _$ShadowsocksServerFromJson(Map<String, dynamic> json) =>
+    ShadowsocksServer(
+      protocol: json['protocol'] as String,
+      address: json['address'] as String,
+      port: json['port'] as int,
+      remark: json['remark'] as String,
+      password: json['password'] as String,
+      encryption: json['encryption'] as String,
+      plugin: json['plugin'] as String?,
+      pluginOpts: json['pluginOpts'] as String?,
+    )
+      ..uplink = json['uplink'] as int?
+      ..downlink = json['downlink'] as int?;
+
+Map<String, dynamic> _$ShadowsocksServerToJson(ShadowsocksServer instance) {
+  final val = <String, dynamic>{
+    'protocol': instance.protocol,
+    'address': instance.address,
+    'port': instance.port,
+    'remark': instance.remark,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('uplink', instance.uplink);
+  writeNotNull('downlink', instance.downlink);
+  val['password'] = instance.password;
+  val['encryption'] = instance.encryption;
+  writeNotNull('plugin', instance.plugin);
+  writeNotNull('pluginOpts', instance.pluginOpts);
+  return val;
+}

+ 436 - 0
lib/app/server/sing-box/config.dart

@@ -0,0 +1,436 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class SingBoxConfig {
+  Log? log;
+  Dns? dns;
+  Route? route;
+  List<Inbound>? inbounds;
+  List<Outbound>? outbounds;
+  Experimental? experimental;
+
+  SingBoxConfig({
+    this.log,
+    this.dns,
+    this.route,
+    this.inbounds,
+    this.outbounds,
+    this.experimental,
+  });
+
+  factory SingBoxConfig.fromJson(Map<String, dynamic> json) =>
+      _$SingBoxConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SingBoxConfigToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Log {
+  bool disabled;
+  String? level;
+  String? output;
+  bool timestamp;
+
+  Log({
+    required this.disabled,
+    this.level,
+    this.output,
+    required this.timestamp,
+  });
+
+  factory Log.fromJson(Map<String, dynamic> json) => _$LogFromJson(json);
+
+  Map<String, dynamic> toJson() => _$LogToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Dns {
+  List<DnsServer> servers;
+  List<DnsRule> rules;
+
+  Dns({
+    required this.servers,
+    required this.rules,
+  });
+
+  factory Dns.fromJson(Map<String, dynamic> json) => _$DnsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DnsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class DnsServer {
+  String tag;
+  String address;
+  @JsonKey(name: 'address_resolver')
+  String? addressResolver;
+  String? strategy;
+  String? detour;
+
+  DnsServer({
+    required this.tag,
+    required this.address,
+    this.addressResolver,
+    this.strategy,
+    this.detour,
+  });
+
+  factory DnsServer.fromJson(Map<String, dynamic> json) =>
+      _$DnsServerFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DnsServerToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class RouteRule {
+  String? protocol;
+  List<String>? geosite;
+  List<String>? geoip;
+  List<String>? domain;
+  @JsonKey(name: 'ip_cidr')
+  List<String>? ipCidr;
+  List<int>? port;
+  @JsonKey(name: 'port_range')
+  List<String>? portRange;
+  String? outbound;
+  @JsonKey(name: 'process_name')
+  List<String>? processName;
+
+  RouteRule({
+    this.protocol,
+    this.geosite,
+    this.geoip,
+    this.domain,
+    this.ipCidr,
+    this.port,
+    this.portRange,
+    this.outbound,
+    this.processName,
+  });
+
+  factory RouteRule.fromJson(Map<String, dynamic> json) =>
+      _$RouteRuleFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RouteRuleToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class DnsRule {
+  List<String>? geosite;
+  List<String>? geoip;
+  List<String>? domain;
+  String? server;
+  @JsonKey(name: 'disable_cache')
+  bool? disableCache;
+  List<String>? outbound;
+
+  DnsRule({
+    this.geosite,
+    this.geoip,
+    this.domain,
+    this.server,
+    this.disableCache,
+    this.outbound,
+  });
+
+  factory DnsRule.fromJson(Map<String, dynamic> json) =>
+      _$DnsRuleFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DnsRuleToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Route {
+  Geoip? geoip;
+  Geosite? geosite;
+  List<RouteRule>? rules;
+  @JsonKey(name: 'auto_detect_interface')
+  bool autoDetectInterface;
+  @JsonKey(name: 'final')
+  String? finalTag;
+
+  Route({
+    this.geoip,
+    this.geosite,
+    this.rules,
+    required this.autoDetectInterface,
+    this.finalTag,
+  });
+
+  factory Route.fromJson(Map<String, dynamic> json) => _$RouteFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RouteToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Geoip {
+  String path;
+
+  Geoip({
+    required this.path,
+  });
+
+  factory Geoip.fromJson(Map<String, dynamic> json) => _$GeoipFromJson(json);
+
+  Map<String, dynamic> toJson() => _$GeoipToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Geosite {
+  String path;
+
+  Geosite({
+    required this.path,
+  });
+
+  factory Geosite.fromJson(Map<String, dynamic> json) =>
+      _$GeositeFromJson(json);
+
+  Map<String, dynamic> toJson() => _$GeositeToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Inbound {
+  String type;
+  String? tag;
+  String? listen;
+  @JsonKey(name: 'listen_port')
+  int? listenPort;
+  List<User>? users;
+  @JsonKey(name: 'interface_name')
+  String? interfaceName;
+  @JsonKey(name: 'inet4_address')
+  String? inet4Address;
+  @JsonKey(name: 'inet6_address')
+  String? inet6Address;
+  int? mtu;
+  @JsonKey(name: 'auto_route')
+  bool? autoRoute;
+  @JsonKey(name: 'strict_route')
+  bool? strictRoute;
+  String? stack;
+  bool? sniff;
+
+  Inbound({
+    required this.type,
+    this.tag,
+    this.listen,
+    this.listenPort,
+    this.users,
+    this.interfaceName,
+    this.inet4Address,
+    this.inet6Address,
+    this.mtu,
+    this.autoRoute,
+    this.strictRoute,
+    this.stack,
+    this.sniff,
+  });
+
+  factory Inbound.fromJson(Map<String, dynamic> json) =>
+      _$InboundFromJson(json);
+
+  Map<String, dynamic> toJson() => _$InboundToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Outbound {
+  String type;
+  String? tag;
+  String? server;
+  @JsonKey(name: 'server_port')
+  int? serverPort;
+  String? version;
+  String? username;
+  String? method;
+  String? password;
+  String? plugin;
+  @JsonKey(name: 'plugin_opts')
+  String? pluginOpts;
+  String? uuid;
+  String? flow;
+  String? security;
+  @JsonKey(name: 'alter_id')
+  int? alterId;
+  String? network;
+  Tls? tls;
+  Transport? transport;
+  int? upMbps;
+  int? downMbps;
+  String? obfs;
+  String? auth;
+  @JsonKey(name: 'auth_str')
+  String? authStr;
+  @JsonKey(name: 'recv_window_conn')
+  int? recvWindowConn;
+  @JsonKey(name: 'recv_window')
+  int? recvWindow;
+  @JsonKey(name: 'disable_mtu_discovery')
+  int? disableMtuDiscovery;
+
+  Outbound({
+    required this.type,
+    this.tag,
+    this.server,
+    this.serverPort,
+    this.version,
+    this.username,
+    this.method,
+    this.password,
+    this.plugin,
+    this.pluginOpts,
+    this.uuid,
+    this.flow,
+    this.security,
+    this.alterId,
+    this.network,
+    this.tls,
+    this.transport,
+    this.upMbps,
+    this.downMbps,
+    this.obfs,
+    this.auth,
+    this.authStr,
+    this.recvWindowConn,
+    this.recvWindow,
+    this.disableMtuDiscovery,
+  });
+
+  factory Outbound.fromJson(Map<String, dynamic> json) =>
+      _$OutboundFromJson(json);
+
+  Map<String, dynamic> toJson() => _$OutboundToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Tls {
+  bool enabled;
+  @JsonKey(name: 'server_name')
+  String serverName;
+  bool insecure;
+  List<String>? alpn;
+  UTls? utls;
+  Reality? reality;
+
+  Tls({
+    required this.enabled,
+    required this.serverName,
+    required this.insecure,
+    this.alpn,
+    this.utls,
+    this.reality,
+  });
+
+  factory Tls.fromJson(Map<String, dynamic> json) => _$TlsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TlsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class UTls {
+  bool enabled;
+  String? fingerprint;
+
+  UTls({
+    required this.enabled,
+    this.fingerprint,
+  });
+
+  factory UTls.fromJson(Map<String, dynamic> json) => _$UTlsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$UTlsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Reality {
+  bool enabled;
+  @JsonKey(name: 'public_key')
+  String publicKey;
+  @JsonKey(name: "short_id")
+  String? shortId;
+
+  Reality({
+    required this.enabled,
+    required this.publicKey,
+    this.shortId,
+  });
+
+  factory Reality.fromJson(Map<String, dynamic> json) =>
+      _$RealityFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RealityToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Transport {
+  String type;
+  String? host;
+  String? path;
+  @JsonKey(name: 'service_name')
+  String? serviceName;
+
+  Transport({
+    required this.type,
+    this.host,
+    this.path,
+    this.serviceName,
+  });
+
+  factory Transport.fromJson(Map<String, dynamic> json) =>
+      _$TransportFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TransportToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class User {
+  String? username;
+  String? password;
+
+  User({
+    this.username,
+    this.password,
+  });
+
+  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
+
+  Map<String, dynamic> toJson() => _$UserToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Experimental {
+  @JsonKey(name: 'clash_api')
+  ClashApi? clashApi;
+
+  Experimental({
+    this.clashApi,
+  });
+
+  factory Experimental.fromJson(Map<String, dynamic> json) =>
+      _$ExperimentalFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ExperimentalToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class ClashApi {
+  @JsonKey(name: 'external_controller')
+  String externalController;
+  @JsonKey(name: 'store_selected')
+  bool storeSelected;
+  @JsonKey(name: 'cache_file')
+  String? cacheFile;
+
+  ClashApi({
+    required this.externalController,
+    required this.storeSelected,
+    this.cacheFile,
+  });
+
+  factory ClashApi.fromJson(Map<String, dynamic> json) =>
+      _$ClashApiFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ClashApiToJson(this);
+}

+ 503 - 0
lib/app/server/sing-box/config.g.dart

@@ -0,0 +1,503 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+SingBoxConfig _$SingBoxConfigFromJson(Map<String, dynamic> json) =>
+    SingBoxConfig(
+      log: json['log'] == null
+          ? null
+          : Log.fromJson(json['log'] as Map<String, dynamic>),
+      dns: json['dns'] == null
+          ? null
+          : Dns.fromJson(json['dns'] as Map<String, dynamic>),
+      route: json['route'] == null
+          ? null
+          : Route.fromJson(json['route'] as Map<String, dynamic>),
+      inbounds: (json['inbounds'] as List<dynamic>?)
+          ?.map((e) => Inbound.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      outbounds: (json['outbounds'] as List<dynamic>?)
+          ?.map((e) => Outbound.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      experimental: json['experimental'] == null
+          ? null
+          : Experimental.fromJson(json['experimental'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$SingBoxConfigToJson(SingBoxConfig instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('log', instance.log);
+  writeNotNull('dns', instance.dns);
+  writeNotNull('route', instance.route);
+  writeNotNull('inbounds', instance.inbounds);
+  writeNotNull('outbounds', instance.outbounds);
+  writeNotNull('experimental', instance.experimental);
+  return val;
+}
+
+Log _$LogFromJson(Map<String, dynamic> json) => Log(
+      disabled: json['disabled'] as bool,
+      level: json['level'] as String?,
+      output: json['output'] as String?,
+      timestamp: json['timestamp'] as bool,
+    );
+
+Map<String, dynamic> _$LogToJson(Log instance) {
+  final val = <String, dynamic>{
+    'disabled': instance.disabled,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('level', instance.level);
+  writeNotNull('output', instance.output);
+  val['timestamp'] = instance.timestamp;
+  return val;
+}
+
+Dns _$DnsFromJson(Map<String, dynamic> json) => Dns(
+      servers: (json['servers'] as List<dynamic>)
+          .map((e) => DnsServer.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      rules: (json['rules'] as List<dynamic>)
+          .map((e) => DnsRule.fromJson(e as Map<String, dynamic>))
+          .toList(),
+    );
+
+Map<String, dynamic> _$DnsToJson(Dns instance) => <String, dynamic>{
+      'servers': instance.servers,
+      'rules': instance.rules,
+    };
+
+DnsServer _$DnsServerFromJson(Map<String, dynamic> json) => DnsServer(
+      tag: json['tag'] as String,
+      address: json['address'] as String,
+      addressResolver: json['address_resolver'] as String?,
+      strategy: json['strategy'] as String?,
+      detour: json['detour'] as String?,
+    );
+
+Map<String, dynamic> _$DnsServerToJson(DnsServer instance) {
+  final val = <String, dynamic>{
+    'tag': instance.tag,
+    'address': instance.address,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('address_resolver', instance.addressResolver);
+  writeNotNull('strategy', instance.strategy);
+  writeNotNull('detour', instance.detour);
+  return val;
+}
+
+RouteRule _$RouteRuleFromJson(Map<String, dynamic> json) => RouteRule(
+      protocol: json['protocol'] as String?,
+      geosite:
+          (json['geosite'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      geoip:
+          (json['geoip'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      domain:
+          (json['domain'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      ipCidr:
+          (json['ip_cidr'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      port: (json['port'] as List<dynamic>?)?.map((e) => e as int).toList(),
+      portRange: (json['port_range'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+      outbound: json['outbound'] as String?,
+      processName: (json['process_name'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+    );
+
+Map<String, dynamic> _$RouteRuleToJson(RouteRule instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('protocol', instance.protocol);
+  writeNotNull('geosite', instance.geosite);
+  writeNotNull('geoip', instance.geoip);
+  writeNotNull('domain', instance.domain);
+  writeNotNull('ip_cidr', instance.ipCidr);
+  writeNotNull('port', instance.port);
+  writeNotNull('port_range', instance.portRange);
+  writeNotNull('outbound', instance.outbound);
+  writeNotNull('process_name', instance.processName);
+  return val;
+}
+
+DnsRule _$DnsRuleFromJson(Map<String, dynamic> json) => DnsRule(
+      geosite:
+          (json['geosite'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      geoip:
+          (json['geoip'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      domain:
+          (json['domain'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      server: json['server'] as String?,
+      disableCache: json['disable_cache'] as bool?,
+      outbound: (json['outbound'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+    );
+
+Map<String, dynamic> _$DnsRuleToJson(DnsRule instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('geosite', instance.geosite);
+  writeNotNull('geoip', instance.geoip);
+  writeNotNull('domain', instance.domain);
+  writeNotNull('server', instance.server);
+  writeNotNull('disable_cache', instance.disableCache);
+  writeNotNull('outbound', instance.outbound);
+  return val;
+}
+
+Route _$RouteFromJson(Map<String, dynamic> json) => Route(
+      geoip: json['geoip'] == null
+          ? null
+          : Geoip.fromJson(json['geoip'] as Map<String, dynamic>),
+      geosite: json['geosite'] == null
+          ? null
+          : Geosite.fromJson(json['geosite'] as Map<String, dynamic>),
+      rules: (json['rules'] as List<dynamic>?)
+          ?.map((e) => RouteRule.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      autoDetectInterface: json['auto_detect_interface'] as bool,
+      finalTag: json['final'] as String?,
+    );
+
+Map<String, dynamic> _$RouteToJson(Route instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('geoip', instance.geoip);
+  writeNotNull('geosite', instance.geosite);
+  writeNotNull('rules', instance.rules);
+  val['auto_detect_interface'] = instance.autoDetectInterface;
+  writeNotNull('final', instance.finalTag);
+  return val;
+}
+
+Geoip _$GeoipFromJson(Map<String, dynamic> json) => Geoip(
+      path: json['path'] as String,
+    );
+
+Map<String, dynamic> _$GeoipToJson(Geoip instance) => <String, dynamic>{
+      'path': instance.path,
+    };
+
+Geosite _$GeositeFromJson(Map<String, dynamic> json) => Geosite(
+      path: json['path'] as String,
+    );
+
+Map<String, dynamic> _$GeositeToJson(Geosite instance) => <String, dynamic>{
+      'path': instance.path,
+    };
+
+Inbound _$InboundFromJson(Map<String, dynamic> json) => Inbound(
+      type: json['type'] as String,
+      tag: json['tag'] as String?,
+      listen: json['listen'] as String?,
+      listenPort: json['listen_port'] as int?,
+      users: (json['users'] as List<dynamic>?)
+          ?.map((e) => User.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      interfaceName: json['interface_name'] as String?,
+      inet4Address: json['inet4_address'] as String?,
+      inet6Address: json['inet6_address'] as String?,
+      mtu: json['mtu'] as int?,
+      autoRoute: json['auto_route'] as bool?,
+      strictRoute: json['strict_route'] as bool?,
+      stack: json['stack'] as String?,
+      sniff: json['sniff'] as bool?,
+    );
+
+Map<String, dynamic> _$InboundToJson(Inbound instance) {
+  final val = <String, dynamic>{
+    'type': instance.type,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('tag', instance.tag);
+  writeNotNull('listen', instance.listen);
+  writeNotNull('listen_port', instance.listenPort);
+  writeNotNull('users', instance.users);
+  writeNotNull('interface_name', instance.interfaceName);
+  writeNotNull('inet4_address', instance.inet4Address);
+  writeNotNull('inet6_address', instance.inet6Address);
+  writeNotNull('mtu', instance.mtu);
+  writeNotNull('auto_route', instance.autoRoute);
+  writeNotNull('strict_route', instance.strictRoute);
+  writeNotNull('stack', instance.stack);
+  writeNotNull('sniff', instance.sniff);
+  return val;
+}
+
+Outbound _$OutboundFromJson(Map<String, dynamic> json) => Outbound(
+      type: json['type'] as String,
+      tag: json['tag'] as String?,
+      server: json['server'] as String?,
+      serverPort: json['server_port'] as int?,
+      version: json['version'] as String?,
+      username: json['username'] as String?,
+      method: json['method'] as String?,
+      password: json['password'] as String?,
+      plugin: json['plugin'] as String?,
+      pluginOpts: json['plugin_opts'] as String?,
+      uuid: json['uuid'] as String?,
+      flow: json['flow'] as String?,
+      security: json['security'] as String?,
+      alterId: json['alter_id'] as int?,
+      network: json['network'] as String?,
+      tls: json['tls'] == null
+          ? null
+          : Tls.fromJson(json['tls'] as Map<String, dynamic>),
+      transport: json['transport'] == null
+          ? null
+          : Transport.fromJson(json['transport'] as Map<String, dynamic>),
+      upMbps: json['upMbps'] as int?,
+      downMbps: json['downMbps'] as int?,
+      obfs: json['obfs'] as String?,
+      auth: json['auth'] as String?,
+      authStr: json['auth_str'] as String?,
+      recvWindowConn: json['recv_window_conn'] as int?,
+      recvWindow: json['recv_window'] as int?,
+      disableMtuDiscovery: json['disable_mtu_discovery'] as int?,
+    );
+
+Map<String, dynamic> _$OutboundToJson(Outbound instance) {
+  final val = <String, dynamic>{
+    'type': instance.type,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('tag', instance.tag);
+  writeNotNull('server', instance.server);
+  writeNotNull('server_port', instance.serverPort);
+  writeNotNull('version', instance.version);
+  writeNotNull('username', instance.username);
+  writeNotNull('method', instance.method);
+  writeNotNull('password', instance.password);
+  writeNotNull('plugin', instance.plugin);
+  writeNotNull('plugin_opts', instance.pluginOpts);
+  writeNotNull('uuid', instance.uuid);
+  writeNotNull('flow', instance.flow);
+  writeNotNull('security', instance.security);
+  writeNotNull('alter_id', instance.alterId);
+  writeNotNull('network', instance.network);
+  writeNotNull('tls', instance.tls);
+  writeNotNull('transport', instance.transport);
+  writeNotNull('upMbps', instance.upMbps);
+  writeNotNull('downMbps', instance.downMbps);
+  writeNotNull('obfs', instance.obfs);
+  writeNotNull('auth', instance.auth);
+  writeNotNull('auth_str', instance.authStr);
+  writeNotNull('recv_window_conn', instance.recvWindowConn);
+  writeNotNull('recv_window', instance.recvWindow);
+  writeNotNull('disable_mtu_discovery', instance.disableMtuDiscovery);
+  return val;
+}
+
+Tls _$TlsFromJson(Map<String, dynamic> json) => Tls(
+      enabled: json['enabled'] as bool,
+      serverName: json['server_name'] as String,
+      insecure: json['insecure'] as bool,
+      alpn: (json['alpn'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      utls: json['utls'] == null
+          ? null
+          : UTls.fromJson(json['utls'] as Map<String, dynamic>),
+      reality: json['reality'] == null
+          ? null
+          : Reality.fromJson(json['reality'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$TlsToJson(Tls instance) {
+  final val = <String, dynamic>{
+    'enabled': instance.enabled,
+    'server_name': instance.serverName,
+    'insecure': instance.insecure,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('alpn', instance.alpn);
+  writeNotNull('utls', instance.utls);
+  writeNotNull('reality', instance.reality);
+  return val;
+}
+
+UTls _$UTlsFromJson(Map<String, dynamic> json) => UTls(
+      enabled: json['enabled'] as bool,
+      fingerprint: json['fingerprint'] as String?,
+    );
+
+Map<String, dynamic> _$UTlsToJson(UTls instance) {
+  final val = <String, dynamic>{
+    'enabled': instance.enabled,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('fingerprint', instance.fingerprint);
+  return val;
+}
+
+Reality _$RealityFromJson(Map<String, dynamic> json) => Reality(
+      enabled: json['enabled'] as bool,
+      publicKey: json['public_key'] as String,
+      shortId: json['short_id'] as String?,
+    );
+
+Map<String, dynamic> _$RealityToJson(Reality instance) {
+  final val = <String, dynamic>{
+    'enabled': instance.enabled,
+    'public_key': instance.publicKey,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('short_id', instance.shortId);
+  return val;
+}
+
+Transport _$TransportFromJson(Map<String, dynamic> json) => Transport(
+      type: json['type'] as String,
+      host: json['host'] as String?,
+      path: json['path'] as String?,
+      serviceName: json['service_name'] as String?,
+    );
+
+Map<String, dynamic> _$TransportToJson(Transport instance) {
+  final val = <String, dynamic>{
+    'type': instance.type,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('host', instance.host);
+  writeNotNull('path', instance.path);
+  writeNotNull('service_name', instance.serviceName);
+  return val;
+}
+
+User _$UserFromJson(Map<String, dynamic> json) => User(
+      username: json['username'] as String?,
+      password: json['password'] as String?,
+    );
+
+Map<String, dynamic> _$UserToJson(User instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('username', instance.username);
+  writeNotNull('password', instance.password);
+  return val;
+}
+
+Experimental _$ExperimentalFromJson(Map<String, dynamic> json) => Experimental(
+      clashApi: json['clash_api'] == null
+          ? null
+          : ClashApi.fromJson(json['clash_api'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$ExperimentalToJson(Experimental instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('clash_api', instance.clashApi);
+  return val;
+}
+
+ClashApi _$ClashApiFromJson(Map<String, dynamic> json) => ClashApi(
+      externalController: json['external_controller'] as String,
+      storeSelected: json['store_selected'] as bool,
+      cacheFile: json['cache_file'] as String?,
+    );
+
+Map<String, dynamic> _$ClashApiToJson(ClashApi instance) {
+  final val = <String, dynamic>{
+    'external_controller': instance.externalController,
+    'store_selected': instance.storeSelected,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('cache_file', instance.cacheFile);
+  return val;
+}

+ 327 - 0
lib/app/server/sing-box/generate.dart

@@ -0,0 +1,327 @@
+import 'dart:io';
+
+import 'package:speed_safe/app/server/hysteria/server.dart';
+import 'package:speed_safe/app/server/shadowsocks/server.dart';
+import 'package:speed_safe/app/server/sing-box/config.dart';
+import 'package:speed_safe/app/server/trojan/server.dart';
+import 'package:speed_safe/app/server/xray/config.dart' as xray_config;
+import 'package:speed_safe/app/server/xray/server.dart';
+import 'package:speed_safe/app/server/server_base.dart';
+import 'package:speed_safe/app/util/system.dart';
+import 'package:path/path.dart' as p;
+class SingBoxGenerate {
+  static const ipRegExp = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
+
+  static Future<String> resolveDns(String dns) async {
+    final dnsHost = Uri.parse(dns).host;
+    if (!RegExp(ipRegExp).hasMatch(dnsHost) && dnsHost.isNotEmpty) {
+      try {
+        final dnsIp = (await InternetAddress.lookup(dnsHost)
+            .timeout(const Duration(seconds: 2)))
+            .first;
+        return dns.replaceFirst(dnsHost, dnsIp.address);
+      } on Exception catch (_) {
+        throw Exception('Failed to resolve DNS server address: $dns');
+      }
+    }
+    return dns;
+  }
+
+
+  static Future<Dns> dns(String remoteDns, String directDns,
+      String serverAddress, bool ipv4Only) async {
+    remoteDns = await resolveDns(remoteDns);
+    directDns = await resolveDns(directDns);
+
+    if (directDns.contains('+local://')) {
+      directDns = directDns.replaceFirst('+local', '');
+    }
+
+    List<DnsRule> dnsRules = [
+      DnsRule(
+        domain: ['geosite:cn'],
+        server: 'local',
+      ),
+    ];
+
+    if (!RegExp(ipRegExp).hasMatch(serverAddress) &&
+        serverAddress != '127.0.0.1') {
+      dnsRules.add(
+        DnsRule(
+          domain: [serverAddress],
+          server: 'local',
+        ),
+      );
+    }
+
+    return Dns(
+      servers: [
+        DnsServer(
+          tag: 'remote',
+          address: remoteDns,
+          detour: 'proxy',
+          strategy: ipv4Only ? 'ipv4_only' : null,
+        ),
+        DnsServer(
+          tag: 'local',
+          address: directDns,
+          detour: 'direct',
+          strategy: ipv4Only ? 'ipv4_only' : null,
+        ),
+      ],
+      rules: dnsRules,
+    );
+  }
+
+
+  static Route route(List<xray_config.XrayRule> rules, bool configureDns) {
+    List<RouteRule> routeRules = [];
+    if (configureDns) {
+      routeRules.add(
+        RouteRule(
+          protocol: 'dns',
+          outbound: 'dns-out',
+        ),
+      );
+    }
+    routeRules.addAll(convertRules(rules));
+    return Route(
+      geoip: Geoip(path: p.join(binPath, 'geoip.db')),
+      geosite: Geosite(path: p.join(binPath, 'geosite.db')),
+      rules: routeRules,
+      autoDetectInterface: true,
+      finalTag: 'proxy',
+    );
+  }
+
+  static List<RouteRule> convertRules(List<xray_config.XrayRule> xrayRules) {
+    List<RouteRule> result = [];
+    result.add(
+      RouteRule(
+        processName: SystemUtil.getCoreFileNames(),
+        outbound: 'direct',
+      ),
+    );
+    for (var xrayRule in xrayRules) {
+      if (xrayRule.enabled) {
+        List<String>? geosite;
+        List<String>? domain;
+        List<String>? geoip;
+        List<String>? ipCidr;
+        List<int>? port;
+        List<String>? portRange;
+        if (xrayRule.domain != null) {
+          for (var domainItem in xrayRule.domain!) {
+            if (domainItem.startsWith('geosite:')) {
+              geosite ??= [];
+              geosite.add(domainItem.replaceFirst('geosite:', ''));
+            } else {
+              domain ??= [];
+              domain.add(domainItem);
+            }
+          }
+        }
+        if (xrayRule.ip != null) {
+          for (var ipItem in xrayRule.ip!) {
+            if (ipItem.startsWith('geoip:')) {
+              geoip ??= [];
+              geoip.add(ipItem.replaceFirst('geoip:', ''));
+            } else {
+              ipCidr ??= [];
+              ipCidr.add(ipItem);
+            }
+          }
+        }
+        if (xrayRule.port != null) {
+          List<String> tempPort = xrayRule.port!.split(',');
+          for (var portItem in tempPort) {
+            if (portItem.contains('-')) {
+              portRange ??= [];
+              portRange.add(portItem.replaceAll('-', ':'));
+            } else {
+              port ??= [];
+              port.add(int.parse(portItem));
+            }
+          }
+        }
+        result.add(RouteRule(
+          geosite: geosite,
+          domain: domain,
+          geoip: geoip,
+          ipCidr: ipCidr,
+          port: port,
+          portRange: portRange,
+          outbound: xrayRule.outboundTag,
+        ));
+      }
+    }
+    return result;
+  }
+
+  static Inbound mixedInbound(
+      String listen, int listenPort, List<User>? users) {
+    return Inbound(
+      type: 'mixed',
+      listen: listen,
+      listenPort: listenPort,
+      users: users,
+    );
+  }
+
+  static Inbound tunInbound(String? inet4Address, String? inet6Address, int mtu,
+      String stack, bool autoRoute, bool strictRoute, bool sniff) {
+    return Inbound(
+      type: 'tun',
+      inet4Address: inet4Address,
+      inet6Address: inet6Address,
+      mtu: mtu,
+      autoRoute: autoRoute,
+      strictRoute: strictRoute,
+      stack: stack,
+      sniff: sniff,
+    );
+  }
+
+
+  static Outbound generateOutbound(ServerBase server) {
+    late Outbound outbound;
+    switch (server.runtimeType) {
+      case XrayServer:
+        outbound = xrayOutbound(server as XrayServer);
+        break;
+      case ShadowsocksServer:
+        outbound = shadowsocksOutbound(server as ShadowsocksServer);
+        break;
+      case TrojanServer:
+        outbound = trojanOutbound(server as TrojanServer);
+        break;
+      case HysteriaServer:
+        outbound = hysteriaOutbound(server as HysteriaServer);
+        break;
+      default:
+        throw Exception(
+            'Sing-Box does not support this server type: ${server.protocol}');
+    }
+    return outbound;
+  }
+
+  static Outbound xrayOutbound(XrayServer server) {
+    if (server.protocol == 'socks') {
+      return socksOutbound(server);
+    } else if (server.protocol == 'vmess' || server.protocol == 'vless') {
+      return vProtocolOutbound(server);
+    } else {
+      throw Exception(
+          'Sing-Box does not support this server type: ${server.protocol}');
+    }
+  }
+
+  static Outbound socksOutbound(XrayServer server) {
+    return Outbound(
+      type: 'socks',
+      tag: 'proxy',
+      server: server.address,
+      serverPort: server.port,
+      version: '5',
+    );
+  }
+
+  static Outbound vProtocolOutbound(XrayServer server) {
+    final utls = UTls(
+      enabled: server.fingerPrint != null && server.fingerPrint != 'none',
+      fingerprint: server.fingerPrint,
+    );
+    final reality = Reality(
+      enabled: server.tls == 'reality',
+      publicKey: server.publicKey ?? '',
+      shortId: server.shortId,
+    );
+    final tls = Tls(
+      enabled: server.tls == 'tls',
+      serverName: server.serverName ?? server.address,
+      insecure: server.allowInsecure,
+      utls: utls,
+      reality: reality,
+    );
+    final transport = Transport(
+      type: server.transport,
+      host: server.transport == 'httpupgrade'
+          ? (server.host ?? server.address)
+          : null,
+      path: server.transport == 'ws' || server.transport == 'httpupgrade'
+          ? (server.path ?? '/')
+          : null,
+      serviceName:
+      server.transport == 'grpc' ? (server.serviceName ?? '/') : null,
+    );
+    return Outbound(
+      type: server.protocol,
+      tag: 'proxy',
+      server: server.address,
+      serverPort: server.port,
+      uuid: server.uuid,
+      flow: server.flow,
+      alterId: server.protocol == 'vmess' ? server.alterId : null,
+      security: server.protocol == 'vmess' ? server.encryption : null,
+      tls: tls,
+      transport: server.tls == 'reality' ? null : transport,
+    );
+  }
+
+  static Outbound shadowsocksOutbound(ShadowsocksServer server) {
+    return Outbound(
+      type: 'shadowsocks',
+      tag: 'proxy',
+      server: server.address,
+      serverPort: server.port,
+      method: server.encryption,
+      password: server.password,
+      plugin: server.plugin,
+      pluginOpts: server.plugin,
+    );
+  }
+
+  static Outbound trojanOutbound(TrojanServer server) {
+    final tls = Tls(
+      enabled: true,
+      serverName: server.serverName ?? server.address,
+      insecure: server.allowInsecure,
+    );
+    return Outbound(
+      type: 'trojan',
+      tag: 'proxy',
+      server: server.address,
+      serverPort: server.port,
+      password: server.password,
+      network: 'tcp',
+      tls: tls,
+    );
+  }
+
+  static Outbound hysteriaOutbound(HysteriaServer server) {
+    final tls = Tls(
+      enabled: true,
+      serverName: server.serverName ?? server.address,
+      insecure: server.insecure,
+      alpn: server.alpn?.split(','),
+    );
+    return Outbound(
+      type: 'hysteria',
+      tag: 'proxy',
+      server: server.address,
+      serverPort: server.port,
+      upMbps: server.upMbps,
+      downMbps: server.downMbps,
+      obfs: server.obfs,
+      auth: server.authType == 'none'
+          ? (server.authType == 'base64' ? server.authPayload : null)
+          : null,
+      authStr: server.authType == 'none'
+          ? (server.authType == 'str' ? server.authPayload : null)
+          : null,
+      recvWindowConn: server.recvWindowConn,
+      recvWindow: server.recvWindow,
+      tls: tls,
+    );
+  }
+}

+ 44 - 0
lib/app/server/trojan/server.dart

@@ -0,0 +1,44 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:speed_safe/app/server/server_base.dart';
+
+
+part 'server.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class TrojanServer extends ServerBase {
+  String password;
+  String? serverName;
+  String? fingerPrint;
+  bool allowInsecure;
+
+  TrojanServer({
+    required String protocol,
+    required String address,
+    required int port,
+    required String remark,
+    required this.password,
+    this.serverName,
+    this.fingerPrint,
+    required this.allowInsecure,
+  }) : super(
+    protocol: protocol,
+    address: address,
+    port: port,
+    remark: remark,
+  );
+
+  factory TrojanServer.defaults() => TrojanServer(
+    protocol: 'trojan',
+    address: '',
+    port: 0,
+    remark: '',
+    password: '',
+    allowInsecure: false,
+  );
+
+  factory TrojanServer.fromJson(Map<String, dynamic> json) =>
+      _$TrojanServerFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$TrojanServerToJson(this);
+}

+ 43 - 0
lib/app/server/trojan/server.g.dart

@@ -0,0 +1,43 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'server.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+TrojanServer _$TrojanServerFromJson(Map<String, dynamic> json) => TrojanServer(
+      protocol: json['protocol'] as String,
+      address: json['address'] as String,
+      port: json['port'] as int,
+      remark: json['remark'] as String,
+      password: json['password'] as String,
+      serverName: json['serverName'] as String?,
+      fingerPrint: json['fingerPrint'] as String?,
+      allowInsecure: json['allowInsecure'] as bool,
+    )
+      ..uplink = json['uplink'] as int?
+      ..downlink = json['downlink'] as int?;
+
+Map<String, dynamic> _$TrojanServerToJson(TrojanServer instance) {
+  final val = <String, dynamic>{
+    'protocol': instance.protocol,
+    'address': instance.address,
+    'port': instance.port,
+    'remark': instance.remark,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('uplink', instance.uplink);
+  writeNotNull('downlink', instance.downlink);
+  val['password'] = instance.password;
+  writeNotNull('serverName', instance.serverName);
+  writeNotNull('fingerPrint', instance.fingerPrint);
+  val['allowInsecure'] = instance.allowInsecure;
+  return val;
+}

+ 608 - 0
lib/app/server/xray/config.dart

@@ -0,0 +1,608 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'config.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class XrayConfig {
+  Log? log;
+  Dns? dns;
+  List<Inbound>? inbounds;
+  List<Outbound>? outbounds;
+  Routing? routing;
+  Api? api;
+  Policy? policy;
+  Stats? stats;
+
+  XrayConfig({
+    this.log,
+    this.dns,
+    this.inbounds,
+    this.outbounds,
+    this.routing,
+    this.api,
+    this.policy,
+    this.stats,
+  });
+
+  factory XrayConfig.fromJson(Map<String, dynamic> json) =>
+      _$XrayConfigFromJson(json);
+
+  Map<String, dynamic> toJson() => _$XrayConfigToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Sniffing {
+  bool enabled = true;
+  List<String> destOverride = ['http', 'tls'];
+
+  Sniffing();
+
+  factory Sniffing.fromJson(Map<String, dynamic> json) =>
+      _$SniffingFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SniffingToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Dns {
+  List<DnsServer> servers;
+
+  Dns({
+    required this.servers,
+  });
+
+  factory Dns.fromJson(Map<String, dynamic> json) => _$DnsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DnsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class DnsServer {
+  String address;
+  List<String>? domains;
+  bool? skipFallback;
+
+  DnsServer({
+    required this.address,
+    this.domains,
+    this.skipFallback,
+  });
+
+  factory DnsServer.fromJson(Map<String, dynamic> json) =>
+      _$DnsServerFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DnsServerToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Log {
+  String? access;
+  String? error;
+  String loglevel;
+
+  Log({
+    this.access,
+    this.error,
+    required this.loglevel,
+  });
+
+  factory Log.fromJson(Map<String, dynamic> json) => _$LogFromJson(json);
+
+  Map<String, dynamic> toJson() => _$LogToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Inbound {
+  String? tag;
+  int port;
+  String listen;
+  String protocol;
+  Sniffing? sniffing;
+  InboundSetting settings;
+
+  Inbound({
+    this.tag,
+    required this.port,
+    required this.listen,
+    required this.protocol,
+    this.sniffing,
+    required this.settings,
+  });
+
+  factory Inbound.fromJson(Map<String, dynamic> json) =>
+      _$InboundFromJson(json);
+
+  Map<String, dynamic> toJson() => _$InboundToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class InboundSetting {
+  String? auth;
+  List<Accounts>? accounts;
+  bool? udp;
+  String? address;
+
+  InboundSetting({
+    this.auth,
+    this.accounts,
+    this.udp,
+    this.address,
+  });
+
+  factory InboundSetting.fromJson(Map<String, dynamic> json) =>
+      _$InboundSettingFromJson(json);
+
+  Map<String, dynamic> toJson() => _$InboundSettingToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Accounts {
+  String user;
+  String pass;
+
+  Accounts({
+    required this.user,
+    required this.pass,
+  });
+
+  factory Accounts.fromJson(Map<String, dynamic> json) =>
+      _$AccountsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$AccountsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Outbound {
+  String? tag;
+  String protocol;
+  OutboundSetting? settings;
+  StreamSettings? streamSettings;
+  Mux? mux;
+
+  Outbound({
+    this.tag,
+    required this.protocol,
+    this.settings,
+    this.streamSettings,
+    this.mux,
+  });
+
+  factory Outbound.fromJson(Map<String, dynamic> json) =>
+      _$OutboundFromJson(json);
+
+  Map<String, dynamic> toJson() => _$OutboundToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class OutboundSetting {
+  List<Vnext>? vnext;
+  List<dynamic>? servers;
+
+  OutboundSetting({
+    this.vnext,
+    this.servers,
+  });
+
+  factory OutboundSetting.fromJson(Map<String, dynamic> json) =>
+      _$OutboundSettingFromJson(json);
+
+  Map<String, dynamic> toJson() => _$OutboundSettingToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Socks {
+  String address;
+  int port;
+  List<User>? users;
+
+  Socks({
+    required this.address,
+    required this.port,
+    this.users,
+  });
+
+  factory Socks.fromJson(Map<String, dynamic> json) => _$SocksFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SocksToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Vnext {
+  String address;
+  int port;
+  List<User> users;
+
+  Vnext({
+    required this.address,
+    required this.port,
+    required this.users,
+  });
+
+  factory Vnext.fromJson(Map<String, dynamic> json) => _$VnextFromJson(json);
+
+  Map<String, dynamic> toJson() => _$VnextToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Shadowsocks {
+  String address;
+  int port;
+  String method;
+  String password;
+
+  Shadowsocks({
+    required this.address,
+    required this.port,
+    required this.method,
+    required this.password,
+  });
+
+  factory Shadowsocks.fromJson(Map<String, dynamic> json) =>
+      _$ShadowsocksFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ShadowsocksToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Trojan {
+  String address;
+  int port;
+  String password;
+
+  Trojan({
+    required this.address,
+    required this.port,
+    required this.password,
+  });
+
+  factory Trojan.fromJson(Map<String, dynamic> json) => _$TrojanFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TrojanToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class User {
+  String? user;
+  String? pass;
+  String? id;
+  int? alterId;
+  String? security;
+  String? encryption;
+  String? flow;
+
+  User({
+    this.user,
+    this.pass,
+    this.id,
+    this.alterId,
+    this.security,
+    this.encryption,
+    this.flow,
+  });
+
+  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
+
+  Map<String, dynamic> toJson() => _$UserToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class StreamSettings {
+  String network;
+  String security;
+  TlsSettings? tlsSettings;
+  RealitySettings? realitySettings;
+  TcpSettings? tcpSettings;
+  WsSettings? wsSettings;
+  GrpcSettings? grpcSettings;
+
+  StreamSettings({
+    required this.network,
+    required this.security,
+    this.tlsSettings,
+    this.realitySettings,
+    this.tcpSettings,
+    this.wsSettings,
+    this.grpcSettings,
+  });
+
+  factory StreamSettings.fromJson(Map<String, dynamic> json) =>
+      _$StreamSettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$StreamSettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class TcpSettings {
+  Header? header;
+
+  TcpSettings({
+    this.header,
+  });
+
+  factory TcpSettings.fromJson(Map<String, dynamic> json) =>
+      _$TcpSettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TcpSettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Header {
+  String? type;
+  Request? request;
+
+  Header({
+    this.type,
+    this.request,
+  });
+
+  factory Header.fromJson(Map<String, dynamic> json) => _$HeaderFromJson(json);
+
+  Map<String, dynamic> toJson() => _$HeaderToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Request {
+  String? version;
+  String? method;
+  List<String>? path;
+  TcpHeaders? headers;
+
+  Request({
+    this.version,
+    this.method,
+    this.path,
+    this.headers,
+  });
+
+  factory Request.defaults() => Request(
+    version: '1.1',
+    method: 'GET',
+    path: ['/'],
+    headers: TcpHeaders.defaults(),
+  );
+
+  factory Request.fromJson(Map<String, dynamic> json) =>
+      _$RequestFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RequestToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class TcpHeaders {
+  @JsonKey(name: 'Host')
+  List<String>? host;
+  @JsonKey(name: 'User-Agent')
+  List<String>? userAgent;
+  @JsonKey(name: 'Accept-Encoding')
+  List<String>? acceptEncoding;
+  @JsonKey(name: 'Connection')
+  List<String>? connection;
+  @JsonKey(name: 'Pragma')
+  String? pragma;
+
+  TcpHeaders({
+    this.host,
+    this.userAgent,
+    this.acceptEncoding,
+    this.connection,
+    this.pragma,
+  });
+
+  factory TcpHeaders.defaults() => TcpHeaders(
+    host: [''],
+    userAgent: [''],
+    acceptEncoding: ['gzip, deflate'],
+    connection: ['keep-alive'],
+    pragma: 'no-cache',
+  );
+
+  factory TcpHeaders.fromJson(Map<String, dynamic> json) =>
+      _$TcpHeadersFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TcpHeadersToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class GrpcSettings {
+  String? serviceName;
+  bool? multiMode;
+
+  GrpcSettings({
+    this.serviceName,
+    this.multiMode,
+  });
+
+  factory GrpcSettings.fromJson(Map<String, dynamic> json) =>
+      _$GrpcSettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$GrpcSettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class WsSettings {
+  String path;
+  Headers? headers;
+
+  WsSettings({
+    required this.path,
+    this.headers,
+  });
+
+  factory WsSettings.fromJson(Map<String, dynamic> json) =>
+      _$WsSettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$WsSettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Headers {
+  String? host;
+
+  Headers({
+    this.host,
+  });
+
+  factory Headers.fromJson(Map<String, dynamic> json) =>
+      _$HeadersFromJson(json);
+
+  Map<String, dynamic> toJson() => _$HeadersToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class TlsSettings {
+  bool allowInsecure;
+  String? serverName;
+  String? fingerprint;
+
+  TlsSettings({
+    required this.allowInsecure,
+    this.serverName,
+    this.fingerprint,
+  });
+
+  factory TlsSettings.fromJson(Map<String, dynamic> json) =>
+      _$TlsSettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$TlsSettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class RealitySettings {
+  String? serverName;
+  String fingerprint;
+  String? shortID;
+  String publicKey;
+  String? spiderX;
+
+  RealitySettings({
+    this.serverName,
+    required this.fingerprint,
+    this.shortID,
+    required this.publicKey,
+    this.spiderX,
+  });
+
+  factory RealitySettings.fromJson(Map<String, dynamic> json) =>
+      _$RealitySettingsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RealitySettingsToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Mux {
+  bool enabled;
+  int concurrency;
+
+  Mux({
+    required this.enabled,
+    required this.concurrency,
+  });
+
+  factory Mux.fromJson(Map<String, dynamic> json) => _$MuxFromJson(json);
+
+  Map<String, dynamic> toJson() => _$MuxToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Routing {
+  String domainStrategy;
+
+  String domainMatcher;
+
+  List<XrayRule> rules;
+
+  Routing({
+    required this.domainStrategy,
+    required this.domainMatcher,
+    required this.rules,
+  });
+
+  factory Routing.fromJson(Map<String, dynamic> json) =>
+      _$RoutingFromJson(json);
+
+  Map<String, dynamic> toJson() => _$RoutingToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class XrayRule {
+  String? name;
+  bool enabled;
+  String type;
+  String? inboundTag;
+  String? outboundTag;
+  List<String>? domain;
+  List<String>? ip;
+  String? port;
+
+  XrayRule({
+    this.name,
+    required this.enabled,
+    required this.type,
+    this.inboundTag,
+    this.outboundTag,
+    this.domain,
+    this.ip,
+    this.port,
+  });
+
+  factory XrayRule.fromJson(Map<String, dynamic> json) =>
+      _$XrayRuleFromJson(json);
+
+  Map<String, dynamic> toJson() => _$XrayRuleToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Api {
+  String tag;
+  List<String>? services;
+
+  Api({
+    required this.tag,
+    this.services,
+  });
+
+  factory Api.fromJson(Map<String, dynamic> json) => _$ApiFromJson(json);
+
+  Map<String, dynamic> toJson() => _$ApiToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Policy {
+  System system;
+
+  Policy({
+    required this.system,
+  });
+
+  factory Policy.fromJson(Map<String, dynamic> json) => _$PolicyFromJson(json);
+
+  Map<String, dynamic> toJson() => _$PolicyToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class System {
+  bool? statsInboundUplink;
+  bool? statsInboundDownlink;
+  bool? statsOutboundUplink;
+  bool? statsOutboundDownlink;
+
+  System({
+    this.statsInboundUplink,
+    this.statsInboundDownlink,
+    this.statsOutboundUplink,
+    this.statsOutboundDownlink,
+  });
+
+  factory System.fromJson(Map<String, dynamic> json) => _$SystemFromJson(json);
+
+  Map<String, dynamic> toJson() => _$SystemToJson(this);
+}
+
+@JsonSerializable(includeIfNull: false)
+class Stats {
+  Stats();
+
+  factory Stats.fromJson(Map<String, dynamic> json) => _$StatsFromJson(json);
+
+  Map<String, dynamic> toJson() => _$StatsToJson(this);
+}

+ 689 - 0
lib/app/server/xray/config.g.dart

@@ -0,0 +1,689 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'config.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+XrayConfig _$XrayConfigFromJson(Map<String, dynamic> json) => XrayConfig(
+      log: json['log'] == null
+          ? null
+          : Log.fromJson(json['log'] as Map<String, dynamic>),
+      dns: json['dns'] == null
+          ? null
+          : Dns.fromJson(json['dns'] as Map<String, dynamic>),
+      inbounds: (json['inbounds'] as List<dynamic>?)
+          ?.map((e) => Inbound.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      outbounds: (json['outbounds'] as List<dynamic>?)
+          ?.map((e) => Outbound.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      routing: json['routing'] == null
+          ? null
+          : Routing.fromJson(json['routing'] as Map<String, dynamic>),
+      api: json['api'] == null
+          ? null
+          : Api.fromJson(json['api'] as Map<String, dynamic>),
+      policy: json['policy'] == null
+          ? null
+          : Policy.fromJson(json['policy'] as Map<String, dynamic>),
+      stats: json['stats'] == null
+          ? null
+          : Stats.fromJson(json['stats'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$XrayConfigToJson(XrayConfig instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('log', instance.log);
+  writeNotNull('dns', instance.dns);
+  writeNotNull('inbounds', instance.inbounds);
+  writeNotNull('outbounds', instance.outbounds);
+  writeNotNull('routing', instance.routing);
+  writeNotNull('api', instance.api);
+  writeNotNull('policy', instance.policy);
+  writeNotNull('stats', instance.stats);
+  return val;
+}
+
+Sniffing _$SniffingFromJson(Map<String, dynamic> json) => Sniffing()
+  ..enabled = json['enabled'] as bool
+  ..destOverride =
+      (json['destOverride'] as List<dynamic>).map((e) => e as String).toList();
+
+Map<String, dynamic> _$SniffingToJson(Sniffing instance) => <String, dynamic>{
+      'enabled': instance.enabled,
+      'destOverride': instance.destOverride,
+    };
+
+Dns _$DnsFromJson(Map<String, dynamic> json) => Dns(
+      servers: (json['servers'] as List<dynamic>)
+          .map((e) => DnsServer.fromJson(e as Map<String, dynamic>))
+          .toList(),
+    );
+
+Map<String, dynamic> _$DnsToJson(Dns instance) => <String, dynamic>{
+      'servers': instance.servers,
+    };
+
+DnsServer _$DnsServerFromJson(Map<String, dynamic> json) => DnsServer(
+      address: json['address'] as String,
+      domains:
+          (json['domains'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      skipFallback: json['skipFallback'] as bool?,
+    );
+
+Map<String, dynamic> _$DnsServerToJson(DnsServer instance) {
+  final val = <String, dynamic>{
+    'address': instance.address,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('domains', instance.domains);
+  writeNotNull('skipFallback', instance.skipFallback);
+  return val;
+}
+
+Log _$LogFromJson(Map<String, dynamic> json) => Log(
+      access: json['access'] as String?,
+      error: json['error'] as String?,
+      loglevel: json['loglevel'] as String,
+    );
+
+Map<String, dynamic> _$LogToJson(Log instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('access', instance.access);
+  writeNotNull('error', instance.error);
+  val['loglevel'] = instance.loglevel;
+  return val;
+}
+
+Inbound _$InboundFromJson(Map<String, dynamic> json) => Inbound(
+      tag: json['tag'] as String?,
+      port: json['port'] as int,
+      listen: json['listen'] as String,
+      protocol: json['protocol'] as String,
+      sniffing: json['sniffing'] == null
+          ? null
+          : Sniffing.fromJson(json['sniffing'] as Map<String, dynamic>),
+      settings:
+          InboundSetting.fromJson(json['settings'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$InboundToJson(Inbound instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('tag', instance.tag);
+  val['port'] = instance.port;
+  val['listen'] = instance.listen;
+  val['protocol'] = instance.protocol;
+  writeNotNull('sniffing', instance.sniffing);
+  val['settings'] = instance.settings;
+  return val;
+}
+
+InboundSetting _$InboundSettingFromJson(Map<String, dynamic> json) =>
+    InboundSetting(
+      auth: json['auth'] as String?,
+      accounts: (json['accounts'] as List<dynamic>?)
+          ?.map((e) => Accounts.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      udp: json['udp'] as bool?,
+      address: json['address'] as String?,
+    );
+
+Map<String, dynamic> _$InboundSettingToJson(InboundSetting instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('auth', instance.auth);
+  writeNotNull('accounts', instance.accounts);
+  writeNotNull('udp', instance.udp);
+  writeNotNull('address', instance.address);
+  return val;
+}
+
+Accounts _$AccountsFromJson(Map<String, dynamic> json) => Accounts(
+      user: json['user'] as String,
+      pass: json['pass'] as String,
+    );
+
+Map<String, dynamic> _$AccountsToJson(Accounts instance) => <String, dynamic>{
+      'user': instance.user,
+      'pass': instance.pass,
+    };
+
+Outbound _$OutboundFromJson(Map<String, dynamic> json) => Outbound(
+      tag: json['tag'] as String?,
+      protocol: json['protocol'] as String,
+      settings: json['settings'] == null
+          ? null
+          : OutboundSetting.fromJson(json['settings'] as Map<String, dynamic>),
+      streamSettings: json['streamSettings'] == null
+          ? null
+          : StreamSettings.fromJson(
+              json['streamSettings'] as Map<String, dynamic>),
+      mux: json['mux'] == null
+          ? null
+          : Mux.fromJson(json['mux'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$OutboundToJson(Outbound instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('tag', instance.tag);
+  val['protocol'] = instance.protocol;
+  writeNotNull('settings', instance.settings);
+  writeNotNull('streamSettings', instance.streamSettings);
+  writeNotNull('mux', instance.mux);
+  return val;
+}
+
+OutboundSetting _$OutboundSettingFromJson(Map<String, dynamic> json) =>
+    OutboundSetting(
+      vnext: (json['vnext'] as List<dynamic>?)
+          ?.map((e) => Vnext.fromJson(e as Map<String, dynamic>))
+          .toList(),
+      servers: json['servers'] as List<dynamic>?,
+    );
+
+Map<String, dynamic> _$OutboundSettingToJson(OutboundSetting instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('vnext', instance.vnext);
+  writeNotNull('servers', instance.servers);
+  return val;
+}
+
+Socks _$SocksFromJson(Map<String, dynamic> json) => Socks(
+      address: json['address'] as String,
+      port: json['port'] as int,
+      users: (json['users'] as List<dynamic>?)
+          ?.map((e) => User.fromJson(e as Map<String, dynamic>))
+          .toList(),
+    );
+
+Map<String, dynamic> _$SocksToJson(Socks instance) {
+  final val = <String, dynamic>{
+    'address': instance.address,
+    'port': instance.port,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('users', instance.users);
+  return val;
+}
+
+Vnext _$VnextFromJson(Map<String, dynamic> json) => Vnext(
+      address: json['address'] as String,
+      port: json['port'] as int,
+      users: (json['users'] as List<dynamic>)
+          .map((e) => User.fromJson(e as Map<String, dynamic>))
+          .toList(),
+    );
+
+Map<String, dynamic> _$VnextToJson(Vnext instance) => <String, dynamic>{
+      'address': instance.address,
+      'port': instance.port,
+      'users': instance.users,
+    };
+
+Shadowsocks _$ShadowsocksFromJson(Map<String, dynamic> json) => Shadowsocks(
+      address: json['address'] as String,
+      port: json['port'] as int,
+      method: json['method'] as String,
+      password: json['password'] as String,
+    );
+
+Map<String, dynamic> _$ShadowsocksToJson(Shadowsocks instance) =>
+    <String, dynamic>{
+      'address': instance.address,
+      'port': instance.port,
+      'method': instance.method,
+      'password': instance.password,
+    };
+
+Trojan _$TrojanFromJson(Map<String, dynamic> json) => Trojan(
+      address: json['address'] as String,
+      port: json['port'] as int,
+      password: json['password'] as String,
+    );
+
+Map<String, dynamic> _$TrojanToJson(Trojan instance) => <String, dynamic>{
+      'address': instance.address,
+      'port': instance.port,
+      'password': instance.password,
+    };
+
+User _$UserFromJson(Map<String, dynamic> json) => User(
+      user: json['user'] as String?,
+      pass: json['pass'] as String?,
+      id: json['id'] as String?,
+      alterId: json['alterId'] as int?,
+      security: json['security'] as String?,
+      encryption: json['encryption'] as String?,
+      flow: json['flow'] as String?,
+    );
+
+Map<String, dynamic> _$UserToJson(User instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('user', instance.user);
+  writeNotNull('pass', instance.pass);
+  writeNotNull('id', instance.id);
+  writeNotNull('alterId', instance.alterId);
+  writeNotNull('security', instance.security);
+  writeNotNull('encryption', instance.encryption);
+  writeNotNull('flow', instance.flow);
+  return val;
+}
+
+StreamSettings _$StreamSettingsFromJson(Map<String, dynamic> json) =>
+    StreamSettings(
+      network: json['network'] as String,
+      security: json['security'] as String,
+      tlsSettings: json['tlsSettings'] == null
+          ? null
+          : TlsSettings.fromJson(json['tlsSettings'] as Map<String, dynamic>),
+      realitySettings: json['realitySettings'] == null
+          ? null
+          : RealitySettings.fromJson(
+              json['realitySettings'] as Map<String, dynamic>),
+      tcpSettings: json['tcpSettings'] == null
+          ? null
+          : TcpSettings.fromJson(json['tcpSettings'] as Map<String, dynamic>),
+      wsSettings: json['wsSettings'] == null
+          ? null
+          : WsSettings.fromJson(json['wsSettings'] as Map<String, dynamic>),
+      grpcSettings: json['grpcSettings'] == null
+          ? null
+          : GrpcSettings.fromJson(json['grpcSettings'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$StreamSettingsToJson(StreamSettings instance) {
+  final val = <String, dynamic>{
+    'network': instance.network,
+    'security': instance.security,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('tlsSettings', instance.tlsSettings);
+  writeNotNull('realitySettings', instance.realitySettings);
+  writeNotNull('tcpSettings', instance.tcpSettings);
+  writeNotNull('wsSettings', instance.wsSettings);
+  writeNotNull('grpcSettings', instance.grpcSettings);
+  return val;
+}
+
+TcpSettings _$TcpSettingsFromJson(Map<String, dynamic> json) => TcpSettings(
+      header: json['header'] == null
+          ? null
+          : Header.fromJson(json['header'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$TcpSettingsToJson(TcpSettings instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('header', instance.header);
+  return val;
+}
+
+Header _$HeaderFromJson(Map<String, dynamic> json) => Header(
+      type: json['type'] as String?,
+      request: json['request'] == null
+          ? null
+          : Request.fromJson(json['request'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$HeaderToJson(Header instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('type', instance.type);
+  writeNotNull('request', instance.request);
+  return val;
+}
+
+Request _$RequestFromJson(Map<String, dynamic> json) => Request(
+      version: json['version'] as String?,
+      method: json['method'] as String?,
+      path: (json['path'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      headers: json['headers'] == null
+          ? null
+          : TcpHeaders.fromJson(json['headers'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$RequestToJson(Request instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('version', instance.version);
+  writeNotNull('method', instance.method);
+  writeNotNull('path', instance.path);
+  writeNotNull('headers', instance.headers);
+  return val;
+}
+
+TcpHeaders _$TcpHeadersFromJson(Map<String, dynamic> json) => TcpHeaders(
+      host: (json['Host'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      userAgent: (json['User-Agent'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+      acceptEncoding: (json['Accept-Encoding'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+      connection: (json['Connection'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+      pragma: json['Pragma'] as String?,
+    );
+
+Map<String, dynamic> _$TcpHeadersToJson(TcpHeaders instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('Host', instance.host);
+  writeNotNull('User-Agent', instance.userAgent);
+  writeNotNull('Accept-Encoding', instance.acceptEncoding);
+  writeNotNull('Connection', instance.connection);
+  writeNotNull('Pragma', instance.pragma);
+  return val;
+}
+
+GrpcSettings _$GrpcSettingsFromJson(Map<String, dynamic> json) => GrpcSettings(
+      serviceName: json['serviceName'] as String?,
+      multiMode: json['multiMode'] as bool?,
+    );
+
+Map<String, dynamic> _$GrpcSettingsToJson(GrpcSettings instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('serviceName', instance.serviceName);
+  writeNotNull('multiMode', instance.multiMode);
+  return val;
+}
+
+WsSettings _$WsSettingsFromJson(Map<String, dynamic> json) => WsSettings(
+      path: json['path'] as String,
+      headers: json['headers'] == null
+          ? null
+          : Headers.fromJson(json['headers'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$WsSettingsToJson(WsSettings instance) {
+  final val = <String, dynamic>{
+    'path': instance.path,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('headers', instance.headers);
+  return val;
+}
+
+Headers _$HeadersFromJson(Map<String, dynamic> json) => Headers(
+      host: json['host'] as String?,
+    );
+
+Map<String, dynamic> _$HeadersToJson(Headers instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('host', instance.host);
+  return val;
+}
+
+TlsSettings _$TlsSettingsFromJson(Map<String, dynamic> json) => TlsSettings(
+      allowInsecure: json['allowInsecure'] as bool,
+      serverName: json['serverName'] as String?,
+      fingerprint: json['fingerprint'] as String?,
+    );
+
+Map<String, dynamic> _$TlsSettingsToJson(TlsSettings instance) {
+  final val = <String, dynamic>{
+    'allowInsecure': instance.allowInsecure,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('serverName', instance.serverName);
+  writeNotNull('fingerprint', instance.fingerprint);
+  return val;
+}
+
+RealitySettings _$RealitySettingsFromJson(Map<String, dynamic> json) =>
+    RealitySettings(
+      serverName: json['serverName'] as String?,
+      fingerprint: json['fingerprint'] as String,
+      shortID: json['shortID'] as String?,
+      publicKey: json['publicKey'] as String,
+      spiderX: json['spiderX'] as String?,
+    );
+
+Map<String, dynamic> _$RealitySettingsToJson(RealitySettings instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('serverName', instance.serverName);
+  val['fingerprint'] = instance.fingerprint;
+  writeNotNull('shortID', instance.shortID);
+  val['publicKey'] = instance.publicKey;
+  writeNotNull('spiderX', instance.spiderX);
+  return val;
+}
+
+Mux _$MuxFromJson(Map<String, dynamic> json) => Mux(
+      enabled: json['enabled'] as bool,
+      concurrency: json['concurrency'] as int,
+    );
+
+Map<String, dynamic> _$MuxToJson(Mux instance) => <String, dynamic>{
+      'enabled': instance.enabled,
+      'concurrency': instance.concurrency,
+    };
+
+Routing _$RoutingFromJson(Map<String, dynamic> json) => Routing(
+      domainStrategy: json['domainStrategy'] as String,
+      domainMatcher: json['domainMatcher'] as String,
+      rules: (json['rules'] as List<dynamic>)
+          .map((e) => XrayRule.fromJson(e as Map<String, dynamic>))
+          .toList(),
+    );
+
+Map<String, dynamic> _$RoutingToJson(Routing instance) => <String, dynamic>{
+      'domainStrategy': instance.domainStrategy,
+      'domainMatcher': instance.domainMatcher,
+      'rules': instance.rules,
+    };
+
+XrayRule _$XrayRuleFromJson(Map<String, dynamic> json) => XrayRule(
+      name: json['name'] as String?,
+      enabled: json['enabled'] as bool,
+      type: json['type'] as String,
+      inboundTag: json['inboundTag'] as String?,
+      outboundTag: json['outboundTag'] as String?,
+      domain:
+          (json['domain'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      ip: (json['ip'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      port: json['port'] as String?,
+    );
+
+Map<String, dynamic> _$XrayRuleToJson(XrayRule instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('name', instance.name);
+  val['enabled'] = instance.enabled;
+  val['type'] = instance.type;
+  writeNotNull('inboundTag', instance.inboundTag);
+  writeNotNull('outboundTag', instance.outboundTag);
+  writeNotNull('domain', instance.domain);
+  writeNotNull('ip', instance.ip);
+  writeNotNull('port', instance.port);
+  return val;
+}
+
+Api _$ApiFromJson(Map<String, dynamic> json) => Api(
+      tag: json['tag'] as String,
+      services: (json['services'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
+    );
+
+Map<String, dynamic> _$ApiToJson(Api instance) {
+  final val = <String, dynamic>{
+    'tag': instance.tag,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('services', instance.services);
+  return val;
+}
+
+Policy _$PolicyFromJson(Map<String, dynamic> json) => Policy(
+      system: System.fromJson(json['system'] as Map<String, dynamic>),
+    );
+
+Map<String, dynamic> _$PolicyToJson(Policy instance) => <String, dynamic>{
+      'system': instance.system,
+    };
+
+System _$SystemFromJson(Map<String, dynamic> json) => System(
+      statsInboundUplink: json['statsInboundUplink'] as bool?,
+      statsInboundDownlink: json['statsInboundDownlink'] as bool?,
+      statsOutboundUplink: json['statsOutboundUplink'] as bool?,
+      statsOutboundDownlink: json['statsOutboundDownlink'] as bool?,
+    );
+
+Map<String, dynamic> _$SystemToJson(System instance) {
+  final val = <String, dynamic>{};
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('statsInboundUplink', instance.statsInboundUplink);
+  writeNotNull('statsInboundDownlink', instance.statsInboundDownlink);
+  writeNotNull('statsOutboundUplink', instance.statsOutboundUplink);
+  writeNotNull('statsOutboundDownlink', instance.statsOutboundDownlink);
+  return val;
+}
+
+Stats _$StatsFromJson(Map<String, dynamic> json) => Stats();
+
+Map<String, dynamic> _$StatsToJson(Stats instance) => <String, dynamic>{};

+ 73 - 0
lib/app/server/xray/server.dart

@@ -0,0 +1,73 @@
+import 'package:json_annotation/json_annotation.dart';
+import 'package:speed_safe/app/server/server_base.dart';
+
+import 'package:uuid/uuid.dart';
+
+part 'server.g.dart';
+
+@JsonSerializable(includeIfNull: false)
+class XrayServer extends ServerBase {
+  String uuid;
+  int? alterId;
+  String encryption;
+  String? flow;
+  String transport;
+  String? host;
+  String? path;
+  String? grpcMode;
+  String? serviceName;
+  String tls;
+  String? serverName;
+  String? fingerPrint;
+  String? publicKey;
+  String? shortId;
+  String? spiderX;
+  bool allowInsecure;
+
+  XrayServer({
+    required String protocol,
+    required String address,
+    required int port,
+    required String remark,
+    required this.uuid,
+    this.alterId,
+    required this.encryption,
+    this.flow,
+    required this.transport,
+    this.host,
+    this.path,
+    this.grpcMode,
+    this.serviceName,
+    required this.tls,
+    this.serverName,
+    this.fingerPrint,
+    this.publicKey,
+    this.shortId,
+    this.spiderX,
+    required this.allowInsecure,
+  }) : super(
+    protocol: protocol,
+    address: address,
+    port: port,
+    remark: remark,
+  );
+
+  factory XrayServer.defaults() => XrayServer(
+    protocol: '',
+    address: '',
+    port: 0,
+    remark: '',
+    uuid: const Uuid().v4(),
+    alterId: 0,
+    encryption: '',
+    transport: 'tcp',
+    tls: 'none',
+    allowInsecure: false,
+  );
+
+  factory XrayServer.fromJson(Map<String, dynamic> json) =>
+      _$XrayServerFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$XrayServerToJson(this);
+}

+ 67 - 0
lib/app/server/xray/server.g.dart

@@ -0,0 +1,67 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'server.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+XrayServer _$XrayServerFromJson(Map<String, dynamic> json) => XrayServer(
+      protocol: json['protocol'] as String,
+      address: json['address'] as String,
+      port: json['port'] as int,
+      remark: json['remark'] as String,
+      uuid: json['uuid'] as String,
+      alterId: json['alterId'] as int?,
+      encryption: json['encryption'] as String,
+      flow: json['flow'] as String?,
+      transport: json['transport'] as String,
+      host: json['host'] as String?,
+      path: json['path'] as String?,
+      grpcMode: json['grpcMode'] as String?,
+      serviceName: json['serviceName'] as String?,
+      tls: json['tls'] as String,
+      serverName: json['serverName'] as String?,
+      fingerPrint: json['fingerPrint'] as String?,
+      publicKey: json['publicKey'] as String?,
+      shortId: json['shortId'] as String?,
+      spiderX: json['spiderX'] as String?,
+      allowInsecure: json['allowInsecure'] as bool,
+    )
+      ..uplink = json['uplink'] as int?
+      ..downlink = json['downlink'] as int?;
+
+Map<String, dynamic> _$XrayServerToJson(XrayServer instance) {
+  final val = <String, dynamic>{
+    'protocol': instance.protocol,
+    'address': instance.address,
+    'port': instance.port,
+    'remark': instance.remark,
+  };
+
+  void writeNotNull(String key, dynamic value) {
+    if (value != null) {
+      val[key] = value;
+    }
+  }
+
+  writeNotNull('uplink', instance.uplink);
+  writeNotNull('downlink', instance.downlink);
+  val['uuid'] = instance.uuid;
+  writeNotNull('alterId', instance.alterId);
+  val['encryption'] = instance.encryption;
+  writeNotNull('flow', instance.flow);
+  val['transport'] = instance.transport;
+  writeNotNull('host', instance.host);
+  writeNotNull('path', instance.path);
+  writeNotNull('grpcMode', instance.grpcMode);
+  writeNotNull('serviceName', instance.serviceName);
+  val['tls'] = instance.tls;
+  writeNotNull('serverName', instance.serverName);
+  writeNotNull('fingerPrint', instance.fingerPrint);
+  writeNotNull('publicKey', instance.publicKey);
+  writeNotNull('shortId', instance.shortId);
+  writeNotNull('spiderX', instance.spiderX);
+  val['allowInsecure'] = instance.allowInsecure;
+  return val;
+}

+ 44 - 10
lib/app/util/system.dart

@@ -10,6 +10,36 @@ const speedBuildNumber = 7;
 const speedFullVersion = '$speedVersion+$speedBuildNumber';
 const speedLastCommitHash = 'SELF_BUILD';
 
+const String singBoxUrl = 'https://github.com/SagerNet/sing-box';
+const String xrayCoreRepoUrl = 'https://github.com/xtls/xray-core';
+const String shadowsocksRustUrl =
+    'https://github.com/shadowsocks/shadowsocks-rust';
+const String hysteriaUrl = 'https://github.com/apernet/hysteria';
+const String singBoxRulesRepoUrl = 'https://github.com/lyc8503/sing-box-rules';
+const String geositeDBUrl =
+    'https://github.com/lyc8503/sing-box-rules/releases/latest/download/geosite.db';
+const String geoipDBUrl =
+    'https://github.com/lyc8503/sing-box-rules/releases/latest/download/geoip.db';
+const String v2rayRulesDatRepoUrl =
+    'https://github.com/Loyalsoldier/v2ray-rules-dat';
+const String geositeDatUrl =
+    'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat';
+const String geoipDatUrl =
+    'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat';
+const String sphiaRepoUrl = 'https://github.com/YukidouSatoru/sphia';
+
+
+final coreRepositories = {
+  'sing-box': singBoxUrl,
+  'xray-core': xrayCoreRepoUrl,
+  'shadowsocks-rust': shadowsocksRustUrl,
+  'hysteria': hysteriaUrl,
+  'sing-box-rules': singBoxRulesRepoUrl,
+  'v2ray-rules-dat': v2rayRulesDatRepoUrl,
+  'sphia': sphiaRepoUrl,
+  // Add new core here
+};
+
 
 class SystemUtil{
   static late final OS os;
@@ -221,16 +251,20 @@ class SystemUtil{
       return fileExists(p.join(binPath, getCoreFileName(coreName)));
     }
   }
-  // static List<String> getCoreFileNames() {
-  //   List<String> fileNames = [];
-  //   coreRepositories.entries
-  //       .toList()
-  //       .sublist(0, coreRepositories.length - 3)
-  //       .forEach((entry) {
-  //     fileNames.add(getCoreFileName(entry.key));
-  //   });
-  //   return fileNames;
-  // }
+
+
+  static List<String> getCoreFileNames() {
+    List<String> fileNames = [];
+    coreRepositories.entries
+        .toList()
+        .sublist(0, coreRepositories.length - 3)
+        .forEach((entry) {
+      fileNames.add(getCoreFileName(entry.key));
+    });
+    return fileNames;
+  }
+
+
   static void initPaths() {
     binPath = p.join(appPath, 'bin');
     configPath = p.join(appPath, 'config');

+ 62 - 9
lib/main.dart

@@ -1,23 +1,26 @@
+import 'dart:async';
 import 'dart:io';
 
 import 'package:flutter/material.dart';
 
 import 'package:get/get.dart';
 import 'package:path_provider/path_provider.dart';
+import 'package:speed_safe/app/controllers/config.dart';
+import 'package:speed_safe/app/controllers/controllers.dart';
 import 'package:speed_safe/app/util/log.dart';
 import 'package:speed_safe/app/util/system.dart';
 import 'package:window_manager/window_manager.dart';
 
 import 'app/routes/app_pages.dart';
 
-
-Future<void> getAppPath() async{
+Future<void> getAppPath() async {
   if (Platform.isLinux && Platform.environment.containsKey('APPIMAGE')) {
     execPath = Platform.environment['APPIMAGE']!;
   } else {
     execPath = Platform.resolvedExecutable;
   }
-  if (const bool.fromEnvironment('dart.vm.product')) { //如果是发布
+  if (const bool.fromEnvironment('dart.vm.product')) {
+    //如果是发布
     if (Platform.isLinux) {
       final linuxAppPath = (await getApplicationSupportDirectory()).path;
       final int firstIndex = linuxAppPath.indexOf('/root/');
@@ -41,7 +44,8 @@ Future<void> getAppPath() async{
           execPath.substring(0, execPath.lastIndexOf(Platform.pathSeparator));
     }
   } else {
-    if (Platform.isMacOS) { //如果是调试
+    if (Platform.isMacOS) {
+      //如果是调试
       // For debug
       appPath = execPath.substring(0, execPath.lastIndexOf('/speed_safe.app'));
     } else {
@@ -52,7 +56,10 @@ Future<void> getAppPath() async{
 }
 
 Future<void> initConfig() async {
+  await controllers.config.init();
+}
 
+Future<void> initApp() async {
   // Get app path
   await getAppPath();
 
@@ -90,7 +97,7 @@ Future<void> initConfig() async {
 
   logger.i(speedInfo);
 
-
+  await initConfig();
 
   WidgetsFlutterBinding.ensureInitialized();
   // 必须加上这一行。
@@ -112,12 +119,58 @@ Future<void> initConfig() async {
 }
 
 void main() async {
-  await initConfig();
+  Get.put(ConfigController());
+  controllers.init();
+  await initApp();
   runApp(
-    GetMaterialApp(
+    const MyApp(),
+  );
+}
+
+class MyApp extends StatefulWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  @override
+  State<MyApp> createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  late StreamSubscription<bool> listenShow;
+
+  @override
+  void initState() {
+ 
+    //controllers.global.init(context);
+    // listenShow = controllers.window.isVisible.stream.listen((event) async {
+    //   if (!event) return;
+    //   await Future.delayed(const Duration(milliseconds: 100));
+    //   await Get.forceAppUpdate();
+    // });
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // const Set<PointerDeviceKind> kTouchLikeDeviceTypes = <PointerDeviceKind>{
+    //   PointerDeviceKind.touch,
+    //   PointerDeviceKind.mouse,
+    //   PointerDeviceKind.stylus,
+    //   PointerDeviceKind.invertedStylus,
+    //   PointerDeviceKind.unknown
+    // };
+
+    return GetMaterialApp(
+      debugShowCheckedModeBanner: false,
       title: "Application",
       initialRoute: AppPages.INITIAL,
       getPages: AppPages.routes,
-    ),
-  );
+    );
+  }
+
+  @override
+  void dispose() {
+    //controllers.global.dispose();
+    listenShow.cancel();
+    super.dispose();
+  }
 }

+ 8 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -7,12 +7,20 @@
 #include "generated_plugin_registrant.h"
 
 #include <screen_retriever/screen_retriever_plugin.h>
+#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
+#include <url_launcher_linux/url_launcher_plugin.h>
 #include <window_manager/window_manager_plugin.h>
 
 void fl_register_plugins(FlPluginRegistry* registry) {
   g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
   screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
+  g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
+  sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
+  g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+  url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
   g_autoptr(FlPluginRegistrar) window_manager_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
   window_manager_plugin_register_with_registrar(window_manager_registrar);

+ 2 - 0
linux/flutter/generated_plugins.cmake

@@ -4,6 +4,8 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   screen_retriever
+  sqlite3_flutter_libs
+  url_launcher_linux
   window_manager
 )
 

+ 4 - 0
macos/Flutter/GeneratedPluginRegistrant.swift

@@ -7,10 +7,14 @@ import Foundation
 
 import path_provider_foundation
 import screen_retriever
+import sqlite3_flutter_libs
+import url_launcher_macos
 import window_manager
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
+  Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
+  UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
   WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
 }

+ 43 - 0
macos/Podfile

@@ -0,0 +1,43 @@
+platform :osx, '10.14'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+  target 'RunnerTests' do
+    inherit! :search_paths
+  end
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_macos_build_settings(target)
+  end
+end

+ 65 - 0
macos/Podfile.lock

@@ -0,0 +1,65 @@
+PODS:
+  - FlutterMacOS (1.0.0)
+  - path_provider_foundation (0.0.1):
+    - Flutter
+    - FlutterMacOS
+  - screen_retriever (0.0.1):
+    - FlutterMacOS
+  - sqlite3 (3.44.0):
+    - sqlite3/common (= 3.44.0)
+  - sqlite3/common (3.44.0)
+  - sqlite3/fts5 (3.44.0):
+    - sqlite3/common
+  - sqlite3/perf-threadsafe (3.44.0):
+    - sqlite3/common
+  - sqlite3/rtree (3.44.0):
+    - sqlite3/common
+  - sqlite3_flutter_libs (0.0.1):
+    - FlutterMacOS
+    - sqlite3 (~> 3.44.0)
+    - sqlite3/fts5
+    - sqlite3/perf-threadsafe
+    - sqlite3/rtree
+  - url_launcher_macos (0.0.1):
+    - FlutterMacOS
+  - window_manager (0.2.0):
+    - FlutterMacOS
+
+DEPENDENCIES:
+  - FlutterMacOS (from `Flutter/ephemeral`)
+  - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
+  - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
+  - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
+  - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
+  - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
+
+SPEC REPOS:
+  trunk:
+    - sqlite3
+
+EXTERNAL SOURCES:
+  FlutterMacOS:
+    :path: Flutter/ephemeral
+  path_provider_foundation:
+    :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
+  screen_retriever:
+    :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
+  sqlite3_flutter_libs:
+    :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
+  url_launcher_macos:
+    :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
+  window_manager:
+    :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
+
+SPEC CHECKSUMS:
+  FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
+  path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
+  screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
+  sqlite3: 6e2d4a4879854d0ec86b476bf3c3e30870bac273
+  sqlite3_flutter_libs: 5b7e226d522d67be60d7ade93f5aa11ebc0cd796
+  url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
+  window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
+
+PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
+
+COCOAPODS: 1.12.1

+ 485 - 0
pubspec.lock

@@ -1,6 +1,38 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
+      url: "https://pub.dev"
+    source: hosted
+    version: "64.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.2.0"
+  analyzer_plugin:
+    dependency: transitive
+    description:
+      name: analyzer_plugin
+      sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.11.3"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.2"
   async:
     dependency: transitive
     description:
@@ -17,6 +49,70 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.1"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.1"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
+  build_daemon:
+    dependency: transitive
+    description:
+      name: build_daemon
+      sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.1"
+  build_resolvers:
+    dependency: transitive
+    description:
+      name: build_resolvers
+      sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.1"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.6"
+  build_runner_core:
+    dependency: transitive
+    description:
+      name: build_runner_core
+      sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.2.11"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
+      url: "https://pub.dev"
+    source: hosted
+    version: "5.1.1"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.7.0"
   characters:
     dependency: transitive
     description:
@@ -25,6 +121,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.0"
+  charcode:
+    dependency: transitive
+    description:
+      name: charcode
+      sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.1"
+  checked_yaml:
+    dependency: transitive
+    description:
+      name: checked_yaml
+      sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.3"
+  cli_util:
+    dependency: transitive
+    description:
+      name: cli_util
+      sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.4.0"
   clock:
     dependency: transitive
     description:
@@ -33,6 +153,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.1.1"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.7.0"
   collection:
     dependency: transitive
     description:
@@ -41,6 +169,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.17.2"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.1"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.3"
   cupertino_icons:
     dependency: "direct main"
     description:
@@ -49,6 +193,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.0.6"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      sha256: abd7625e16f51f554ea244d090292945ec4d4be7bfbaf2ec8cccea568919d334
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.3"
+  drift:
+    dependency: transitive
+    description:
+      name: drift
+      sha256: ef2ddafe89c1f5f26767e5eada65d739de4e9d2820303f7249f15a005999d5fc
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.13.1"
+  drift_dev:
+    dependency: "direct dev"
+    description:
+      name: drift_dev
+      sha256: "369d2769d84e0c2d2cb4cd420e4fdb4f975852c83ebb934733c3b382c62961cd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.13.2"
   fake_async:
     dependency: transitive
     description:
@@ -65,6 +233,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.0"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "7.0.0"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -83,6 +267,19 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.0"
   get:
     dependency: "direct main"
     description:
@@ -91,6 +288,38 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.6.6"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.1"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.0.2"
   intl:
     dependency: "direct main"
     description:
@@ -99,6 +328,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.18.1"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.6.7"
   json_annotation:
     dependency: "direct main"
     description:
@@ -107,6 +352,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.8.1"
+  json_serializable:
+    dependency: "direct dev"
+    description:
+      name: json_serializable
+      sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.7.1"
   lints:
     dependency: transitive
     description:
@@ -123,6 +376,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.0.2+1"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.0"
   matcher:
     dependency: transitive
     description:
@@ -147,6 +408,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.9.1"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
   path:
     dependency: "direct main"
     description:
@@ -219,6 +496,38 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.6"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.5.1"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.4"
+  pubspec_parse:
+    dependency: transitive
+    description:
+      name: pubspec_parse
+      sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.3"
+  recase:
+    dependency: transitive
+    description:
+      name: recase
+      sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.0"
   screen_retriever:
     dependency: transitive
     description:
@@ -227,11 +536,43 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.1.9"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.1"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.4"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.0"
+  source_helper:
+    dependency: transitive
+    description:
+      name: source_helper
+      sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.4"
   source_span:
     dependency: transitive
     description:
@@ -240,6 +581,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.10.0"
+  sqlite3:
+    dependency: transitive
+    description:
+      name: sqlite3
+      sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
+  sqlite3_flutter_libs:
+    dependency: "direct main"
+    description:
+      name: sqlite3_flutter_libs
+      sha256: "3e3583b77cf888a68eae2e49ee4f025f66b86623ef0d83c297c8d903daa14871"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.18"
+  sqlparser:
+    dependency: transitive
+    description:
+      name: sqlparser
+      sha256: db6354e8ba71acc50bc4afeafff2a248710ae2c00c9412e2c8b796916d4b1c45
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.32.1"
   stack_trace:
     dependency: transitive
     description:
@@ -256,6 +621,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.1"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.0"
   string_scanner:
     dependency: transitive
     description:
@@ -280,6 +653,94 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.6.0"
+  timing:
+    dependency: transitive
+    description:
+      name: timing
+      sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.1"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.3.2"
+  url_launcher:
+    dependency: "direct main"
+    description:
+      name: url_launcher
+      sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.2.1"
+  url_launcher_android:
+    dependency: transitive
+    description:
+      name: url_launcher_android
+      sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.2.0"
+  url_launcher_ios:
+    dependency: transitive
+    description:
+      name: url_launcher_ios
+      sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "6.2.0"
+  url_launcher_linux:
+    dependency: transitive
+    description:
+      name: url_launcher_linux
+      sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
+  url_launcher_macos:
+    dependency: transitive
+    description:
+      name: url_launcher_macos
+      sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
+  url_launcher_platform_interface:
+    dependency: transitive
+    description:
+      name: url_launcher_platform_interface
+      sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.0"
+  url_launcher_web:
+    dependency: transitive
+    description:
+      name: url_launcher_web
+      sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.0"
+  url_launcher_windows:
+    dependency: transitive
+    description:
+      name: url_launcher_windows
+      sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
+  uuid:
+    dependency: "direct main"
+    description:
+      name: uuid
+      sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.7"
   vector_math:
     dependency: transitive
     description:
@@ -288,6 +749,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.0"
   web:
     dependency: transitive
     description:
@@ -296,6 +765,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.1.4-beta"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
   win32:
     dependency: transitive
     description:
@@ -320,6 +797,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.0.3"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.2"
 sdks:
   dart: ">=3.1.1 <4.0.0"
   flutter: ">=3.13.0"

+ 7 - 0
pubspec.yaml

@@ -16,10 +16,17 @@ dependencies:
   json_annotation: ^4.8.1
   path_provider: ^2.1.1
   path: ^1.8.3
+  url_launcher: ^6.1.12
+  uuid: ^3.0.7
+  sqlite3_flutter_libs: ^0.5.0
+  
 dev_dependencies: 
   flutter_lints: ^2.0.0
   flutter_test: 
     sdk: flutter
+  build_runner: ^2.4.4
+  drift_dev: ^2.11.0
+  json_serializable: ^6.7.0
 
 flutter: 
   uses-material-design: true

+ 6 - 0
windows/flutter/generated_plugin_registrant.cc

@@ -7,11 +7,17 @@
 #include "generated_plugin_registrant.h"
 
 #include <screen_retriever/screen_retriever_plugin.h>
+#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
+#include <url_launcher_windows/url_launcher_windows.h>
 #include <window_manager/window_manager_plugin.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
   ScreenRetrieverPluginRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
+  Sqlite3FlutterLibsPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
+  UrlLauncherWindowsRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("UrlLauncherWindows"));
   WindowManagerPluginRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("WindowManagerPlugin"));
 }

+ 2 - 0
windows/flutter/generated_plugins.cmake

@@ -4,6 +4,8 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   screen_retriever
+  sqlite3_flutter_libs
+  url_launcher_windows
   window_manager
 )