This commit is contained in:
2024-09-04 17:18:13 +08:00
parent 3da318ec71
commit 74fe0457f0
276 changed files with 901 additions and 14439 deletions

View File

@ -1,37 +1,27 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart';
import 'package:hexcolor/hexcolor.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/ui/home/performance/performance_ui.dart';
import 'package:starcitizen_doctor/ui/splash_ui.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:uuid/uuid.dart';
import 'package:window_manager/window_manager.dart';
import 'api/analytics.dart';
import 'api/api.dart';
import 'common/helper/system_helper.dart';
import 'common/io/rs_http.dart';
import 'common/rust/frb_generated.dart';
import 'common/rust/api/win32_api.dart' as win32;
import 'data/app_version_data.dart';
import 'generated/no_l10n_strings.dart';
import 'ui/home/downloader/home_downloader_ui.dart';
import 'ui/home/game_doctor/game_doctor_ui.dart';
import 'ui/home/localization/advanced_localization_ui.dart';
import 'ui/index_ui.dart';
import 'ui/settings/upgrade_dialog.dart';
import 'ui/tools/unp4kc/unp4kc_ui.dart';
part 'app.g.dart';
@ -47,32 +37,26 @@ class AppGlobalState with _$AppGlobalState {
@Default(ThemeConf()) ThemeConf themeConf,
Locale? appLocale,
Box? appConfBox,
@Default("assets/backgrounds/SC_01_Wallpaper_3840x2160.webp")
String backgroundImageAssetsPath,
}) = _AppGlobalState;
}
@riverpod
GoRouter router(RouterRef ref) {
return GoRouter(
initialLocation: "/splash",
routes: [
GoRoute(
path: '/',
path: '/splash',
pageBuilder: (context, state) =>
myPageBuilder(context, state, const SplashUI()),
),
GoRoute(
path: '/index',
path: '/',
pageBuilder: (context, state) =>
myPageBuilder(context, state, const IndexUI()),
routes: [
GoRoute(
path: "downloader",
pageBuilder: (context, state) =>
myPageBuilder(context, state, const HomeDownloaderUI())),
GoRoute(
path: 'game_doctor',
pageBuilder: (context, state) =>
myPageBuilder(context, state, const HomeGameDoctorUI()),
),
GoRoute(
path: 'performance',
pageBuilder: (context, state) =>
@ -84,13 +68,7 @@ GoRouter router(RouterRef ref) {
myPageBuilder(context, state, const AdvancedLocalizationUI()))
],
),
GoRoute(path: '/tools', builder: (_, __) => const SizedBox(), routes: [
GoRoute(
path: 'unp4kc',
pageBuilder: (context, state) =>
myPageBuilder(context, state, const UnP4kcUI()),
),
]),
GoRoute(path: '/tools', builder: (_, __) => const SizedBox()),
],
);
}
@ -115,7 +93,7 @@ class AppGlobalModel extends _$AppGlobalModel {
Future<void> initApp() async {
if (_initialized) return;
// init Data
final applicationSupportDir = await _initAppDir();
// final applicationSupportDir = await _initAppDir();
// init Rust bridge
await RustLib.init();
@ -124,12 +102,12 @@ class AppGlobalModel extends _$AppGlobalModel {
// init Hive
try {
Hive.init("$applicationSupportDir/db");
// Hive.init("$applicationSupportDir/db");
final box = await Hive.openBox("app_conf");
state = state.copyWith(appConfBox: box);
if (box.get("install_id", defaultValue: "") == "") {
await box.put("install_id", const Uuid().v4());
AnalyticsApi.touch("firstLaunch");
// AnalyticsApi.touch("firstLaunch");
}
final deviceUUID = box.get("install_id", defaultValue: "");
final localeCode = box.get("app_locale", defaultValue: null);
@ -144,66 +122,50 @@ class AppGlobalModel extends _$AppGlobalModel {
}
state = state.copyWith(deviceUUID: deviceUUID, appLocale: locale);
} catch (e) {
await win32.setForegroundWindow(windowName: "SCToolBox");
// await win32.setForegroundWindow(windowName: "SCToolBox");
dPrint("exit: db is locking ...");
exit(0);
// exit(0);
}
// init powershell
if (Platform.isWindows) {
try {
await SystemHelper.initPowershellPath();
dPrint("---- Powershell init -----");
} catch (e) {
dPrint("powershell init failed : $e");
}
}
// get windows info
WindowsDeviceInfo? windowsDeviceInfo;
try {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
windowsDeviceInfo = await deviceInfo.windowsInfo;
} catch (e) {
dPrint("DeviceInfo.windowsInfo error: $e");
}
// init windows
windowManager.waitUntilReadyToShow().then((_) async {
await windowManager.setTitle("SCToolBox");
await windowManager.setSkipTaskbar(false);
await windowManager.show();
if (Platform.isWindows) {
await Window.initialize();
await Window.hideWindowControls();
if (windowsDeviceInfo?.productName.contains("Windows 11") ?? false) {
await Window.setEffect(
effect: WindowEffect.acrylic,
);
}
}
});
dPrint("---- Window init -----");
_startBackgroundLoop();
_initialized = true;
ref.keepAlive();
}
Timer? _loopTimer;
_startBackgroundLoop() async {
_loopTimer?.cancel();
_loopTimer = null;
final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle);
final imageAssetsList = assetManifest
.listAssets()
.where((string) => string.startsWith("assets/backgrounds"))
.toList();
void rollImage() {
final random = Random();
final index = random.nextInt(imageAssetsList.length);
final image = imageAssetsList[index];
state = state.copyWith(backgroundImageAssetsPath: image);
dPrint("rollImage: [$index] $image");
}
rollImage();
// 使用 timer 每 30 秒 更换一次随机图片
_loopTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
rollImage();
});
}
String getUpgradePath() {
return "${state.applicationSupportDir}/._upgrade";
}
// ignore: avoid_build_context_in_providers
Future<bool> checkUpdate(BuildContext context) async {
if (!ConstConf.isMSE) {
final dir = Directory(getUpgradePath());
if (await dir.exists()) {
dir.delete(recursive: true);
}
}
dynamic checkUpdateError;
try {
final networkVersionData = await Api.getAppVersion();
checkActivityThemeColor(networkVersionData);
@ -229,26 +191,6 @@ class AppGlobalModel extends _$AppGlobalModel {
ConstConf.appVersionDate, checkUpdateError.toString()));
return false;
}
if (!Platform.isWindows) return false;
final lastVersion = ConstConf.isMSE
? state.networkVersionData?.mSELastVersionCode
: state.networkVersionData?.lastVersionCode;
if ((lastVersion ?? 0) > ConstConf.appVersionCode) {
// need update
if (!context.mounted) return false;
final r = await showDialog(
dismissWithEsc: false,
context: context,
builder: (context) => const UpgradeDialogUI());
if (r != true) {
if (!context.mounted) return false;
await showToast(context, S.current.app_common_upgrade_info_error);
return false;
}
return true;
}
return false;
}
@ -314,44 +256,6 @@ class AppGlobalModel extends _$AppGlobalModel {
state = state.copyWith(appLocale: value);
}
}
Future<String> _initAppDir() async {
if (Platform.isWindows) {
final userProfileDir = Platform.environment["USERPROFILE"];
final applicationSupportDir =
(await getApplicationSupportDirectory()).absolute.path;
String? applicationBinaryModuleDir;
try {
await initDPrintFile(applicationSupportDir);
} catch (e) {
dPrint("initDPrintFile Error: $e");
}
if (ConstConf.isMSE && userProfileDir != null) {
applicationBinaryModuleDir =
"$userProfileDir\\AppData\\Local\\Temp\\SCToolbox\\modules";
} else {
applicationBinaryModuleDir = "$applicationSupportDir\\modules";
}
dPrint("applicationSupportDir == $applicationSupportDir");
dPrint("applicationBinaryModuleDir == $applicationBinaryModuleDir");
state = state.copyWith(
applicationSupportDir: applicationSupportDir,
applicationBinaryModuleDir: applicationBinaryModuleDir,
);
return applicationSupportDir;
} else {
final applicationSupportDir =
(await getApplicationSupportDirectory()).absolute.path;
final applicationBinaryModuleDir = "$applicationSupportDir/modules";
dPrint("applicationSupportDir == $applicationSupportDir");
dPrint("applicationBinaryModuleDir == $applicationBinaryModuleDir");
state = state.copyWith(
applicationSupportDir: applicationSupportDir,
applicationBinaryModuleDir: applicationBinaryModuleDir,
);
return applicationSupportDir;
}
}
}
@freezed

View File

@ -23,6 +23,7 @@ mixin _$AppGlobalState {
ThemeConf get themeConf => throw _privateConstructorUsedError;
Locale? get appLocale => throw _privateConstructorUsedError;
Box<dynamic>? get appConfBox => throw _privateConstructorUsedError;
String get backgroundImageAssetsPath => throw _privateConstructorUsedError;
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@ -44,7 +45,8 @@ abstract class $AppGlobalStateCopyWith<$Res> {
AppVersionData? networkVersionData,
ThemeConf themeConf,
Locale? appLocale,
Box<dynamic>? appConfBox});
Box<dynamic>? appConfBox,
String backgroundImageAssetsPath});
$ThemeConfCopyWith<$Res> get themeConf;
}
@ -71,6 +73,7 @@ class _$AppGlobalStateCopyWithImpl<$Res, $Val extends AppGlobalState>
Object? themeConf = null,
Object? appLocale = freezed,
Object? appConfBox = freezed,
Object? backgroundImageAssetsPath = null,
}) {
return _then(_value.copyWith(
deviceUUID: freezed == deviceUUID
@ -101,6 +104,10 @@ class _$AppGlobalStateCopyWithImpl<$Res, $Val extends AppGlobalState>
? _value.appConfBox
: appConfBox // ignore: cast_nullable_to_non_nullable
as Box<dynamic>?,
backgroundImageAssetsPath: null == backgroundImageAssetsPath
? _value.backgroundImageAssetsPath
: backgroundImageAssetsPath // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
@ -130,7 +137,8 @@ abstract class _$$AppGlobalStateImplCopyWith<$Res>
AppVersionData? networkVersionData,
ThemeConf themeConf,
Locale? appLocale,
Box<dynamic>? appConfBox});
Box<dynamic>? appConfBox,
String backgroundImageAssetsPath});
@override
$ThemeConfCopyWith<$Res> get themeConf;
@ -156,6 +164,7 @@ class __$$AppGlobalStateImplCopyWithImpl<$Res>
Object? themeConf = null,
Object? appLocale = freezed,
Object? appConfBox = freezed,
Object? backgroundImageAssetsPath = null,
}) {
return _then(_$AppGlobalStateImpl(
deviceUUID: freezed == deviceUUID
@ -186,6 +195,10 @@ class __$$AppGlobalStateImplCopyWithImpl<$Res>
? _value.appConfBox
: appConfBox // ignore: cast_nullable_to_non_nullable
as Box<dynamic>?,
backgroundImageAssetsPath: null == backgroundImageAssetsPath
? _value.backgroundImageAssetsPath
: backgroundImageAssetsPath // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
@ -200,7 +213,9 @@ class _$AppGlobalStateImpl implements _AppGlobalState {
this.networkVersionData,
this.themeConf = const ThemeConf(),
this.appLocale,
this.appConfBox});
this.appConfBox,
this.backgroundImageAssetsPath =
"assets/backgrounds/SC_01_Wallpaper_3840x2160.jpg"});
@override
final String? deviceUUID;
@ -217,10 +232,13 @@ class _$AppGlobalStateImpl implements _AppGlobalState {
final Locale? appLocale;
@override
final Box<dynamic>? appConfBox;
@override
@JsonKey()
final String backgroundImageAssetsPath;
@override
String toString() {
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox)';
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf, appLocale: $appLocale, appConfBox: $appConfBox, backgroundImageAssetsPath: $backgroundImageAssetsPath)';
}
@override
@ -243,7 +261,10 @@ class _$AppGlobalStateImpl implements _AppGlobalState {
(identical(other.appLocale, appLocale) ||
other.appLocale == appLocale) &&
(identical(other.appConfBox, appConfBox) ||
other.appConfBox == appConfBox));
other.appConfBox == appConfBox) &&
(identical(other.backgroundImageAssetsPath,
backgroundImageAssetsPath) ||
other.backgroundImageAssetsPath == backgroundImageAssetsPath));
}
@override
@ -255,7 +276,8 @@ class _$AppGlobalStateImpl implements _AppGlobalState {
networkVersionData,
themeConf,
appLocale,
appConfBox);
appConfBox,
backgroundImageAssetsPath);
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.
@ -275,7 +297,8 @@ abstract class _AppGlobalState implements AppGlobalState {
final AppVersionData? networkVersionData,
final ThemeConf themeConf,
final Locale? appLocale,
final Box<dynamic>? appConfBox}) = _$AppGlobalStateImpl;
final Box<dynamic>? appConfBox,
final String backgroundImageAssetsPath}) = _$AppGlobalStateImpl;
@override
String? get deviceUUID;
@ -291,6 +314,8 @@ abstract class _AppGlobalState implements AppGlobalState {
Locale? get appLocale;
@override
Box<dynamic>? get appConfBox;
@override
String get backgroundImageAssetsPath;
/// Create a copy of AppGlobalState
/// with the given fields replaced by the non-null parameter values.

View File

@ -6,7 +6,7 @@ part of 'app.dart';
// RiverpodGenerator
// **************************************************************************
String _$routerHash() => r'4fb9802d06347972b530f17ea7d66724a6ded997';
String _$routerHash() => r'b35ab7db63955c2d8507e811a5e0d202f65c04be';
/// See also [router].
@ProviderFor(router)
@ -20,7 +20,7 @@ final routerProvider = AutoDisposeProvider<GoRouter>.internal(
);
typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
String _$appGlobalModelHash() => r'cf3d526a61cbadea4252c6f8e096a527d5cef50f';
String _$appGlobalModelHash() => r'1fb13f5be4249fdeb2d78906e1bfe94031b76afc';
/// See also [AppGlobalModel].
@ProviderFor(AppGlobalModel)

View File

@ -1,47 +0,0 @@
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class BinaryModuleConf {
static const _modules = {
"aria2c": "0",
"unp4kc": "1",
};
static Future extractModule(List<String> modules, String workingDir) async {
for (var m in _modules.entries) {
if (!modules.contains(m.key)) continue;
final name = m.key;
final version = m.value;
final dir = "$workingDir\\$name";
final versionFile = File("$dir\\version");
if (kReleaseMode &&
await versionFile.exists() &&
(await versionFile.readAsString()).trim() == version) {
dPrint(
"BinaryModuleConf.extractModule skip $name version == $version");
continue;
}
// write model file
final zipBuffer = await rootBundle.load("assets/binary/$name.zip");
final decoder = ZipDecoder().decodeBytes(zipBuffer.buffer.asUint8List());
for (var value in decoder.files) {
final filename = value.name;
if (value.isFile) {
final data = value.content as List<int>;
final file = File('$dir\\$filename');
await file.create(recursive: true);
await file.writeAsBytes(data);
} else {
await Directory('$dir\\$filename').create(recursive: true);
}
}
// write version file
await versionFile.writeAsString(version);
dPrint("BinaryModuleConf.extractModule $name $dir");
}
}
}

View File

@ -1,8 +1,7 @@
class ConstConf {
static const String appVersion = "2.12.0";
static const int appVersionCode = 54;
static const String appVersionDate = "2024-6-28";
static const String appVersion = "Lite";
static const int appVersionCode = 0;
static const String appVersionDate = "2024-09-04";
static const gameChannels = ["LIVE", "PTU", "EPTU", "TECH-PREVIEW"];
static const isMSE =
String.fromEnvironment("MSE", defaultValue: "false") == "true";
static const isMSE = true;
}

View File

@ -37,24 +37,7 @@ class URLConf {
static String get devReleaseUrl => "$gitApiHome/SCToolBox/Release/releases";
static Future<bool> checkHost() async {
// 使用 DNS 获取可用列表
final gitApiList =
_genFinalList(await RSHttp.dnsLookupTxt("git.dns.scbox.org"));
dPrint("DNS gitApiList ==== $gitApiList");
final fasterGit = await getFasterUrl(gitApiList);
dPrint("gitApiList.Faster ==== $fasterGit");
if (fasterGit != null) {
gitApiHome = fasterGit;
}
final rssApiList =
_genFinalList(await RSHttp.dnsLookupTxt("rss.dns.scbox.org"));
final fasterRss = await getFasterUrl(rssApiList);
dPrint("DNS rssApiList ==== $rssApiList");
dPrint("rssApiList.Faster ==== $fasterRss");
if (fasterRss != null) {
rssApiHome = fasterRss;
}
isUrlCheckPass = fasterGit != null && fasterRss != null;
isUrlCheckPass = true;
return isUrlCheckPass;
}
@ -87,14 +70,4 @@ class URLConf {
}
}
static List<String> _genFinalList(List<String> sList) {
List<String> list = [];
for (var ll in sList) {
final ssList = ll.split(",");
for (var value in ssList) {
list.add(value);
}
}
return list;
}
}

View File

@ -1,209 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class SCLoggerHelper {
static Future<String?> getLogFilePath() async {
if (!Platform.isWindows) return null;
Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["appdata"];
if (appDataPath == null) {
return null;
}
final rsiLauncherPath = "$appDataPath\\rsilauncher";
dPrint("rsiLauncherPath:$rsiLauncherPath");
final jsonLogPath = "$rsiLauncherPath\\logs\\log.log";
return jsonLogPath;
}
static Future<String?> getShaderCachePath() async {
Map<String, String> envVars = Platform.environment;
final appDataPath = envVars["LOCALAPPDATA"];
if (appDataPath == null) {
return null;
}
final scCachePath = "$appDataPath\\Star Citizen";
dPrint("getShaderCachePath === $scCachePath");
return scCachePath;
}
static Future<List?> getLauncherLogList() async {
if (!Platform.isWindows) return [];
try {
final jsonLogPath = await getLogFilePath();
if (jsonLogPath == null) throw "no file path";
var jsonString = utf8.decode(await File(jsonLogPath).readAsBytes());
if (jsonString.endsWith("\n")) {
jsonString = jsonString.substring(0, jsonString.length - 3);
}
if (jsonString.endsWith(" ")) {
jsonString = jsonString.substring(0, jsonString.length - 3);
}
if (jsonString.endsWith(",")) {
jsonString = jsonString.substring(0, jsonString.length - 3);
}
return json.decode("[$jsonString]");
} catch (e) {
dPrint(e);
return [];
}
}
static Future<List<String>> getGameInstallPath(List listData,
{bool checkExists = true,
List<String> withVersion = const ["LIVE"]}) async {
List<String> scInstallPaths = [];
checkAndAddPath(String path, bool checkExists) async {
if (path.isNotEmpty && !scInstallPaths.contains(path)) {
if (!checkExists) {
dPrint("find installPath == $path");
scInstallPaths.add(path);
} else if (await File("$path/Bin64/StarCitizen.exe").exists() &&
await File("$path/Data.p4k").exists()) {
dPrint("find installPath == $path");
scInstallPaths.add(path);
}
}
}
final confBox = await Hive.openBox("app_conf");
final path = confBox.get("custom_game_path");
if (path != null && path != "") {
for (var v in withVersion) {
await checkAndAddPath("$path\\$v", checkExists);
}
}
try {
for (var v in withVersion) {
for (var i = listData.length - 1; i > 0; i--) {
final m = listData[i];
final info = m["[browser][info] "];
if (info is String) {
String installPath = "";
if (info.contains("Installing Star Citizen $v")) {
installPath = "${info.split(" at ")[1]}\\$v";
}
if (info.contains("Verifying Star Citizen $v")) {
installPath = "${info.split(" at ")[1]}\\$v";
}
if (info.contains("Launching Star Citizen $v from")) {
installPath = info
.replaceAll("Launching Star Citizen $v from (", "")
.replaceAll(")", "");
}
await checkAndAddPath(installPath, checkExists);
}
}
if (scInstallPaths.isNotEmpty) {
// 动态检测更多位置
for (var v in withVersion) {
for (var fileName in List.from(scInstallPaths)) {
if (fileName.toString().endsWith(v)) {
for (var nv in withVersion) {
final nextName =
"${fileName.toString().replaceAll("\\$v", "")}\\$nv";
await checkAndAddPath(nextName, true);
}
}
}
}
}
}
} catch (e) {
dPrint(e);
if (scInstallPaths.isEmpty) rethrow;
}
return scInstallPaths;
}
static String getGameChannelID(String installPath) {
for (var value in ConstConf.gameChannels) {
if (installPath.endsWith("\\$value")) {
return value;
}
}
return "UNKNOWN";
}
static Future<List<String>?> getGameRunningLogs(String gameDir) async {
final logFile = File("$gameDir/Game.log");
if (!await logFile.exists()) {
return null;
}
return await logFile.readAsLines(
encoding: const Utf8Codec(allowMalformed: true));
}
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
for (var i = logs.length - 1; i > 0; i--) {
final line = logs[i];
final r = _checkRunningLine(line);
if (r != null) {
return r;
}
}
return null;
}
static MapEntry<String, String>? _checkRunningLine(String line) {
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
return MapEntry(S.current.doctor_game_error_low_memory,
S.current.doctor_game_error_low_memory_info);
}
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
return MapEntry(S.current.doctor_game_error_generic_info,
"https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
}
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
return MapEntry(S.current.doctor_game_error_gpu_crash,
"https://www.bilibili.com/read/cv19335199");
}
if (line.contains("Wakeup socket sendto error")) {
return MapEntry(S.current.doctor_game_error_socket_error,
S.current.doctor_game_error_socket_error_info);
}
if (line.contains("The requested operation requires elevated")) {
return MapEntry(S.current.doctor_game_error_permissions_error,
S.current.doctor_game_error_permissions_error_info);
}
if (line.contains(
"The process cannot access the file because is is being used by another process")) {
return MapEntry(S.current.doctor_game_error_game_process_error,
S.current.doctor_game_error_game_process_error_info);
}
if (line.contains("0xc0000043")) {
return MapEntry(S.current.doctor_game_error_game_damaged_file,
S.current.doctor_game_error_game_damaged_file_info);
}
if (line.contains("option to verify the content of the Data.p4k file")) {
return MapEntry(S.current.doctor_game_error_game_damaged_p4k_file,
S.current.doctor_game_error_game_damaged_p4k_file_info);
}
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
return MapEntry(S.current.doctor_game_error_low_gpu_memory,
S.current.doctor_game_error_low_gpu_memory_info);
}
if (line.contains(
"try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash,
S.current.doctor_game_error_gpu_vulkan_crash_info);
}
/// Unknown
if (line.contains("network.replicatedEntityHandle")) {
return const MapEntry("_", "network.replicatedEntityHandle");
}
if (line.contains("Exception Unknown")) {
return const MapEntry("_", "Exception Unknown");
}
return null;
}
}

View File

@ -1,280 +0,0 @@
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class SystemHelper {
static String powershellPath = "powershell.exe";
static initPowershellPath() async {
try {
var result = await Process.run(powershellPath, ["echo", "ping"]);
if (!result.stdout.toString().startsWith("ping") &&
powershellPath == "powershell.exe") {
throw "powershell check failed";
}
} catch (e) {
Map<String, String> envVars = Platform.environment;
final systemRoot = envVars["SYSTEMROOT"];
if (systemRoot != null) {
final autoSearchPath =
"$systemRoot\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
dPrint("auto search powershell path === $autoSearchPath");
powershellPath = autoSearchPath;
}
}
}
static Future<bool> checkNvmePatchStatus() async {
try {
var result = await Process.run(SystemHelper.powershellPath, [
"Get-ItemProperty",
"-Path",
"\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"",
"-Name",
"\"ForcedPhysicalSectorSizeInBytes\""
]);
dPrint("checkNvmePatchStatus result ==== ${result.stdout}");
if (result.stderr == "" &&
result.stdout.toString().contains("{* 4095}")) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
static Future<String> addNvmePatch() async {
var result = await Process.run(powershellPath, [
'New-ItemProperty',
"-Path",
"\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"",
"-Name",
"ForcedPhysicalSectorSizeInBytes",
"-PropertyType MultiString",
"-Force -Value",
"\"* 4095\""
]);
dPrint("nvme_PhysicalBytes result == ${result.stdout}");
return result.stderr;
}
static doRemoveNvmePath() async {
try {
var result = await Process.run(powershellPath, [
"Clear-ItemProperty",
"-Path",
"\"HKLM:\\SYSTEM\\CurrentControlSet\\Services\\stornvme\\Parameters\\Device\"",
"-Name",
"\"ForcedPhysicalSectorSizeInBytes\""
]);
dPrint("doRemoveNvmePath result ==== ${result.stdout}");
if (result.stderr == "") {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
/// 获取 RSI 启动器 目录
static Future<String> getRSILauncherPath({bool skipEXE = false}) async {
final confBox = await Hive.openBox("app_conf");
final path = confBox.get("custom_launcher_path");
if (path != null && path != "") {
if (await File(path).exists()) {
if (skipEXE) {
return "${path.toString().replaceAll("\\RSI Launcher.exe", "")}\\";
}
return path;
}
}
Map<String, String> envVars = Platform.environment;
final programDataPath = envVars["programdata"];
final rsiFilePath =
"$programDataPath\\Microsoft\\Windows\\Start Menu\\Programs\\Roberts Space Industries\\RSI Launcher.lnk";
final rsiLinkFile = File(rsiFilePath);
if (await rsiLinkFile.exists()) {
final r = await Process.run(SystemHelper.powershellPath, [
"(New-Object -ComObject WScript.Shell).CreateShortcut(\"$rsiFilePath\").targetpath"
]);
if (r.stdout.toString().contains("RSI Launcher.exe")) {
final start = r.stdout.toString().split("RSI Launcher.exe");
if (skipEXE) {
return start[0];
}
return "${start[0]}RSI Launcher.exe";
}
}
return "";
}
static killRSILauncher() async {
var psr = await Process.run(
powershellPath, ["ps", "\"RSI Launcher\"", "|select -expand id"]);
if (psr.stderr == "") {
for (var value in (psr.stdout ?? "").toString().split("\n")) {
dPrint(value);
if (value != "") {
Process.killPid(int.parse(value));
}
}
}
}
static Future<List<String>> getPID(String name) async {
final r = await Process.run(powershellPath, ["(ps $name).Id"]);
final str = r.stdout.toString().trim();
dPrint(str);
if (str.isEmpty) return [];
return str.split("\n");
}
static checkAndLaunchRSILauncher(String path) async {
// check running and kill
await killRSILauncher();
// launch
final processorAffinity = await SystemHelper.getCpuAffinity();
if (processorAffinity == null) {
Process.run(path, []);
} else {
Process.run("cmd.exe", [
'/C',
'Start',
'""',
'/High',
'/Affinity',
processorAffinity,
path,
]);
}
dPrint(path);
}
static Future<int> getSystemMemorySizeGB() async {
final r = await Process.run(powershellPath, [
"(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property capacity -Sum).sum /1gb"
]);
return int.tryParse(r.stdout.toString().trim()) ?? 0;
}
static Future<String> getSystemCimInstance(String win32InstanceName,
{pathName = "Name"}) async {
final r = await Process.run(
powershellPath, ["(Get-CimInstance $win32InstanceName).$pathName"]);
return r.stdout.toString().trim();
}
static Future<String> getSystemName() async {
final r = await Process.run(
powershellPath, ["(Get-ComputerInfo | Select-Object -expand OsName)"]);
return r.stdout.toString().trim();
}
static Future<String> getCpuName() async {
final r = await Process.run(
powershellPath, ["(Get-WmiObject -Class Win32_Processor).Name"]);
return r.stdout.toString().trim();
}
static Future<String> getGpuInfo() async {
const cmd = r"""
$adapterMemory = (Get-ItemProperty -Path "HKLM:\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0*" -Name "HardwareInformation.AdapterString", "HardwareInformation.qwMemorySize" -Exclude PSPath -ErrorAction SilentlyContinue)
foreach ($adapter in $adapterMemory) {
[PSCustomObject] @{
Model=$adapter."HardwareInformation.AdapterString"
"VRAM (GB)"=[math]::round($adapter."HardwareInformation.qwMemorySize"/1GB)
}
}
""";
final r = await Process.run(powershellPath, [cmd]);
return r.stdout.toString().trim();
}
static Future<String> getDiskInfo() async {
return (await Process.run(powershellPath,
["Get-PhysicalDisk | format-table BusType,FriendlyName,Size"]))
.stdout
.toString()
.trim();
}
static Future<int> getDirLen(String path, {List<String>? skipPath}) async {
if (path == "") return 0;
int totalSize = 0;
try {
final l = await Directory(path).list(recursive: true).toList();
for (var element in l) {
if (element is File) {
bool skip = false;
if (skipPath != null) {
for (var value in skipPath) {
if (element.absolute.path.startsWith(value)) {
skip = true;
break;
}
}
}
if (!skip) totalSize += await element.length();
}
}
} catch (_) {}
return totalSize;
}
static Future<int> getNumberOfLogicalProcessors() async {
final cpuNumberResult = await Process.run(powershellPath,
["(Get-WmiObject -Class Win32_Processor).NumberOfLogicalProcessors"]);
if (cpuNumberResult.exitCode != 0) return 0;
return int.tryParse(cpuNumberResult.stdout.toString().trim()) ?? 0;
}
static Future<String?> getCpuAffinity() async {
final confBox = await Hive.openBox("app_conf");
final eCoreCount = int.tryParse(
confBox.get("gameLaunch_eCore_count", defaultValue: "0")) ??
0;
final cpuNumber = await getNumberOfLogicalProcessors();
if (cpuNumber == 0 || eCoreCount == 0 || eCoreCount > cpuNumber) {
return null;
}
StringBuffer sb = StringBuffer();
for (var i = 0; i < cpuNumber; i++) {
if (i < eCoreCount) {
sb.write("0");
} else {
sb.write("1");
}
}
final binaryString = sb.toString();
int hexDigits = (binaryString.length / 4).ceil();
dPrint("Affinity sb ==== $sb");
return int.parse(binaryString, radix: 2)
.toRadixString(16)
.padLeft(hexDigits, '0')
.toUpperCase();
}
static Future openDir(path, {bool isFile = false}) async {
dPrint("SystemHelper.openDir path === $path");
if (Platform.isWindows) {
await Process.run(SystemHelper.powershellPath,
["explorer.exe", isFile ? "/select,$path" : "\"/select,\"$path\"\""]);
}
}
static String getHostsFilePath() {
if (Platform.isWindows) {
final envVars = Platform.environment;
final systemRoot = envVars["SYSTEMROOT"];
return "$systemRoot\\System32\\drivers\\etc\\hosts";
}
return "/etc/hosts";
}
}

View File

@ -17,10 +17,7 @@ class RSHttp {
static Future<RustHttpResponse> get(String url,
{Map<String, String>? headers, String? withIpAddress}) async {
final r = await rust_http.fetch(
method: MyMethod.gets,
url: url,
headers: headers,
withIpAddress: withIpAddress);
method: MyMethod.gets, url: url, headers: headers);
return r;
}
@ -42,29 +39,14 @@ class RSHttp {
headers["Content-Type"] = contentType;
}
final r = await rust_http.fetch(
method: MyMethod.post,
url: url,
headers: headers,
inputData: data,
withIpAddress: withIpAddress);
method: MyMethod.post, url: url, headers: headers, inputData: data);
return r;
}
static Future<RustHttpResponse> head(String url,
{Map<String, String>? headers, String? withIpAddress}) async {
final r = await rust_http.fetch(
method: MyMethod.head,
url: url,
headers: headers,
withIpAddress: withIpAddress);
method: MyMethod.head, url: url, headers: headers);
return r;
}
static Future<List<String>> dnsLookupTxt(String host) async {
return await rust_http.dnsLookupTxt(host: host);
}
static Future<List<String>> dnsLookupIps(String host) async {
return await rust_http.dnsLookupIps(host: host);
}
}

View File

@ -1,41 +0,0 @@
// This file is automatically generated, so please do not edit it.
// Generated by `flutter_rust_bridge`@ 2.3.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
Future<RsiLauncherAsarData> getRsiLauncherAsarData(
{required String asarPath}) =>
RustLib.instance.api
.crateApiAsarApiGetRsiLauncherAsarData(asarPath: asarPath);
class RsiLauncherAsarData {
final String asarPath;
final String mainJsPath;
final Uint8List mainJsContent;
const RsiLauncherAsarData({
required this.asarPath,
required this.mainJsPath,
required this.mainJsContent,
});
Future<void> writeMainJs({required List<int> content}) =>
RustLib.instance.api.crateApiAsarApiRsiLauncherAsarDataWriteMainJs(
that: this, content: content);
@override
int get hashCode =>
asarPath.hashCode ^ mainJsPath.hashCode ^ mainJsContent.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RsiLauncherAsarData &&
runtimeType == other.runtimeType &&
asarPath == other.asarPath &&
mainJsPath == other.mainJsPath &&
mainJsContent == other.mainJsContent;
}

View File

@ -16,20 +16,9 @@ Future<RustHttpResponse> fetch(
{required MyMethod method,
required String url,
Map<String, String>? headers,
Uint8List? inputData,
String? withIpAddress}) =>
Uint8List? inputData}) =>
RustLib.instance.api.crateApiHttpApiFetch(
method: method,
url: url,
headers: headers,
inputData: inputData,
withIpAddress: withIpAddress);
Future<List<String>> dnsLookupTxt({required String host}) =>
RustLib.instance.api.crateApiHttpApiDnsLookupTxt(host: host);
Future<List<String>> dnsLookupIps({required String host}) =>
RustLib.instance.api.crateApiHttpApiDnsLookupIps(host: host);
method: method, url: url, headers: headers, inputData: inputData);
enum MyMethod {
options,

View File

@ -1,54 +0,0 @@
// This file is automatically generated, so please do not edit it.
// Generated by `flutter_rust_bridge`@ 2.3.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `_process_output`
// These types are ignored because they are not used by any `pub` functions: `RsProcess`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`
Stream<RsProcessStreamData> start(
{required String executable,
required List<String> arguments,
required String workingDirectory}) =>
RustLib.instance.api.crateApiRsProcessStart(
executable: executable,
arguments: arguments,
workingDirectory: workingDirectory);
Future<void> write({required int rsPid, required String data}) =>
RustLib.instance.api.crateApiRsProcessWrite(rsPid: rsPid, data: data);
class RsProcessStreamData {
final RsProcessStreamDataType dataType;
final String data;
final int rsPid;
const RsProcessStreamData({
required this.dataType,
required this.data,
required this.rsPid,
});
@override
int get hashCode => dataType.hashCode ^ data.hashCode ^ rsPid.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RsProcessStreamData &&
runtimeType == other.runtimeType &&
dataType == other.dataType &&
data == other.data &&
rsPid == other.rsPid;
}
enum RsProcessStreamDataType {
output,
error,
exit,
;
}

View File

@ -1,16 +0,0 @@
// This file is automatically generated, so please do not edit it.
// Generated by `flutter_rust_bridge`@ 2.3.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
Future<void> sendNotify(
{String? summary, String? body, String? appName, String? appId}) =>
RustLib.instance.api.crateApiWin32ApiSendNotify(
summary: summary, body: body, appName: appName, appId: appId);
Future<bool> setForegroundWindow({required String windowName}) =>
RustLib.instance.api
.crateApiWin32ApiSetForegroundWindow(windowName: windowName);

View File

@ -3,10 +3,7 @@
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api/asar_api.dart';
import 'api/http_api.dart';
import 'api/rs_process.dart';
import 'api/win32_api.dart';
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
@ -70,7 +67,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
String get codegenVersion => '2.3.0';
@override
int get rustContentHash => 1832496273;
int get rustContentHash => -669496402;
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
@ -81,39 +78,14 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
}
abstract class RustLibApi extends BaseApi {
Future<RsiLauncherAsarData> crateApiAsarApiGetRsiLauncherAsarData(
{required String asarPath});
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs(
{required RsiLauncherAsarData that, required List<int> content});
Future<List<String>> crateApiHttpApiDnsLookupIps({required String host});
Future<List<String>> crateApiHttpApiDnsLookupTxt({required String host});
Future<RustHttpResponse> crateApiHttpApiFetch(
{required MyMethod method,
required String url,
Map<String, String>? headers,
Uint8List? inputData,
String? withIpAddress});
Uint8List? inputData});
Future<void> crateApiHttpApiSetDefaultHeader(
{required Map<String, String> headers});
Stream<RsProcessStreamData> crateApiRsProcessStart(
{required String executable,
required List<String> arguments,
required String workingDirectory});
Future<void> crateApiRsProcessWrite(
{required int rsPid, required String data});
Future<void> crateApiWin32ApiSendNotify(
{String? summary, String? body, String? appName, String? appId});
Future<bool> crateApiWin32ApiSetForegroundWindow(
{required String windowName});
}
class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
@ -124,134 +96,34 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
required super.portManager,
});
@override
Future<RsiLauncherAsarData> crateApiAsarApiGetRsiLauncherAsarData(
{required String asarPath}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(asarPath);
return wire.wire__crate__api__asar_api__get_rsi_launcher_asar_data(
port_, arg0);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_rsi_launcher_asar_data,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiAsarApiGetRsiLauncherAsarDataConstMeta,
argValues: [asarPath],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiAsarApiGetRsiLauncherAsarDataConstMeta =>
const TaskConstMeta(
debugName: "get_rsi_launcher_asar_data",
argNames: ["asarPath"],
);
@override
Future<void> crateApiAsarApiRsiLauncherAsarDataWriteMainJs(
{required RsiLauncherAsarData that, required List<int> content}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_box_autoadd_rsi_launcher_asar_data(that);
var arg1 = cst_encode_list_prim_u_8_loose(content);
return wire
.wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js(
port_, arg0, arg1);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiAsarApiRsiLauncherAsarDataWriteMainJsConstMeta,
argValues: [that, content],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiAsarApiRsiLauncherAsarDataWriteMainJsConstMeta =>
const TaskConstMeta(
debugName: "rsi_launcher_asar_data_write_main_js",
argNames: ["that", "content"],
);
@override
Future<List<String>> crateApiHttpApiDnsLookupIps({required String host}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(host);
return wire.wire__crate__api__http_api__dns_lookup_ips(port_, arg0);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_list_String,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiHttpApiDnsLookupIpsConstMeta,
argValues: [host],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiHttpApiDnsLookupIpsConstMeta =>
const TaskConstMeta(
debugName: "dns_lookup_ips",
argNames: ["host"],
);
@override
Future<List<String>> crateApiHttpApiDnsLookupTxt({required String host}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(host);
return wire.wire__crate__api__http_api__dns_lookup_txt(port_, arg0);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_list_String,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiHttpApiDnsLookupTxtConstMeta,
argValues: [host],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiHttpApiDnsLookupTxtConstMeta =>
const TaskConstMeta(
debugName: "dns_lookup_txt",
argNames: ["host"],
);
@override
Future<RustHttpResponse> crateApiHttpApiFetch(
{required MyMethod method,
required String url,
Map<String, String>? headers,
Uint8List? inputData,
String? withIpAddress}) {
Uint8List? inputData}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_my_method(method);
var arg1 = cst_encode_String(url);
var arg2 = cst_encode_opt_Map_String_String(headers);
var arg3 = cst_encode_opt_list_prim_u_8_strict(inputData);
var arg4 = cst_encode_opt_String(withIpAddress);
return wire.wire__crate__api__http_api__fetch(
port_, arg0, arg1, arg2, arg3, arg4);
port_, arg0, arg1, arg2, arg3);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_rust_http_response,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiHttpApiFetchConstMeta,
argValues: [method, url, headers, inputData, withIpAddress],
argValues: [method, url, headers, inputData],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiHttpApiFetchConstMeta => const TaskConstMeta(
debugName: "fetch",
argNames: ["method", "url", "headers", "inputData", "withIpAddress"],
argNames: ["method", "url", "headers", "inputData"],
);
@override
@ -278,113 +150,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
argNames: ["headers"],
);
@override
Stream<RsProcessStreamData> crateApiRsProcessStart(
{required String executable,
required List<String> arguments,
required String workingDirectory}) {
final streamSink = RustStreamSink<RsProcessStreamData>();
unawaited(handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(executable);
var arg1 = cst_encode_list_String(arguments);
var arg2 = cst_encode_String(workingDirectory);
var arg3 = cst_encode_StreamSink_rs_process_stream_data_Dco(streamSink);
return wire.wire__crate__api__rs_process__start(
port_, arg0, arg1, arg2, arg3);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: null,
),
constMeta: kCrateApiRsProcessStartConstMeta,
argValues: [executable, arguments, workingDirectory, streamSink],
apiImpl: this,
)));
return streamSink.stream;
}
TaskConstMeta get kCrateApiRsProcessStartConstMeta => const TaskConstMeta(
debugName: "start",
argNames: ["executable", "arguments", "workingDirectory", "streamSink"],
);
@override
Future<void> crateApiRsProcessWrite(
{required int rsPid, required String data}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_u_32(rsPid);
var arg1 = cst_encode_String(data);
return wire.wire__crate__api__rs_process__write(port_, arg0, arg1);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: null,
),
constMeta: kCrateApiRsProcessWriteConstMeta,
argValues: [rsPid, data],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiRsProcessWriteConstMeta => const TaskConstMeta(
debugName: "write",
argNames: ["rsPid", "data"],
);
@override
Future<void> crateApiWin32ApiSendNotify(
{String? summary, String? body, String? appName, String? appId}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_opt_String(summary);
var arg1 = cst_encode_opt_String(body);
var arg2 = cst_encode_opt_String(appName);
var arg3 = cst_encode_opt_String(appId);
return wire.wire__crate__api__win32_api__send_notify(
port_, arg0, arg1, arg2, arg3);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_unit,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiWin32ApiSendNotifyConstMeta,
argValues: [summary, body, appName, appId],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiWin32ApiSendNotifyConstMeta => const TaskConstMeta(
debugName: "send_notify",
argNames: ["summary", "body", "appName", "appId"],
);
@override
Future<bool> crateApiWin32ApiSetForegroundWindow(
{required String windowName}) {
return handler.executeNormal(NormalTask(
callFfi: (port_) {
var arg0 = cst_encode_String(windowName);
return wire.wire__crate__api__win32_api__set_foreground_window(
port_, arg0);
},
codec: DcoCodec(
decodeSuccessData: dco_decode_bool,
decodeErrorData: dco_decode_AnyhowException,
),
constMeta: kCrateApiWin32ApiSetForegroundWindowConstMeta,
argValues: [windowName],
apiImpl: this,
));
}
TaskConstMeta get kCrateApiWin32ApiSetForegroundWindowConstMeta =>
const TaskConstMeta(
debugName: "set_foreground_window",
argNames: ["windowName"],
);
@protected
AnyhowException dco_decode_AnyhowException(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -398,32 +163,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
.map((e) => MapEntry(e.$1, e.$2)));
}
@protected
RustStreamSink<RsProcessStreamData>
dco_decode_StreamSink_rs_process_stream_data_Dco(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
throw UnimplementedError();
}
@protected
String dco_decode_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as String;
}
@protected
bool dco_decode_bool(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as bool;
}
@protected
RsiLauncherAsarData dco_decode_box_autoadd_rsi_launcher_asar_data(
dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_rsi_launcher_asar_data(raw);
}
@protected
BigInt dco_decode_box_autoadd_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -436,18 +181,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as int;
}
@protected
List<String> dco_decode_list_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return (raw as List<dynamic>).map(dco_decode_String).toList();
}
@protected
List<int> dco_decode_list_prim_u_8_loose(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as List<int>;
}
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -460,12 +193,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (raw as List<dynamic>).map(dco_decode_record_string_string).toList();
}
@protected
MyHttpVersion dco_decode_my_http_version(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return MyHttpVersion.values[raw as int];
}
@protected
MyMethod dco_decode_my_method(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -478,12 +205,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw == null ? null : dco_decode_Map_String_String(raw);
}
@protected
String? dco_decode_opt_String(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null ? null : dco_decode_String(raw);
}
@protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -509,52 +230,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
);
}
@protected
RsProcessStreamData dco_decode_rs_process_stream_data(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 3)
throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
return RsProcessStreamData(
dataType: dco_decode_rs_process_stream_data_type(arr[0]),
data: dco_decode_String(arr[1]),
rsPid: dco_decode_u_32(arr[2]),
);
}
@protected
RsProcessStreamDataType dco_decode_rs_process_stream_data_type(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return RsProcessStreamDataType.values[raw as int];
}
@protected
RsiLauncherAsarData dco_decode_rsi_launcher_asar_data(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 3)
throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
return RsiLauncherAsarData(
asarPath: dco_decode_String(arr[0]),
mainJsPath: dco_decode_String(arr[1]),
mainJsContent: dco_decode_list_prim_u_8_strict(arr[2]),
);
}
@protected
RustHttpResponse dco_decode_rust_http_response(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 7)
throw Exception('unexpected arr length: expect 7 but see ${arr.length}');
if (arr.length != 5)
throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
return RustHttpResponse(
statusCode: dco_decode_u_16(arr[0]),
headers: dco_decode_Map_String_String(arr[1]),
url: dco_decode_String(arr[2]),
contentLength: dco_decode_opt_box_autoadd_u_64(arr[3]),
version: dco_decode_my_http_version(arr[4]),
remoteAddr: dco_decode_String(arr[5]),
data: dco_decode_opt_list_prim_u_8_strict(arr[6]),
data: dco_decode_opt_list_prim_u_8_strict(arr[4]),
);
}
@ -564,12 +251,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw as int;
}
@protected
int dco_decode_u_32(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw as int;
}
@protected
BigInt dco_decode_u_64(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
@ -603,14 +284,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return Map.fromEntries(inner.map((e) => MapEntry(e.$1, e.$2)));
}
@protected
RustStreamSink<RsProcessStreamData>
sse_decode_StreamSink_rs_process_stream_data_Dco(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
throw UnimplementedError('Unreachable ()');
}
@protected
String sse_decode_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -618,19 +291,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return utf8.decoder.convert(inner);
}
@protected
bool sse_decode_bool(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getUint8() != 0;
}
@protected
RsiLauncherAsarData sse_decode_box_autoadd_rsi_launcher_asar_data(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_rsi_launcher_asar_data(deserializer));
}
@protected
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -643,25 +303,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getInt32();
}
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var len_ = sse_decode_i_32(deserializer);
var ans_ = <String>[];
for (var idx_ = 0; idx_ < len_; ++idx_) {
ans_.add(sse_decode_String(deserializer));
}
return ans_;
}
@protected
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var len_ = sse_decode_i_32(deserializer);
return deserializer.buffer.getUint8List(len_);
}
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -682,13 +323,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return ans_;
}
@protected
MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var inner = sse_decode_i_32(deserializer);
return MyHttpVersion.values[inner];
}
@protected
MyMethod sse_decode_my_method(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -708,17 +342,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
String? sse_decode_opt_String(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_String(deserializer));
} else {
return null;
}
}
@protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -750,38 +373,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (var_field0, var_field1);
}
@protected
RsProcessStreamData sse_decode_rs_process_stream_data(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_dataType = sse_decode_rs_process_stream_data_type(deserializer);
var var_data = sse_decode_String(deserializer);
var var_rsPid = sse_decode_u_32(deserializer);
return RsProcessStreamData(
dataType: var_dataType, data: var_data, rsPid: var_rsPid);
}
@protected
RsProcessStreamDataType sse_decode_rs_process_stream_data_type(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var inner = sse_decode_i_32(deserializer);
return RsProcessStreamDataType.values[inner];
}
@protected
RsiLauncherAsarData sse_decode_rsi_launcher_asar_data(
SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_asarPath = sse_decode_String(deserializer);
var var_mainJsPath = sse_decode_String(deserializer);
var var_mainJsContent = sse_decode_list_prim_u_8_strict(deserializer);
return RsiLauncherAsarData(
asarPath: var_asarPath,
mainJsPath: var_mainJsPath,
mainJsContent: var_mainJsContent);
}
@protected
RustHttpResponse sse_decode_rust_http_response(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -789,16 +380,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
var var_headers = sse_decode_Map_String_String(deserializer);
var var_url = sse_decode_String(deserializer);
var var_contentLength = sse_decode_opt_box_autoadd_u_64(deserializer);
var var_version = sse_decode_my_http_version(deserializer);
var var_remoteAddr = sse_decode_String(deserializer);
var var_data = sse_decode_opt_list_prim_u_8_strict(deserializer);
return RustHttpResponse(
statusCode: var_statusCode,
headers: var_headers,
url: var_url,
contentLength: var_contentLength,
version: var_version,
remoteAddr: var_remoteAddr,
data: var_data);
}
@ -808,12 +395,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return deserializer.buffer.getUint16();
}
@protected
int sse_decode_u_32(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getUint32();
}
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -832,9 +413,9 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
@protected
bool cst_encode_bool(bool raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw;
bool sse_decode_bool(SseDeserializer deserializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
return deserializer.buffer.getUint8() != 0;
}
@protected
@ -843,36 +424,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return raw;
}
@protected
int cst_encode_my_http_version(MyHttpVersion raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_i_32(raw.index);
}
@protected
int cst_encode_my_method(MyMethod raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_i_32(raw.index);
}
@protected
int cst_encode_rs_process_stream_data_type(RsProcessStreamDataType raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_i_32(raw.index);
}
@protected
int cst_encode_u_16(int raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw;
}
@protected
int cst_encode_u_32(int raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw;
}
@protected
int cst_encode_u_8(int raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
@ -900,38 +463,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
self.entries.map((e) => (e.key, e.value)).toList(), serializer);
}
@protected
void sse_encode_StreamSink_rs_process_stream_data_Dco(
RustStreamSink<RsProcessStreamData> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(
self.setupAndSerialize(
codec: DcoCodec(
decodeSuccessData: dco_decode_rs_process_stream_data,
decodeErrorData: dco_decode_AnyhowException,
)),
serializer);
}
@protected
void sse_encode_String(String self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer);
}
@protected
void sse_encode_bool(bool self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putUint8(self ? 1 : 0);
}
@protected
void sse_encode_box_autoadd_rsi_launcher_asar_data(
RsiLauncherAsarData self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_rsi_launcher_asar_data(self, serializer);
}
@protected
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -944,24 +481,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putInt32(self);
}
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self.length, serializer);
for (final item in self) {
sse_encode_String(item, serializer);
}
}
@protected
void sse_encode_list_prim_u_8_loose(
List<int> self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self.length, serializer);
serializer.buffer
.putUint8List(self is Uint8List ? self : Uint8List.fromList(self));
}
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self, SseSerializer serializer) {
@ -980,13 +499,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_my_http_version(
MyHttpVersion self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self.index, serializer);
}
@protected
void sse_encode_my_method(MyMethod self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -1004,16 +516,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
}
}
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_String(self, serializer);
}
}
@protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -1043,31 +545,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_String(self.$2, serializer);
}
@protected
void sse_encode_rs_process_stream_data(
RsProcessStreamData self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_rs_process_stream_data_type(self.dataType, serializer);
sse_encode_String(self.data, serializer);
sse_encode_u_32(self.rsPid, serializer);
}
@protected
void sse_encode_rs_process_stream_data_type(
RsProcessStreamDataType self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_i_32(self.index, serializer);
}
@protected
void sse_encode_rsi_launcher_asar_data(
RsiLauncherAsarData self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_String(self.asarPath, serializer);
sse_encode_String(self.mainJsPath, serializer);
sse_encode_list_prim_u_8_strict(self.mainJsContent, serializer);
}
@protected
void sse_encode_rust_http_response(
RustHttpResponse self, SseSerializer serializer) {
@ -1076,8 +553,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_Map_String_String(self.headers, serializer);
sse_encode_String(self.url, serializer);
sse_encode_opt_box_autoadd_u_64(self.contentLength, serializer);
sse_encode_my_http_version(self.version, serializer);
sse_encode_String(self.remoteAddr, serializer);
sse_encode_opt_list_prim_u_8_strict(self.data, serializer);
}
@ -1087,12 +562,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
serializer.buffer.putUint16(self);
}
@protected
void sse_encode_u_32(int self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putUint32(self);
}
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -1109,4 +578,10 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
void sse_encode_unit(void self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
}
@protected
void sse_encode_bool(bool self, SseSerializer serializer) {
// Codec=Sse (Serialization based), see doc to use other codecs
serializer.buffer.putUint8(self ? 1 : 0);
}
}

View File

@ -3,10 +3,7 @@
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
import 'api/asar_api.dart';
import 'api/http_api.dart';
import 'api/rs_process.dart';
import 'api/win32_api.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' as ffi;
@ -28,50 +25,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
Map<String, String> dco_decode_Map_String_String(dynamic raw);
@protected
RustStreamSink<RsProcessStreamData>
dco_decode_StreamSink_rs_process_stream_data_Dco(dynamic raw);
@protected
String dco_decode_String(dynamic raw);
@protected
bool dco_decode_bool(dynamic raw);
@protected
RsiLauncherAsarData dco_decode_box_autoadd_rsi_launcher_asar_data(
dynamic raw);
@protected
BigInt dco_decode_box_autoadd_u_64(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@protected
List<String> dco_decode_list_String(dynamic raw);
@protected
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
List<(String, String)> dco_decode_list_record_string_string(dynamic raw);
@protected
MyHttpVersion dco_decode_my_http_version(dynamic raw);
@protected
MyMethod dco_decode_my_method(dynamic raw);
@protected
Map<String, String>? dco_decode_opt_Map_String_String(dynamic raw);
@protected
String? dco_decode_opt_String(dynamic raw);
@protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw);
@ -81,24 +55,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
(String, String) dco_decode_record_string_string(dynamic raw);
@protected
RsProcessStreamData dco_decode_rs_process_stream_data(dynamic raw);
@protected
RsProcessStreamDataType dco_decode_rs_process_stream_data_type(dynamic raw);
@protected
RsiLauncherAsarData dco_decode_rsi_launcher_asar_data(dynamic raw);
@protected
RustHttpResponse dco_decode_rust_http_response(dynamic raw);
@protected
int dco_decode_u_16(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@protected
BigInt dco_decode_u_64(dynamic raw);
@ -115,33 +77,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
Map<String, String> sse_decode_Map_String_String(
SseDeserializer deserializer);
@protected
RustStreamSink<RsProcessStreamData>
sse_decode_StreamSink_rs_process_stream_data_Dco(
SseDeserializer deserializer);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
RsiLauncherAsarData sse_decode_box_autoadd_rsi_launcher_asar_data(
SseDeserializer deserializer);
@protected
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
List<String> sse_decode_list_String(SseDeserializer deserializer);
@protected
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@ -149,9 +93,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
List<(String, String)> sse_decode_list_record_string_string(
SseDeserializer deserializer);
@protected
MyHttpVersion sse_decode_my_http_version(SseDeserializer deserializer);
@protected
MyMethod sse_decode_my_method(SseDeserializer deserializer);
@ -159,9 +100,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
Map<String, String>? sse_decode_opt_Map_String_String(
SseDeserializer deserializer);
@protected
String? sse_decode_opt_String(SseDeserializer deserializer);
@protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer);
@ -172,27 +110,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
(String, String) sse_decode_record_string_string(
SseDeserializer deserializer);
@protected
RsProcessStreamData sse_decode_rs_process_stream_data(
SseDeserializer deserializer);
@protected
RsProcessStreamDataType sse_decode_rs_process_stream_data_type(
SseDeserializer deserializer);
@protected
RsiLauncherAsarData sse_decode_rsi_launcher_asar_data(
SseDeserializer deserializer);
@protected
RustHttpResponse sse_decode_rust_http_response(SseDeserializer deserializer);
@protected
int sse_decode_u_16(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer);
@ -202,6 +125,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_AnyhowException(
AnyhowException raw) {
@ -217,58 +143,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
raw.entries.map((e) => (e.key, e.value)).toList());
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict>
cst_encode_StreamSink_rs_process_stream_data_Dco(
RustStreamSink<RsProcessStreamData> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_String(raw.setupAndSerialize(
codec: DcoCodec(
decodeSuccessData: dco_decode_rs_process_stream_data,
decodeErrorData: dco_decode_AnyhowException,
)));
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_String(String raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_list_prim_u_8_strict(utf8.encoder.convert(raw));
}
@protected
ffi.Pointer<wire_cst_rsi_launcher_asar_data>
cst_encode_box_autoadd_rsi_launcher_asar_data(RsiLauncherAsarData raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ptr = wire.cst_new_box_autoadd_rsi_launcher_asar_data();
cst_api_fill_to_wire_rsi_launcher_asar_data(raw, ptr.ref);
return ptr;
}
@protected
ffi.Pointer<ffi.Uint64> cst_encode_box_autoadd_u_64(BigInt raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return wire.cst_new_box_autoadd_u_64(cst_encode_u_64(raw));
}
@protected
ffi.Pointer<wire_cst_list_String> cst_encode_list_String(List<String> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ans = wire.cst_new_list_String(raw.length);
for (var i = 0; i < raw.length; ++i) {
ans.ref.ptr[i] = cst_encode_String(raw[i]);
}
return ans;
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_loose> cst_encode_list_prim_u_8_loose(
List<int> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
final ans = wire.cst_new_list_prim_u_8_loose(raw.length);
ans.ref.ptr.asTypedList(raw.length).setAll(0, raw);
return ans;
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_list_prim_u_8_strict(
Uint8List raw) {
@ -296,13 +182,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return raw == null ? ffi.nullptr : cst_encode_Map_String_String(raw);
}
@protected
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_encode_opt_String(
String? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw == null ? ffi.nullptr : cst_encode_String(raw);
}
@protected
ffi.Pointer<ffi.Uint64> cst_encode_opt_box_autoadd_u_64(BigInt? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
@ -322,13 +201,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
return raw.toSigned(64).toInt();
}
@protected
void cst_api_fill_to_wire_box_autoadd_rsi_launcher_asar_data(
RsiLauncherAsarData apiObj,
ffi.Pointer<wire_cst_rsi_launcher_asar_data> wireObj) {
cst_api_fill_to_wire_rsi_launcher_asar_data(apiObj, wireObj.ref);
}
@protected
void cst_api_fill_to_wire_record_string_string(
(String, String) apiObj, wire_cst_record_string_string wireObj) {
@ -336,23 +208,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.field1 = cst_encode_String(apiObj.$2);
}
@protected
void cst_api_fill_to_wire_rs_process_stream_data(
RsProcessStreamData apiObj, wire_cst_rs_process_stream_data wireObj) {
wireObj.data_type = cst_encode_rs_process_stream_data_type(apiObj.dataType);
wireObj.data = cst_encode_String(apiObj.data);
wireObj.rs_pid = cst_encode_u_32(apiObj.rsPid);
}
@protected
void cst_api_fill_to_wire_rsi_launcher_asar_data(
RsiLauncherAsarData apiObj, wire_cst_rsi_launcher_asar_data wireObj) {
wireObj.asar_path = cst_encode_String(apiObj.asarPath);
wireObj.main_js_path = cst_encode_String(apiObj.mainJsPath);
wireObj.main_js_content =
cst_encode_list_prim_u_8_strict(apiObj.mainJsContent);
}
@protected
void cst_api_fill_to_wire_rust_http_response(
RustHttpResponse apiObj, wire_cst_rust_http_response wireObj) {
@ -361,32 +216,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
wireObj.url = cst_encode_String(apiObj.url);
wireObj.content_length =
cst_encode_opt_box_autoadd_u_64(apiObj.contentLength);
wireObj.version = cst_encode_my_http_version(apiObj.version);
wireObj.remote_addr = cst_encode_String(apiObj.remoteAddr);
wireObj.data = cst_encode_opt_list_prim_u_8_strict(apiObj.data);
}
@protected
bool cst_encode_bool(bool raw);
@protected
int cst_encode_i_32(int raw);
@protected
int cst_encode_my_http_version(MyHttpVersion raw);
@protected
int cst_encode_my_method(MyMethod raw);
@protected
int cst_encode_rs_process_stream_data_type(RsProcessStreamDataType raw);
@protected
int cst_encode_u_16(int raw);
@protected
int cst_encode_u_32(int raw);
@protected
int cst_encode_u_8(int raw);
@ -401,32 +242,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_Map_String_String(
Map<String, String> self, SseSerializer serializer);
@protected
void sse_encode_StreamSink_rs_process_stream_data_Dco(
RustStreamSink<RsProcessStreamData> self, SseSerializer serializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_rsi_launcher_asar_data(
RsiLauncherAsarData self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_list_String(List<String> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self, SseSerializer serializer);
@ -435,9 +259,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_list_record_string_string(
List<(String, String)> self, SseSerializer serializer);
@protected
void sse_encode_my_http_version(MyHttpVersion self, SseSerializer serializer);
@protected
void sse_encode_my_method(MyMethod self, SseSerializer serializer);
@ -445,9 +266,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_opt_Map_String_String(
Map<String, String>? self, SseSerializer serializer);
@protected
void sse_encode_opt_String(String? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer);
@ -459,18 +277,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
void sse_encode_record_string_string(
(String, String) self, SseSerializer serializer);
@protected
void sse_encode_rs_process_stream_data(
RsProcessStreamData self, SseSerializer serializer);
@protected
void sse_encode_rs_process_stream_data_type(
RsProcessStreamDataType self, SseSerializer serializer);
@protected
void sse_encode_rsi_launcher_asar_data(
RsiLauncherAsarData self, SseSerializer serializer);
@protected
void sse_encode_rust_http_response(
RustHttpResponse self, SseSerializer serializer);
@ -478,9 +284,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_u_16(int self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer);
@ -489,6 +292,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
}
// Section: wire_class
@ -532,96 +338,12 @@ class RustLibWire implements BaseWire {
late final _store_dart_post_cobject = _store_dart_post_cobjectPtr
.asFunction<void Function(DartPostCObjectFnType)>();
void wire__crate__api__asar_api__get_rsi_launcher_asar_data(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> asar_path,
) {
return _wire__crate__api__asar_api__get_rsi_launcher_asar_data(
port_,
asar_path,
);
}
late final _wire__crate__api__asar_api__get_rsi_launcher_asar_dataPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__asar_api__get_rsi_launcher_asar_data');
late final _wire__crate__api__asar_api__get_rsi_launcher_asar_data =
_wire__crate__api__asar_api__get_rsi_launcher_asar_dataPtr.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js(
int port_,
ffi.Pointer<wire_cst_rsi_launcher_asar_data> that,
ffi.Pointer<wire_cst_list_prim_u_8_loose> content,
) {
return _wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js(
port_,
that,
content,
);
}
late final _wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_jsPtr =
_lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_rsi_launcher_asar_data>,
ffi.Pointer<wire_cst_list_prim_u_8_loose>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js');
late final _wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_js =
_wire__crate__api__asar_api__rsi_launcher_asar_data_write_main_jsPtr
.asFunction<
void Function(int, ffi.Pointer<wire_cst_rsi_launcher_asar_data>,
ffi.Pointer<wire_cst_list_prim_u_8_loose>)>();
void wire__crate__api__http_api__dns_lookup_ips(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> host,
) {
return _wire__crate__api__http_api__dns_lookup_ips(
port_,
host,
);
}
late final _wire__crate__api__http_api__dns_lookup_ipsPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__http_api__dns_lookup_ips');
late final _wire__crate__api__http_api__dns_lookup_ips =
_wire__crate__api__http_api__dns_lookup_ipsPtr.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__http_api__dns_lookup_txt(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> host,
) {
return _wire__crate__api__http_api__dns_lookup_txt(
port_,
host,
);
}
late final _wire__crate__api__http_api__dns_lookup_txtPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__http_api__dns_lookup_txt');
late final _wire__crate__api__http_api__dns_lookup_txt =
_wire__crate__api__http_api__dns_lookup_txtPtr.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__http_api__fetch(
int port_,
int method,
ffi.Pointer<wire_cst_list_prim_u_8_strict> url,
ffi.Pointer<wire_cst_list_record_string_string> headers,
ffi.Pointer<wire_cst_list_prim_u_8_strict> input_data,
ffi.Pointer<wire_cst_list_prim_u_8_strict> with_ip_address,
) {
return _wire__crate__api__http_api__fetch(
port_,
@ -629,7 +351,6 @@ class RustLibWire implements BaseWire {
url,
headers,
input_data,
with_ip_address,
);
}
@ -640,7 +361,6 @@ class RustLibWire implements BaseWire {
ffi.Int32,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_record_string_string>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__http_api__fetch');
late final _wire__crate__api__http_api__fetch =
@ -650,7 +370,6 @@ class RustLibWire implements BaseWire {
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_record_string_string>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__http_api__set_default_header(
@ -673,128 +392,6 @@ class RustLibWire implements BaseWire {
void Function(
int, ffi.Pointer<wire_cst_list_record_string_string>)>();
void wire__crate__api__rs_process__start(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> executable,
ffi.Pointer<wire_cst_list_String> arguments,
ffi.Pointer<wire_cst_list_prim_u_8_strict> working_directory,
ffi.Pointer<wire_cst_list_prim_u_8_strict> stream_sink,
) {
return _wire__crate__api__rs_process__start(
port_,
executable,
arguments,
working_directory,
stream_sink,
);
}
late final _wire__crate__api__rs_process__startPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__rs_process__start');
late final _wire__crate__api__rs_process__start =
_wire__crate__api__rs_process__startPtr.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_String>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__rs_process__write(
int port_,
int rs_pid,
ffi.Pointer<wire_cst_list_prim_u_8_strict> data,
) {
return _wire__crate__api__rs_process__write(
port_,
rs_pid,
data,
);
}
late final _wire__crate__api__rs_process__writePtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(ffi.Int64, ffi.Uint32,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__rs_process__write');
late final _wire__crate__api__rs_process__write =
_wire__crate__api__rs_process__writePtr.asFunction<
void Function(
int, int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__win32_api__send_notify(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> summary,
ffi.Pointer<wire_cst_list_prim_u_8_strict> body,
ffi.Pointer<wire_cst_list_prim_u_8_strict> app_name,
ffi.Pointer<wire_cst_list_prim_u_8_strict> app_id,
) {
return _wire__crate__api__win32_api__send_notify(
port_,
summary,
body,
app_name,
app_id,
);
}
late final _wire__crate__api__win32_api__send_notifyPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__win32_api__send_notify');
late final _wire__crate__api__win32_api__send_notify =
_wire__crate__api__win32_api__send_notifyPtr.asFunction<
void Function(
int,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>,
ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
void wire__crate__api__win32_api__set_foreground_window(
int port_,
ffi.Pointer<wire_cst_list_prim_u_8_strict> window_name,
) {
return _wire__crate__api__win32_api__set_foreground_window(
port_,
window_name,
);
}
late final _wire__crate__api__win32_api__set_foreground_windowPtr = _lookup<
ffi.NativeFunction<
ffi.Void Function(
ffi.Int64, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>>(
'frbgen_starcitizen_doctor_wire__crate__api__win32_api__set_foreground_window');
late final _wire__crate__api__win32_api__set_foreground_window =
_wire__crate__api__win32_api__set_foreground_windowPtr.asFunction<
void Function(int, ffi.Pointer<wire_cst_list_prim_u_8_strict>)>();
ffi.Pointer<wire_cst_rsi_launcher_asar_data>
cst_new_box_autoadd_rsi_launcher_asar_data() {
return _cst_new_box_autoadd_rsi_launcher_asar_data();
}
late final _cst_new_box_autoadd_rsi_launcher_asar_dataPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<wire_cst_rsi_launcher_asar_data> Function()>>(
'frbgen_starcitizen_doctor_cst_new_box_autoadd_rsi_launcher_asar_data');
late final _cst_new_box_autoadd_rsi_launcher_asar_data =
_cst_new_box_autoadd_rsi_launcher_asar_dataPtr.asFunction<
ffi.Pointer<wire_cst_rsi_launcher_asar_data> Function()>();
ffi.Pointer<ffi.Uint64> cst_new_box_autoadd_u_64(
int value,
) {
@ -809,36 +406,6 @@ class RustLibWire implements BaseWire {
late final _cst_new_box_autoadd_u_64 = _cst_new_box_autoadd_u_64Ptr
.asFunction<ffi.Pointer<ffi.Uint64> Function(int)>();
ffi.Pointer<wire_cst_list_String> cst_new_list_String(
int len,
) {
return _cst_new_list_String(
len,
);
}
late final _cst_new_list_StringPtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<wire_cst_list_String> Function(
ffi.Int32)>>('frbgen_starcitizen_doctor_cst_new_list_String');
late final _cst_new_list_String = _cst_new_list_StringPtr
.asFunction<ffi.Pointer<wire_cst_list_String> Function(int)>();
ffi.Pointer<wire_cst_list_prim_u_8_loose> cst_new_list_prim_u_8_loose(
int len,
) {
return _cst_new_list_prim_u_8_loose(
len,
);
}
late final _cst_new_list_prim_u_8_loosePtr = _lookup<
ffi.NativeFunction<
ffi.Pointer<wire_cst_list_prim_u_8_loose> Function(ffi.Int32)>>(
'frbgen_starcitizen_doctor_cst_new_list_prim_u_8_loose');
late final _cst_new_list_prim_u_8_loose = _cst_new_list_prim_u_8_loosePtr
.asFunction<ffi.Pointer<wire_cst_list_prim_u_8_loose> Function(int)>();
ffi.Pointer<wire_cst_list_prim_u_8_strict> cst_new_list_prim_u_8_strict(
int len,
) {
@ -899,21 +466,6 @@ final class wire_cst_list_prim_u_8_strict extends ffi.Struct {
external int len;
}
final class wire_cst_rsi_launcher_asar_data extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> asar_path;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> main_js_path;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> main_js_content;
}
final class wire_cst_list_prim_u_8_loose extends ffi.Struct {
external ffi.Pointer<ffi.Uint8> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_record_string_string extends ffi.Struct {
external ffi.Pointer<wire_cst_list_prim_u_8_strict> field0;
@ -927,23 +479,6 @@ final class wire_cst_list_record_string_string extends ffi.Struct {
external int len;
}
final class wire_cst_list_String extends ffi.Struct {
external ffi.Pointer<ffi.Pointer<wire_cst_list_prim_u_8_strict>> ptr;
@ffi.Int32()
external int len;
}
final class wire_cst_rs_process_stream_data extends ffi.Struct {
@ffi.Int32()
external int data_type;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> data;
@ffi.Uint32()
external int rs_pid;
}
final class wire_cst_rust_http_response extends ffi.Struct {
@ffi.Uint16()
external int status_code;
@ -954,10 +489,5 @@ final class wire_cst_rust_http_response extends ffi.Struct {
external ffi.Pointer<ffi.Uint64> content_length;
@ffi.Int32()
external int version;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> remote_addr;
external ffi.Pointer<wire_cst_list_prim_u_8_strict> data;
}

View File

@ -0,0 +1,316 @@
// This file is automatically generated, so please do not edit it.
// Generated by `flutter_rust_bridge`@ 2.3.0.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
// Static analysis wrongly picks the IO variant, thus ignore this
// ignore_for_file: argument_type_not_assignable
import 'api/http_api.dart';
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'http_package.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
RustLibApiImplPlatform({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@protected
AnyhowException dco_decode_AnyhowException(dynamic raw);
@protected
Map<String, String> dco_decode_Map_String_String(dynamic raw);
@protected
String dco_decode_String(dynamic raw);
@protected
BigInt dco_decode_box_autoadd_u_64(dynamic raw);
@protected
int dco_decode_i_32(dynamic raw);
@protected
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
@protected
List<(String, String)> dco_decode_list_record_string_string(dynamic raw);
@protected
MyMethod dco_decode_my_method(dynamic raw);
@protected
Map<String, String>? dco_decode_opt_Map_String_String(dynamic raw);
@protected
BigInt? dco_decode_opt_box_autoadd_u_64(dynamic raw);
@protected
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
@protected
(String, String) dco_decode_record_string_string(dynamic raw);
@protected
RustHttpResponse dco_decode_rust_http_response(dynamic raw);
@protected
int dco_decode_u_16(dynamic raw);
@protected
BigInt dco_decode_u_64(dynamic raw);
@protected
int dco_decode_u_8(dynamic raw);
@protected
void dco_decode_unit(dynamic raw);
@protected
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
@protected
Map<String, String> sse_decode_Map_String_String(
SseDeserializer deserializer);
@protected
String sse_decode_String(SseDeserializer deserializer);
@protected
BigInt sse_decode_box_autoadd_u_64(SseDeserializer deserializer);
@protected
int sse_decode_i_32(SseDeserializer deserializer);
@protected
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
List<(String, String)> sse_decode_list_record_string_string(
SseDeserializer deserializer);
@protected
MyMethod sse_decode_my_method(SseDeserializer deserializer);
@protected
Map<String, String>? sse_decode_opt_Map_String_String(
SseDeserializer deserializer);
@protected
BigInt? sse_decode_opt_box_autoadd_u_64(SseDeserializer deserializer);
@protected
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
@protected
(String, String) sse_decode_record_string_string(
SseDeserializer deserializer);
@protected
RustHttpResponse sse_decode_rust_http_response(SseDeserializer deserializer);
@protected
int sse_decode_u_16(SseDeserializer deserializer);
@protected
BigInt sse_decode_u_64(SseDeserializer deserializer);
@protected
int sse_decode_u_8(SseDeserializer deserializer);
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
bool sse_decode_bool(SseDeserializer deserializer);
@protected
String cst_encode_AnyhowException(AnyhowException raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
throw UnimplementedError();
}
@protected
JSAny cst_encode_Map_String_String(Map<String, String> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_list_record_string_string(
raw.entries.map((e) => (e.key, e.value)).toList());
}
@protected
String cst_encode_String(String raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw;
}
@protected
JSAny cst_encode_box_autoadd_u_64(BigInt raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return cst_encode_u_64(raw);
}
@protected
JSAny cst_encode_list_prim_u_8_strict(Uint8List raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw.jsify()!;
}
@protected
JSAny cst_encode_list_record_string_string(List<(String, String)> raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw.map(cst_encode_record_string_string).toList().jsify()!;
}
@protected
JSAny? cst_encode_opt_Map_String_String(Map<String, String>? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw == null ? null : cst_encode_Map_String_String(raw);
}
@protected
JSAny? cst_encode_opt_box_autoadd_u_64(BigInt? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw == null ? null : cst_encode_box_autoadd_u_64(raw);
}
@protected
JSAny? cst_encode_opt_list_prim_u_8_strict(Uint8List? raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return raw == null ? null : cst_encode_list_prim_u_8_strict(raw);
}
@protected
JSAny cst_encode_record_string_string((String, String) raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return [cst_encode_String(raw.$1), cst_encode_String(raw.$2)].jsify()!;
}
@protected
JSAny cst_encode_rust_http_response(RustHttpResponse raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return [
cst_encode_u_16(raw.statusCode),
cst_encode_Map_String_String(raw.headers),
cst_encode_String(raw.url),
cst_encode_opt_box_autoadd_u_64(raw.contentLength),
cst_encode_opt_list_prim_u_8_strict(raw.data)
].jsify()!;
}
@protected
JSAny cst_encode_u_64(BigInt raw) {
// Codec=Cst (C-struct based), see doc to use other codecs
return castNativeBigInt(raw);
}
@protected
int cst_encode_i_32(int raw);
@protected
int cst_encode_my_method(MyMethod raw);
@protected
int cst_encode_u_16(int raw);
@protected
int cst_encode_u_8(int raw);
@protected
void cst_encode_unit(void raw);
@protected
void sse_encode_AnyhowException(
AnyhowException self, SseSerializer serializer);
@protected
void sse_encode_Map_String_String(
Map<String, String> self, SseSerializer serializer);
@protected
void sse_encode_String(String self, SseSerializer serializer);
@protected
void sse_encode_box_autoadd_u_64(BigInt self, SseSerializer serializer);
@protected
void sse_encode_i_32(int self, SseSerializer serializer);
@protected
void sse_encode_list_prim_u_8_strict(
Uint8List self, SseSerializer serializer);
@protected
void sse_encode_list_record_string_string(
List<(String, String)> self, SseSerializer serializer);
@protected
void sse_encode_my_method(MyMethod self, SseSerializer serializer);
@protected
void sse_encode_opt_Map_String_String(
Map<String, String>? self, SseSerializer serializer);
@protected
void sse_encode_opt_box_autoadd_u_64(BigInt? self, SseSerializer serializer);
@protected
void sse_encode_opt_list_prim_u_8_strict(
Uint8List? self, SseSerializer serializer);
@protected
void sse_encode_record_string_string(
(String, String) self, SseSerializer serializer);
@protected
void sse_encode_rust_http_response(
RustHttpResponse self, SseSerializer serializer);
@protected
void sse_encode_u_16(int self, SseSerializer serializer);
@protected
void sse_encode_u_64(BigInt self, SseSerializer serializer);
@protected
void sse_encode_u_8(int self, SseSerializer serializer);
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_bool(bool self, SseSerializer serializer);
}
// Section: wire_class
class RustLibWire implements BaseWire {
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
void wire__crate__api__http_api__fetch(NativePortType port_, int method,
String url, JSAny? headers, JSAny? input_data) =>
wasmModule.wire__crate__api__http_api__fetch(
port_, method, url, headers, input_data);
void wire__crate__api__http_api__set_default_header(
NativePortType port_, JSAny headers) =>
wasmModule.wire__crate__api__http_api__set_default_header(port_, headers);
}
@JS('wasm_bindgen')
external RustLibWasmModule get wasmModule;
@JS()
@anonymous
extension type RustLibWasmModule._(JSObject _) implements JSObject {
external void wire__crate__api__http_api__fetch(NativePortType port_,
int method, String url, JSAny? headers, JSAny? input_data);
external void wire__crate__api__http_api__set_default_header(
NativePortType port_, JSAny headers);
}

View File

@ -6,23 +6,11 @@
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
enum MyHttpVersion {
http09,
http10,
http11,
http2,
http3,
httpUnknown,
;
}
class RustHttpResponse {
final int statusCode;
final Map<String, String> headers;
final String url;
final BigInt? contentLength;
final MyHttpVersion version;
final String remoteAddr;
final Uint8List? data;
const RustHttpResponse({
@ -30,8 +18,6 @@ class RustHttpResponse {
required this.headers,
required this.url,
this.contentLength,
required this.version,
required this.remoteAddr,
this.data,
});
@ -41,8 +27,6 @@ class RustHttpResponse {
headers.hashCode ^
url.hashCode ^
contentLength.hashCode ^
version.hashCode ^
remoteAddr.hashCode ^
data.hashCode;
@override
@ -54,7 +38,5 @@ class RustHttpResponse {
headers == other.headers &&
url == other.url &&
contentLength == other.contentLength &&
version == other.version &&
remoteAddr == other.remoteAddr &&
data == other.data;
}

View File

@ -25,7 +25,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(v0, v1) =>
"Network anomaly!\nThis may be that your network environment has DNS pollution, please try to replace DNS.\nOr the server is being maintained or attacked and tried it later.\nEnter the offline mode ...\n\nPlease use it carefully in the offline mode.\nThe current version of the construction date: ${v0}\n QQ group: 940696487\nError message: ${v1}";
static String m2(v0, v1) => "SCToolBox V ${v0} ${v1}";
static String m2(v0, v1) => "SCToolBox ${v0} ${v1}";
static String m3(v0) => "Initialization failure: ${v0}";
@ -198,7 +198,7 @@ class MessageLookup extends MessageLookupByLibrary {
"about_app_description": MessageLookupByLibrary.simpleMessage(
"Not just about localization!\n\nThe SCToolBox is your helper to explore the verse. We are committed to solving common in-game problems for citizens, in addition to facilitating localization from the community, game performance tuning, and localization of some commonly used website."),
"about_check_update":
MessageLookupByLibrary.simpleMessage("Check for updates"),
MessageLookupByLibrary.simpleMessage("Get the full version"),
"about_disclaimer": MessageLookupByLibrary.simpleMessage(
"This is an unofficial Star Citizen fan-made tools, not affiliated with the Cloud Imperium group of companies. All content on this Software not authored by its host or users are property of their respective owners. \nStar Citizen®, Roberts Space Industries® and Cloud Imperium® are registered trademarks of Cloud Imperium Rights LLC."),
"about_info_latest_version": MessageLookupByLibrary.simpleMessage(

View File

@ -20,7 +20,7 @@ typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'ja';
static String m2(v0, v1) => "SCToolBox V ${v0} ${v1}";
static String m2(v0, v1) => "SCToolBox ${v0} ${v1}";
static String m19(v0, v1) => "ダウンロード: ${v0}/s アップロード:${v1}/s";

View File

@ -25,7 +25,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(v0, v1) =>
"网络异常!\n这可能是您的网络环境存在DNS污染请尝试更换DNS。\n或服务器正在维护或遭受攻击,稍后再试。 \n进入离线模式... \n\n请谨慎在离线模式中使用。 \n当前版本构建日期:${v0}\n QQ群940696487 \n错误信息:${v1}";
static String m2(v0, v1) => "SC汉化盒子 V${v0} ${v1}";
static String m2(v0, v1) => "SC汉化盒子 ${v0} ${v1}";
static String m3(v0) => "初始化失败:${v0}";
@ -180,7 +180,7 @@ class MessageLookup extends MessageLookupByLibrary {
"about_analytics_units_user": MessageLookupByLibrary.simpleMessage(""),
"about_app_description": MessageLookupByLibrary.simpleMessage(
"不仅仅是汉化!\n\nSC汉化盒子是你探索宇宙的好帮手我们致力于为各位公民解决游戏中的常见问题并为社区汉化、性能调优、常用网站汉化 等操作提供便利。"),
"about_check_update": MessageLookupByLibrary.simpleMessage("检查更新"),
"about_check_update": MessageLookupByLibrary.simpleMessage("安装完整版"),
"about_disclaimer": MessageLookupByLibrary.simpleMessage(
"这是一个非官方的星际公民工具,不隶属于 Cloud Imperium 公司集团。 本软件中非由其主机或用户创作的所有内容均为其各自所有者的财产。 \nStar Citizen®、Roberts Space Industries® 和 Cloud Imperium® 是 Cloud Imperium Rights LLC 的注册商标。"),
"about_info_latest_version":

View File

@ -25,7 +25,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(v0, v1) =>
"網路異常!\n這可能是您的網路環境存在DNS汙染請嘗試更換DNS。\n或伺服器正在維護或遭受攻擊,稍後再試。 \n進入離線模式... \n\n請謹慎在離線模式中使用。 \n目前版本構建日期:${v0}\n QQ群940696487 \n錯誤資訊:${v1}";
static String m2(v0, v1) => "SC工具箱 V${v0} ${v1}";
static String m2(v0, v1) => "SC工具箱 ${v0} ${v1}";
static String m3(v0) => "初始化失敗:${v0}";
@ -179,7 +179,7 @@ class MessageLookup extends MessageLookupByLibrary {
"about_analytics_units_user": MessageLookupByLibrary.simpleMessage(""),
"about_app_description": MessageLookupByLibrary.simpleMessage(
"這個工具能做的不僅僅是翻譯!\n\nSC工具箱是你探索宇宙的好幫手我們致力於為各位公民解決遊戲中的常見問題並為社群翻譯、性能改善、常用網站翻譯 等操作提供便利。"),
"about_check_update": MessageLookupByLibrary.simpleMessage("檢查更新"),
"about_check_update": MessageLookupByLibrary.simpleMessage("安裝完整版"),
"about_disclaimer": MessageLookupByLibrary.simpleMessage(
"SC工具箱非官方的星際公民工具不隸屬於 Cloud Imperium 公司集團。 本軟體中非由其主機或使用者創作的所有內容均為其各自所有者的財產。 \nStar Citizen®、Roberts Space Industries® 和 Cloud Imperium® 是 Cloud Imperium Rights LLC 的註冊商標。"),
"about_info_latest_version":

View File

@ -70,10 +70,10 @@ class S {
);
}
/// `SCToolBox V {v0} {v1}`
/// `SCToolBox {v0} {v1}`
String app_index_version_info(Object v0, Object v1) {
return Intl.message(
'SCToolBox V $v0 $v1',
'SCToolBox $v0 $v1',
name: 'app_index_version_info',
desc: '',
args: [v0, v1],
@ -90,10 +90,10 @@ class S {
);
}
/// `Check for updates`
/// `Get the full version`
String get about_check_update {
return Intl.message(
'Check for updates',
'Get the full version',
name: 'about_check_update',
desc: '',
args: [],

View File

@ -5,11 +5,11 @@
"@app_language_name": {},
"app_language_code": "en",
"@app_language_code": {},
"app_index_version_info": "SCToolBox V {v0} {v1}",
"app_index_version_info": "SCToolBox {v0} {v1}",
"@app_index_version_info": {},
"app_shortcut_name": "SCToolBox.lnk",
"@app_shortcut_name": {},
"about_check_update": "Check for updates",
"about_check_update": "Get the full version",
"@about_check_update": {},
"about_app_description": "Not just about localization!\n\nThe SCToolBox is your helper to explore the verse. We are committed to solving common in-game problems for citizens, in addition to facilitating localization from the community, game performance tuning, and localization of some commonly used website.",
"@about_app_description": {},

View File

@ -5,7 +5,7 @@
"@app_language_name": {},
"app_language_code": "ja",
"@app_language_code": {},
"app_index_version_info": "SCToolBox V {v0} {v1}",
"app_index_version_info": "SCToolBox {v0} {v1}",
"@app_index_version_info": {},
"about_check_update": "更新チェック",
"@about_check_update": {},

View File

@ -4,11 +4,11 @@
"app_language_name": "简体中文",
"@app_language_name": {},
"app_language_code": "zh_CN",
"app_index_version_info": "SC汉化盒子 V{v0} {v1}",
"app_index_version_info": "SC汉化盒子 {v0} {v1}",
"@app_index_version_info": {},
"app_shortcut_name": "SC汉化盒子DEV.lnk",
"@app_shortcut_name": {},
"about_check_update": "检查更新",
"about_check_update": "安装完整版",
"@about_check_update": {},
"about_app_description": "不仅仅是汉化!\n\nSC汉化盒子是你探索宇宙的好帮手我们致力于为各位公民解决游戏中的常见问题并为社区汉化、性能调优、常用网站汉化 等操作提供便利。",
"@about_app_description": {},

View File

@ -4,11 +4,11 @@
"app_language_name": "繁體中文",
"@app_language_name": {},
"app_language_code": "zh_TW",
"app_index_version_info": "SC工具箱 V{v0} {v1}",
"app_index_version_info": "SC工具箱 {v0} {v1}",
"@app_index_version_info": {},
"app_shortcut_name": "SC工具箱DEV.lnk",
"@app_shortcut_name": {},
"about_check_update": "檢查更新",
"about_check_update": "安裝完整版",
"@about_check_update": {},
"about_app_description": "這個工具能做的不僅僅是翻譯!\n\nSC工具箱是你探索宇宙的好幫手我們致力於為各位公民解決遊戲中的常見問題並為社群翻譯、性能改善、常用網站翻譯 等操作提供便利。",
"@about_app_description": {},

View File

@ -1,36 +1,16 @@
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app.dart';
void main(List<String> args) async {
// webview window
if (runWebViewTitleBarWidget(args,
backgroundColor: const Color.fromRGBO(19, 36, 49, 1),
builder: _defaultWebviewTitleBar)) {
return;
}
WidgetsFlutterBinding.ensureInitialized();
await _initWindow();
// run app
runApp(const ProviderScope(child: App()));
}
_initWindow() async {
await windowManager.ensureInitialized();
await windowManager.setTitleBarStyle(
TitleBarStyle.hidden,
windowButtonVisibility: false,
);
await windowManager.setSize(const Size(1280, 810));
await windowManager.setMinimumSize(const Size(1280, 810));
await windowManager.center(animate: true);
}
class App extends HookConsumerWidget {
const App({super.key});
@ -81,46 +61,3 @@ class App extends HookConsumerWidget {
}
}
Widget _defaultWebviewTitleBar(BuildContext context) {
final state = TitleBarWebViewState.of(context);
final controller = TitleBarWebViewController.of(context);
return FluentTheme(
data: FluentThemeData.dark(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (Platform.isMacOS) const SizedBox(width: 96),
IconButton(
onPressed: !state.canGoBack ? null : controller.back,
icon: const Icon(FluentIcons.chevron_left),
),
const SizedBox(width: 12),
IconButton(
onPressed: !state.canGoForward ? null : controller.forward,
icon: const Icon(FluentIcons.chevron_right),
),
const SizedBox(width: 12),
if (state.isLoading)
IconButton(
onPressed: controller.stop,
icon: const Icon(FluentIcons.chrome_close),
)
else
IconButton(
onPressed: controller.reload,
icon: const Icon(FluentIcons.refresh),
),
const SizedBox(width: 12),
(state.isLoading)
? const SizedBox(
width: 24,
height: 24,
child: ProgressRing(),
)
: const SizedBox(width: 24),
const SizedBox(width: 12),
SelectableText(state.url ?? ""),
const Spacer()
],
));
}

View File

@ -1,216 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
import 'dart:io';
import 'dart:math';
import 'package:aria2/aria2.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart'
as rs_process;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
part 'aria2c.g.dart';
part 'aria2c.freezed.dart';
@freezed
class Aria2cModelState with _$Aria2cModelState {
const factory Aria2cModelState({
required String aria2cDir,
Aria2c? aria2c,
Aria2GlobalStat? aria2globalStat,
}) = _Aria2cModelState;
}
extension Aria2cModelExt on Aria2cModelState {
bool get isRunning => aria2c != null;
bool get hasDownloadTask => aria2globalStat != null && aria2TotalTaskNum > 0;
int get aria2TotalTaskNum => aria2globalStat == null
? 0
: ((aria2globalStat!.numActive ?? 0) +
(aria2globalStat!.numWaiting ?? 0));
}
@riverpod
class Aria2cModel extends _$Aria2cModel {
bool _disposed = false;
@override
Aria2cModelState build() {
if (appGlobalState.applicationBinaryModuleDir == null) {
throw Exception("applicationBinaryModuleDir is null");
}
ref.onDispose(() {
_disposed = true;
});
ref.keepAlive();
final aria2cDir = "${appGlobalState.applicationBinaryModuleDir}\\aria2c";
// LazyLoad init
() async {
try {
final sessionFile = File("$aria2cDir\\aria2.session");
// 有下载任务则第一时间初始化
if (await sessionFile.exists() &&
(await sessionFile.readAsString()).trim().isNotEmpty) {
dPrint("launch Aria2c daemon");
await launchDaemon(appGlobalState.applicationBinaryModuleDir!);
} else {
dPrint("LazyLoad Aria2c daemon");
}
} catch (e) {
dPrint("Aria2cManager.checkLazyLoad Error:$e");
}
}();
return Aria2cModelState(aria2cDir: aria2cDir);
}
Future launchDaemon(String applicationBinaryModuleDir) async {
if (state.aria2c != null) return;
await BinaryModuleConf.extractModule(
["aria2c"], applicationBinaryModuleDir);
/// skip for debug hot reload
if (kDebugMode) {
if ((await SystemHelper.getPID("aria2c")).isNotEmpty) {
dPrint("[Aria2cManager] debug skip for hot reload");
return;
}
}
final sessionFile = File("${state.aria2cDir}\\aria2.session");
if (!await sessionFile.exists()) {
await sessionFile.create(recursive: true);
}
final exePath = "${state.aria2cDir}\\aria2c.exe";
final port = await getFreePort();
final pwd = generateRandomPassword(16);
dPrint("pwd === $pwd");
final trackerList = await Api.getTorrentTrackerList();
dPrint("trackerList === $trackerList");
dPrint("Aria2cManager .----- aria2c start $port------");
final stream = rs_process.start(
executable: exePath,
arguments: [
"-V",
"-c",
"-x 16",
"--dir=${state.aria2cDir}\\downloads",
"--disable-ipv6",
"--enable-rpc",
"--pause",
"--rpc-listen-port=$port",
"--rpc-secret=$pwd",
"--input-file=${sessionFile.absolute.path.trim()}",
"--save-session=${sessionFile.absolute.path.trim()}",
"--save-session-interval=60",
"--file-allocation=trunc",
"--seed-time=0",
],
workingDirectory: state.aria2cDir);
String launchError = "";
stream.listen((event) {
dPrint("Aria2cManager.rs_process event === $event");
switch (event.dataType) {
case rs_process.RsProcessStreamDataType.output:
if (event.data.contains("IPv4 RPC: listening on TCP port")) {
_onLaunch(port, pwd, trackerList);
}
break;
case rs_process.RsProcessStreamDataType.error:
launchError = event.data;
state = state.copyWith(aria2c: null);
break;
case rs_process.RsProcessStreamDataType.exit:
launchError = event.data;
state = state.copyWith(aria2c: null);
break;
}
});
while (true) {
if (state.aria2c != null) return;
if (launchError.isNotEmpty) throw launchError;
await Future.delayed(const Duration(milliseconds: 100));
}
}
Future<int> getFreePort() async {
final serverSocket = await ServerSocket.bind("127.0.0.1", 0);
final port = serverSocket.port;
await serverSocket.close();
return port;
}
String generateRandomPassword(int length) {
const String charset =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = Random();
StringBuffer buffer = StringBuffer();
for (int i = 0; i < length; i++) {
int randomIndex = random.nextInt(charset.length);
buffer.write(charset[randomIndex]);
}
return buffer.toString();
}
int textToByte(String text) {
if (text.length == 1) {
return 0;
}
if (int.tryParse(text) != null) {
return int.parse(text);
}
if (text.endsWith("k")) {
return int.parse(text.substring(0, text.length - 1)) * 1024;
}
if (text.endsWith("m")) {
return int.parse(text.substring(0, text.length - 1)) * 1024 * 1024;
}
return 0;
}
Future<void> _onLaunch(int port, String pwd, String trackerList) async {
final aria2c = Aria2c("ws://127.0.0.1:$port/jsonrpc", "websocket", pwd);
state = state.copyWith(aria2c: aria2c);
aria2c.getVersion().then((value) {
dPrint("Aria2cManager.connected! version == ${value.version}");
_listenState(aria2c);
});
final box = await Hive.openBox("app_conf");
aria2c.changeGlobalOption(Aria2Option()
..maxOverallUploadLimit =
textToByte(box.get("downloader_up_limit", defaultValue: "0"))
..maxOverallDownloadLimit =
textToByte(box.get("downloader_down_limit", defaultValue: "0"))
..btTracker = trackerList);
}
Future<void> _listenState(Aria2c aria2c) async {
dPrint("Aria2cModel._listenState start");
while (true) {
if (_disposed || state.aria2c == null) {
dPrint("Aria2cModel._listenState end");
return;
}
try {
final aria2globalStat = await aria2c.getGlobalStat();
state = state.copyWith(aria2globalStat: aria2globalStat);
} catch (e) {
dPrint("aria2globalStat update error:$e");
}
await Future.delayed(const Duration(seconds: 1));
}
}
}

View File

@ -1,197 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'aria2c.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Aria2cModelState {
String get aria2cDir => throw _privateConstructorUsedError;
Aria2c? get aria2c => throw _privateConstructorUsedError;
Aria2GlobalStat? get aria2globalStat => throw _privateConstructorUsedError;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$Aria2cModelStateCopyWith<Aria2cModelState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $Aria2cModelStateCopyWith<$Res> {
factory $Aria2cModelStateCopyWith(
Aria2cModelState value, $Res Function(Aria2cModelState) then) =
_$Aria2cModelStateCopyWithImpl<$Res, Aria2cModelState>;
@useResult
$Res call(
{String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat});
}
/// @nodoc
class _$Aria2cModelStateCopyWithImpl<$Res, $Val extends Aria2cModelState>
implements $Aria2cModelStateCopyWith<$Res> {
_$Aria2cModelStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? aria2cDir = null,
Object? aria2c = freezed,
Object? aria2globalStat = freezed,
}) {
return _then(_value.copyWith(
aria2cDir: null == aria2cDir
? _value.aria2cDir
: aria2cDir // ignore: cast_nullable_to_non_nullable
as String,
aria2c: freezed == aria2c
? _value.aria2c
: aria2c // ignore: cast_nullable_to_non_nullable
as Aria2c?,
aria2globalStat: freezed == aria2globalStat
? _value.aria2globalStat
: aria2globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
) as $Val);
}
}
/// @nodoc
abstract class _$$Aria2cModelStateImplCopyWith<$Res>
implements $Aria2cModelStateCopyWith<$Res> {
factory _$$Aria2cModelStateImplCopyWith(_$Aria2cModelStateImpl value,
$Res Function(_$Aria2cModelStateImpl) then) =
__$$Aria2cModelStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat});
}
/// @nodoc
class __$$Aria2cModelStateImplCopyWithImpl<$Res>
extends _$Aria2cModelStateCopyWithImpl<$Res, _$Aria2cModelStateImpl>
implements _$$Aria2cModelStateImplCopyWith<$Res> {
__$$Aria2cModelStateImplCopyWithImpl(_$Aria2cModelStateImpl _value,
$Res Function(_$Aria2cModelStateImpl) _then)
: super(_value, _then);
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? aria2cDir = null,
Object? aria2c = freezed,
Object? aria2globalStat = freezed,
}) {
return _then(_$Aria2cModelStateImpl(
aria2cDir: null == aria2cDir
? _value.aria2cDir
: aria2cDir // ignore: cast_nullable_to_non_nullable
as String,
aria2c: freezed == aria2c
? _value.aria2c
: aria2c // ignore: cast_nullable_to_non_nullable
as Aria2c?,
aria2globalStat: freezed == aria2globalStat
? _value.aria2globalStat
: aria2globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
));
}
}
/// @nodoc
class _$Aria2cModelStateImpl
with DiagnosticableTreeMixin
implements _Aria2cModelState {
const _$Aria2cModelStateImpl(
{required this.aria2cDir, this.aria2c, this.aria2globalStat});
@override
final String aria2cDir;
@override
final Aria2c? aria2c;
@override
final Aria2GlobalStat? aria2globalStat;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Aria2cModelState(aria2cDir: $aria2cDir, aria2c: $aria2c, aria2globalStat: $aria2globalStat)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Aria2cModelState'))
..add(DiagnosticsProperty('aria2cDir', aria2cDir))
..add(DiagnosticsProperty('aria2c', aria2c))
..add(DiagnosticsProperty('aria2globalStat', aria2globalStat));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Aria2cModelStateImpl &&
(identical(other.aria2cDir, aria2cDir) ||
other.aria2cDir == aria2cDir) &&
(identical(other.aria2c, aria2c) || other.aria2c == aria2c) &&
(identical(other.aria2globalStat, aria2globalStat) ||
other.aria2globalStat == aria2globalStat));
}
@override
int get hashCode =>
Object.hash(runtimeType, aria2cDir, aria2c, aria2globalStat);
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$Aria2cModelStateImplCopyWith<_$Aria2cModelStateImpl> get copyWith =>
__$$Aria2cModelStateImplCopyWithImpl<_$Aria2cModelStateImpl>(
this, _$identity);
}
abstract class _Aria2cModelState implements Aria2cModelState {
const factory _Aria2cModelState(
{required final String aria2cDir,
final Aria2c? aria2c,
final Aria2GlobalStat? aria2globalStat}) = _$Aria2cModelStateImpl;
@override
String get aria2cDir;
@override
Aria2c? get aria2c;
@override
Aria2GlobalStat? get aria2globalStat;
/// Create a copy of Aria2cModelState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$Aria2cModelStateImplCopyWith<_$Aria2cModelStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'aria2c.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$aria2cModelHash() => r'55dea5bd2e1c81fec0ef8ef1a10f41179775d7ee';
/// See also [Aria2cModel].
@ProviderFor(Aria2cModel)
final aria2cModelProvider =
AutoDisposeNotifierProvider<Aria2cModel, Aria2cModelState>.internal(
Aria2cModel.new,
name: r'aria2cModelProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$aria2cModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$Aria2cModel = AutoDisposeNotifier<Aria2cModelState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,278 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:file/memory.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
import 'package:starcitizen_doctor/common/rust/api/rs_process.dart'
as rs_process;
part 'unp4kc.freezed.dart';
part 'unp4kc.g.dart';
@freezed
class Unp4kcState with _$Unp4kcState {
const factory Unp4kcState({
required bool startUp,
Map<String, AppUnp4kP4kItemData>? files,
MemoryFileSystem? fs,
required String curPath,
String? endMessage,
MapEntry<String, String>? tempOpenFile,
@Default("") String errorMessage,
}) = _Unp4kcState;
}
@riverpod
class Unp4kCModel extends _$Unp4kCModel {
int? _rsPid;
@override
Unp4kcState build() {
state = Unp4kcState(
startUp: false,
curPath: '\\',
endMessage: S.current.tools_unp4k_msg_init);
_init();
return state;
}
ToolsUIState get _toolsState => ref.read(toolsUIModelProvider);
String getGamePath() => _toolsState.scInstalledPath;
bool _hasUnp4kRunTimeError = false;
void _init() async {
final execDir = "${appGlobalState.applicationBinaryModuleDir}\\unp4kc";
await BinaryModuleConf.extractModule(
["unp4kc"], appGlobalState.applicationBinaryModuleDir!);
final exec = "$execDir\\unp4kc.exe";
final stream = rs_process.start(
executable: exec, arguments: [], workingDirectory: execDir);
stream.listen((event) async {
switch (event.dataType) {
case RsProcessStreamDataType.output:
_rsPid = event.rsPid;
try {
final eventJson = await compute(json.decode, event.data);
_handleMessage(eventJson, event.rsPid);
} catch (e) {
dPrint("[unp4kc] json error: $e");
}
break;
case RsProcessStreamDataType.error:
dPrint("[unp4kc] stderr: ${event.data}");
if (state.errorMessage.isEmpty) {
state = state.copyWith(errorMessage: event.data);
} else {
state = state.copyWith(
errorMessage: "${state.errorMessage}\n${event.data}");
}
if (!_hasUnp4kRunTimeError) {
if (checkRunTimeError(state.errorMessage)) {
_hasUnp4kRunTimeError = true;
AnalyticsApi.touch("unp4k_no_runtime");
}
}
break;
case RsProcessStreamDataType.exit:
dPrint("[unp4kc] exit: ${event.data}");
break;
}
});
ref.onDispose(() {
state = state.copyWith(fs: null);
if (_rsPid != null) {
Process.killPid(_rsPid!);
dPrint("[unp4kc] kill ...");
}
});
}
DateTime? _loadStartTime;
void _handleMessage(Map<String, dynamic> eventJson, int rsPid) async {
final action = eventJson["action"];
final data = eventJson["data"];
final gamePath = getGamePath();
final gameP4kPath = "$gamePath\\Data.p4k";
switch (action.toString().trim()) {
case "info: startup":
rs_process.write(rsPid: rsPid, data: "$gameP4kPath\n");
break;
case "info: Reading_p4k_file":
_loadStartTime = DateTime.now();
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading);
break;
case "info: All Ready":
state = state.copyWith(endMessage: S.current.tools_unp4k_msg_reading2);
break;
case "data: P4K_Files":
final p4kFiles = (data as List<dynamic>);
final files = <String, AppUnp4kP4kItemData>{};
final fs = MemoryFileSystem(style: FileSystemStyle.posix);
var nextAwait = 0;
for (var i = 0; i < p4kFiles.length; i++) {
final item = AppUnp4kP4kItemData.fromJson(p4kFiles[i]);
item.name = "${item.name}";
files["\\${item.name}"] = item;
await fs
.file(item.name?.replaceAll("\\", "/") ?? "")
.create(recursive: true);
if (i == nextAwait) {
state = state.copyWith(
endMessage:
S.current.tools_unp4k_msg_reading3(i, p4kFiles.length));
await Future.delayed(Duration.zero);
nextAwait += 20000;
}
}
final endTime = DateTime.now();
state = state.copyWith(
files: files,
fs: fs,
endMessage: S.current.tools_unp4k_msg_read_completed(files.length,
endTime.difference(_loadStartTime!).inMilliseconds));
_loadStartTime = null;
break;
case "info: Extracted_Open":
final filePath = data.toString();
dPrint("[unp4kc] Extracted_Open file: $filePath");
const textExt = [".txt", ".xml", ".json", ".lua", ".cfg", ".ini"];
const imgExt = [".png"];
String openType = "unknown";
for (var element in textExt) {
if (filePath.toLowerCase().endsWith(element)) {
openType = "text";
}
}
for (var element in imgExt) {
if (filePath.endsWith(element)) {
openType = "image";
}
}
state = state.copyWith(
tempOpenFile: MapEntry(openType, filePath),
endMessage: S.current.tools_unp4k_msg_open_file(filePath));
break;
default:
dPrint("[unp4kc] unknown action: $action");
break;
}
}
List<AppUnp4kP4kItemData>? getFiles() {
final path = state.curPath.replaceAll("\\", "/");
final fs = state.fs;
if (fs == null) return null;
final dir = fs.directory(path);
if (!dir.existsSync()) return [];
final files = dir.listSync(recursive: false, followLinks: false);
files.sort((a, b) {
if (a is Directory && b is File) {
return -1;
} else if (a is File && b is Directory) {
return 1;
} else {
return a.path.compareTo(b.path);
}
});
final result = <AppUnp4kP4kItemData>[];
for (var file in files) {
if (file is File) {
final f = state.files?[file.path.replaceAll("/", "\\")];
if (f != null) {
if (!(f.name?.startsWith("\\") ?? true)) {
f.name = "\\${f.name}";
}
result.add(f);
}
} else {
result.add(AppUnp4kP4kItemData(
name: file.path.replaceAll("/", "\\"), isDirectory: true));
}
}
return result;
}
void changeDir(String name, {bool fullPath = false}) {
if (fullPath) {
state = state.copyWith(curPath: name);
} else {
state = state.copyWith(curPath: "${state.curPath}$name\\");
}
}
openFile(String filePath) async {
final tempDir = await getTemporaryDirectory();
final tempPath =
"${tempDir.absolute.path}\\SCToolbox_unp4kc\\${SCLoggerHelper.getGameChannelID(getGamePath())}\\";
state = state.copyWith(
tempOpenFile: const MapEntry("loading", ""),
endMessage: S.current.tools_unp4k_msg_open_file(filePath));
extractFile(filePath, tempPath, mode: "extract_open");
}
extractFile(String filePath, String outputPath,
{String mode = "extract"}) async {
// remove first \\
if (filePath.startsWith("\\")) {
filePath = filePath.substring(1);
}
outputPath = "$outputPath$filePath";
dPrint("extractFile .... $filePath");
if (_rsPid != null) {
rs_process.write(
rsPid: _rsPid!, data: "$mode<:,:>$filePath<:,:>$outputPath\n");
}
}
static bool checkRunTimeError(String errorMessage) {
if (errorMessage
.contains("You must install .NET to run this application") ||
errorMessage.contains(
"You must install or update .NET to run this application") ||
errorMessage.contains(
"It was not possible to find any compatible framework version")) {
AnalyticsApi.touch("unp4k_no_runtime");
return true;
}
return false;
}
static Future<Uint8List> unp4kTools(
String applicationBinaryModuleDir, List<String> args) async {
await BinaryModuleConf.extractModule(
["unp4kc"], applicationBinaryModuleDir);
final execDir = "$applicationBinaryModuleDir\\unp4kc";
final exec = "$execDir\\unp4kc.exe";
final r = await Process.run(exec, args);
if (r.exitCode != 0) {
Process.killPid(r.pid);
throw Exception(
"error: ${r.exitCode} , info= ${r.stdout} , err= ${r.stderr}");
}
final eventJson = await compute(json.decode, r.stdout.toString());
if (eventJson["action"] == "data: Uint8List") {
final data = eventJson["data"];
return Uint8List.fromList((data as List).cast<int>());
}
throw Exception("error: data error");
}
}

View File

@ -1,304 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'unp4kc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$Unp4kcState {
bool get startUp => throw _privateConstructorUsedError;
Map<String, AppUnp4kP4kItemData>? get files =>
throw _privateConstructorUsedError;
MemoryFileSystem? get fs => throw _privateConstructorUsedError;
String get curPath => throw _privateConstructorUsedError;
String? get endMessage => throw _privateConstructorUsedError;
MapEntry<String, String>? get tempOpenFile =>
throw _privateConstructorUsedError;
String get errorMessage => throw _privateConstructorUsedError;
/// Create a copy of Unp4kcState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$Unp4kcStateCopyWith<Unp4kcState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $Unp4kcStateCopyWith<$Res> {
factory $Unp4kcStateCopyWith(
Unp4kcState value, $Res Function(Unp4kcState) then) =
_$Unp4kcStateCopyWithImpl<$Res, Unp4kcState>;
@useResult
$Res call(
{bool startUp,
Map<String, AppUnp4kP4kItemData>? files,
MemoryFileSystem? fs,
String curPath,
String? endMessage,
MapEntry<String, String>? tempOpenFile,
String errorMessage});
}
/// @nodoc
class _$Unp4kcStateCopyWithImpl<$Res, $Val extends Unp4kcState>
implements $Unp4kcStateCopyWith<$Res> {
_$Unp4kcStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Unp4kcState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startUp = null,
Object? files = freezed,
Object? fs = freezed,
Object? curPath = null,
Object? endMessage = freezed,
Object? tempOpenFile = freezed,
Object? errorMessage = null,
}) {
return _then(_value.copyWith(
startUp: null == startUp
? _value.startUp
: startUp // ignore: cast_nullable_to_non_nullable
as bool,
files: freezed == files
? _value.files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, AppUnp4kP4kItemData>?,
fs: freezed == fs
? _value.fs
: fs // ignore: cast_nullable_to_non_nullable
as MemoryFileSystem?,
curPath: null == curPath
? _value.curPath
: curPath // ignore: cast_nullable_to_non_nullable
as String,
endMessage: freezed == endMessage
? _value.endMessage
: endMessage // ignore: cast_nullable_to_non_nullable
as String?,
tempOpenFile: freezed == tempOpenFile
? _value.tempOpenFile
: tempOpenFile // ignore: cast_nullable_to_non_nullable
as MapEntry<String, String>?,
errorMessage: null == errorMessage
? _value.errorMessage
: errorMessage // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$Unp4kcStateImplCopyWith<$Res>
implements $Unp4kcStateCopyWith<$Res> {
factory _$$Unp4kcStateImplCopyWith(
_$Unp4kcStateImpl value, $Res Function(_$Unp4kcStateImpl) then) =
__$$Unp4kcStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool startUp,
Map<String, AppUnp4kP4kItemData>? files,
MemoryFileSystem? fs,
String curPath,
String? endMessage,
MapEntry<String, String>? tempOpenFile,
String errorMessage});
}
/// @nodoc
class __$$Unp4kcStateImplCopyWithImpl<$Res>
extends _$Unp4kcStateCopyWithImpl<$Res, _$Unp4kcStateImpl>
implements _$$Unp4kcStateImplCopyWith<$Res> {
__$$Unp4kcStateImplCopyWithImpl(
_$Unp4kcStateImpl _value, $Res Function(_$Unp4kcStateImpl) _then)
: super(_value, _then);
/// Create a copy of Unp4kcState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startUp = null,
Object? files = freezed,
Object? fs = freezed,
Object? curPath = null,
Object? endMessage = freezed,
Object? tempOpenFile = freezed,
Object? errorMessage = null,
}) {
return _then(_$Unp4kcStateImpl(
startUp: null == startUp
? _value.startUp
: startUp // ignore: cast_nullable_to_non_nullable
as bool,
files: freezed == files
? _value._files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, AppUnp4kP4kItemData>?,
fs: freezed == fs
? _value.fs
: fs // ignore: cast_nullable_to_non_nullable
as MemoryFileSystem?,
curPath: null == curPath
? _value.curPath
: curPath // ignore: cast_nullable_to_non_nullable
as String,
endMessage: freezed == endMessage
? _value.endMessage
: endMessage // ignore: cast_nullable_to_non_nullable
as String?,
tempOpenFile: freezed == tempOpenFile
? _value.tempOpenFile
: tempOpenFile // ignore: cast_nullable_to_non_nullable
as MapEntry<String, String>?,
errorMessage: null == errorMessage
? _value.errorMessage
: errorMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$Unp4kcStateImpl with DiagnosticableTreeMixin implements _Unp4kcState {
const _$Unp4kcStateImpl(
{required this.startUp,
final Map<String, AppUnp4kP4kItemData>? files,
this.fs,
required this.curPath,
this.endMessage,
this.tempOpenFile,
this.errorMessage = ""})
: _files = files;
@override
final bool startUp;
final Map<String, AppUnp4kP4kItemData>? _files;
@override
Map<String, AppUnp4kP4kItemData>? get files {
final value = _files;
if (value == null) return null;
if (_files is EqualUnmodifiableMapView) return _files;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
final MemoryFileSystem? fs;
@override
final String curPath;
@override
final String? endMessage;
@override
final MapEntry<String, String>? tempOpenFile;
@override
@JsonKey()
final String errorMessage;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Unp4kcState(startUp: $startUp, files: $files, fs: $fs, curPath: $curPath, endMessage: $endMessage, tempOpenFile: $tempOpenFile, errorMessage: $errorMessage)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Unp4kcState'))
..add(DiagnosticsProperty('startUp', startUp))
..add(DiagnosticsProperty('files', files))
..add(DiagnosticsProperty('fs', fs))
..add(DiagnosticsProperty('curPath', curPath))
..add(DiagnosticsProperty('endMessage', endMessage))
..add(DiagnosticsProperty('tempOpenFile', tempOpenFile))
..add(DiagnosticsProperty('errorMessage', errorMessage));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Unp4kcStateImpl &&
(identical(other.startUp, startUp) || other.startUp == startUp) &&
const DeepCollectionEquality().equals(other._files, _files) &&
(identical(other.fs, fs) || other.fs == fs) &&
(identical(other.curPath, curPath) || other.curPath == curPath) &&
(identical(other.endMessage, endMessage) ||
other.endMessage == endMessage) &&
(identical(other.tempOpenFile, tempOpenFile) ||
other.tempOpenFile == tempOpenFile) &&
(identical(other.errorMessage, errorMessage) ||
other.errorMessage == errorMessage));
}
@override
int get hashCode => Object.hash(
runtimeType,
startUp,
const DeepCollectionEquality().hash(_files),
fs,
curPath,
endMessage,
tempOpenFile,
errorMessage);
/// Create a copy of Unp4kcState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$Unp4kcStateImplCopyWith<_$Unp4kcStateImpl> get copyWith =>
__$$Unp4kcStateImplCopyWithImpl<_$Unp4kcStateImpl>(this, _$identity);
}
abstract class _Unp4kcState implements Unp4kcState {
const factory _Unp4kcState(
{required final bool startUp,
final Map<String, AppUnp4kP4kItemData>? files,
final MemoryFileSystem? fs,
required final String curPath,
final String? endMessage,
final MapEntry<String, String>? tempOpenFile,
final String errorMessage}) = _$Unp4kcStateImpl;
@override
bool get startUp;
@override
Map<String, AppUnp4kP4kItemData>? get files;
@override
MemoryFileSystem? get fs;
@override
String get curPath;
@override
String? get endMessage;
@override
MapEntry<String, String>? get tempOpenFile;
@override
String get errorMessage;
/// Create a copy of Unp4kcState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$Unp4kcStateImplCopyWith<_$Unp4kcStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'unp4kc.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$unp4kCModelHash() => r'636da3fe20d1fa94917dd63934f08f8dbffc8a9d';
/// See also [Unp4kCModel].
@ProviderFor(Unp4kCModel)
final unp4kCModelProvider =
AutoDisposeNotifierProvider<Unp4kCModel, Unp4kcState>.internal(
Unp4kCModel.new,
name: r'unp4kCModelProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$unp4kCModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$Unp4kCModel = AutoDisposeNotifier<Unp4kcState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,121 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'home_game_login_dialog_ui_model.dart';
class HomeGameLoginDialogUI extends HookConsumerWidget {
final BuildContext launchContext;
const HomeGameLoginDialogUI(this.launchContext, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final loginState = ref.watch(homeGameLoginUIModelProvider);
useEffect(() {
ref
.read(homeGameLoginUIModelProvider.notifier)
.launchWebLogin(launchContext);
return null;
}, []);
return ContentDialog(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .56,
),
title: (loginState.loginStatus == 2)
? null
: Text(S.current.home_action_one_click_launch),
content: AnimatedSize(
duration: const Duration(milliseconds: 230),
child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Row(),
if (loginState.loginStatus == 0) ...[
Center(
child: Column(
children: [
Text(S.current.home_title_logging_in),
const SizedBox(height: 12),
const ProgressRing(),
],
),
),
] else if (loginState.loginStatus == 2 ||
loginState.loginStatus == 3) ...[
Center(
child: Column(
children: [
const SizedBox(height: 12),
Text(
S.current.home_login_title_welcome_back,
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 24),
if (loginState.avatarUrl != null)
ClipRRect(
borderRadius: BorderRadius.circular(1000),
child: CacheNetImage(
url: loginState.avatarUrl!,
width: 128,
height: 128,
fit: BoxFit.fill,
),
),
const SizedBox(height: 12),
Text(
loginState.nickname ?? "",
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
if (loginState.libraryData?.games != null) ...[
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (final game in loginState.libraryData!.games!)
Padding(
padding: const EdgeInsets.only(
left: 12, right: 12),
child: Row(
children: [
Icon(
FluentIcons.skype_circle_check,
color: Colors.green,
),
const SizedBox(width: 6),
Text("${game.name}"),
],
),
)
],
),
),
const SizedBox(height: 24)
],
const SizedBox(height: 12),
Text(S.current.home_login_title_launching_game),
const SizedBox(height: 12),
const ProgressRing(),
const SizedBox(height: 12),
],
),
)
]
],
),
),
),
);
}
}

View File

@ -1,227 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:jwt_decode/jwt_decode.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/rsi_game_library_data.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/ui/webview/webview.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:uuid/uuid.dart';
part 'home_game_login_dialog_ui_model.freezed.dart';
part 'home_game_login_dialog_ui_model.g.dart';
@freezed
class HomeGameLoginState with _$HomeGameLoginState {
factory HomeGameLoginState({
required int loginStatus,
String? nickname,
String? avatarUrl,
String? authToken,
String? webToken,
Map? releaseInfo,
RsiGameLibraryData? libraryData,
String? installPath,
bool? isDeviceSupportWinHello,
}) = _LoginStatus;
}
@riverpod
class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
@override
HomeGameLoginState build() {
return HomeGameLoginState(loginStatus: 0);
}
// ignore: avoid_build_context_in_providers
Future<void> launchWebLogin(BuildContext context) async {
final homeState = ref.read(homeUIModelProvider);
if (!context.mounted) return;
goWebView(context, S.current.home_action_login_rsi_account,
"https://robertsspaceindustries.com/connect?jumpto=/connect",
loginMode: true, rsiLoginCallback: (message, ok) async {
// dPrint(
// "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
if (message == null || !ok) {
Navigator.pop(context);
return;
}
// final emailBox = await Hive.openBox("quick_login_email");
final data = message["data"];
final authToken = data["authToken"];
final webToken = data["webToken"];
final releaseInfo = data["releaseInfo"];
final libraryData = RsiGameLibraryData.fromJson(data["libraryData"]);
final avatarUrl = data["avatar"]
?.toString()
.replaceAll("url(\"", "")
.replaceAll("\")", "");
final Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
final nickname = payload["nickname"] ?? "";
state = state.copyWith(
nickname: nickname,
avatarUrl: avatarUrl,
authToken: authToken,
webToken: webToken,
releaseInfo: releaseInfo,
libraryData: libraryData,
);
final buildInfoFile =
File("${homeState.scInstalledPath}\\build_manifest.id");
if (await buildInfoFile.exists()) {
final buildInfo =
json.decode(await buildInfoFile.readAsString())["Data"];
if (releaseInfo?["versionLabel"] != null &&
buildInfo["RequestedP4ChangeNum"] != null) {
if (!(releaseInfo!["versionLabel"]!
.toString()
.endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.home_login_info_game_version_outdated,
Text(S.current.home_login_info_rsi_server_report(
releaseInfo?["versionLabel"],
buildInfo["RequestedP4ChangeNum"])),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .4),
cancel: S.current.home_login_info_action_ignore);
if (ok == true) {
if (!context.mounted) return;
Navigator.pop(context);
return;
}
}
}
}
if (!context.mounted) return;
_readyForLaunch(homeState, context);
}, useLocalization: true, homeState: homeState);
}
// ignore: avoid_build_context_in_providers
goWebView(BuildContext context, String title, String url,
{bool useLocalization = false,
bool loginMode = false,
RsiLoginCallback? rsiLoginCallback,
required HomeUIModelState homeState}) async {
if (useLocalization) {
const tipVersion = 2;
final box = await Hive.openBox("app_conf");
final skip = await box.get("skip_web_login_version", defaultValue: 0);
if (skip != tipVersion) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.home_login_action_title_box_one_click_launch,
Text(
S.current.home_login_info_one_click_launch_description,
style: const TextStyle(fontSize: 16),
),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .6));
if (!ok) {
if (loginMode) {
rsiLoginCallback?.call(null, false);
}
return;
}
await box.put("skip_web_login_version", tipVersion);
}
}
if (!await WebviewWindow.isWebviewAvailable()) {
if (!context.mounted) return;
await showToast(
context, S.current.home_login_action_title_need_webview2_runtime);
if (!context.mounted) return;
await launchUrlString(
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
if (!context.mounted) return;
Navigator.pop(context);
return;
}
if (!context.mounted) return;
final webViewModel = WebViewModel(context,
loginMode: loginMode,
loginCallback: rsiLoginCallback,
loginChannel: getChannelID(homeState.scInstalledPath!));
if (useLocalization) {
try {
await webViewModel
.initLocalization(homeState.webLocalizationVersionsData!);
} catch (_) {}
}
await Future.delayed(const Duration(milliseconds: 500));
await webViewModel.initWebView(
title: title,
applicationSupportDir: appGlobalState.applicationSupportDir!,
appVersionData: appGlobalState.networkVersionData!,
);
await webViewModel.launch(url, appGlobalState.networkVersionData!);
}
Future<void> _readyForLaunch(
HomeUIModelState homeState,
// ignore: avoid_build_context_in_providers
BuildContext context) async {
final userBox = await Hive.openBox("rsi_account_data");
state = state.copyWith(loginStatus: 2);
final launchData = {
"username": userBox.get("account_email", defaultValue: ""),
"token": state.webToken,
"auth_token": state.authToken,
"star_network": {
"services_endpoint": state.releaseInfo?["servicesEndpoint"],
"hostname": state.releaseInfo?["universeHost"],
"port": state.releaseInfo?["universePort"],
},
"TMid": const Uuid().v4(),
};
final executable = state.releaseInfo?["executable"];
final launchOptions = state.releaseInfo?["launchOptions"];
// dPrint("----------launch data ====== -----------\n$launchData");
// dPrint(
// "----------executable data ====== -----------\n${homeState.scInstalledPath}\\$executable $launchOptions");
final launchFile = File("${homeState.scInstalledPath}\\loginData.json");
if (await launchFile.exists()) {
await launchFile.delete();
}
await launchFile.create();
await launchFile.writeAsString(json.encode(launchData));
final processorAffinity = await SystemHelper.getCpuAffinity();
final homeUIModel = ref.read(homeUIModelProvider.notifier);
if (!context.mounted) return;
homeUIModel.doLaunchGame(
context,
'${homeState.scInstalledPath}\\$executable',
["-no_login_dialog", ...launchOptions.toString().split(" ")],
homeState.scInstalledPath!,
processorAffinity);
await Future.delayed(const Duration(seconds: 1));
if (!context.mounted) return;
Navigator.pop(context);
}
String getChannelID(String installPath) {
if (installPath.endsWith("\\LIVE")) {
return "LIVE";
}
return "PTU";
}
}

View File

@ -1,336 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'home_game_login_dialog_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$HomeGameLoginState {
int get loginStatus => throw _privateConstructorUsedError;
String? get nickname => throw _privateConstructorUsedError;
String? get avatarUrl => throw _privateConstructorUsedError;
String? get authToken => throw _privateConstructorUsedError;
String? get webToken => throw _privateConstructorUsedError;
Map<dynamic, dynamic>? get releaseInfo => throw _privateConstructorUsedError;
RsiGameLibraryData? get libraryData => throw _privateConstructorUsedError;
String? get installPath => throw _privateConstructorUsedError;
bool? get isDeviceSupportWinHello => throw _privateConstructorUsedError;
/// Create a copy of HomeGameLoginState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HomeGameLoginStateCopyWith<HomeGameLoginState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HomeGameLoginStateCopyWith<$Res> {
factory $HomeGameLoginStateCopyWith(
HomeGameLoginState value, $Res Function(HomeGameLoginState) then) =
_$HomeGameLoginStateCopyWithImpl<$Res, HomeGameLoginState>;
@useResult
$Res call(
{int loginStatus,
String? nickname,
String? avatarUrl,
String? authToken,
String? webToken,
Map<dynamic, dynamic>? releaseInfo,
RsiGameLibraryData? libraryData,
String? installPath,
bool? isDeviceSupportWinHello});
}
/// @nodoc
class _$HomeGameLoginStateCopyWithImpl<$Res, $Val extends HomeGameLoginState>
implements $HomeGameLoginStateCopyWith<$Res> {
_$HomeGameLoginStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of HomeGameLoginState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? loginStatus = null,
Object? nickname = freezed,
Object? avatarUrl = freezed,
Object? authToken = freezed,
Object? webToken = freezed,
Object? releaseInfo = freezed,
Object? libraryData = freezed,
Object? installPath = freezed,
Object? isDeviceSupportWinHello = freezed,
}) {
return _then(_value.copyWith(
loginStatus: null == loginStatus
? _value.loginStatus
: loginStatus // ignore: cast_nullable_to_non_nullable
as int,
nickname: freezed == nickname
? _value.nickname
: nickname // ignore: cast_nullable_to_non_nullable
as String?,
avatarUrl: freezed == avatarUrl
? _value.avatarUrl
: avatarUrl // ignore: cast_nullable_to_non_nullable
as String?,
authToken: freezed == authToken
? _value.authToken
: authToken // ignore: cast_nullable_to_non_nullable
as String?,
webToken: freezed == webToken
? _value.webToken
: webToken // ignore: cast_nullable_to_non_nullable
as String?,
releaseInfo: freezed == releaseInfo
? _value.releaseInfo
: releaseInfo // ignore: cast_nullable_to_non_nullable
as Map<dynamic, dynamic>?,
libraryData: freezed == libraryData
? _value.libraryData
: libraryData // ignore: cast_nullable_to_non_nullable
as RsiGameLibraryData?,
installPath: freezed == installPath
? _value.installPath
: installPath // ignore: cast_nullable_to_non_nullable
as String?,
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
? _value.isDeviceSupportWinHello
: isDeviceSupportWinHello // ignore: cast_nullable_to_non_nullable
as bool?,
) as $Val);
}
}
/// @nodoc
abstract class _$$LoginStatusImplCopyWith<$Res>
implements $HomeGameLoginStateCopyWith<$Res> {
factory _$$LoginStatusImplCopyWith(
_$LoginStatusImpl value, $Res Function(_$LoginStatusImpl) then) =
__$$LoginStatusImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int loginStatus,
String? nickname,
String? avatarUrl,
String? authToken,
String? webToken,
Map<dynamic, dynamic>? releaseInfo,
RsiGameLibraryData? libraryData,
String? installPath,
bool? isDeviceSupportWinHello});
}
/// @nodoc
class __$$LoginStatusImplCopyWithImpl<$Res>
extends _$HomeGameLoginStateCopyWithImpl<$Res, _$LoginStatusImpl>
implements _$$LoginStatusImplCopyWith<$Res> {
__$$LoginStatusImplCopyWithImpl(
_$LoginStatusImpl _value, $Res Function(_$LoginStatusImpl) _then)
: super(_value, _then);
/// Create a copy of HomeGameLoginState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? loginStatus = null,
Object? nickname = freezed,
Object? avatarUrl = freezed,
Object? authToken = freezed,
Object? webToken = freezed,
Object? releaseInfo = freezed,
Object? libraryData = freezed,
Object? installPath = freezed,
Object? isDeviceSupportWinHello = freezed,
}) {
return _then(_$LoginStatusImpl(
loginStatus: null == loginStatus
? _value.loginStatus
: loginStatus // ignore: cast_nullable_to_non_nullable
as int,
nickname: freezed == nickname
? _value.nickname
: nickname // ignore: cast_nullable_to_non_nullable
as String?,
avatarUrl: freezed == avatarUrl
? _value.avatarUrl
: avatarUrl // ignore: cast_nullable_to_non_nullable
as String?,
authToken: freezed == authToken
? _value.authToken
: authToken // ignore: cast_nullable_to_non_nullable
as String?,
webToken: freezed == webToken
? _value.webToken
: webToken // ignore: cast_nullable_to_non_nullable
as String?,
releaseInfo: freezed == releaseInfo
? _value._releaseInfo
: releaseInfo // ignore: cast_nullable_to_non_nullable
as Map<dynamic, dynamic>?,
libraryData: freezed == libraryData
? _value.libraryData
: libraryData // ignore: cast_nullable_to_non_nullable
as RsiGameLibraryData?,
installPath: freezed == installPath
? _value.installPath
: installPath // ignore: cast_nullable_to_non_nullable
as String?,
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
? _value.isDeviceSupportWinHello
: isDeviceSupportWinHello // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// @nodoc
class _$LoginStatusImpl implements _LoginStatus {
_$LoginStatusImpl(
{required this.loginStatus,
this.nickname,
this.avatarUrl,
this.authToken,
this.webToken,
final Map<dynamic, dynamic>? releaseInfo,
this.libraryData,
this.installPath,
this.isDeviceSupportWinHello})
: _releaseInfo = releaseInfo;
@override
final int loginStatus;
@override
final String? nickname;
@override
final String? avatarUrl;
@override
final String? authToken;
@override
final String? webToken;
final Map<dynamic, dynamic>? _releaseInfo;
@override
Map<dynamic, dynamic>? get releaseInfo {
final value = _releaseInfo;
if (value == null) return null;
if (_releaseInfo is EqualUnmodifiableMapView) return _releaseInfo;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
final RsiGameLibraryData? libraryData;
@override
final String? installPath;
@override
final bool? isDeviceSupportWinHello;
@override
String toString() {
return 'HomeGameLoginState(loginStatus: $loginStatus, nickname: $nickname, avatarUrl: $avatarUrl, authToken: $authToken, webToken: $webToken, releaseInfo: $releaseInfo, libraryData: $libraryData, installPath: $installPath, isDeviceSupportWinHello: $isDeviceSupportWinHello)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoginStatusImpl &&
(identical(other.loginStatus, loginStatus) ||
other.loginStatus == loginStatus) &&
(identical(other.nickname, nickname) ||
other.nickname == nickname) &&
(identical(other.avatarUrl, avatarUrl) ||
other.avatarUrl == avatarUrl) &&
(identical(other.authToken, authToken) ||
other.authToken == authToken) &&
(identical(other.webToken, webToken) ||
other.webToken == webToken) &&
const DeepCollectionEquality()
.equals(other._releaseInfo, _releaseInfo) &&
(identical(other.libraryData, libraryData) ||
other.libraryData == libraryData) &&
(identical(other.installPath, installPath) ||
other.installPath == installPath) &&
(identical(
other.isDeviceSupportWinHello, isDeviceSupportWinHello) ||
other.isDeviceSupportWinHello == isDeviceSupportWinHello));
}
@override
int get hashCode => Object.hash(
runtimeType,
loginStatus,
nickname,
avatarUrl,
authToken,
webToken,
const DeepCollectionEquality().hash(_releaseInfo),
libraryData,
installPath,
isDeviceSupportWinHello);
/// Create a copy of HomeGameLoginState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoginStatusImplCopyWith<_$LoginStatusImpl> get copyWith =>
__$$LoginStatusImplCopyWithImpl<_$LoginStatusImpl>(this, _$identity);
}
abstract class _LoginStatus implements HomeGameLoginState {
factory _LoginStatus(
{required final int loginStatus,
final String? nickname,
final String? avatarUrl,
final String? authToken,
final String? webToken,
final Map<dynamic, dynamic>? releaseInfo,
final RsiGameLibraryData? libraryData,
final String? installPath,
final bool? isDeviceSupportWinHello}) = _$LoginStatusImpl;
@override
int get loginStatus;
@override
String? get nickname;
@override
String? get avatarUrl;
@override
String? get authToken;
@override
String? get webToken;
@override
Map<dynamic, dynamic>? get releaseInfo;
@override
RsiGameLibraryData? get libraryData;
@override
String? get installPath;
@override
bool? get isDeviceSupportWinHello;
/// Create a copy of HomeGameLoginState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoginStatusImplCopyWith<_$LoginStatusImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'home_game_login_dialog_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$homeGameLoginUIModelHash() =>
r'e8afccb7bba7c79e766e30a27f64128918a63dd7';
/// See also [HomeGameLoginUIModel].
@ProviderFor(HomeGameLoginUIModel)
final homeGameLoginUIModelProvider = AutoDisposeNotifierProvider<
HomeGameLoginUIModel, HomeGameLoginState>.internal(
HomeGameLoginUIModel.new,
name: r'homeGameLoginUIModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$homeGameLoginUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HomeGameLoginUIModel = AutoDisposeNotifier<HomeGameLoginState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,260 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:file_sizes/file_sizes.dart';
import 'home_downloader_ui_model.dart';
class HomeDownloaderUI extends HookConsumerWidget {
const HomeDownloaderUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(homeDownloaderUIModelProvider);
final model = ref.read(homeDownloaderUIModelProvider.notifier);
return makeDefaultPage(context,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Row(
children: [
const Spacer(),
const SizedBox(width: 24),
const SizedBox(width: 12),
for (final item in <MapEntry<String, IconData>, String>{
const MapEntry("settings", FluentIcons.settings):
S.current.downloader_speed_limit_settings,
if (state.tasks.isNotEmpty)
const MapEntry("pause_all", FluentIcons.pause):
S.current.downloader_action_pause_all,
if (state.waitingTasks.isNotEmpty)
const MapEntry("resume_all", FluentIcons.download):
S.current.downloader_action_resume_all,
if (state.tasks.isNotEmpty || state.waitingTasks.isNotEmpty)
const MapEntry("cancel_all", FluentIcons.cancel):
S.current.downloader_action_cancel_all,
}.entries)
Padding(
padding: const EdgeInsets.only(left: 6, right: 6),
child: Button(
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
Icon(item.key.value),
const SizedBox(width: 6),
Text(item.value),
],
),
),
onPressed: () =>
model.onTapButton(context, item.key.key)),
),
const SizedBox(width: 12),
],
),
if (model.getTasksLen() == 0)
Expanded(
child: Center(
child: Text(S.current.downloader_info_no_download_tasks),
))
else
Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
final (task, type, isFirstType) = model.getTaskAndType(index);
final nt = HomeDownloaderUIModel.getTaskTypeAndName(task);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isFirstType)
Column(
children: [
Container(
padding: EdgeInsets.only(
left: 24,
right: 24,
top: index == 0 ? 0 : 12,
bottom: 12),
margin: const EdgeInsets.only(top: 6, bottom: 6),
child: Row(
children: [
Expanded(
child: Row(
children: [
Text(
"${model.listHeaderStatusMap[type]}",
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold),
),
],
)),
],
),
),
],
),
Container(
padding: const EdgeInsets.only(
left: 12, right: 12, top: 12, bottom: 12),
margin: const EdgeInsets.only(
left: 12, right: 12, top: 6, bottom: 6),
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withOpacity(.06),
borderRadius: BorderRadius.circular(7),
),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nt.value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 6),
Row(
children: [
Text(
S.current.downloader_info_total_size(
FileSize.getSize(
task.totalLength ?? 0)),
style: const TextStyle(fontSize: 14),
),
const SizedBox(width: 12),
if (nt.key == "torrent" &&
task.verifiedLength != null &&
task.verifiedLength != 0)
Text(
S.current.downloader_info_verifying(
FileSize.getSize(
task.verifiedLength)),
style: const TextStyle(fontSize: 14),
)
else if (task.status == "active")
Text(S.current
.downloader_info_downloading(
((task.completedLength ?? 0) *
100 /
(task.totalLength ?? 1))
.toStringAsFixed(4)))
else
Text(S.current.downloader_info_status(
model.statusMap[task.status] ??
"Unknown")),
const SizedBox(width: 24),
if (task.status == "active" &&
task.verifiedLength == null)
Text(
"ETA: ${model.formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}"),
],
),
],
),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current.downloader_info_uploaded(
FileSize.getSize(task.uploadLength))),
Text(S.current.downloader_info_downloaded(
FileSize.getSize(task.completedLength))),
],
),
const SizedBox(width: 18),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"↑:${FileSize.getSize(task.uploadSpeed)}/s"),
Text(
"↓:${FileSize.getSize(task.downloadSpeed)}/s"),
],
),
const SizedBox(width: 32),
if (type != "stopped")
DropDownButton(
closeAfterClick: false,
title: Padding(
padding: const EdgeInsets.all(3),
child:
Text(S.current.downloader_action_options),
),
items: [
if (task.status == "paused")
MenuFlyoutItem(
leading:
const Icon(FluentIcons.download),
text: Text(S.current
.downloader_action_continue_download),
onPressed: () =>
model.resumeTask(task.gid))
else if (task.status == "active")
MenuFlyoutItem(
leading: const Icon(FluentIcons.pause),
text: Text(S.current
.downloader_action_pause_download),
onPressed: () =>
model.pauseTask(task.gid)),
const MenuFlyoutSeparator(),
MenuFlyoutItem(
leading: const Icon(
FluentIcons.chrome_close,
size: 14,
),
text: Text(S.current
.downloader_action_cancel_download),
onPressed: () =>
model.cancelTask(context, task.gid)),
MenuFlyoutItem(
leading: const Icon(
FluentIcons.folder_open,
size: 14,
),
text: Text(S.current.action_open_folder),
onPressed: () => model.openFolder(task)),
],
),
const SizedBox(width: 12),
],
),
),
],
);
},
itemCount: model.getTasksLen(),
)),
Container(
color: FluentTheme.of(context).cardColor.withOpacity(.06),
child: Padding(
padding: const EdgeInsets.only(left: 12, bottom: 3, top: 3),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: state.isAvailable ? Colors.green : Colors.white,
borderRadius: BorderRadius.circular(1000),
),
),
const SizedBox(width: 12),
Text(S.current.downloader_info_download_upload_speed(
FileSize.getSize(state.globalStat?.downloadSpeed ?? 0),
FileSize.getSize(state.globalStat?.uploadSpeed ?? 0)))
],
),
),
),
],
),
useBodyContainer: true);
}
}

View File

@ -1,310 +0,0 @@
// ignore_for_file: avoid_build_context_in_providers, avoid_public_notifier_properties
import 'dart:io';
import 'package:aria2/aria2.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import '../../../widgets/widgets.dart';
part 'home_downloader_ui_model.g.dart';
part 'home_downloader_ui_model.freezed.dart';
@freezed
class HomeDownloaderUIState with _$HomeDownloaderUIState {
factory HomeDownloaderUIState({
@Default([]) List<Aria2Task> tasks,
@Default([]) List<Aria2Task> waitingTasks,
@Default([]) List<Aria2Task> stoppedTasks,
Aria2GlobalStat? globalStat,
}) = _HomeDownloaderUIState;
}
extension HomeDownloaderUIStateExtension on HomeDownloaderUIState {
bool get isAvailable => globalStat != null;
}
@riverpod
class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
final DateFormat formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
bool _disposed = false;
final statusMap = {
"active": S.current.downloader_info_downloading_status,
"waiting": S.current.downloader_info_waiting,
"paused": S.current.downloader_info_paused,
"error": S.current.downloader_info_download_failed,
"complete": S.current.downloader_info_download_completed,
"removed": S.current.downloader_info_deleted,
};
final listHeaderStatusMap = {
"active": S.current.downloader_title_downloading,
"waiting": S.current.downloader_info_waiting,
"stopped": S.current.downloader_title_ended,
};
@override
HomeDownloaderUIState build() {
state = HomeDownloaderUIState();
_listenDownloader();
ref.onDispose(() {
_disposed = true;
});
return state;
}
onTapButton(BuildContext context, String key) async {
final aria2cState = ref.read(aria2cModelProvider);
switch (key) {
case "pause_all":
if (!aria2cState.isRunning) return;
await aria2cState.aria2c?.pauseAll();
await aria2cState.aria2c?.saveSession();
return;
case "resume_all":
if (!aria2cState.isRunning) return;
await aria2cState.aria2c?.unpauseAll();
await aria2cState.aria2c?.saveSession();
return;
case "cancel_all":
final userOK = await showConfirmDialogs(
context,
S.current.downloader_action_confirm_cancel_all_tasks,
Text(S.current.downloader_info_manual_file_deletion_note));
if (userOK == true) {
if (!aria2cState.isRunning) return;
try {
for (var value in [...state.tasks, ...state.waitingTasks]) {
await aria2cState.aria2c?.remove(value.gid!);
}
await aria2cState.aria2c?.saveSession();
} catch (e) {
dPrint("DownloadsUIModel cancel_all Error: $e");
}
}
return;
case "settings":
_showDownloadSpeedSettings(context);
return;
}
}
int getTasksLen() {
return state.tasks.length +
state.waitingTasks.length +
state.stoppedTasks.length;
}
(Aria2Task, String, bool) getTaskAndType(int index) {
final tempList = <Aria2Task>[
...state.tasks,
...state.waitingTasks,
...state.stoppedTasks
];
if (index >= 0 && index < state.tasks.length) {
return (tempList[index], "active", index == 0);
}
if (index >= state.tasks.length &&
index < state.tasks.length + state.waitingTasks.length) {
return (tempList[index], "waiting", index == state.tasks.length);
}
if (index >= state.tasks.length + state.waitingTasks.length &&
index < tempList.length) {
return (
tempList[index],
"stopped",
index == state.tasks.length + state.waitingTasks.length
);
}
throw Exception("Index out of range or element is null");
}
static MapEntry<String, String> getTaskTypeAndName(Aria2Task task) {
if (task.bittorrent == null) {
String uri = task.files?[0]['uris'][0]['uri'] as String;
return MapEntry("url", uri.split('/').last);
} else if (task.bittorrent != null) {
if (task.bittorrent!.containsKey('info')) {
var btName = task.bittorrent?["info"]["name"];
return MapEntry("torrent", btName ?? 'torrent');
} else {
return MapEntry("magnet", '[METADATA]${task.infoHash}');
}
} else {
return const MapEntry("metaLink", '==========metaLink============');
}
}
int getETA(Aria2Task task) {
if (task.downloadSpeed == null || task.downloadSpeed == 0) return 0;
final remainingBytes =
(task.totalLength ?? 0) - (task.completedLength ?? 0);
return remainingBytes ~/ (task.downloadSpeed!);
}
Future<void> resumeTask(String? gid) async {
final aria2c = ref.read(aria2cModelProvider).aria2c;
if (gid != null) {
await aria2c?.unpause(gid);
}
}
Future<void> pauseTask(String? gid) async {
final aria2c = ref.read(aria2cModelProvider).aria2c;
if (gid != null) {
await aria2c?.pause(gid);
}
}
Future<void> cancelTask(BuildContext context, String? gid) async {
await Future.delayed(const Duration(milliseconds: 300));
if (gid != null) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.downloader_action_confirm_cancel_download,
Text(S.current.downloader_info_manual_file_deletion_note));
if (ok == true) {
final aria2c = ref.read(aria2cModelProvider).aria2c;
await aria2c?.remove(gid);
await aria2c?.saveSession();
}
}
}
List<Aria2File> getFilesFormTask(Aria2Task task) {
List<Aria2File> l = [];
if (task.files != null) {
for (var element in task.files!) {
final f = Aria2File.fromJson(element);
l.add(f);
}
}
return l;
}
openFolder(Aria2Task task) {
final f = getFilesFormTask(task).firstOrNull;
if (f != null) {
SystemHelper.openDir(File(f.path!).absolute.path.replaceAll("/", "\\"));
}
}
_listenDownloader() async {
try {
while (true) {
final aria2cState = ref.read(aria2cModelProvider);
if (_disposed) return;
if (aria2cState.isRunning) {
final aria2c = aria2cState.aria2c!;
final tasks = await aria2c.tellActive();
final waitingTasks = await aria2c.tellWaiting(0, 1000000);
final stoppedTasks = await aria2c.tellStopped(0, 1000000);
final globalStat = await aria2c.getGlobalStat();
state = state.copyWith(
tasks: tasks,
waitingTasks: waitingTasks,
stoppedTasks: stoppedTasks,
globalStat: globalStat,
);
} else {
state = state.copyWith(
tasks: [],
waitingTasks: [],
stoppedTasks: [],
globalStat: null,
);
}
await Future.delayed(const Duration(seconds: 1));
}
} catch (e) {
dPrint("[DownloadsUIModel]._listenDownloader Error: $e");
}
}
Future<void> _showDownloadSpeedSettings(BuildContext context) async {
final box = await Hive.openBox("app_conf");
final upCtrl = TextEditingController(
text: box.get("downloader_up_limit", defaultValue: ""));
final downCtrl = TextEditingController(
text: box.get("downloader_down_limit", defaultValue: ""));
final ifr = FilteringTextInputFormatter.allow(RegExp(r'^\d*[km]?$'));
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.downloader_speed_limit_settings,
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.current.downloader_info_p2p_network_note,
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(.6),
),
),
const SizedBox(height: 24),
Text(S.current.downloader_info_download_unit_input_prompt),
const SizedBox(height: 12),
Text(S.current.downloader_input_upload_speed_limit),
const SizedBox(height: 6),
TextFormBox(
placeholder: "1、100k、10m、0",
controller: upCtrl,
placeholderStyle: TextStyle(color: Colors.white.withOpacity(.6)),
inputFormatters: [ifr],
),
const SizedBox(height: 12),
Text(S.current.downloader_input_download_speed_limit),
const SizedBox(height: 6),
TextFormBox(
placeholder: "1、100k、10m、0",
controller: downCtrl,
placeholderStyle: TextStyle(color: Colors.white.withOpacity(.6)),
inputFormatters: [ifr],
),
const SizedBox(height: 24),
Text(
S.current.downloader_input_info_p2p_upload_note,
style: TextStyle(
fontSize: 13,
color: Colors.white.withOpacity(.6),
),
)
],
));
if (ok == true) {
final aria2cState = ref.read(aria2cModelProvider);
final aria2cModel = ref.read(aria2cModelProvider.notifier);
await aria2cModel
.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
final aria2c = aria2cState.aria2c!;
final upByte = aria2cModel.textToByte(upCtrl.text.trim());
final downByte = aria2cModel.textToByte(downCtrl.text.trim());
final r = await aria2c
.changeGlobalOption(Aria2Option()
..maxOverallUploadLimit = upByte
..maxOverallDownloadLimit = downByte)
.unwrap();
if (r != null) {
await box.put('downloader_up_limit', upCtrl.text.trim());
await box.put('downloader_down_limit', downCtrl.text.trim());
}
}
}
}

View File

@ -1,243 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'home_downloader_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$HomeDownloaderUIState {
List<Aria2Task> get tasks => throw _privateConstructorUsedError;
List<Aria2Task> get waitingTasks => throw _privateConstructorUsedError;
List<Aria2Task> get stoppedTasks => throw _privateConstructorUsedError;
Aria2GlobalStat? get globalStat => throw _privateConstructorUsedError;
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HomeDownloaderUIStateCopyWith<HomeDownloaderUIState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HomeDownloaderUIStateCopyWith<$Res> {
factory $HomeDownloaderUIStateCopyWith(HomeDownloaderUIState value,
$Res Function(HomeDownloaderUIState) then) =
_$HomeDownloaderUIStateCopyWithImpl<$Res, HomeDownloaderUIState>;
@useResult
$Res call(
{List<Aria2Task> tasks,
List<Aria2Task> waitingTasks,
List<Aria2Task> stoppedTasks,
Aria2GlobalStat? globalStat});
}
/// @nodoc
class _$HomeDownloaderUIStateCopyWithImpl<$Res,
$Val extends HomeDownloaderUIState>
implements $HomeDownloaderUIStateCopyWith<$Res> {
_$HomeDownloaderUIStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? tasks = null,
Object? waitingTasks = null,
Object? stoppedTasks = null,
Object? globalStat = freezed,
}) {
return _then(_value.copyWith(
tasks: null == tasks
? _value.tasks
: tasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
waitingTasks: null == waitingTasks
? _value.waitingTasks
: waitingTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
stoppedTasks: null == stoppedTasks
? _value.stoppedTasks
: stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
globalStat: freezed == globalStat
? _value.globalStat
: globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
) as $Val);
}
}
/// @nodoc
abstract class _$$HomeDownloaderUIStateImplCopyWith<$Res>
implements $HomeDownloaderUIStateCopyWith<$Res> {
factory _$$HomeDownloaderUIStateImplCopyWith(
_$HomeDownloaderUIStateImpl value,
$Res Function(_$HomeDownloaderUIStateImpl) then) =
__$$HomeDownloaderUIStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{List<Aria2Task> tasks,
List<Aria2Task> waitingTasks,
List<Aria2Task> stoppedTasks,
Aria2GlobalStat? globalStat});
}
/// @nodoc
class __$$HomeDownloaderUIStateImplCopyWithImpl<$Res>
extends _$HomeDownloaderUIStateCopyWithImpl<$Res,
_$HomeDownloaderUIStateImpl>
implements _$$HomeDownloaderUIStateImplCopyWith<$Res> {
__$$HomeDownloaderUIStateImplCopyWithImpl(_$HomeDownloaderUIStateImpl _value,
$Res Function(_$HomeDownloaderUIStateImpl) _then)
: super(_value, _then);
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? tasks = null,
Object? waitingTasks = null,
Object? stoppedTasks = null,
Object? globalStat = freezed,
}) {
return _then(_$HomeDownloaderUIStateImpl(
tasks: null == tasks
? _value._tasks
: tasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
waitingTasks: null == waitingTasks
? _value._waitingTasks
: waitingTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
stoppedTasks: null == stoppedTasks
? _value._stoppedTasks
: stoppedTasks // ignore: cast_nullable_to_non_nullable
as List<Aria2Task>,
globalStat: freezed == globalStat
? _value.globalStat
: globalStat // ignore: cast_nullable_to_non_nullable
as Aria2GlobalStat?,
));
}
}
/// @nodoc
class _$HomeDownloaderUIStateImpl implements _HomeDownloaderUIState {
_$HomeDownloaderUIStateImpl(
{final List<Aria2Task> tasks = const [],
final List<Aria2Task> waitingTasks = const [],
final List<Aria2Task> stoppedTasks = const [],
this.globalStat})
: _tasks = tasks,
_waitingTasks = waitingTasks,
_stoppedTasks = stoppedTasks;
final List<Aria2Task> _tasks;
@override
@JsonKey()
List<Aria2Task> get tasks {
if (_tasks is EqualUnmodifiableListView) return _tasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_tasks);
}
final List<Aria2Task> _waitingTasks;
@override
@JsonKey()
List<Aria2Task> get waitingTasks {
if (_waitingTasks is EqualUnmodifiableListView) return _waitingTasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_waitingTasks);
}
final List<Aria2Task> _stoppedTasks;
@override
@JsonKey()
List<Aria2Task> get stoppedTasks {
if (_stoppedTasks is EqualUnmodifiableListView) return _stoppedTasks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_stoppedTasks);
}
@override
final Aria2GlobalStat? globalStat;
@override
String toString() {
return 'HomeDownloaderUIState(tasks: $tasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HomeDownloaderUIStateImpl &&
const DeepCollectionEquality().equals(other._tasks, _tasks) &&
const DeepCollectionEquality()
.equals(other._waitingTasks, _waitingTasks) &&
const DeepCollectionEquality()
.equals(other._stoppedTasks, _stoppedTasks) &&
(identical(other.globalStat, globalStat) ||
other.globalStat == globalStat));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_tasks),
const DeepCollectionEquality().hash(_waitingTasks),
const DeepCollectionEquality().hash(_stoppedTasks),
globalStat);
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HomeDownloaderUIStateImplCopyWith<_$HomeDownloaderUIStateImpl>
get copyWith => __$$HomeDownloaderUIStateImplCopyWithImpl<
_$HomeDownloaderUIStateImpl>(this, _$identity);
}
abstract class _HomeDownloaderUIState implements HomeDownloaderUIState {
factory _HomeDownloaderUIState(
{final List<Aria2Task> tasks,
final List<Aria2Task> waitingTasks,
final List<Aria2Task> stoppedTasks,
final Aria2GlobalStat? globalStat}) = _$HomeDownloaderUIStateImpl;
@override
List<Aria2Task> get tasks;
@override
List<Aria2Task> get waitingTasks;
@override
List<Aria2Task> get stoppedTasks;
@override
Aria2GlobalStat? get globalStat;
/// Create a copy of HomeDownloaderUIState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HomeDownloaderUIStateImplCopyWith<_$HomeDownloaderUIStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'home_downloader_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$homeDownloaderUIModelHash() =>
r'ece2e6da4576b945ead5767aea2ccacf5e3e17aa';
/// See also [HomeDownloaderUIModel].
@ProviderFor(HomeDownloaderUIModel)
final homeDownloaderUIModelProvider = AutoDisposeNotifierProvider<
HomeDownloaderUIModel, HomeDownloaderUIState>.internal(
HomeDownloaderUIModel.new,
name: r'homeDownloaderUIModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$homeDownloaderUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HomeDownloaderUIModel = AutoDisposeNotifier<HomeDownloaderUIState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,305 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_tilt/flutter_tilt.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'game_doctor_ui_model.dart';
class HomeGameDoctorUI extends HookConsumerWidget {
const HomeGameDoctorUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(homeGameDoctorUIModelProvider);
final homeState = ref.watch(homeUIModelProvider);
final model = ref.read(homeGameDoctorUIModelProvider.notifier);
useEffect(() {
AnalyticsApi.touch("auto_scan_issues");
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
dPrint("HomeGameDoctorUI useEffect doCheck timeStamp === $timeStamp");
model.doCheck(context);
});
return null;
}, []);
return makeDefaultPage(context,
title: S.current
.doctor_title_one_click_diagnosis(homeState.scInstalledPath ?? ""),
useBodyContainer: true,
content: Stack(
children: [
Column(
children: [
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
for (final item in {
"rsi_log": S.current.doctor_action_rsi_launcher_log,
"game_log": S.current.doctor_action_game_run_log,
}.entries)
Padding(
padding: const EdgeInsets.only(left: 6, right: 6),
child: Button(
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.folder_open),
const SizedBox(width: 6),
Text(item.value),
],
),
),
onPressed: () =>
_onTapButton(context, item.key, homeState)),
),
],
),
if (state.isChecking)
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(state.lastScreenInfo)
],
),
))
else if (state.checkResult == null ||
state.checkResult!.isEmpty) ...[
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 12),
Text(S.current.doctor_info_scan_complete_no_issues,
maxLines: 1),
const SizedBox(height: 64),
],
),
))
] else
...makeResult(context, state, model),
],
),
if (state.isFixing)
Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(150),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(state.isFixingString.isNotEmpty
? state.isFixingString
: S.current.doctor_info_processing),
],
),
),
),
Positioned(
bottom: 20,
right: 20,
child: makeRescueBanner(context),
)
],
));
}
Widget makeRescueBanner(BuildContext context) {
return GestureDetector(
onTap: () async {
await showToast(
context, S.current.doctor_info_game_rescue_service_note);
launchUrlString(
"https://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=-M4wEme_bCXbUGT4LFKLH0bAYTFt70Ad&authKey=vHVr0TNgRmKu%2BHwywoJV6EiLa7La2VX74Vkyixr05KA0H9TqB6qWlCdY%2B9jLQ4Ha&noverify=0&group_code=536454632");
},
child: Tilt(
shadowConfig: const ShadowConfig(maxIntensity: .2),
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/rescue.png", width: 24, height: 24),
const SizedBox(width: 12),
Text(S.current.doctor_info_need_help),
],
),
)),
),
);
}
List<Widget> makeResult(BuildContext context, HomeGameDoctorState state,
HomeGameDoctorUIModel model) {
return [
const SizedBox(height: 24),
Text(state.lastScreenInfo, maxLines: 1),
const SizedBox(height: 12),
Text(
S.current.doctor_info_tool_check_result_note,
style: TextStyle(color: Colors.red, fontSize: 16),
),
const SizedBox(height: 24),
ListView.builder(
itemCount: state.checkResult!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
final item = state.checkResult![index];
return makeResultItem(context, item, state, model);
},
),
const SizedBox(height: 64),
];
}
Widget makeResultItem(BuildContext context, MapEntry<String, String> item,
HomeGameDoctorState state, HomeGameDoctorUIModel model) {
final errorNames = {
"unSupport_system": MapEntry(S.current.doctor_info_result_unsupported_os,
S.current.doctor_info_result_upgrade_system(item.value)),
"no_live_path": MapEntry(S.current.doctor_info_result_missing_live_folder,
S.current.doctor_info_result_create_live_folder(item.value)),
"nvme_PhysicalBytes": MapEntry(
S.current.doctor_info_result_incompatible_nvme_device,
S.current.doctor_info_result_add_registry_value(item.value)),
"eac_file_miss": MapEntry(
S.current.doctor_info_result_missing_easyanticheat_files,
S.current.doctor_info_result_verify_files_with_rsi_launcher),
"eac_not_install": MapEntry(
S.current.doctor_info_result_easyanticheat_not_installed,
S.current.doctor_info_result_install_easyanticheat),
"cn_user_name": MapEntry(S.current.doctor_info_result_chinese_username,
S.current.doctor_info_result_chinese_username_error),
"cn_install_path": MapEntry(
S.current.doctor_info_result_chinese_install_path,
S.current.doctor_info_result_chinese_install_path_error(item.value)),
"low_ram": MapEntry(S.current.doctor_info_result_low_physical_memory,
S.current.doctor_info_result_memory_requirement(item.value)),
};
bool isCheckedError = errorNames.containsKey(item.key);
if (isCheckedError) {
return Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text(
errorNames[item.key]?.key ?? "",
style: const TextStyle(fontSize: 18),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 4, bottom: 4),
child: Column(
children: [
const SizedBox(height: 4),
Text(
S.current.doctor_info_result_fix_suggestion(
errorNames[item.key]?.value ??
S.current.doctor_info_result_no_solution),
style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.7)),
),
],
),
),
trailing: Button(
onPressed: (errorNames[item.key]?.value == null || state.isFixing)
? null
: () async {
await model.doFix(context, item);
},
child: Padding(
padding:
const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
child: Text(S.current.doctor_info_action_fix),
),
),
),
);
}
final isSubTitleUrl = item.value.startsWith("https://");
return Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
),
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text(
item.key,
style: const TextStyle(fontSize: 18),
),
subtitle: isSubTitleUrl
? null
: Column(
children: [
const SizedBox(height: 4),
Text(
item.value,
style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.7)),
),
],
),
trailing: isSubTitleUrl
? Button(
onPressed: () {
launchUrlString(item.value);
},
child: Padding(
padding: const EdgeInsets.only(
left: 8, right: 8, top: 4, bottom: 4),
child: Text(S.current.doctor_action_view_solution),
),
)
: null,
),
);
}
_onTapButton(
BuildContext context, String key, HomeUIModelState homeState) async {
switch (key) {
case "rsi_log":
final path = await SCLoggerHelper.getLogFilePath();
if (path == null) return;
SystemHelper.openDir(path);
return;
case "game_log":
if (homeState.scInstalledPath == "not_install" ||
homeState.scInstalledPath == null) {
showToast(context, S.current.doctor_tip_title_select_game_directory);
return;
}
SystemHelper.openDir("${homeState.scInstalledPath}\\Game.log");
return;
}
}
}

View File

@ -1,284 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'game_doctor_ui_model.g.dart';
part 'game_doctor_ui_model.freezed.dart';
@freezed
class HomeGameDoctorState with _$HomeGameDoctorState {
factory HomeGameDoctorState({
@Default(false) bool isChecking,
@Default(false) bool isFixing,
@Default("") String lastScreenInfo,
@Default("") String isFixingString,
List<MapEntry<String, String>>? checkResult,
}) = _HomeGameDoctorState;
}
@riverpod
class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel {
@override
HomeGameDoctorState build() {
state = HomeGameDoctorState();
return state;
}
Future<void> doFix(
// ignore: avoid_build_context_in_providers
BuildContext context,
MapEntry<String, String> item) async {
final checkResult =
List<MapEntry<String, String>>.from(state.checkResult ?? []);
state = state.copyWith(isFixing: true, isFixingString: "");
switch (item.key) {
case "unSupport_system":
showToast(context, S.current.doctor_action_result_try_latest_windows);
break;
case "no_live_path":
try {
await Directory(item.value).create(recursive: true);
if (!context.mounted) break;
showToast(
context, S.current.doctor_action_result_create_folder_success);
checkResult.remove(item);
state = state.copyWith(checkResult: checkResult);
} catch (e) {
showToast(context,
S.current.doctor_action_result_create_folder_fail(item.value, e));
}
break;
case "nvme_PhysicalBytes":
final r = await SystemHelper.addNvmePatch();
if (r == "") {
if (!context.mounted) break;
showToast(context, S.current.doctor_action_result_fix_success);
checkResult.remove(item);
state = state.copyWith(checkResult: checkResult);
} else {
if (!context.mounted) break;
showToast(context, S.current.doctor_action_result_fix_fail(r));
}
break;
case "eac_file_miss":
showToast(context,
S.current.doctor_info_result_verify_files_with_rsi_launcher);
break;
case "eac_not_install":
final eacJsonPath = "${item.value}\\Settings.json";
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
try {
var result = await Process.run(
"${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]);
dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID");
if (result.stderr == "") {
if (!context.mounted) break;
showToast(
context, S.current.doctor_action_result_game_start_success);
checkResult.remove(item);
state = state.copyWith(checkResult: checkResult);
} else {
if (!context.mounted) break;
showToast(context,
S.current.doctor_action_result_fix_fail(result.stderr));
}
} catch (e) {
if (!context.mounted) break;
showToast(context, S.current.doctor_action_result_fix_fail(e));
}
break;
case "cn_user_name":
showToast(context, S.current.doctor_action_result_redirect_warning);
await Future.delayed(const Duration(milliseconds: 300));
launchUrlString(
"https://jingyan.baidu.com/article/59703552a318a08fc0074021.html");
break;
default:
showToast(context, S.current.doctor_action_result_issue_not_supported);
break;
}
state = state.copyWith(isFixing: false, isFixingString: "");
}
// ignore: avoid_build_context_in_providers
doCheck(BuildContext context) async {
if (state.isChecking) return;
state = state.copyWith(
isChecking: true, lastScreenInfo: S.current.doctor_action_analyzing);
dPrint("-------- start docker check -----");
if (!context.mounted) return;
await _statCheck(context);
state = state.copyWith(isChecking: false);
}
// ignore: avoid_build_context_in_providers
_statCheck(BuildContext context) async {
final homeState = ref.read(homeUIModelProvider);
final scInstalledPath = homeState.scInstalledPath!;
final checkResult = <MapEntry<String, String>>[];
// TODO for debug
// checkResult?.add(MapEntry("unSupport_system", "android"));
// checkResult?.add(MapEntry("nvme_PhysicalBytes", "C"));
// checkResult?.add(MapEntry("no_live_path", ""));
await _checkPreInstall(context, scInstalledPath, checkResult);
if (!context.mounted) return;
await _checkEAC(context, scInstalledPath, checkResult);
if (!context.mounted) return;
await _checkGameRunningLog(context, scInstalledPath, checkResult);
if (checkResult.isEmpty) {
final lastScreenInfo = S.current.doctor_action_result_analysis_no_issue;
state = state.copyWith(checkResult: null, lastScreenInfo: lastScreenInfo);
} else {
final lastScreenInfo = S.current
.doctor_action_result_analysis_issues_found(
checkResult.length.toString());
state = state.copyWith(
checkResult: checkResult, lastScreenInfo: lastScreenInfo);
}
if (scInstalledPath == "not_install" && (checkResult.isEmpty)) {
if (!context.mounted) return;
showToast(context, S.current.doctor_action_result_toast_scan_no_issue);
}
}
// ignore: avoid_build_context_in_providers
Future _checkGameRunningLog(BuildContext context, String scInstalledPath,
List<MapEntry<String, String>> checkResult) async {
if (scInstalledPath == "not_install") return;
final lastScreenInfo = S.current.doctor_action_tip_checking_game_log;
state = state.copyWith(lastScreenInfo: lastScreenInfo);
final logs = await SCLoggerHelper.getGameRunningLogs(scInstalledPath);
if (logs == null) return;
final info = SCLoggerHelper.getGameRunningLogInfo(logs);
if (info != null) {
if (info.key != "_") {
checkResult.add(MapEntry(
S.current.doctor_action_info_game_abnormal_exit(info.key),
info.value));
} else {
checkResult.add(MapEntry(
S.current.doctor_action_info_game_abnormal_exit_unknown,
S.current.doctor_action_info_info_feedback(info.value)));
}
}
}
// ignore: avoid_build_context_in_providers
Future _checkEAC(BuildContext context, String scInstalledPath,
List<MapEntry<String, String>> checkResult) async {
if (scInstalledPath == "not_install") return;
final lastScreenInfo = S.current.doctor_action_info_checking_eac;
state = state.copyWith(lastScreenInfo: lastScreenInfo);
final eacPath = "$scInstalledPath\\EasyAntiCheat";
final eacJsonPath = "$eacPath\\Settings.json";
if (!await Directory(eacPath).exists() ||
!await File(eacJsonPath).exists()) {
checkResult.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacJsonData = await File(eacJsonPath).readAsBytes();
final Map eacJson = json.decode(utf8.decode(eacJsonData));
final eacID = eacJson["productid"];
final eacDeploymentId = eacJson["deploymentid"];
if (eacID == null || eacDeploymentId == null) {
checkResult.add(const MapEntry("eac_file_miss", ""));
return;
}
final eacFilePath =
"${Platform.environment["appdata"]}\\EasyAntiCheat\\$eacID\\$eacDeploymentId\\anticheatlauncher.log";
if (!await File(eacFilePath).exists()) {
checkResult.add(MapEntry("eac_not_install", eacPath));
return;
}
}
final _cnExp = RegExp(r"[^\x00-\xff]");
// ignore: avoid_build_context_in_providers
Future _checkPreInstall(BuildContext context, String scInstalledPath,
List<MapEntry<String, String>> checkResult) async {
final lastScreenInfo = S.current.doctor_action_info_checking_runtime;
state = state.copyWith(lastScreenInfo: lastScreenInfo);
if (!(Platform.operatingSystemVersion.contains("Windows 10") ||
Platform.operatingSystemVersion.contains("Windows 11"))) {
checkResult
.add(MapEntry("unSupport_system", Platform.operatingSystemVersion));
final lastScreenInfo = S.current.doctor_action_result_info_unsupported_os(
Platform.operatingSystemVersion);
state = state.copyWith(lastScreenInfo: lastScreenInfo);
await showToast(context, lastScreenInfo);
}
if (_cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) {
checkResult.add(const MapEntry("cn_user_name", ""));
}
// 检查 RAM
final ramSize = await SystemHelper.getSystemMemorySizeGB();
if (ramSize < 16) {
checkResult.add(MapEntry("low_ram", "$ramSize"));
}
state = state.copyWith(
lastScreenInfo: S.current.doctor_action_info_checking_install_info);
// 检查安装分区
try {
final listData = await SCLoggerHelper.getGameInstallPath(
await SCLoggerHelper.getLauncherLogList() ?? []);
final p = [];
final checkedPath = [];
for (var installPath in listData) {
if (!checkedPath.contains(installPath)) {
if (_cnExp.hasMatch(installPath)) {
checkResult.add(MapEntry("cn_install_path", installPath));
}
if (scInstalledPath == "not_install") {
checkedPath.add(installPath);
if (!await Directory(installPath).exists()) {
checkResult.add(MapEntry("no_live_path", installPath));
}
}
final tp = installPath.split(":")[0];
if (!p.contains(tp)) {
p.add(tp);
}
}
}
// call check
for (var element in p) {
var result = await Process.run('powershell', [
"(fsutil fsinfo sectorinfo $element: | Select-String 'PhysicalBytesPerSectorForPerformance').ToString().Split(':')[1].Trim()"
]);
dPrint(
"fsutil info sector info: ->>> ${result.stdout.toString().trim()}");
if (result.stderr == "") {
final rs = result.stdout.toString().trim();
final physicalBytesPerSectorForPerformance = (int.tryParse(rs) ?? 0);
if (physicalBytesPerSectorForPerformance > 4096) {
checkResult.add(MapEntry("nvme_PhysicalBytes", element));
}
}
}
} catch (e) {
dPrint(e);
}
}
}

View File

@ -1,253 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'game_doctor_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$HomeGameDoctorState {
bool get isChecking => throw _privateConstructorUsedError;
bool get isFixing => throw _privateConstructorUsedError;
String get lastScreenInfo => throw _privateConstructorUsedError;
String get isFixingString => throw _privateConstructorUsedError;
List<MapEntry<String, String>>? get checkResult =>
throw _privateConstructorUsedError;
/// Create a copy of HomeGameDoctorState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HomeGameDoctorStateCopyWith<HomeGameDoctorState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HomeGameDoctorStateCopyWith<$Res> {
factory $HomeGameDoctorStateCopyWith(
HomeGameDoctorState value, $Res Function(HomeGameDoctorState) then) =
_$HomeGameDoctorStateCopyWithImpl<$Res, HomeGameDoctorState>;
@useResult
$Res call(
{bool isChecking,
bool isFixing,
String lastScreenInfo,
String isFixingString,
List<MapEntry<String, String>>? checkResult});
}
/// @nodoc
class _$HomeGameDoctorStateCopyWithImpl<$Res, $Val extends HomeGameDoctorState>
implements $HomeGameDoctorStateCopyWith<$Res> {
_$HomeGameDoctorStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of HomeGameDoctorState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isChecking = null,
Object? isFixing = null,
Object? lastScreenInfo = null,
Object? isFixingString = null,
Object? checkResult = freezed,
}) {
return _then(_value.copyWith(
isChecking: null == isChecking
? _value.isChecking
: isChecking // ignore: cast_nullable_to_non_nullable
as bool,
isFixing: null == isFixing
? _value.isFixing
: isFixing // ignore: cast_nullable_to_non_nullable
as bool,
lastScreenInfo: null == lastScreenInfo
? _value.lastScreenInfo
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
as String,
isFixingString: null == isFixingString
? _value.isFixingString
: isFixingString // ignore: cast_nullable_to_non_nullable
as String,
checkResult: freezed == checkResult
? _value.checkResult
: checkResult // ignore: cast_nullable_to_non_nullable
as List<MapEntry<String, String>>?,
) as $Val);
}
}
/// @nodoc
abstract class _$$HomeGameDoctorStateImplCopyWith<$Res>
implements $HomeGameDoctorStateCopyWith<$Res> {
factory _$$HomeGameDoctorStateImplCopyWith(_$HomeGameDoctorStateImpl value,
$Res Function(_$HomeGameDoctorStateImpl) then) =
__$$HomeGameDoctorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool isChecking,
bool isFixing,
String lastScreenInfo,
String isFixingString,
List<MapEntry<String, String>>? checkResult});
}
/// @nodoc
class __$$HomeGameDoctorStateImplCopyWithImpl<$Res>
extends _$HomeGameDoctorStateCopyWithImpl<$Res, _$HomeGameDoctorStateImpl>
implements _$$HomeGameDoctorStateImplCopyWith<$Res> {
__$$HomeGameDoctorStateImplCopyWithImpl(_$HomeGameDoctorStateImpl _value,
$Res Function(_$HomeGameDoctorStateImpl) _then)
: super(_value, _then);
/// Create a copy of HomeGameDoctorState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isChecking = null,
Object? isFixing = null,
Object? lastScreenInfo = null,
Object? isFixingString = null,
Object? checkResult = freezed,
}) {
return _then(_$HomeGameDoctorStateImpl(
isChecking: null == isChecking
? _value.isChecking
: isChecking // ignore: cast_nullable_to_non_nullable
as bool,
isFixing: null == isFixing
? _value.isFixing
: isFixing // ignore: cast_nullable_to_non_nullable
as bool,
lastScreenInfo: null == lastScreenInfo
? _value.lastScreenInfo
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
as String,
isFixingString: null == isFixingString
? _value.isFixingString
: isFixingString // ignore: cast_nullable_to_non_nullable
as String,
checkResult: freezed == checkResult
? _value._checkResult
: checkResult // ignore: cast_nullable_to_non_nullable
as List<MapEntry<String, String>>?,
));
}
}
/// @nodoc
class _$HomeGameDoctorStateImpl implements _HomeGameDoctorState {
_$HomeGameDoctorStateImpl(
{this.isChecking = false,
this.isFixing = false,
this.lastScreenInfo = "",
this.isFixingString = "",
final List<MapEntry<String, String>>? checkResult})
: _checkResult = checkResult;
@override
@JsonKey()
final bool isChecking;
@override
@JsonKey()
final bool isFixing;
@override
@JsonKey()
final String lastScreenInfo;
@override
@JsonKey()
final String isFixingString;
final List<MapEntry<String, String>>? _checkResult;
@override
List<MapEntry<String, String>>? get checkResult {
final value = _checkResult;
if (value == null) return null;
if (_checkResult is EqualUnmodifiableListView) return _checkResult;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
String toString() {
return 'HomeGameDoctorState(isChecking: $isChecking, isFixing: $isFixing, lastScreenInfo: $lastScreenInfo, isFixingString: $isFixingString, checkResult: $checkResult)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HomeGameDoctorStateImpl &&
(identical(other.isChecking, isChecking) ||
other.isChecking == isChecking) &&
(identical(other.isFixing, isFixing) ||
other.isFixing == isFixing) &&
(identical(other.lastScreenInfo, lastScreenInfo) ||
other.lastScreenInfo == lastScreenInfo) &&
(identical(other.isFixingString, isFixingString) ||
other.isFixingString == isFixingString) &&
const DeepCollectionEquality()
.equals(other._checkResult, _checkResult));
}
@override
int get hashCode => Object.hash(
runtimeType,
isChecking,
isFixing,
lastScreenInfo,
isFixingString,
const DeepCollectionEquality().hash(_checkResult));
/// Create a copy of HomeGameDoctorState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
__$$HomeGameDoctorStateImplCopyWithImpl<_$HomeGameDoctorStateImpl>(
this, _$identity);
}
abstract class _HomeGameDoctorState implements HomeGameDoctorState {
factory _HomeGameDoctorState(
{final bool isChecking,
final bool isFixing,
final String lastScreenInfo,
final String isFixingString,
final List<MapEntry<String, String>>? checkResult}) =
_$HomeGameDoctorStateImpl;
@override
bool get isChecking;
@override
bool get isFixing;
@override
String get lastScreenInfo;
@override
String get isFixingString;
@override
List<MapEntry<String, String>>? get checkResult;
/// Create a copy of HomeGameDoctorState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,27 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_doctor_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$homeGameDoctorUIModelHash() =>
r'b69a19a937ca375214a7c7e73b8288f577265625';
/// See also [HomeGameDoctorUIModel].
@ProviderFor(HomeGameDoctorUIModel)
final homeGameDoctorUIModelProvider = AutoDisposeNotifierProvider<
HomeGameDoctorUIModel, HomeGameDoctorState>.internal(
HomeGameDoctorUIModel.new,
name: r'homeGameDoctorUIModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$homeGameDoctorUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HomeGameDoctorUIModel = AutoDisposeNotifier<HomeGameDoctorState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -7,8 +7,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -117,76 +115,6 @@ class HomeUI extends HookConsumerWidget {
),
],
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current.home_install_location),
const SizedBox(width: 6),
Expanded(
child: ComboBox<String>(
value: homeState.scInstalledPath,
isExpanded: true,
items: [
ComboBoxItem(
value: "not_install",
child: Text(S.current.home_not_installed_or_failed),
),
for (final path in homeState.scInstallPaths)
ComboBoxItem(
value: path,
child: Row(
children: [Text(path)],
),
)
],
onChanged: model.onChangeInstallPath,
),
),
const SizedBox(width: 12),
Button(
onPressed: homeState.webLocalizationVersionsData == null
? null
: () => model.launchRSI(context),
style: homeState.isCurGameRunning
? null
: ButtonStyle(
backgroundColor:
WidgetStateProperty.resolveWith(_getRunButtonColor),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: Icon(
homeState.isCurGameRunning
? FluentIcons.stop_solid
: FluentIcons.play_solid,
color: homeState.isCurGameRunning
? Colors.red.withOpacity(.8)
: Colors.white,
),
)),
const SizedBox(width: 12),
Button(
onPressed: () =>
SystemHelper.openDir("${homeState.scInstalledPath}"),
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_open),
),
),
const SizedBox(width: 12),
Button(
onPressed: model.reScanPath,
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.refresh),
),
),
],
),
),
const SizedBox(height: 8),
Text(homeState.lastScreenInfo, maxLines: 1),
makeIndexActionLists(context, model, homeState, ref),
@ -796,7 +724,8 @@ class HomeUI extends HookConsumerWidget {
switch (key) {
case "localization":
if (homeState.scInstalledPath == "not_install") {
ToolsUIModel.rsiEnhance(context, showNotGameInstallMsg: true);
// TODO
// ToolsUIModel.English(context, showNotGameInstallMsg: true);
break;
}
final model = ref.watch(homeUIModelProvider.notifier);

View File

@ -1,22 +1,15 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dart_rss/domain/rss_item.dart';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/api/rss.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32;
import 'package:starcitizen_doctor/common/utils/async.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
@ -24,12 +17,10 @@ import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_placard_data.dart';
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart';
import 'package:starcitizen_doctor/ui/home/dialogs/home_game_login_dialog_ui.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:html/parser.dart' as html;
import 'package:html/dom.dart' as html_dom;
import 'package:url_launcher/url_launcher_string.dart';
import '../webview/webview.dart';
import 'localization/localization_ui_model.dart';
part 'home_ui_model.freezed.dart';
@ -76,40 +67,7 @@ class HomeUIModel extends _$HomeUIModel {
}
Future<void> reScanPath() async {
state = state.copyWith(
scInstalledPath: "not_install",
lastScreenInfo: S.current.home_action_info_scanning);
try {
final listData = await SCLoggerHelper.getLauncherLogList();
if (listData == null) {
state = state.copyWith(scInstalledPath: "not_install");
return;
}
final scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
String scInstalledPath = "not_install";
if (scInstallPaths.isNotEmpty) {
if (scInstallPaths.first.isNotEmpty) {
scInstalledPath = scInstallPaths.first;
}
}
final lastScreenInfo = S.current
.home_action_info_scan_complete_valid_directories_found(
scInstallPaths.length.toString());
state = state.copyWith(
scInstalledPath: scInstalledPath,
scInstallPaths: scInstallPaths,
lastScreenInfo: lastScreenInfo);
} catch (e) {
state = state.copyWith(
scInstalledPath: "not_install",
lastScreenInfo: S.current.home_action_info_log_file_parse_fail);
AnalyticsApi.touch("error_launchLogs");
// showToast(context!,
// "${S.current.home_action_info_log_file_parse_fail} \n请关闭游戏退出RSI启动器后重试若仍有问题请使用工具箱中的 RSI Launcher log 修复。");
}
state = state.copyWith(scInstalledPath: "not_install", lastScreenInfo: "");
}
String getRssImage(RssItem item) {
@ -134,65 +92,8 @@ class HomeUIModel extends _$HomeUIModel {
// ignore: avoid_build_context_in_providers
Future<void> goWebView(BuildContext context, String title, String url,
{bool useLocalization = false,
bool loginMode = false,
RsiLoginCallback? rsiLoginCallback}) async {
if (useLocalization) {
const tipVersion = 2;
final box = await Hive.openBox("app_conf");
final skip =
await box.get("skip_web_localization_tip_version", defaultValue: 0);
if (skip != tipVersion) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.home_action_title_star_citizen_website_localization,
Text(
S.current.home_action_info_web_localization_plugin_disclaimer,
style: const TextStyle(fontSize: 16),
),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .6));
if (!ok) {
if (loginMode) {
rsiLoginCallback?.call(null, false);
}
return;
}
await box.put("skip_web_localization_tip_version", tipVersion);
}
}
if (!await WebviewWindow.isWebviewAvailable()) {
if (!context.mounted) return;
showToast(
context, S.current.home_login_action_title_need_webview2_runtime);
launchUrlString(
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
return;
}
if (!context.mounted) return;
final webViewModel = WebViewModel(context,
loginMode: loginMode, loginCallback: rsiLoginCallback);
if (useLocalization) {
state = state.copyWith(
isFixing: true,
isFixingString: S.current.home_action_info_initializing_resources);
try {
await webViewModel.initLocalization(state.webLocalizationVersionsData!);
} catch (e) {
if (!context.mounted) return;
showToast(context, S.current.home_action_info_initialization_failed(e));
}
state = state.copyWith(isFixingString: "", isFixing: false);
}
await webViewModel.initWebView(
title: title,
applicationSupportDir: appGlobalState.applicationSupportDir!,
appVersionData: appGlobalState.networkVersionData!,
);
await Future.delayed(const Duration(milliseconds: 500));
await webViewModel.launch(url, appGlobalState.networkVersionData!);
{bool useLocalization = false, bool loginMode = false}) async {
launchUrlString(url);
}
bool isRSIServerStatusOK(Map map) {
@ -277,28 +178,9 @@ class HomeUIModel extends _$HomeUIModel {
Future<void> checkLocalizationUpdate({bool skipReload = false}) async {
dPrint("_checkLocalizationUpdate");
final updates = await (ref.read(localizationUIModelProvider.notifier))
await (ref.read(localizationUIModelProvider.notifier))
.checkLangUpdate(skipReload: skipReload)
.unwrap<List<String>>();
if (updates == null || updates.isEmpty) {
state = state.copyWith(localizationUpdateInfo: null);
return;
}
state =
state.copyWith(localizationUpdateInfo: MapEntry(updates.first, true));
if (_appUpdateTimer != null) {
_appUpdateTimer?.cancel();
_appUpdateTimer = null;
// 发送通知
await win32.sendNotify(
summary: S.current.home_localization_new_version_available,
body:
S.current.home_localization_new_version_installed(updates.first),
appName: S.current.home_title_app_name,
appId: ConstConf.isMSE
? "56575xkeyC.MSE_bsn1nexg8e4qe!starcitizendoctor"
: "{6D809377-6AF0-444B-8957-A3773F02200E}\\Starcitizen_Doctor\\starcitizen_doctor.exe");
}
}
// ignore: avoid_build_context_in_providers
@ -307,109 +189,10 @@ class HomeUIModel extends _$HomeUIModel {
showToast(context, S.current.home_info_valid_installation_required);
return;
}
if (ConstConf.isMSE) {
if (state.isCurGameRunning) {
await Process.run(
SystemHelper.powershellPath, ["ps \"StarCitizen\" | kill"]);
return;
}
AnalyticsApi.touch("gameLaunch");
showDialog(
context: context,
dismissWithEsc: false,
builder: (context) => HomeGameLoginDialogUI(context));
} else {
final ok = await showConfirmDialogs(
context,
S.current.home_info_one_click_launch_warning,
Text(S.current.home_info_account_security_warning),
confirm: S.current.home_action_install_microsoft_store_version,
cancel: S.current.home_action_cancel);
if (ok == true) {
await launchUrlString(
"https://apps.microsoft.com/detail/9NF3SWFWNKL1?launch=true");
await Future.delayed(const Duration(seconds: 2));
exit(0);
}
}
}
void onChangeInstallPath(String? value) {
if (value == null) return;
state = state.copyWith(scInstalledPath: value);
}
doLaunchGame(
// ignore: avoid_build_context_in_providers
BuildContext context,
String launchExe,
List<String> args,
String installPath,
String? processorAffinity) async {
var runningMap = Map<String, bool>.from(state.isGameRunning);
runningMap[installPath] = true;
state = state.copyWith(isGameRunning: runningMap);
try {
late ProcessResult result;
if (processorAffinity == null) {
result = await Process.run(launchExe, args);
} else {
dPrint("set Affinity === $processorAffinity launchExe === $launchExe");
result = await Process.run("cmd.exe", [
'/C',
'Start',
'"StarCitizen"',
'/High',
'/Affinity',
processorAffinity,
launchExe,
...args
]);
}
dPrint('Exit code: ${result.exitCode}');
dPrint('stdout: ${result.stdout}');
dPrint('stderr: ${result.stderr}');
if (result.exitCode != 0) {
final logs = await SCLoggerHelper.getGameRunningLogs(installPath);
MapEntry<String, String>? exitInfo;
bool hasUrl = false;
if (logs != null) {
exitInfo = SCLoggerHelper.getGameRunningLogInfo(logs);
if (exitInfo!.value.startsWith("https://")) {
hasUrl = true;
}
}
if (!context.mounted) return;
// showToast(context,
// "游戏非正常退出\nexitCode=${result.exitCode}\nstdout=${result.stdout ?? ""}\nstderr=${result.stderr ?? ""}\n\n诊断信息${exitInfo == null ? "未知错误,请通过一键诊断加群反馈。" : exitInfo.key} \n${hasUrl ? "请查看弹出的网页链接获得详细信息。" : exitInfo?.value ?? ""}");
// S.current.home_action_info_abnormal_game_exit
showToast(
context,
S.current.home_action_info_abnormal_game_exit(
result.exitCode.toString(),
result.stdout ?? "",
result.stderr ?? "",
exitInfo == null
? S.current.home_action_info_unknown_error
: exitInfo.key,
hasUrl
? S.current.home_action_info_check_web_link
: exitInfo?.value ?? ""));
if (hasUrl) {
await Future.delayed(const Duration(seconds: 3));
launchUrlString(exitInfo!.value);
}
}
final launchFile = File("$installPath\\loginData.json");
if (await launchFile.exists()) {
await launchFile.delete();
}
} catch (_) {}
runningMap = Map<String, bool>.from(state.isGameRunning);
runningMap[installPath] = false;
state = state.copyWith(isGameRunning: runningMap);
}
}

View File

@ -6,7 +6,7 @@ part of 'home_ui_model.dart';
// RiverpodGenerator
// **************************************************************************
String _$homeUIModelHash() => r'85d3242abb4264a814768a2d5ce108df46df38d9';
String _$homeUIModelHash() => r'6a768281606856766737a63aaeebb392c4613d2b';
/// See also [HomeUIModel].
@ProviderFor(HomeUIModel)

View File

@ -10,7 +10,6 @@ import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/ui/home/localization/advanced_localization_ui_model.dart';
import 'package:starcitizen_doctor/ui/tools/unp4kc/unp4kc_ui.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
@ -58,7 +57,7 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
: Column(
children: [
if (state.errorMessage.isNotEmpty)
UnP4kErrorWidget(
ErrorMessageWidget(
errorMessage: state.errorMessage,
)
else ...[
@ -313,3 +312,32 @@ class AdvancedLocalizationUI extends HookConsumerWidget {
);
}
}
class ErrorMessageWidget extends HookConsumerWidget {
final String errorMessage;
const ErrorMessageWidget({super.key, required this.errorMessage});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withOpacity(.1),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
const Icon(FluentIcons.error),
const SizedBox(width: 12),
Expanded(
child: Text(
errorMessage,
style: TextStyle(color: Colors.red),
),
),
],
),
);
}
}

View File

@ -6,10 +6,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import '../home_ui_model.dart';
import 'advanced_localization_ui.json.dart';
@ -217,32 +215,7 @@ class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
}
Future<String> readEnglishInI(String gameDir) async {
try {
var data = await Unp4kCModel.unp4kTools(
appGlobalState.applicationBinaryModuleDir!, [
"extract_memory",
"$gameDir\\Data.p4k",
"Data\\Localization\\english\\global.ini"
]);
// remove bom
if (data.length > 3 &&
data[0] == 0xEF &&
data[1] == 0xBB &&
data[2] == 0xBF) {
data = data.sublist(3);
}
final iniData = String.fromCharCodes(data);
return iniData;
} catch (e) {
final errorMessage = e.toString();
if (Unp4kCModel.checkRunTimeError(errorMessage)) {
AnalyticsApi.touch("advanced_localization_no_runtime");
}
state = state.copyWith(
errorMessage: errorMessage,
);
// rethrow;
}
// TODO read English p4kGlobalIni
return "";
}

View File

@ -7,7 +7,7 @@ part of 'advanced_localization_ui_model.dart';
// **************************************************************************
String _$advancedLocalizationUIModelHash() =>
r'8241143c6dec93cd705e6b2e65cbca711cdfe2fb';
r'60ccd50f54b948d16be001f5ea07972a0fd9ed3f';
/// See also [AdvancedLocalizationUIModel].
@ProviderFor(AdvancedLocalizationUIModel)

View File

@ -5,7 +5,6 @@ import 'package:flutter_tilt/flutter_tilt.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'localization_form_file_dialog_ui.dart';
@ -495,7 +494,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
? () async {
switch (item.key) {
case "launcher_mod":
ToolsUIModel.rsiEnhance(context);
// ToolsUIModel.rsiEnhance(context);
break;
case "advanced":
context.push("/index/advanced_localization");

View File

@ -6,8 +6,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/data/game_performance_data.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
@ -165,20 +163,20 @@ class HomePerformanceUIModel extends _$HomePerformanceUIModel {
}
cleanShaderCache(BuildContext? context) async {
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final l =
await Directory(gameShaderCachePath!).list(recursive: false).toList();
for (var value in l) {
if (value is Directory) {
if (!value.absolute.path.contains("Crashes")) {
await value.delete(recursive: true);
}
}
}
await Future.delayed(const Duration(milliseconds: 300));
if (context != null && context.mounted) {
showToast(context, S.current.performance_info_shader_clearing_warning);
}
// final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
// final l =
// await Directory(gameShaderCachePath!).list(recursive: false).toList();
// for (var value in l) {
// if (value is Directory) {
// if (!value.absolute.path.contains("Crashes")) {
// await value.delete(recursive: true);
// }
// }
// }
// await Future.delayed(const Duration(milliseconds: 300));
// if (context != null && context.mounted) {
// showToast(context, S.current.performance_info_shader_clearing_warning);
// }
}
applyProfile(bool cleanShader) async {

View File

@ -7,7 +7,7 @@ part of 'performance_ui_model.dart';
// **************************************************************************
String _$homePerformanceUIModelHash() =>
r'83fbdbbae287892dd0c67f5fd86d42a73d0ab91f';
r'a33d8c621f4cd150b1a091bfd0243d578f4a705c';
/// See also [HomePerformanceUIModel].
@ProviderFor(HomePerformanceUIModel)

View File

@ -1,19 +1,15 @@
import 'package:extended_image/extended_image.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
import 'package:starcitizen_doctor/widgets/src/blur_oval_widget.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:window_manager/window_manager.dart';
import 'about/about_ui.dart';
import 'home/home_ui.dart';
import 'settings/settings_ui.dart';
import 'tools/tools_ui.dart';
class IndexUI extends HookConsumerWidget {
const IndexUI({super.key});
@ -22,67 +18,65 @@ class IndexUI extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// pre init child
ref.watch(homeUIModelProvider.select((value) => null));
ref.watch(settingsUIModelProvider.select((value) => null));
ref.watch(appGlobalModelProvider);
// ref.watch(settingsUIModelProvider.select((value) => null));
final globalState = ref.watch(appGlobalModelProvider);
final curIndex = useState(0);
return NavigationView(
appBar: NavigationAppBar(
automaticallyImplyLeading: false,
title: () {
return DragToMoveArea(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Row(
children: [
Image.asset(
"assets/app_logo_mini.png",
width: 20,
height: 20,
fit: BoxFit.cover,
),
const SizedBox(width: 12),
Text(S.current.app_index_version_info(
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
],
),
),
);
}(),
actions: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Stack(
children: [
Padding(
padding: const EdgeInsets.all(6),
child: Icon(
FluentIcons.installation,
size: 22,
color: Colors.white.withOpacity(.6),
),
),
_makeAria2TaskNumWidget()
],
),
onPressed: () => _goDownloader(context),
// onPressed: model.goDownloader
),
const SizedBox(width: 24),
const WindowButtons()
],
)),
pane: NavigationPane(
key: Key("NavigationPane_${S.current.app_language_code}"),
selected: curIndex.value,
items: getNavigationPaneItems(curIndex),
size: NavigationPaneSize(
openWidth: S.current.app_language_code.startsWith("zh") ? 64 : 74),
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image:
ExtendedAssetImageProvider(globalState.backgroundImageAssetsPath),
fit: BoxFit.cover,
),
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: BlurOvalWidget(
child: Container(
constraints:
const BoxConstraints(maxWidth: 1440, maxHeight: 1000),
child: NavigationView(
appBar: NavigationAppBar(
automaticallyImplyLeading: false,
title: () {
return Align(
alignment: AlignmentDirectional.centerStart,
child: Row(
children: [
Image.asset(
"assets/app_logo_mini.png",
width: 20,
height: 20,
fit: BoxFit.cover,
),
const SizedBox(width: 12),
Text(S.current.app_index_version_info(
ConstConf.appVersion,
ConstConf.isMSE ? "" : " Dev")),
],
),
);
}(),
),
pane: NavigationPane(
key: Key("NavigationPane_${S.current.app_language_code}"),
selected: curIndex.value,
items: getNavigationPaneItems(curIndex),
size: NavigationPaneSize(
openWidth: S.current.app_language_code.startsWith("zh")
? 64
: 74),
),
paneBodyBuilder: (item, child) {
return item!.body;
},
),
),
),
),
),
paneBodyBuilder: (item, child) {
return item!.body;
},
);
}
@ -93,11 +87,11 @@ class IndexUI extends HookConsumerWidget {
),
FluentIcons.toolbox: (
S.current.app_index_menu_tools,
const ToolsUI(),
const SizedBox(),
),
FluentIcons.settings: (
S.current.app_index_menu_settings,
const SettingsUI()
const SizedBox()
),
FluentIcons.info: (
S.current.app_index_menu_about,
@ -140,37 +134,4 @@ class IndexUI extends HookConsumerWidget {
pageMenus.values.toList().indexWhere((element) => element.$1 == value);
curIndexState.value = pageIndex;
}
Widget _makeAria2TaskNumWidget() {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? child) {
final aria2cState = ref.watch(aria2cModelProvider);
if (!aria2cState.hasDownloadTask) {
return const SizedBox();
}
return Positioned(
bottom: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.only(
left: 6, right: 6, bottom: 1.5, top: 1.5),
child: Text(
"${aria2cState.aria2TotalTaskNum}",
style: const TextStyle(
fontSize: 8,
color: Colors.white,
),
),
));
},
);
}
_goDownloader(BuildContext context) {
context.push('/index/downloader');
}
}

View File

@ -1,41 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:url_launcher/url_launcher_string.dart';
class PartyRoomUI extends HookConsumerWidget {
const PartyRoomUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
S.current.lobby_online_lobby_coming_soon,
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
launchUrlString("https://wj.qq.com/s2/14112124/f4c8/");
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current.lobby_invitation_to_participate),
Text(
S.current.lobby_survey,
style: const TextStyle(
color: Colors.blue,
),
)
],
),
),
],
),
);
}
}

View File

@ -1,161 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
class SettingsUI extends HookConsumerWidget {
const SettingsUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final sate = ref.watch(settingsUIModelProvider);
final model = ref.read(settingsUIModelProvider.notifier);
final appGlobalState = ref.watch(appGlobalModelProvider);
final appGlobalModel = ref.read(appGlobalModelProvider.notifier);
return ListView(padding: const EdgeInsets.all(16), children: [
makeSettingsItem(
const Icon(FontAwesomeIcons.language, size: 20),
S.current.settings_app_language,
subTitle: S.current.settings_app_language_switch_info,
onTap: () {},
onComboChanged: appGlobalModel.changeLocale,
comboMenus: AppGlobalModel.appLocaleSupport,
selectedComboValue: appGlobalState.appLocale ?? const Locale("auto"),
showGoIcon: false,
),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.link, size: 20),
S.current.setting_action_create_settings_shortcut,
subTitle: S.current.setting_action_create_desktop_shortcut,
onTap: () => model.addShortCut(context)),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FontAwesomeIcons.microchip, size: 20),
S.current.setting_action_ignore_efficiency_cores_on_launch,
subTitle: S.current
.setting_action_set_core_count(sate.inputGameLaunchECore),
onTap: () => model.setGameLaunchECore(context)),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.folder_open, size: 20),
S.current.setting_action_set_launcher_file,
subTitle: sate.customLauncherPath != null
? "${sate.customLauncherPath}"
: S.current.setting_action_info_manual_launcher_location_setting,
onTap: () => model.setLauncherPath(context),
onDel: () {
model.delName("custom_launcher_path");
}),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.game, size: 20),
S.current.setting_action_set_game_file,
subTitle: sate.customGamePath != null
? "${sate.customGamePath}"
: S.current.setting_action_info_manual_game_location_setting,
onTap: () => model.setGamePath(context),
onDel: () {
model.delName("custom_game_path");
}),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.delete, size: 20),
S.current.setting_action_clear_translation_file_cache,
subTitle: S.current.setting_action_info_cache_clearing_info(
(sate.locationCacheSize / 1024 / 1024).toStringAsFixed(2)),
onTap: () => model.cleanLocationCache(context)),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.speed_high, size: 20),
S.current.setting_action_tool_site_access_acceleration,
onTap: () =>
model.onChangeToolSiteMirror(!sate.isEnableToolSiteMirrors),
subTitle: S.current.setting_action_info_mirror_server_info,
onSwitch: model.onChangeToolSiteMirror,
switchStatus: sate.isEnableToolSiteMirrors),
const SizedBox(height: 12),
makeSettingsItem(const Icon(FluentIcons.document_set, size: 20),
S.current.setting_action_view_log,
onTap: () => model.showLogs(),
subTitle: S.current.setting_action_info_view_log_file),
]);
}
Widget makeSettingsItem(
Widget icon,
String title, {
String? subTitle,
VoidCallback? onTap,
VoidCallback? onDel,
void Function(bool? b)? onSwitch,
bool switchStatus = false,
bool showGoIcon = true,
Map<dynamic, String> comboMenus = const {},
ValueChanged? onComboChanged,
dynamic selectedComboValue,
}) {
return Button(
onPressed: onTap,
child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 12),
child: Row(
children: [
icon,
const SizedBox(width: 18),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(title),
const Spacer(),
],
),
if (subTitle != null) ...[
const SizedBox(height: 3),
Padding(
padding: const EdgeInsets.only(right: 12),
child: Text(
subTitle,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: 12, color: Colors.white.withOpacity(.6)),
),
),
]
],
),
),
if (onDel != null) ...[
Button(
onPressed: onDel,
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.delete),
)),
],
if (onSwitch != null) ...[
ToggleSwitch(checked: switchStatus, onChanged: onSwitch),
],
if (comboMenus.isNotEmpty) ...[
SizedBox(
height: 36,
child: ComboBox(
value: selectedComboValue,
items: [
for (final mkv in comboMenus.entries)
ComboBoxItem(
value: mkv.key,
child: Text(mkv.value),
)
],
onChanged: onComboChanged,
),
)
],
const SizedBox(width: 12),
if (showGoIcon) const Icon(FluentIcons.chevron_right),
],
),
),
);
}
}

View File

@ -1,196 +0,0 @@
// ignore_for_file: avoid_build_context_in_providers
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
part 'settings_ui_model.g.dart';
part 'settings_ui_model.freezed.dart';
@freezed
class SettingsUIState with _$SettingsUIState {
factory SettingsUIState({
@Default(false) bool isEnableToolSiteMirrors,
@Default("0") String inputGameLaunchECore,
String? customLauncherPath,
String? customGamePath,
@Default(0) int locationCacheSize,
}) = _SettingsUIState;
}
@riverpod
class SettingsUIModel extends _$SettingsUIModel {
@override
SettingsUIState build() {
state = SettingsUIState();
_initState();
return state;
}
void _initState() async {
_updateGameLaunchECore();
_loadCustomPath();
_loadLocationCacheSize();
_loadToolSiteMirrorState();
}
Future<void> setGameLaunchECore(BuildContext context) async {
final userBox = await Hive.openBox("app_conf");
final defaultInput =
userBox.get("gameLaunch_eCore_count", defaultValue: "0");
if (!context.mounted) return;
final input = await showInputDialogs(context,
title: S.current.setting_action_info_enter_cpu_core_to_ignore,
content: S.current.setting_action_info_cpu_core_tip,
initialValue: defaultInput,
inputFormatters: [FilteringTextInputFormatter.digitsOnly]);
if (input == null) return;
userBox.put("gameLaunch_eCore_count", input);
_initState();
}
Future _updateGameLaunchECore() async {
final userBox = await Hive.openBox("app_conf");
final inputGameLaunchECore =
userBox.get("gameLaunch_eCore_count", defaultValue: "0");
state = state.copyWith(inputGameLaunchECore: inputGameLaunchECore);
}
Future<void> setLauncherPath(BuildContext context) async {
final r = await FilePicker.platform.pickFiles(
dialogTitle: S.current.setting_action_info_select_rsi_launcher_location,
type: FileType.custom,
allowedExtensions: ["exe"]);
if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!;
if (fileName.endsWith("\\RSI Launcher.exe")) {
await _saveCustomPath("custom_launcher_path", fileName);
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_setting_success);
_initState();
} else {
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_file_error);
}
}
Future<void> setGamePath(BuildContext context) async {
final r = await FilePicker.platform.pickFiles(
dialogTitle: S.current.setting_action_info_select_game_install_location,
type: FileType.custom,
allowedExtensions: ["exe"]);
if (r == null || r.files.firstOrNull?.path == null) return;
final fileName = r.files.first.path!;
dPrint(fileName);
final fileNameRegExp =
RegExp(r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$");
if (fileNameRegExp.hasMatch(fileName)) {
RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$");
String extractedPath = fileName.replaceFirst(pathRegex, '');
await _saveCustomPath("custom_game_path", extractedPath);
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_setting_success);
_initState();
} else {
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_file_error);
}
}
_saveCustomPath(String pathKey, String dir) async {
final confBox = await Hive.openBox("app_conf");
await confBox.put(pathKey, dir);
}
_loadCustomPath() async {
final confBox = await Hive.openBox("app_conf");
final customLauncherPath = confBox.get("custom_launcher_path");
final customGamePath = confBox.get("custom_game_path");
state = state.copyWith(
customLauncherPath: customLauncherPath, customGamePath: customGamePath);
}
Future<void> delName(String key) async {
final confBox = await Hive.openBox("app_conf");
await confBox.delete(key);
_initState();
}
_loadLocationCacheSize() async {
final len = await SystemHelper.getDirLen(
"${appGlobalState.applicationSupportDir}/Localizations");
final locationCacheSize = len;
state = state.copyWith(locationCacheSize: locationCacheSize);
}
Future<void> cleanLocationCache(BuildContext context) async {
final ok = await showConfirmDialogs(
context,
S.current.setting_action_info_confirm_clear_cache,
Text(S.current.setting_action_info_clear_cache_warning));
if (ok == true) {
final dir =
Directory("${appGlobalState.applicationSupportDir}/Localizations");
if (!context.mounted) return;
await dir.delete(recursive: true).unwrap(context: context);
_initState();
}
}
Future<void> addShortCut(BuildContext context) async {
if (ConstConf.isMSE) {
showToast(
context, S.current.setting_action_info_microsoft_version_limitation);
await Future.delayed(const Duration(seconds: 1));
Process.run("explorer.exe", ["shell:AppsFolder"]);
return;
}
dPrint(Platform.resolvedExecutable);
final shortCuntName = S.current.app_shortcut_name;
final script = """
\$targetPath = "${Platform.resolvedExecutable}";
\$shortcutPath = [System.IO.Path]::Combine([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::DesktopDirectory), "$shortCuntName");
\$shell = New-Object -ComObject WScript.Shell
\$shortcut = \$shell.CreateShortcut(\$shortcutPath)
if (\$shortcut -eq \$null) {
Write-Host "Failed to create shortcut."
} else {
\$shortcut.TargetPath = \$targetPath
\$shortcut.Save()
Write-Host "Shortcut created successfully."
}
""";
await Process.run(SystemHelper.powershellPath, [script]);
if (!context.mounted) return;
showToast(context, S.current.setting_action_info_shortcut_created);
}
_loadToolSiteMirrorState() async {
final userBox = await Hive.openBox("app_conf");
final isEnableToolSiteMirrors =
userBox.get("isEnableToolSiteMirrors", defaultValue: false);
state = state.copyWith(isEnableToolSiteMirrors: isEnableToolSiteMirrors);
}
void onChangeToolSiteMirror(bool? b) async {
final userBox = await Hive.openBox("app_conf");
final isEnableToolSiteMirrors = b == true;
await userBox.put("isEnableToolSiteMirrors", isEnableToolSiteMirrors);
_initState();
}
showLogs() async {
SystemHelper.openDir(getDPrintFile()?.absolute.path.replaceAll("/", "\\"),
isFile: true);
}
}

View File

@ -1,243 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'settings_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$SettingsUIState {
bool get isEnableToolSiteMirrors => throw _privateConstructorUsedError;
String get inputGameLaunchECore => throw _privateConstructorUsedError;
String? get customLauncherPath => throw _privateConstructorUsedError;
String? get customGamePath => throw _privateConstructorUsedError;
int get locationCacheSize => throw _privateConstructorUsedError;
/// Create a copy of SettingsUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SettingsUIStateCopyWith<SettingsUIState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SettingsUIStateCopyWith<$Res> {
factory $SettingsUIStateCopyWith(
SettingsUIState value, $Res Function(SettingsUIState) then) =
_$SettingsUIStateCopyWithImpl<$Res, SettingsUIState>;
@useResult
$Res call(
{bool isEnableToolSiteMirrors,
String inputGameLaunchECore,
String? customLauncherPath,
String? customGamePath,
int locationCacheSize});
}
/// @nodoc
class _$SettingsUIStateCopyWithImpl<$Res, $Val extends SettingsUIState>
implements $SettingsUIStateCopyWith<$Res> {
_$SettingsUIStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SettingsUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isEnableToolSiteMirrors = null,
Object? inputGameLaunchECore = null,
Object? customLauncherPath = freezed,
Object? customGamePath = freezed,
Object? locationCacheSize = null,
}) {
return _then(_value.copyWith(
isEnableToolSiteMirrors: null == isEnableToolSiteMirrors
? _value.isEnableToolSiteMirrors
: isEnableToolSiteMirrors // ignore: cast_nullable_to_non_nullable
as bool,
inputGameLaunchECore: null == inputGameLaunchECore
? _value.inputGameLaunchECore
: inputGameLaunchECore // ignore: cast_nullable_to_non_nullable
as String,
customLauncherPath: freezed == customLauncherPath
? _value.customLauncherPath
: customLauncherPath // ignore: cast_nullable_to_non_nullable
as String?,
customGamePath: freezed == customGamePath
? _value.customGamePath
: customGamePath // ignore: cast_nullable_to_non_nullable
as String?,
locationCacheSize: null == locationCacheSize
? _value.locationCacheSize
: locationCacheSize // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$SettingsUIStateImplCopyWith<$Res>
implements $SettingsUIStateCopyWith<$Res> {
factory _$$SettingsUIStateImplCopyWith(_$SettingsUIStateImpl value,
$Res Function(_$SettingsUIStateImpl) then) =
__$$SettingsUIStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool isEnableToolSiteMirrors,
String inputGameLaunchECore,
String? customLauncherPath,
String? customGamePath,
int locationCacheSize});
}
/// @nodoc
class __$$SettingsUIStateImplCopyWithImpl<$Res>
extends _$SettingsUIStateCopyWithImpl<$Res, _$SettingsUIStateImpl>
implements _$$SettingsUIStateImplCopyWith<$Res> {
__$$SettingsUIStateImplCopyWithImpl(
_$SettingsUIStateImpl _value, $Res Function(_$SettingsUIStateImpl) _then)
: super(_value, _then);
/// Create a copy of SettingsUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? isEnableToolSiteMirrors = null,
Object? inputGameLaunchECore = null,
Object? customLauncherPath = freezed,
Object? customGamePath = freezed,
Object? locationCacheSize = null,
}) {
return _then(_$SettingsUIStateImpl(
isEnableToolSiteMirrors: null == isEnableToolSiteMirrors
? _value.isEnableToolSiteMirrors
: isEnableToolSiteMirrors // ignore: cast_nullable_to_non_nullable
as bool,
inputGameLaunchECore: null == inputGameLaunchECore
? _value.inputGameLaunchECore
: inputGameLaunchECore // ignore: cast_nullable_to_non_nullable
as String,
customLauncherPath: freezed == customLauncherPath
? _value.customLauncherPath
: customLauncherPath // ignore: cast_nullable_to_non_nullable
as String?,
customGamePath: freezed == customGamePath
? _value.customGamePath
: customGamePath // ignore: cast_nullable_to_non_nullable
as String?,
locationCacheSize: null == locationCacheSize
? _value.locationCacheSize
: locationCacheSize // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
class _$SettingsUIStateImpl implements _SettingsUIState {
_$SettingsUIStateImpl(
{this.isEnableToolSiteMirrors = false,
this.inputGameLaunchECore = "0",
this.customLauncherPath,
this.customGamePath,
this.locationCacheSize = 0});
@override
@JsonKey()
final bool isEnableToolSiteMirrors;
@override
@JsonKey()
final String inputGameLaunchECore;
@override
final String? customLauncherPath;
@override
final String? customGamePath;
@override
@JsonKey()
final int locationCacheSize;
@override
String toString() {
return 'SettingsUIState(isEnableToolSiteMirrors: $isEnableToolSiteMirrors, inputGameLaunchECore: $inputGameLaunchECore, customLauncherPath: $customLauncherPath, customGamePath: $customGamePath, locationCacheSize: $locationCacheSize)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SettingsUIStateImpl &&
(identical(
other.isEnableToolSiteMirrors, isEnableToolSiteMirrors) ||
other.isEnableToolSiteMirrors == isEnableToolSiteMirrors) &&
(identical(other.inputGameLaunchECore, inputGameLaunchECore) ||
other.inputGameLaunchECore == inputGameLaunchECore) &&
(identical(other.customLauncherPath, customLauncherPath) ||
other.customLauncherPath == customLauncherPath) &&
(identical(other.customGamePath, customGamePath) ||
other.customGamePath == customGamePath) &&
(identical(other.locationCacheSize, locationCacheSize) ||
other.locationCacheSize == locationCacheSize));
}
@override
int get hashCode => Object.hash(
runtimeType,
isEnableToolSiteMirrors,
inputGameLaunchECore,
customLauncherPath,
customGamePath,
locationCacheSize);
/// Create a copy of SettingsUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SettingsUIStateImplCopyWith<_$SettingsUIStateImpl> get copyWith =>
__$$SettingsUIStateImplCopyWithImpl<_$SettingsUIStateImpl>(
this, _$identity);
}
abstract class _SettingsUIState implements SettingsUIState {
factory _SettingsUIState(
{final bool isEnableToolSiteMirrors,
final String inputGameLaunchECore,
final String? customLauncherPath,
final String? customGamePath,
final int locationCacheSize}) = _$SettingsUIStateImpl;
@override
bool get isEnableToolSiteMirrors;
@override
String get inputGameLaunchECore;
@override
String? get customLauncherPath;
@override
String? get customGamePath;
@override
int get locationCacheSize;
/// Create a copy of SettingsUIState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SettingsUIStateImplCopyWith<_$SettingsUIStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,26 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$settingsUIModelHash() => r'2be0fe230d23f84796e0a3a39dd3248c2aa90f23';
/// See also [SettingsUIModel].
@ProviderFor(SettingsUIModel)
final settingsUIModelProvider =
AutoDisposeNotifierProvider<SettingsUIModel, SettingsUIState>.internal(
SettingsUIModel.new,
name: r'settingsUIModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$settingsUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SettingsUIModel = AutoDisposeNotifier<SettingsUIState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,265 +0,0 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show Material;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:markdown/markdown.dart' as markdown;
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:url_launcher/url_launcher_string.dart';
class UpgradeDialogUI extends HookConsumerWidget {
const UpgradeDialogUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final appState = ref.watch(appGlobalModelProvider);
final appModel = ref.read(appGlobalModelProvider.notifier);
final description = useState<String?>(null);
final isUsingDiversion = useState(false);
final isUpgrading = useState(false);
final progress = useState(0.0);
final downloadUrl = useState("");
final targetVersion = ConstConf.isMSE
? appState.networkVersionData!.mSELastVersion!
: appState.networkVersionData!.lastVersion!;
final minVersionCode = ConstConf.isMSE
? appState.networkVersionData?.mSEMinVersionCode
: appState.networkVersionData?.minVersionCode;
useEffect(() {
_getUpdateInfo(context, targetVersion, description, downloadUrl);
return null;
}, []);
return Material(
child: ContentDialog(
title:
Text(S.current.app_upgrade_title_new_version_found(targetVersion)),
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (description.value == null) ...[
Center(
child: Column(
children: [
const ProgressRing(),
const SizedBox(height: 16),
Text(S.current
.app_upgrade_info_getting_new_version_details)
],
),
)
] else
...makeMarkdownView(description.value!,
attachmentsUrl: URLConf.giteaAttachmentsUrl),
],
),
),
)),
if (isUsingDiversion.value) ...[
const SizedBox(height: 24),
GestureDetector(
onTap: _launchReleaseUrl,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(.1),
borderRadius: BorderRadius.circular(7)),
child: Text(
S.current.app_upgrade_info_update_server_tip,
style: TextStyle(
fontSize: 14, color: Colors.white.withOpacity(.7)),
),
),
),
],
if (isUpgrading.value) ...[
const SizedBox(height: 24),
Row(
children: [
Text(progress.value == 100
? S.current.app_upgrade_info_installing
: S.current.app_upgrade_info_downloading(
progress.value.toStringAsFixed(2))),
Expanded(
child: ProgressBar(
value: progress.value == 100 ? null : progress.value,
)),
],
),
],
],
),
actions: isUpgrading.value
? null
: [
if (downloadUrl.value.isNotEmpty)
FilledButton(
onPressed: () => _doUpgrade(
context,
appState,
isUpgrading,
appModel,
downloadUrl,
description,
isUsingDiversion,
progress),
child: Padding(
padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 8, right: 8),
child: Text(S.current.app_upgrade_action_update_now),
)),
if (ConstConf.appVersionCode >= (minVersionCode ?? 0))
Button(
onPressed: () => _doCancel(context),
child: Padding(
padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 8, right: 8),
child: Text(S.current.app_upgrade_action_next_time),
)),
],
),
);
}
Future<void> _getUpdateInfo(
BuildContext context,
String targetVersion,
ValueNotifier<String?> description,
ValueNotifier<String> downloadUrl) async {
try {
final r = await Api.getAppReleaseDataByVersionName(targetVersion);
description.value = r["body"];
final assets = List.of(r["assets"] ?? []);
for (var asset in assets) {
if (asset["name"].toString().endsWith("SETUP.exe")) {
downloadUrl.value = asset["browser_download_url"];
}
}
} catch (e) {
dPrint("UpgradeDialogUIModel.loadData Error : $e");
if (!context.mounted) return;
Navigator.pop(context, false);
}
}
void _launchReleaseUrl() {
launchUrlString(URLConf.devReleaseUrl);
}
void _doCancel(BuildContext context) {
Navigator.pop(context, true);
}
String _getDiversionUrl(String description) {
try {
final htmlStr = markdown.markdownToHtml(description);
final html = html_parser.parse(htmlStr);
for (var element in html.querySelectorAll('a')) {
String linkText = element.text;
String linkUrl = element.attributes['href'] ?? '';
if (linkText.trim().endsWith("_SETUP.exe")) {
final diversionDownloadUrl = linkUrl.trim();
dPrint("diversionDownloadUrl === $diversionDownloadUrl");
return diversionDownloadUrl;
}
}
} catch (e) {
dPrint("_checkDiversionUrl Error:$e");
}
return "";
}
Future<void> _doUpgrade(
BuildContext context,
AppGlobalState appState,
ValueNotifier<bool> isUpgrading,
AppGlobalModel appModel,
ValueNotifier<String> downloadUrl,
ValueNotifier<String?> description,
ValueNotifier<bool> isUsingDiversion,
ValueNotifier<double> progress) async {
if (ConstConf.isMSE) {
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
await Future.delayed(const Duration(seconds: 3));
if (ConstConf.appVersionCode <
(appState.networkVersionData?.minVersionCode ?? 0)) {
exit(0);
}
if (!context.mounted) return;
_doCancel(context);
return;
}
isUpgrading.value = true;
final fileName = "${appModel.getUpgradePath()}/next_SETUP.exe";
try {
// check diversionDownloadUrl
var url = downloadUrl.value;
final diversionDownloadUrl = _getDiversionUrl(description.value!);
final dio = Dio();
if (diversionDownloadUrl.isNotEmpty) {
try {
final resp = await dio.head(diversionDownloadUrl,
options: Options(
sendTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10)));
if (resp.statusCode == 200) {
isUsingDiversion.value = true;
url = diversionDownloadUrl;
} else {
isUsingDiversion.value = false;
}
dPrint("diversionDownloadUrl head resp == ${resp.headers}");
} catch (e) {
dPrint("diversionDownloadUrl err:$e");
}
}
await dio.download(url, fileName,
onReceiveProgress: (int count, int total) {
progress.value = (count / total) * 100;
});
} catch (_) {
isUpgrading.value = false;
progress.value = 0;
if (!context.mounted) return;
showToast(context, S.current.app_upgrade_info_download_failed);
return;
}
try {
final r = await (Process.run(
SystemHelper.powershellPath, ["start", fileName, "/SILENT"]));
if (r.stderr.toString().isNotEmpty) {
throw r.stderr;
}
exit(0);
} catch (_) {
isUpgrading.value = false;
progress.value = 0;
if (!context.mounted) return;
showToast(context, S.current.app_upgrade_info_run_failed);
SystemHelper.openDir(fileName);
}
}
}

View File

@ -12,7 +12,6 @@ import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
class SplashUI extends HookConsumerWidget {
@ -86,17 +85,18 @@ class SplashUI extends HookConsumerWidget {
dPrint("_initApp checkUpdate");
await appModel.checkUpdate(context);
stepState.value = 2;
dPrint("_initApp aria2cModelProvider");
ref.read(aria2cModelProvider);
if (!context.mounted) return;
context.go("/index");
context.go("/");
}
_showAlert(BuildContext context, Box<dynamic> appConf) async {
final userOk = await showConfirmDialogs(
context,
S.current.app_splash_dialog_u_a_p_p,
MarkdownWidget(data: S.current.app_splash_dialog_u_a_p_p_content),
MarkdownWidget(
data: S.current.app_splash_dialog_u_a_p_p_content,
shrinkWrap: true,
),
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .5));
if (userOk) {

View File

@ -1,296 +0,0 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/utils/async.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
class HostsBoosterDialogUI extends HookConsumerWidget {
const HostsBoosterDialogUI({super.key});
static final _hostsMap = {
"Recaptcha": ["www.recaptcha.net", "recaptcha.net"],
S.current.tools_hosts_info_rsi_official_website: [
"robertsspaceindustries.com"
],
S.current.tools_hosts_info_rsi_customer_service: [
"support.robertsspaceindustries.com"
],
};
@override
Widget build(BuildContext context, WidgetRef ref) {
final checkedMap = useState<Map<String, bool>>({});
final workingMap = useState<Map<String, int?>>({});
final workingText = useState<String>("");
doHost(BuildContext context) async {
if (workingMap.value.isEmpty) {
final hasTrue =
checkedMap.value.values.where((element) => element).firstOrNull !=
null;
if (!hasTrue) {
for (var k in _hostsMap.keys) {
checkedMap.value[k] = true;
}
checkedMap.value = Map.from(checkedMap.value);
}
}
workingText.value = S.current.tools_hosts_info_dns_query_and_test;
final ipsMap = await _doCheckDns(workingMap, checkedMap);
workingText.value = S.current.tools_hosts_info_writing_hosts;
if (!context.mounted) return;
await _doWriteHosts(ipsMap).unwrap(context: context);
workingText.value = S.current.tools_hosts_info_reading_config;
await _readHostsState(workingMap, checkedMap);
workingText.value = "";
}
useEffect(() {
AnalyticsApi.touch("host_dns_boost");
// 监听 Hosts 文件变更
_readHostsState(workingMap, checkedMap);
return null;
}, []);
return ContentDialog(
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
title: Row(
children: [
IconButton(
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed:
workingText.value.isEmpty ? Navigator.of(context).pop : null),
const SizedBox(width: 12),
Text(S.current.tools_hosts_info_hosts_acceleration),
const Spacer(),
Button(
onPressed: () => _openHostsFile(context),
child: Padding(
padding: const EdgeInsets.all(3),
child: Row(
children: [
const Icon(FluentIcons.open_file),
const SizedBox(width: 6),
Text(S.current.tools_hosts_info_open_hosts_file),
],
),
))
],
),
content: AnimatedSize(
duration: const Duration(milliseconds: 200),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 12),
Row(
children: [
const SizedBox(width: 12),
Text(S.current.tools_hosts_info_status),
const SizedBox(width: 38),
Text(S.current.tools_hosts_info_site),
const Spacer(),
Text(S.current.tools_hosts_info_enable),
const SizedBox(width: 12),
],
),
const SizedBox(height: 12),
ListView.builder(
itemCount: _hostsMap.length,
shrinkWrap: true,
padding: const EdgeInsets.all(6),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
final isEnable =
checkedMap.value[_hostsMap.keys.elementAt(index)] ?? false;
final workingState =
workingMap.value[_hostsMap.keys.elementAt(index)];
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
if (workingState == null)
Icon(FontAwesomeIcons.xmark,
size: 24, color: Colors.red),
if (workingState == 0)
const SizedBox(
width: 24, height: 24, child: ProgressRing()),
if (workingState == 1)
Icon(FontAwesomeIcons.check,
size: 24, color: Colors.green),
const SizedBox(width: 24),
const SizedBox(width: 12),
Text(_hostsMap.keys.elementAt(index)),
const Spacer(),
ToggleSwitch(
onChanged: (value) {
checkedMap.value[_hostsMap.keys.elementAt(index)] =
value;
checkedMap.value = Map.from(checkedMap.value);
},
checked: isEnable,
),
],
),
);
},
),
const SizedBox(height: 12),
if (workingText.value.isNotEmpty)
SizedBox(
height: 86,
child: Column(
children: [
const SizedBox(
height: 42, width: 42, child: ProgressRing()),
const SizedBox(height: 12),
Text(workingText.value),
],
),
)
else
Padding(
padding: const EdgeInsets.all(12),
child: FilledButton(
onPressed: () => doHost(context),
child: Padding(
padding: const EdgeInsets.only(
top: 3, bottom: 3, left: 12, right: 12),
child: Text(
S.current.tools_hosts_action_one_click_acceleration),
),
),
),
],
),
),
);
}
Future<void> _openHostsFile(BuildContext context) async {
// 使用管理员权限调用记事本${S.current.tools_hosts_info_open_hosts_file}
Process.run(SystemHelper.powershellPath, [
"-Command",
"Start-Process notepad.exe -Verb runAs -ArgumentList ${SystemHelper.getHostsFilePath()}"
// ignore: use_build_context_synchronously
]).unwrap(context: context);
}
Future<Map<String, String>> _doCheckDns(
ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap) async {
Map<String, String> result = {};
final trueLen = checkedMap.value.values.where((element) => element).length;
if (trueLen == 0) {
return result;
}
for (var kv in _hostsMap.entries) {
final siteName = kv.key;
final siteHost = kv.value.first;
if (!(checkedMap.value[siteName] ?? false)) {
continue;
}
workingMap.value[siteName] = 0;
workingMap.value = Map.from(workingMap.value);
RSHttp.dnsLookupIps(siteHost).then((ips) async {
int tryCount = ips.length;
try {
for (var ip in ips) {
final resp =
await RSHttp.head("https://$siteHost", withIpAddress: ip);
dPrint(
"[HostsBooster] host== $siteHost ip== $ip resp== ${resp.headers}");
if (resp.headers.isNotEmpty) {
if (result[siteName] == null) {
result[siteName] = ip;
workingMap.value[siteName] = 1;
workingMap.value = Map.from(workingMap.value);
break;
}
}
}
} catch (e) {
tryCount--;
if (tryCount == 0) {
workingMap.value[siteName] = null;
workingMap.value = Map.from(workingMap.value);
result[siteName] = "";
}
}
}, onError: (e) {
workingMap.value[siteName] = null;
workingMap.value = Map.from(workingMap.value);
result[siteName] = "";
});
}
while (true) {
await Future.delayed(const Duration(milliseconds: 100));
if (result.length == trueLen) {
return result;
}
}
}
Future<void> _doWriteHosts(Map<String, String> ipsMap) async {
// 读取 hosts 文件
final hostsFile = File(SystemHelper.getHostsFilePath());
final hostsFileString = await hostsFile.readAsString();
final hostsFileLines = hostsFileString.split("\n");
final newHostsFileLines = <String>[];
// copy Lines
for (var line in hostsFileLines) {
if (line.contains("#StarCitizenToolBox")) {
break;
}
newHostsFileLines.add(line);
}
dPrint("userHostsFile == $hostsFileString");
for (var kv in ipsMap.entries) {
final domains = _hostsMap[kv.key] ?? <String>[];
for (var domain in domains) {
if (kv.value != "") {
newHostsFileLines
.add("${kv.value} $domain #StarCitizenToolBox");
}
}
}
await hostsFile.writeAsString(newHostsFileLines.join("\n"), flush: true);
}
Future<void> _readHostsState(ValueNotifier<Map<String, int?>> workingMap,
ValueNotifier<Map<String, bool>> checkedMap) async {
workingMap.value.clear();
final hostsFile = File(SystemHelper.getHostsFilePath());
final hostsFileString = await hostsFile.readAsString();
final hostsFileLines = hostsFileString.split("\n");
dPrint("userHostsFile == $hostsFileString");
for (var line in hostsFileLines) {
if (line.contains("#StarCitizenToolBox")) {
for (var host in _hostsMap.entries) {
if (line.contains(" ${host.value.first}")) {
workingMap.value[host.key] = 1;
workingMap.value = Map.from(workingMap.value);
checkedMap.value[host.key] = true;
checkedMap.value = Map.from(checkedMap.value);
}
}
}
}
}
}

View File

@ -1,482 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/app.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/rust/api/asar_api.dart' as asar_api;
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/generated/no_l10n_strings.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
part 'rsi_launcher_enhance_dialog_ui.freezed.dart';
@freezed
class RSILauncherStateData with _$RSILauncherStateData {
const factory RSILauncherStateData({
required String version,
required asar_api.RsiLauncherAsarData data,
required String serverData,
@Default(false) bool isPatchInstalled,
String? enabledLocalization,
bool? enableDownloaderBoost,
}) = _RSILauncherStateData;
}
class RsiLauncherEnhanceDialogUI extends HookConsumerWidget {
final bool showNotGameInstallMsg;
const RsiLauncherEnhanceDialogUI(
{super.key, this.showNotGameInstallMsg = false});
static const supportLocalizationMap = {
"en": NoL10n.langEn,
"zh_CN": NoL10n.langZHS,
"zh_TW": NoL10n.langZHT,
"fr": NoL10n.langFR,
};
@override
Widget build(BuildContext context, WidgetRef ref) {
final workingText = useState("");
final assarState = useState<RSILauncherStateData?>(null);
final expandEnhance = useState(false);
Future<void> readState() async {
workingText.value = S.current.tools_rsi_launcher_enhance_init_msg1;
assarState.value = await _readState(context).unwrap(context: context);
if (assarState.value == null) {
workingText.value = "";
return;
}
workingText.value = S.current.tools_rsi_launcher_enhance_init_msg2;
if (!context.mounted) return;
await _loadEnhanceData(context, ref, assarState)
.unwrap(context: context)
.unwrap(context: context);
workingText.value = "";
}
void doInstall() async {
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .35));
return;
}
if (!context.mounted) return;
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg1;
final newScript =
await _genNewScript(assarState).unwrap(context: context);
workingText.value = S.current.tools_rsi_launcher_enhance_working_msg2;
if (!context.mounted) return;
await assarState.value?.data
.writeMainJs(content: utf8.encode(newScript))
.unwrap(context: context);
AnalyticsApi.touch("rsi_launcher_mod_apply");
await readState();
}
useEffect(() {
AnalyticsApi.touch("rsi_launcher_mod_launch");
readState();
return null;
}, const []);
return ContentDialog(
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .48),
title: Row(children: [
IconButton(
icon: const Icon(
FluentIcons.back,
size: 22,
),
onPressed:
workingText.value.isEmpty ? Navigator.of(context).pop : null),
const SizedBox(width: 12),
Text(S.current.tools_rsi_launcher_enhance_title),
]),
content: AnimatedSize(
duration: const Duration(milliseconds: 130),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (showNotGameInstallMsg) ...[
InfoBar(
title: const SizedBox(),
content: Text(S.current
.home_localization_action_rsi_launcher_no_game_path_msg),
style: InfoBarThemeData(decoration: (severity) {
return BoxDecoration(
color: Colors.orange,
);
}, iconColor: (severity) {
return Colors.white;
}),
),
const SizedBox(
height: 12,
),
],
if (workingText.value.isNotEmpty) ...[
Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Row(),
const SizedBox(height: 12),
const ProgressRing(),
const SizedBox(height: 12),
Text(workingText.value),
const SizedBox(height: 12),
],
),
),
] else ...[
Row(
children: [
Expanded(
child: Text(
S.current.tools_rsi_launcher_enhance_msg_version(
assarState.value?.version ?? ""),
style: TextStyle(
color: Colors.white.withOpacity(.6),
),
),
),
Text(
S.current.tools_rsi_launcher_enhance_msg_patch_status(
(assarState.value?.isPatchInstalled ?? false)
? S.current.localization_info_installed
: S.current.tools_action_info_not_installed),
style: TextStyle(
color: Colors.white.withOpacity(.6),
),
)
],
),
if (assarState.value?.serverData.isEmpty ?? true) ...[
Text(S.current.tools_rsi_launcher_enhance_msg_error),
] else ...[
const SizedBox(height: 24),
if (assarState.value?.enabledLocalization != null)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current
.tools_rsi_launcher_enhance_title_localization),
const SizedBox(height: 3),
Text(
S.current
.tools_rsi_launcher_enhance_subtitle_localization,
style: TextStyle(
fontSize: 13,
color: Colors.white.withOpacity(.6),
),
),
],
)),
ComboBox(
items: [
for (final key in supportLocalizationMap.keys)
ComboBoxItem(
value: key,
child: Text(supportLocalizationMap[key]!))
],
value: assarState.value?.enabledLocalization,
onChanged: (v) {
assarState.value = assarState.value!
.copyWith(enabledLocalization: v);
},
),
],
)),
const SizedBox(height: 3),
if (assarState.value?.enableDownloaderBoost != null) ...[
IconButton(
icon: Padding(
padding: const EdgeInsets.only(top: 3, bottom: 3),
child: Row(
children: [
Expanded(
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(expandEnhance.value
? FluentIcons.chevron_up
: FluentIcons.chevron_down),
const SizedBox(width: 12),
Text(expandEnhance.value
? S.current
.tools_rsi_launcher_enhance_action_fold
: S.current
.tools_rsi_launcher_enhance_action_expand),
],
))),
],
),
),
onPressed: () async {
if (!expandEnhance.value) {
final userOK = await showConfirmDialogs(
context,
S.current.tools_rsi_launcher_enhance_note_title,
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current
.tools_rsi_launcher_enhance_note_msg),
],
),
constraints: BoxConstraints(
maxWidth:
MediaQuery.of(context).size.width * .55));
if (!userOK) return;
}
expandEnhance.value = !expandEnhance.value;
},
),
if (expandEnhance.value)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(S.current
.tools_rsi_launcher_enhance_title_download_booster),
const SizedBox(height: 3),
Text(
S.current
.tools_rsi_launcher_enhance_subtitle_download_booster,
style: TextStyle(
fontSize: 13,
color: Colors.white.withOpacity(.6),
),
),
],
)),
ToggleSwitch(
onChanged: (value) {
assarState.value = assarState.value
?.copyWith(enableDownloaderBoost: value);
},
checked: assarState.value?.enableDownloaderBoost ??
false,
)
])),
],
const SizedBox(height: 12),
Center(
child: FilledButton(
onPressed: doInstall,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
child: Text(S.current
.tools_rsi_launcher_enhance_action_install),
))),
],
const SizedBox(height: 16),
Text(
S.current.tools_rsi_launcher_enhance_msg_uninstall,
style: TextStyle(
color: Colors.white.withOpacity(.6), fontSize: 13),
),
],
],
),
),
);
}
Future<RSILauncherStateData?> _readState(BuildContext context) async {
final lPath = await SystemHelper.getRSILauncherPath(skipEXE: true);
if (lPath.isEmpty) {
if (!context.mounted) return null;
showToast(context,
S.current.tools_rsi_launcher_enhance_msg_error_launcher_notfound);
return null;
}
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherPath ==== $lPath");
final dataPath = "${lPath}resources\\app.asar";
dPrint("[RsiLauncherEnhanceDialogUI] rsiLauncherDataPath ==== $dataPath");
try {
final data = await asar_api.getRsiLauncherAsarData(asarPath: dataPath);
dPrint(
"[RsiLauncherEnhanceDialogUI] rsiLauncherPath main.js path == ${data.mainJsPath}");
final version =
RegExp(r"main\.(\w+)\.js").firstMatch(data.mainJsPath)?.group(1);
if (version == null) {
if (!context.mounted) return null;
showToast(
context,
S.current
.tools_rsi_launcher_enhance_msg_error_get_launcher_info_error);
return null;
}
dPrint(
"[RsiLauncherEnhanceDialogUI] rsiLauncherPath main.js version == $version");
final mainJsString = String.fromCharCodes(data.mainJsContent);
final (enabledLocalization, enableDownloaderBoost) =
_readScriptState(mainJsString);
return RSILauncherStateData(
version: version,
data: data,
serverData: "",
isPatchInstalled: mainJsString.contains("SC_TOOLBOX"),
enabledLocalization: enabledLocalization,
enableDownloaderBoost: enableDownloaderBoost,
);
} catch (e) {
if (!context.mounted) return null;
showToast(
context,
S.current
.tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args(
e));
return null;
}
}
Future<String> _loadEnhanceData(BuildContext context, WidgetRef ref,
ValueNotifier<RSILauncherStateData?> assarState) async {
final globalModel = ref.read(appGlobalModelProvider);
final enhancePath =
"${globalModel.applicationSupportDir}/launcher_enhance_data";
final enhanceFile =
File("$enhancePath/${assarState.value?.version}.tar.gz");
if (!await enhanceFile.exists()) {
final downloadUrl =
"${URLConf.gitApiRSILauncherEnhanceUrl}/archive/${assarState.value?.version}.tar.gz";
final r = await RSHttp.get(downloadUrl).unwrap();
if (r.statusCode != 200 || r.data == null) {
return "";
}
await enhanceFile.create(recursive: true);
await enhanceFile.writeAsBytes(r.data!, flush: true);
}
final severMainJS =
await compute(_readArchive, (enhanceFile.path, "main.js"));
final serverMainJSString = severMainJS.toString();
final scriptState = _readScriptState(serverMainJSString);
if (assarState.value?.enabledLocalization == null) {
assarState.value =
assarState.value?.copyWith(enabledLocalization: scriptState.$1);
dPrint(
"[RsiLauncherEnhanceDialogUI] _loadEnhanceData enabledLocalization == ${scriptState.$1}");
}
if (assarState.value?.enableDownloaderBoost == null) {
assarState.value =
assarState.value?.copyWith(enableDownloaderBoost: scriptState.$2);
dPrint(
"[RsiLauncherEnhanceDialogUI] _loadEnhanceData enableDownloaderBoost == ${scriptState.$2}");
}
assarState.value =
assarState.value?.copyWith(serverData: serverMainJSString);
return serverMainJSString;
}
static StringBuffer _readArchive((String savePath, String fileName) data) {
final inputStream = InputFileStream(data.$1);
final archive =
TarDecoder().decodeBytes(GZipDecoder().decodeBuffer(inputStream));
StringBuffer dataBuffer = StringBuffer("");
for (var element in archive.files) {
if (element.name.endsWith(data.$2)) {
for (var value
in (element.rawContent?.readString() ?? "").split("\n")) {
final tv = value;
if (tv.isNotEmpty) dataBuffer.writeln(tv);
}
}
}
archive.clear();
return dataBuffer;
}
// ignore: constant_identifier_names
static const SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START =
"const SC_TOOLBOX_ENABLED_LOCALIZATION = ";
// ignore: constant_identifier_names
static const SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START =
"const SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST = ";
(String?, bool?) _readScriptState(String mainJsString) {
String? enabledLocalization;
bool? enableDownloaderBoost;
for (final line in mainJsString.split("\n")) {
final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
enabledLocalization = lineTrim
.substring(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START.length)
.replaceAll("\"", "")
.replaceAll(";", "");
} else if (lineTrim
.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
enableDownloaderBoost = lineTrim
.substring(
SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START.length)
.toLowerCase() ==
"true;";
}
}
return (enabledLocalization, enableDownloaderBoost);
}
Future<String> _genNewScript(
ValueNotifier<RSILauncherStateData?> assarState) async {
final serverScriptLines = assarState.value!.serverData.split("\n");
final StringBuffer scriptBuffer = StringBuffer("");
for (final line in serverScriptLines) {
final lineTrim = line.trim();
if (lineTrim.startsWith(SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START)) {
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLED_LOCALIZATION_SCRIPT_START\"${assarState.value!.enabledLocalization}\";");
} else if (lineTrim
.startsWith(SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START)) {
scriptBuffer.writeln(
"$SC_TOOLBOX_ENABLE_DOWNLOADER_BOOST_SCRIPT_START${assarState.value!.enableDownloaderBoost};");
} else {
scriptBuffer.writeln(line);
}
}
return scriptBuffer.toString();
}
}

View File

@ -1,256 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'rsi_launcher_enhance_dialog_ui.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$RSILauncherStateData {
String get version => throw _privateConstructorUsedError;
asar_api.RsiLauncherAsarData get data => throw _privateConstructorUsedError;
String get serverData => throw _privateConstructorUsedError;
bool get isPatchInstalled => throw _privateConstructorUsedError;
String? get enabledLocalization => throw _privateConstructorUsedError;
bool? get enableDownloaderBoost => throw _privateConstructorUsedError;
/// Create a copy of RSILauncherStateData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RSILauncherStateDataCopyWith<RSILauncherStateData> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $RSILauncherStateDataCopyWith<$Res> {
factory $RSILauncherStateDataCopyWith(RSILauncherStateData value,
$Res Function(RSILauncherStateData) then) =
_$RSILauncherStateDataCopyWithImpl<$Res, RSILauncherStateData>;
@useResult
$Res call(
{String version,
asar_api.RsiLauncherAsarData data,
String serverData,
bool isPatchInstalled,
String? enabledLocalization,
bool? enableDownloaderBoost});
}
/// @nodoc
class _$RSILauncherStateDataCopyWithImpl<$Res,
$Val extends RSILauncherStateData>
implements $RSILauncherStateDataCopyWith<$Res> {
_$RSILauncherStateDataCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of RSILauncherStateData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? version = null,
Object? data = null,
Object? serverData = null,
Object? isPatchInstalled = null,
Object? enabledLocalization = freezed,
Object? enableDownloaderBoost = freezed,
}) {
return _then(_value.copyWith(
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as asar_api.RsiLauncherAsarData,
serverData: null == serverData
? _value.serverData
: serverData // ignore: cast_nullable_to_non_nullable
as String,
isPatchInstalled: null == isPatchInstalled
? _value.isPatchInstalled
: isPatchInstalled // ignore: cast_nullable_to_non_nullable
as bool,
enabledLocalization: freezed == enabledLocalization
? _value.enabledLocalization
: enabledLocalization // ignore: cast_nullable_to_non_nullable
as String?,
enableDownloaderBoost: freezed == enableDownloaderBoost
? _value.enableDownloaderBoost
: enableDownloaderBoost // ignore: cast_nullable_to_non_nullable
as bool?,
) as $Val);
}
}
/// @nodoc
abstract class _$$RSILauncherStateDataImplCopyWith<$Res>
implements $RSILauncherStateDataCopyWith<$Res> {
factory _$$RSILauncherStateDataImplCopyWith(_$RSILauncherStateDataImpl value,
$Res Function(_$RSILauncherStateDataImpl) then) =
__$$RSILauncherStateDataImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String version,
asar_api.RsiLauncherAsarData data,
String serverData,
bool isPatchInstalled,
String? enabledLocalization,
bool? enableDownloaderBoost});
}
/// @nodoc
class __$$RSILauncherStateDataImplCopyWithImpl<$Res>
extends _$RSILauncherStateDataCopyWithImpl<$Res, _$RSILauncherStateDataImpl>
implements _$$RSILauncherStateDataImplCopyWith<$Res> {
__$$RSILauncherStateDataImplCopyWithImpl(_$RSILauncherStateDataImpl _value,
$Res Function(_$RSILauncherStateDataImpl) _then)
: super(_value, _then);
/// Create a copy of RSILauncherStateData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? version = null,
Object? data = null,
Object? serverData = null,
Object? isPatchInstalled = null,
Object? enabledLocalization = freezed,
Object? enableDownloaderBoost = freezed,
}) {
return _then(_$RSILauncherStateDataImpl(
version: null == version
? _value.version
: version // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as asar_api.RsiLauncherAsarData,
serverData: null == serverData
? _value.serverData
: serverData // ignore: cast_nullable_to_non_nullable
as String,
isPatchInstalled: null == isPatchInstalled
? _value.isPatchInstalled
: isPatchInstalled // ignore: cast_nullable_to_non_nullable
as bool,
enabledLocalization: freezed == enabledLocalization
? _value.enabledLocalization
: enabledLocalization // ignore: cast_nullable_to_non_nullable
as String?,
enableDownloaderBoost: freezed == enableDownloaderBoost
? _value.enableDownloaderBoost
: enableDownloaderBoost // ignore: cast_nullable_to_non_nullable
as bool?,
));
}
}
/// @nodoc
class _$RSILauncherStateDataImpl implements _RSILauncherStateData {
const _$RSILauncherStateDataImpl(
{required this.version,
required this.data,
required this.serverData,
this.isPatchInstalled = false,
this.enabledLocalization,
this.enableDownloaderBoost});
@override
final String version;
@override
final asar_api.RsiLauncherAsarData data;
@override
final String serverData;
@override
@JsonKey()
final bool isPatchInstalled;
@override
final String? enabledLocalization;
@override
final bool? enableDownloaderBoost;
@override
String toString() {
return 'RSILauncherStateData(version: $version, data: $data, serverData: $serverData, isPatchInstalled: $isPatchInstalled, enabledLocalization: $enabledLocalization, enableDownloaderBoost: $enableDownloaderBoost)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RSILauncherStateDataImpl &&
(identical(other.version, version) || other.version == version) &&
(identical(other.data, data) || other.data == data) &&
(identical(other.serverData, serverData) ||
other.serverData == serverData) &&
(identical(other.isPatchInstalled, isPatchInstalled) ||
other.isPatchInstalled == isPatchInstalled) &&
(identical(other.enabledLocalization, enabledLocalization) ||
other.enabledLocalization == enabledLocalization) &&
(identical(other.enableDownloaderBoost, enableDownloaderBoost) ||
other.enableDownloaderBoost == enableDownloaderBoost));
}
@override
int get hashCode => Object.hash(runtimeType, version, data, serverData,
isPatchInstalled, enabledLocalization, enableDownloaderBoost);
/// Create a copy of RSILauncherStateData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RSILauncherStateDataImplCopyWith<_$RSILauncherStateDataImpl>
get copyWith =>
__$$RSILauncherStateDataImplCopyWithImpl<_$RSILauncherStateDataImpl>(
this, _$identity);
}
abstract class _RSILauncherStateData implements RSILauncherStateData {
const factory _RSILauncherStateData(
{required final String version,
required final asar_api.RsiLauncherAsarData data,
required final String serverData,
final bool isPatchInstalled,
final String? enabledLocalization,
final bool? enableDownloaderBoost}) = _$RSILauncherStateDataImpl;
@override
String get version;
@override
asar_api.RsiLauncherAsarData get data;
@override
String get serverData;
@override
bool get isPatchInstalled;
@override
String? get enabledLocalization;
@override
bool? get enableDownloaderBoost;
/// Create a copy of RSILauncherStateData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RSILauncherStateDataImplCopyWith<_$RSILauncherStateDataImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -1,265 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
class ToolsUI extends HookConsumerWidget {
const ToolsUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(toolsUIModelProvider);
final model = ref.read(toolsUIModelProvider.notifier);
useEffect(() {
addPostFrameCallback(() {
model.loadToolsCard(context, skipPathScan: false);
});
return null;
}, []);
return Stack(
children: [
Column(
children: [
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.only(left: 22, right: 22),
child: Row(
children: [
Expanded(
child: Column(
children: [
makeGameLauncherPathSelect(context, model, state),
const SizedBox(height: 12),
makeGamePathSelect(context, model, state),
],
),
),
const SizedBox(width: 12),
Button(
onPressed: state.working
? null
: () =>
model.loadToolsCard(context, skipPathScan: false),
child: const Padding(
padding: EdgeInsets.only(
top: 30, bottom: 30, left: 12, right: 12),
child: Icon(FluentIcons.refresh),
),
),
],
),
),
const SizedBox(height: 12),
if (state.items.isEmpty)
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(S.current.tools_info_scanning),
],
),
),
)
else
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: MasonryGridView.count(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
itemCount: (state.isItemLoading)
? state.items.length + 1
: state.items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
if (index == state.items.length) {
return Container(
width: 300,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: FluentTheme.of(context).cardColor,
),
child: makeLoading(context));
}
final item = state.items[index];
return Container(
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: FluentTheme.of(context).cardColor,
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(.2),
borderRadius:
BorderRadius.circular(1000)),
child: Padding(
padding: const EdgeInsets.all(12),
child: item.icon,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
item.name,
style: const TextStyle(fontSize: 16),
)),
const SizedBox(width: 12),
],
),
const SizedBox(height: 12),
Text(
item.infoString,
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(.6)),
),
const SizedBox(height: 12),
Row(
children: [
const Spacer(),
Button(
onPressed: state.working
? null
: item.onTap == null
? null
: () {
try {
item.onTap?.call();
} catch (e) {
showToast(
context,
S.current
.tools_info_processing_failed(
e));
}
},
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.play),
),
),
],
)
],
),
),
);
},
),
),
)
],
),
if (state.working)
Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(150),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(S.current.doctor_info_processing),
],
),
),
)
],
);
}
Widget makeGamePathSelect(
BuildContext context, ToolsUIModel model, ToolsUIState state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current.tools_info_game_install_location),
const SizedBox(width: 6),
Expanded(
child: SizedBox(
height: 36,
child: ComboBox<String>(
isExpanded: true,
value: state.scInstalledPath,
items: [
for (final path in state.scInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
onChanged: (v) {
model.loadToolsCard(context, skipPathScan: true);
model.onChangeGamePath(v!);
},
),
),
),
const SizedBox(width: 8),
Button(
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_open),
),
onPressed: () => model.openDir(state.scInstalledPath))
],
);
}
Widget makeGameLauncherPathSelect(
BuildContext context, ToolsUIModel model, ToolsUIState state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current.tools_info_rsi_launcher_location),
const SizedBox(width: 6),
Expanded(
child: SizedBox(
height: 36,
child: ComboBox<String>(
isExpanded: true,
value: state.rsiLauncherInstalledPath,
items: [
for (final path in state.rsiLauncherInstallPaths)
ComboBoxItem(
value: path,
child: Text(path),
)
],
onChanged: (v) {
model.loadToolsCard(context, skipPathScan: true);
model.onChangeLauncherPath(v!);
},
),
),
),
const SizedBox(width: 8),
Button(
child: const Padding(
padding: EdgeInsets.all(6),
child: Icon(FluentIcons.folder_open),
),
onPressed: () => model.openDir(state.rsiLauncherInstalledPath))
],
);
}
}

View File

@ -1,613 +0,0 @@
// ignore_for_file: avoid_build_context_in_providers
import 'dart:convert';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/provider/aria2c.dart';
import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:xml/xml.dart';
import 'dialogs/hosts_booster_dialog_ui.dart';
import 'dialogs/rsi_launcher_enhance_dialog_ui.dart';
part 'tools_ui_model.g.dart';
part 'tools_ui_model.freezed.dart';
class ToolsItemData {
String key;
ToolsItemData(this.key, this.name, this.infoString, this.icon, {this.onTap});
String name;
String infoString;
Widget icon;
AsyncCallback? onTap;
}
@freezed
class ToolsUIState with _$ToolsUIState {
factory ToolsUIState({
@Default(false) bool working,
@Default("") String scInstalledPath,
@Default("") String rsiLauncherInstalledPath,
@Default([]) List<String> scInstallPaths,
@Default([]) List<String> rsiLauncherInstallPaths,
@Default([]) List<ToolsItemData> items,
@Default(false) bool isItemLoading,
}) = _ToolsUIState;
}
@riverpod
class ToolsUIModel extends _$ToolsUIModel {
@override
ToolsUIState build() {
state = ToolsUIState();
return state;
}
loadToolsCard(BuildContext context, {bool skipPathScan = false}) async {
if (state.isItemLoading) return;
var items = <ToolsItemData>[];
state = state.copyWith(items: items, isItemLoading: true);
if (!skipPathScan) {
await reScanPath(context);
}
try {
items = [
ToolsItemData(
"systemnfo",
S.current.tools_action_view_system_info,
S.current.tools_action_info_view_critical_system_info,
const Icon(FluentIcons.system, size: 24),
onTap: () => _showSystemInfo(context),
),
ToolsItemData(
"p4k_downloader",
S.current.tools_action_p4k_download_repair,
S.current.tools_action_info_p4k_download_repair_tip,
const Icon(FontAwesomeIcons.download, size: 24),
onTap: () => _downloadP4k(context),
),
ToolsItemData(
"hosts_booster",
S.current.tools_action_hosts_acceleration_experimental,
S.current.tools_action_info_hosts_acceleration_experimental_tip,
const Icon(FluentIcons.virtual_network, size: 24),
onTap: () => _doHostsBooster(context),
),
ToolsItemData(
"rsilauncher_enhance_mod",
S.current.tools_rsi_launcher_enhance_title,
S.current.tools_action_rsi_launcher_enhance_info,
const Icon(FluentIcons.c_plus_plus, size: 24),
onTap: () => rsiEnhance(context),
),
ToolsItemData(
"reinstall_eac",
S.current.tools_action_reinstall_easyanticheat,
S.current.tools_action_info_reinstall_eac,
const Icon(FluentIcons.game, size: 24),
onTap: () => _reinstallEAC(context),
),
ToolsItemData(
"rsilauncher_admin_mode",
S.current.tools_action_rsi_launcher_admin_mode,
S.current.tools_action_info_run_rsi_as_admin,
const Icon(FluentIcons.admin, size: 24),
onTap: () => _adminRSILauncher(context),
),
ToolsItemData(
"unp4kc",
S.current.tools_action_unp4k,
S.current.tools_action_unp4k_info,
const Icon(FontAwesomeIcons.fileZipper, size: 24),
onTap: () => _unp4kc(context),
),
];
state = state.copyWith(items: items);
if (!context.mounted) return;
items.add(await _addShaderCard(context));
state = state.copyWith(items: items);
if (!context.mounted) return;
items.add(await _addPhotographyCard(context));
state = state.copyWith(items: items);
if (!context.mounted) return;
items.addAll(await _addLogCard(context));
state = state.copyWith(items: items);
if (!context.mounted) return;
items.addAll(await _addNvmePatchCard(context));
state = state.copyWith(items: items, isItemLoading: false);
} catch (e) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_init_failed(e));
}
}
Future<List<ToolsItemData>> _addLogCard(BuildContext context) async {
double logPathLen = 0;
try {
logPathLen =
(await File(await SCLoggerHelper.getLogFilePath() ?? "").length()) /
1024 /
1024;
} catch (_) {}
return [
ToolsItemData(
"rsilauncher_log_fix",
S.current.tools_action_rsi_launcher_log_fix,
S.current.tools_action_info_rsi_launcher_log_issue(
logPathLen.toStringAsFixed(4)),
const Icon(FontAwesomeIcons.bookBible, size: 24),
onTap: () => _rsiLogFix(context),
),
];
}
Future<List<ToolsItemData>> _addNvmePatchCard(BuildContext context) async {
final nvmePatchStatus = await SystemHelper.checkNvmePatchStatus();
return [
if (nvmePatchStatus)
ToolsItemData(
"remove_nvme_settings",
S.current.tools_action_remove_nvme_registry_patch,
S.current.tools_action_info_nvme_patch_issue(nvmePatchStatus
? S.current.localization_info_installed
: S.current.tools_action_info_not_installed),
const Icon(FluentIcons.hard_drive, size: 24),
onTap: nvmePatchStatus
? () async {
state = state.copyWith(working: true);
await SystemHelper.doRemoveNvmePath();
state = state.copyWith(working: false);
if (!context.mounted) return;
showToast(context,
S.current.tools_action_info_removed_restart_effective);
loadToolsCard(context, skipPathScan: true);
}
: null,
),
if (!nvmePatchStatus)
ToolsItemData(
"add_nvme_settings",
S.current.tools_action_write_nvme_registry_patch,
S.current.tools_action_info_manual_nvme_patch,
const Icon(FontAwesomeIcons.cashRegister, size: 24),
onTap: () async {
state = state.copyWith(working: true);
final r = await SystemHelper.addNvmePatch();
if (r == "") {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_fix_success_restart);
} else {
if (!context.mounted) return;
showToast(context, S.current.doctor_action_result_fix_fail(r));
}
state = state.copyWith(working: false);
loadToolsCard(context, skipPathScan: true);
},
)
];
}
Future<ToolsItemData> _addShaderCard(BuildContext context) async {
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final shaderSize = ((await SystemHelper.getDirLen(gameShaderCachePath ?? "",
skipPath: ["$gameShaderCachePath\\Crashes"])) /
1024 /
1024)
.toStringAsFixed(4);
return ToolsItemData(
"clean_shaders",
S.current.tools_action_clear_shader_cache,
S.current.tools_action_info_shader_cache_issue(shaderSize),
const Icon(FontAwesomeIcons.shapes, size: 24),
onTap: () => _cleanShaderCache(context),
);
}
Future<ToolsItemData> _addPhotographyCard(BuildContext context) async {
// 获取配置文件状态
final isEnable = await _checkPhotographyStatus(context);
return ToolsItemData(
"photography_mode",
isEnable
? S.current.tools_action_close_photography_mode
: S.current.tools_action_open_photography_mode,
isEnable
? S.current.tools_action_info_restore_lens_shake
: S.current.tools_action_info_one_key_close_lens_shake,
const Icon(FontAwesomeIcons.camera, size: 24),
onTap: () => _onChangePhotographyMode(context, isEnable),
);
}
/// ---------------------------- func -------------------------------------------------------
/// -----------------------------------------------------------------------------------------
/// -----------------------------------------------------------------------------------------
/// -----------------------------------------------------------------------------------------
Future<void> reScanPath(BuildContext context) async {
var scInstallPaths = <String>[];
var rsiLauncherInstallPaths = <String>[];
var scInstalledPath = "";
var rsiLauncherInstalledPath = "";
state = state.copyWith(
scInstalledPath: scInstalledPath,
rsiLauncherInstalledPath: rsiLauncherInstalledPath,
scInstallPaths: scInstallPaths,
rsiLauncherInstallPaths: rsiLauncherInstallPaths,
);
try {
rsiLauncherInstalledPath = await SystemHelper.getRSILauncherPath();
rsiLauncherInstallPaths.add(rsiLauncherInstalledPath);
final listData = await SCLoggerHelper.getLauncherLogList();
if (listData == null) {
return;
}
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
checkExists: false, withVersion: ["LIVE", "PTU", "EPTU"]);
if (scInstallPaths.isNotEmpty) {
scInstalledPath = scInstallPaths.first;
}
state = state.copyWith(
scInstalledPath: scInstalledPath,
rsiLauncherInstalledPath: rsiLauncherInstalledPath,
scInstallPaths: scInstallPaths,
rsiLauncherInstallPaths: rsiLauncherInstallPaths,
);
} catch (e) {
dPrint(e);
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_log_file_parse_failed);
}
if (rsiLauncherInstalledPath == "") {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_rsi_launcher_not_found);
}
if (scInstalledPath == "") {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_star_citizen_not_found);
}
}
/// 重装EAC
Future<void> _reinstallEAC(BuildContext context) async {
if (state.scInstalledPath.isEmpty) {
showToast(
context, S.current.tools_action_info_valid_game_directory_needed);
return;
}
state = state.copyWith(working: true);
try {
final eacPath = "${state.scInstalledPath}\\EasyAntiCheat";
final eacJsonPath = "$eacPath\\Settings.json";
if (await File(eacJsonPath).exists()) {
Map<String, String> envVars = Platform.environment;
final eacJsonData = await File(eacJsonPath).readAsString();
final Map eacJson = json.decode(eacJsonData);
final eacID = eacJson["productid"];
if (eacID != null) {
final eacCacheDir =
Directory("${envVars["appdata"]}\\EasyAntiCheat\\$eacID");
if (await eacCacheDir.exists()) {
await eacCacheDir.delete(recursive: true);
}
}
}
final dir = Directory(eacPath);
if (await dir.exists()) {
await dir.delete(recursive: true);
}
final eacLauncher =
File("${state.scInstalledPath}\\StarCitizen_Launcher.exe");
if (await eacLauncher.exists()) {
await eacLauncher.delete(recursive: true);
}
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_eac_file_removed);
_adminRSILauncher(context);
} catch (e) {
showToast(context, S.current.tools_action_info_error_occurred(e));
}
state = state.copyWith(working: false);
loadToolsCard(context, skipPathScan: true);
}
Future<String> getSystemInfo() async {
return S.current.tools_action_info_system_info_content(
await SystemHelper.getSystemName(),
await SystemHelper.getCpuName(),
await SystemHelper.getSystemMemorySizeGB(),
await SystemHelper.getGpuInfo(),
await SystemHelper.getDiskInfo());
}
/// 管理员模式运行 RSI 启动器
Future _adminRSILauncher(BuildContext context) async {
if (state.rsiLauncherInstalledPath == "") {
showToast(context,
S.current.tools_action_info_rsi_launcher_directory_not_found);
}
SystemHelper.checkAndLaunchRSILauncher(state.rsiLauncherInstalledPath);
}
Future<void> _rsiLogFix(BuildContext context) async {
state = state.copyWith(working: true);
final path = await SCLoggerHelper.getLogFilePath();
if (!await File(path!).exists()) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_log_file_not_exist);
return;
}
try {
SystemHelper.killRSILauncher();
await File(path).delete(recursive: true);
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_cleanup_complete);
SystemHelper.checkAndLaunchRSILauncher(state.rsiLauncherInstalledPath);
} catch (_) {
if (!context.mounted) return;
showToast(context, S.current.tools_action_info_cleanup_failed(path));
}
state = state.copyWith(working: false);
}
openDir(path) async {
SystemHelper.openDir(path);
}
Future _showSystemInfo(BuildContext context) async {
state = state.copyWith(working: true);
final systemInfo = await getSystemInfo();
if (!context.mounted) return;
showDialog<String>(
context: context,
builder: (context) => ContentDialog(
title: Text(S.current.tools_action_info_system_info_title),
content: Text(systemInfo),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .65,
),
actions: [
FilledButton(
child: Padding(
padding:
const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
child: Text(S.current.action_close),
),
onPressed: () => Navigator.pop(context),
),
],
),
);
state = state.copyWith(working: false);
}
Future<void> _cleanShaderCache(BuildContext context) async {
state = state.copyWith(working: true);
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
final l =
await Directory(gameShaderCachePath!).list(recursive: false).toList();
for (var value in l) {
if (value is Directory) {
if (!value.absolute.path.contains("Crashes")) {
await value.delete(recursive: true);
}
}
}
if (!context.mounted) return;
loadToolsCard(context, skipPathScan: true);
state = state.copyWith(working: false);
}
Future<void> _downloadP4k(BuildContext context) async {
String savePath = state.scInstalledPath;
String fileName = "Data.p4k";
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .35));
return;
}
if (!context.mounted) return;
await showToast(context, S.current.tools_action_info_p4k_file_description);
try {
state = state.copyWith(working: true);
final aria2cManager = ref.read(aria2cModelProvider.notifier);
await aria2cManager
.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
final aria2c = ref.read(aria2cModelProvider).aria2c!;
// check download task list
for (var value in [
...await aria2c.tellActive(),
...await aria2c.tellWaiting(0, 100000)
]) {
final t = HomeDownloaderUIModel.getTaskTypeAndName(value);
if (t.key == "torrent" && t.value.contains("Data.p4k")) {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_p4k_download_in_progress);
state = state.copyWith(working: false);
return;
}
}
var torrentUrl = "";
final l = await Api.getAppTorrentDataList();
for (var torrent in l) {
if (torrent.name == "Data.p4k") {
torrentUrl = torrent.url!;
}
}
if (torrentUrl == "") {
state = state.copyWith(working: false);
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_function_under_maintenance);
return;
}
final userSelect = await FilePicker.platform.saveFile(
initialDirectory: savePath,
fileName: fileName,
lockParentWindow: true);
if (userSelect == null) {
state = state.copyWith(working: false);
return;
}
savePath = userSelect;
dPrint(savePath);
if (savePath.endsWith("\\$fileName")) {
savePath = savePath.substring(0, savePath.length - fileName.length - 1);
}
if (!context.mounted) return;
final btData = await RSHttp.get(torrentUrl).unwrap(context: context);
if (btData == null || btData.data == null) {
state = state.copyWith(working: false);
return;
}
final b64Str = base64Encode(btData.data!);
final gid =
await aria2c.addTorrent(b64Str, extraParams: {"dir": savePath});
state = state.copyWith(working: false);
dPrint("Aria2cManager.aria2c.addUri resp === $gid");
await aria2c.saveSession();
AnalyticsApi.touch("p4k_download");
if (!context.mounted) return;
context.push("/index/downloader");
} catch (e) {
state = state.copyWith(working: false);
if (!context.mounted) return;
showToast(context, S.current.app_init_failed_with_reason(e));
}
await Future.delayed(const Duration(seconds: 3));
launchUrlString(
"https://citizenwiki.cn/SC%E6%B1%89%E5%8C%96%E7%9B%92%E5%AD%90#%E5%88%86%E6%B5%81%E4%B8%8B%E8%BD%BD%E6%95%99%E7%A8%8B");
}
Future<bool> _checkPhotographyStatus(BuildContext context,
{bool? setMode}) async {
final scInstalledPath = state.scInstalledPath;
final keys = ["AudioShakeStrength", "CameraSpringMovement", "ShakeScale"];
final attributesFile = File(
"$scInstalledPath\\USER\\Client\\0\\Profiles\\default\\attributes.xml");
if (setMode == null) {
bool isEnable = false;
if (scInstalledPath.isNotEmpty) {
if (await attributesFile.exists()) {
final xmlFile =
XmlDocument.parse(await attributesFile.readAsString());
isEnable = true;
for (var k in keys) {
if (!isEnable) break;
final e = xmlFile.rootElement.children
.where((element) => element.getAttribute("name") == k)
.firstOrNull;
if (e != null && e.getAttribute("value") == "0") {
} else {
isEnable = false;
}
}
}
}
return isEnable;
} else {
if (!await attributesFile.exists()) {
if (!context.mounted) return false;
showToast(context, S.current.tools_action_info_config_file_not_exist);
return false;
}
final xmlFile = XmlDocument.parse(await attributesFile.readAsString());
// clear all
xmlFile.rootElement.children.removeWhere(
(element) => keys.contains(element.getAttribute("name")));
if (setMode) {
for (var element in keys) {
XmlElement newNode = XmlElement(XmlName('Attr'), [
XmlAttribute(XmlName('name'), element),
XmlAttribute(XmlName('value'), '0'),
]);
xmlFile.rootElement.children.add(newNode);
}
}
dPrint(xmlFile);
await attributesFile.delete();
await attributesFile.writeAsString(xmlFile.toXmlString(pretty: true));
}
return true;
}
_onChangePhotographyMode(BuildContext context, bool isEnable) async {
_checkPhotographyStatus(context, setMode: !isEnable)
.unwrap(context: context);
loadToolsCard(context, skipPathScan: true);
}
void onChangeGamePath(String v) {
state = state.copyWith(scInstalledPath: v);
}
void onChangeLauncherPath(String s) {
state = state.copyWith(rsiLauncherInstalledPath: s);
}
_doHostsBooster(BuildContext context) async {
showDialog(
context: context,
builder: (BuildContext context) => const HostsBoosterDialogUI());
}
_unp4kc(BuildContext context) async {
context.push("/tools/unp4kc");
}
static rsiEnhance(BuildContext context,
{bool showNotGameInstallMsg = false}) async {
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
if (!context.mounted) return;
showToast(
context, S.current.tools_action_info_rsi_launcher_running_warning,
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .35));
return;
}
if (!context.mounted) return;
showDialog(
context: context,
builder: (BuildContext context) => RsiLauncherEnhanceDialogUI(
showNotGameInstallMsg: showNotGameInstallMsg,
));
}
}

View File

@ -1,311 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'tools_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ToolsUIState {
bool get working => throw _privateConstructorUsedError;
String get scInstalledPath => throw _privateConstructorUsedError;
String get rsiLauncherInstalledPath => throw _privateConstructorUsedError;
List<String> get scInstallPaths => throw _privateConstructorUsedError;
List<String> get rsiLauncherInstallPaths =>
throw _privateConstructorUsedError;
List<ToolsItemData> get items => throw _privateConstructorUsedError;
bool get isItemLoading => throw _privateConstructorUsedError;
/// Create a copy of ToolsUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ToolsUIStateCopyWith<ToolsUIState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ToolsUIStateCopyWith<$Res> {
factory $ToolsUIStateCopyWith(
ToolsUIState value, $Res Function(ToolsUIState) then) =
_$ToolsUIStateCopyWithImpl<$Res, ToolsUIState>;
@useResult
$Res call(
{bool working,
String scInstalledPath,
String rsiLauncherInstalledPath,
List<String> scInstallPaths,
List<String> rsiLauncherInstallPaths,
List<ToolsItemData> items,
bool isItemLoading});
}
/// @nodoc
class _$ToolsUIStateCopyWithImpl<$Res, $Val extends ToolsUIState>
implements $ToolsUIStateCopyWith<$Res> {
_$ToolsUIStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ToolsUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? working = null,
Object? scInstalledPath = null,
Object? rsiLauncherInstalledPath = null,
Object? scInstallPaths = null,
Object? rsiLauncherInstallPaths = null,
Object? items = null,
Object? isItemLoading = null,
}) {
return _then(_value.copyWith(
working: null == working
? _value.working
: working // ignore: cast_nullable_to_non_nullable
as bool,
scInstalledPath: null == scInstalledPath
? _value.scInstalledPath
: scInstalledPath // ignore: cast_nullable_to_non_nullable
as String,
rsiLauncherInstalledPath: null == rsiLauncherInstalledPath
? _value.rsiLauncherInstalledPath
: rsiLauncherInstalledPath // ignore: cast_nullable_to_non_nullable
as String,
scInstallPaths: null == scInstallPaths
? _value.scInstallPaths
: scInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
rsiLauncherInstallPaths: null == rsiLauncherInstallPaths
? _value.rsiLauncherInstallPaths
: rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
items: null == items
? _value.items
: items // ignore: cast_nullable_to_non_nullable
as List<ToolsItemData>,
isItemLoading: null == isItemLoading
? _value.isItemLoading
: isItemLoading // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$ToolsUIStateImplCopyWith<$Res>
implements $ToolsUIStateCopyWith<$Res> {
factory _$$ToolsUIStateImplCopyWith(
_$ToolsUIStateImpl value, $Res Function(_$ToolsUIStateImpl) then) =
__$$ToolsUIStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool working,
String scInstalledPath,
String rsiLauncherInstalledPath,
List<String> scInstallPaths,
List<String> rsiLauncherInstallPaths,
List<ToolsItemData> items,
bool isItemLoading});
}
/// @nodoc
class __$$ToolsUIStateImplCopyWithImpl<$Res>
extends _$ToolsUIStateCopyWithImpl<$Res, _$ToolsUIStateImpl>
implements _$$ToolsUIStateImplCopyWith<$Res> {
__$$ToolsUIStateImplCopyWithImpl(
_$ToolsUIStateImpl _value, $Res Function(_$ToolsUIStateImpl) _then)
: super(_value, _then);
/// Create a copy of ToolsUIState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? working = null,
Object? scInstalledPath = null,
Object? rsiLauncherInstalledPath = null,
Object? scInstallPaths = null,
Object? rsiLauncherInstallPaths = null,
Object? items = null,
Object? isItemLoading = null,
}) {
return _then(_$ToolsUIStateImpl(
working: null == working
? _value.working
: working // ignore: cast_nullable_to_non_nullable
as bool,
scInstalledPath: null == scInstalledPath
? _value.scInstalledPath
: scInstalledPath // ignore: cast_nullable_to_non_nullable
as String,
rsiLauncherInstalledPath: null == rsiLauncherInstalledPath
? _value.rsiLauncherInstalledPath
: rsiLauncherInstalledPath // ignore: cast_nullable_to_non_nullable
as String,
scInstallPaths: null == scInstallPaths
? _value._scInstallPaths
: scInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
rsiLauncherInstallPaths: null == rsiLauncherInstallPaths
? _value._rsiLauncherInstallPaths
: rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable
as List<String>,
items: null == items
? _value._items
: items // ignore: cast_nullable_to_non_nullable
as List<ToolsItemData>,
isItemLoading: null == isItemLoading
? _value.isItemLoading
: isItemLoading // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
class _$ToolsUIStateImpl implements _ToolsUIState {
_$ToolsUIStateImpl(
{this.working = false,
this.scInstalledPath = "",
this.rsiLauncherInstalledPath = "",
final List<String> scInstallPaths = const [],
final List<String> rsiLauncherInstallPaths = const [],
final List<ToolsItemData> items = const [],
this.isItemLoading = false})
: _scInstallPaths = scInstallPaths,
_rsiLauncherInstallPaths = rsiLauncherInstallPaths,
_items = items;
@override
@JsonKey()
final bool working;
@override
@JsonKey()
final String scInstalledPath;
@override
@JsonKey()
final String rsiLauncherInstalledPath;
final List<String> _scInstallPaths;
@override
@JsonKey()
List<String> get scInstallPaths {
if (_scInstallPaths is EqualUnmodifiableListView) return _scInstallPaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_scInstallPaths);
}
final List<String> _rsiLauncherInstallPaths;
@override
@JsonKey()
List<String> get rsiLauncherInstallPaths {
if (_rsiLauncherInstallPaths is EqualUnmodifiableListView)
return _rsiLauncherInstallPaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_rsiLauncherInstallPaths);
}
final List<ToolsItemData> _items;
@override
@JsonKey()
List<ToolsItemData> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@override
@JsonKey()
final bool isItemLoading;
@override
String toString() {
return 'ToolsUIState(working: $working, scInstalledPath: $scInstalledPath, rsiLauncherInstalledPath: $rsiLauncherInstalledPath, scInstallPaths: $scInstallPaths, rsiLauncherInstallPaths: $rsiLauncherInstallPaths, items: $items, isItemLoading: $isItemLoading)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ToolsUIStateImpl &&
(identical(other.working, working) || other.working == working) &&
(identical(other.scInstalledPath, scInstalledPath) ||
other.scInstalledPath == scInstalledPath) &&
(identical(
other.rsiLauncherInstalledPath, rsiLauncherInstalledPath) ||
other.rsiLauncherInstalledPath == rsiLauncherInstalledPath) &&
const DeepCollectionEquality()
.equals(other._scInstallPaths, _scInstallPaths) &&
const DeepCollectionEquality().equals(
other._rsiLauncherInstallPaths, _rsiLauncherInstallPaths) &&
const DeepCollectionEquality().equals(other._items, _items) &&
(identical(other.isItemLoading, isItemLoading) ||
other.isItemLoading == isItemLoading));
}
@override
int get hashCode => Object.hash(
runtimeType,
working,
scInstalledPath,
rsiLauncherInstalledPath,
const DeepCollectionEquality().hash(_scInstallPaths),
const DeepCollectionEquality().hash(_rsiLauncherInstallPaths),
const DeepCollectionEquality().hash(_items),
isItemLoading);
/// Create a copy of ToolsUIState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ToolsUIStateImplCopyWith<_$ToolsUIStateImpl> get copyWith =>
__$$ToolsUIStateImplCopyWithImpl<_$ToolsUIStateImpl>(this, _$identity);
}
abstract class _ToolsUIState implements ToolsUIState {
factory _ToolsUIState(
{final bool working,
final String scInstalledPath,
final String rsiLauncherInstalledPath,
final List<String> scInstallPaths,
final List<String> rsiLauncherInstallPaths,
final List<ToolsItemData> items,
final bool isItemLoading}) = _$ToolsUIStateImpl;
@override
bool get working;
@override
String get scInstalledPath;
@override
String get rsiLauncherInstalledPath;
@override
List<String> get scInstallPaths;
@override
List<String> get rsiLauncherInstallPaths;
@override
List<ToolsItemData> get items;
@override
bool get isItemLoading;
/// Create a copy of ToolsUIState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ToolsUIStateImplCopyWith<_$ToolsUIStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,25 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tools_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$toolsUIModelHash() => r'b61ae444063db4c550fbf71e724eddd0f7104dc5';
/// See also [ToolsUIModel].
@ProviderFor(ToolsUIModel)
final toolsUIModelProvider =
AutoDisposeNotifierProvider<ToolsUIModel, ToolsUIState>.internal(
ToolsUIModel.new,
name: r'toolsUIModelProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$toolsUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ToolsUIModel = AutoDisposeNotifier<ToolsUIState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,330 +0,0 @@
import 'dart:io';
import 'package:file_sizes/file_sizes.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:re_editor/re_editor.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/data/app_unp4k_p4k_item_data.dart';
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
import 'package:url_launcher/url_launcher_string.dart';
class UnP4kcUI extends HookConsumerWidget {
const UnP4kcUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(unp4kCModelProvider);
final model = ref.read(unp4kCModelProvider.notifier);
final files = model.getFiles();
final paths = state.curPath.trim().split("\\");
useEffect(() {
AnalyticsApi.touch("unp4k_launch");
return null;
}, const []);
return makeDefaultPage(context,
title: S.current.tools_unp4k_title(model.getGamePath()),
useBodyContainer: false,
content: makeBody(context, state, model, files, paths));
}
Widget makeBody(BuildContext context, Unp4kcState state, Unp4kCModel model,
List<AppUnp4kP4kItemData>? files, List<String> paths) {
if (state.errorMessage.isNotEmpty) {
return UnP4kErrorWidget(errorMessage: state.errorMessage);
}
return state.files == null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: makeLoading(context)),
if (state.endMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${state.endMessage}",
style: const TextStyle(fontSize: 12),
),
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withOpacity(.06)),
height: 36,
padding: const EdgeInsets.only(left: 12, right: 12),
child: SuperListView.builder(
itemCount: paths.length - 1,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
var path = paths[index];
if (path.isEmpty) {
path = "\\";
}
final fullPath =
"${paths.sublist(0, index + 1).join("\\")}\\";
return Row(
children: [
IconButton(
icon: Text(path),
onPressed: () {
model.changeDir(fullPath, fullPath: true);
},
),
const Icon(
FluentIcons.chevron_right,
size: 12,
),
],
);
},
),
),
Expanded(
child: Row(
children: [
Container(
width: MediaQuery.of(context).size.width * .3,
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor.withOpacity(.01),
),
child: SuperListView.builder(
padding: const EdgeInsets.only(
top: 6, bottom: 6, left: 3, right: 12),
itemBuilder: (BuildContext context, int index) {
final item = files![index];
final fileName =
item.name?.replaceAll(state.curPath.trim(), "") ??
"?";
return Container(
margin: const EdgeInsets.only(top: 4, bottom: 4),
decoration: BoxDecoration(
color: FluentTheme.of(context)
.cardColor
.withOpacity(.05),
),
child: IconButton(
onPressed: () {
if (item.isDirectory ?? false) {
model.changeDir(fileName);
} else {
model.openFile(item.name ?? "");
}
},
icon: Padding(
padding: const EdgeInsets.only(left: 4, right: 4),
child: Row(
children: [
if (item.isDirectory ?? false)
const Icon(
FluentIcons.folder_fill,
color: Color.fromRGBO(255, 224, 138, 1),
)
else if (fileName.endsWith(".xml"))
const Icon(
FluentIcons.file_code,
)
else
const Icon(
FluentIcons.open_file,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 13),
textAlign: TextAlign.start,
),
),
],
),
if (!(item.isDirectory ?? true)) ...[
const SizedBox(height: 1),
Row(
children: [
Text(
FileSize.getSize(item.size),
style: TextStyle(
fontSize: 10,
color: Colors.white
.withOpacity(.6)),
),
const SizedBox(width: 12),
Text(
"${item.dateTime}",
style: TextStyle(
fontSize: 10,
color: Colors.white
.withOpacity(.6)),
),
],
),
],
],
),
),
const SizedBox(width: 3),
Icon(
FluentIcons.chevron_right,
size: 14,
color: Colors.white.withOpacity(.6),
)
],
),
),
),
);
},
itemCount: files?.length ?? 0,
),
),
Expanded(
child: Container(
child: state.tempOpenFile == null
? Center(
child: Text(S.current.tools_unp4k_view_file),
)
: state.tempOpenFile?.key == "loading"
? makeLoading(context)
: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
if (state.tempOpenFile?.key == "text")
Expanded(
child: _TextTempWidget(
state.tempOpenFile?.value ?? ""))
else
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(S.current
.tools_unp4k_msg_unknown_file_type(
state.tempOpenFile
?.value ??
"")),
const SizedBox(height: 32),
FilledButton(
child: Padding(
padding:
const EdgeInsets.all(4),
child: Text(S.current
.action_open_folder),
),
onPressed: () {
SystemHelper.openDir(state
.tempOpenFile
?.value ??
"");
})
],
),
),
)
],
),
),
))
],
)),
if (state.endMessage != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"${state.endMessage}",
style: const TextStyle(fontSize: 12),
),
),
],
);
}
}
class _TextTempWidget extends HookConsumerWidget {
final String filePath;
const _TextTempWidget(this.filePath);
@override
Widget build(BuildContext context, WidgetRef ref) {
final textData = useState<String?>(null);
useEffect(() {
File(filePath).readAsString().then((value) {
textData.value = value;
}).catchError((err) {
textData.value = "Error: $err";
});
return null;
}, const []);
if (textData.value == null) return makeLoading(context);
return CodeEditor(
controller: CodeLineEditingController.fromText('${textData.value}'),
readOnly: true,
);
}
}
class UnP4kErrorWidget extends StatelessWidget {
final String errorMessage;
const UnP4kErrorWidget({super.key, required this.errorMessage});
static const _downloadUrl =
"https://aka.ms/dotnet-core-applaunch?missing_runtime=true&arch=x64&rid=win-x64&os=win10&apphost_version=8.0.0";
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (Unp4kCModel.checkRunTimeError(errorMessage)) ...[
Text(
S.current.tools_unp4k_missing_runtime,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 6),
Text(S.current.tools_unp4k_missing_runtime_info),
const SizedBox(height: 16),
FilledButton(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
child: Text(
S.current.tools_unp4k_missing_runtime_action_install),
),
onPressed: () {
launchUrlString(_downloadUrl);
}),
] else
Text(errorMessage),
],
),
),
);
}
}

View File

@ -1,304 +0,0 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:desktop_webview_window/desktop_webview_window.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart';
import 'package:starcitizen_doctor/api/analytics.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/data/app_version_data.dart';
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
typedef RsiLoginCallback = void Function(Map? data, bool success);
class WebViewModel {
late Webview webview;
final BuildContext context;
bool _isClosed = false;
bool get isClosed => _isClosed;
WebViewModel(this.context,
{this.loginMode = false, this.loginCallback, this.loginChannel = "LIVE"});
String url = "";
bool canGoBack = false;
final localizationResource = <String, dynamic>{};
var localizationScript = "";
bool enableCapture = false;
bool isEnableToolSiteMirrors = false;
Map<String, String>? _curReplaceWords;
Map<String, String>? get curReplaceWords => _curReplaceWords;
final bool loginMode;
final String loginChannel;
bool _loginModeSuccess = false;
final RsiLoginCallback? loginCallback;
initWebView(
{String title = "",
required String applicationSupportDir,
required AppVersionData appVersionData}) async {
try {
final userBox = await Hive.openBox("app_conf");
isEnableToolSiteMirrors =
userBox.get("isEnableToolSiteMirrors", defaultValue: false);
webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowWidth: loginMode ? 960 : 1920,
windowHeight: loginMode ? 720 : 1080,
userDataFolderWindows: "$applicationSupportDir/webview_data",
title: Platform.isMacOS ? "" : title));
// webview.openDevToolsWindow();
webview.isNavigating.addListener(() async {
if (!webview.isNavigating.value && localizationResource.isNotEmpty) {
dPrint("webview Navigating url === $url");
if (url.contains("robertsspaceindustries.com")) {
// SC 官网
dPrint("load script");
await Future.delayed(const Duration(milliseconds: 100));
await webview.evaluateJavaScript(localizationScript);
dPrint("update replaceWords");
final replaceWords = _getLocalizationResource("zh-CN");
const org = "https://robertsspaceindustries.com/orgs";
const citizens = "https://robertsspaceindustries.com/citizens";
const organization =
"https://robertsspaceindustries.com/account/organization";
const concierge =
"https://robertsspaceindustries.com/account/concierge";
const referral =
"https://robertsspaceindustries.com/account/referral-program";
const address =
"https://robertsspaceindustries.com/account/addresses";
const hangar = "https://robertsspaceindustries.com/account/pledges";
const spectrum =
"https://robertsspaceindustries.com/spectrum/community/";
// 跳过光谱论坛 https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx/issues/1
if (url.startsWith(spectrum)) {
return;
}
if (url.startsWith(org) ||
url.startsWith(citizens) ||
url.startsWith(organization)) {
replaceWords.add({
"word": 'members',
"replacement": S.current.webview_localization_name_member
});
replaceWords.addAll(_getLocalizationResource("orgs"));
}
if (address.startsWith(address)) {
replaceWords.addAll(_getLocalizationResource("address"));
}
if (url.startsWith(referral)) {
replaceWords.addAll([
{
"word": 'Total recruits: ',
"replacement":
S.current.webview_localization_total_invitations
},
{
"word": 'Prospects ',
"replacement":
S.current.webview_localization_unfinished_invitations
},
{
"word": 'Recruits',
"replacement":
S.current.webview_localization_finished_invitations
},
]);
}
if (url.startsWith(concierge)) {
replaceWords.clear();
replaceWords.addAll(_getLocalizationResource("concierge"));
}
if (url.startsWith(hangar)) {
replaceWords.addAll(_getLocalizationResource("hangar"));
}
_curReplaceWords = {};
for (var element in replaceWords) {
_curReplaceWords?[element["word"] ?? ""] =
element["replacement"] ?? "";
}
await Future.delayed(const Duration(milliseconds: 100));
await webview.evaluateJavaScript(
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)");
/// loginMode
if (loginMode) {
dPrint(
"--- do rsi login ---\n run === getRSILauncherToken(\"$loginChannel\");");
await Future.delayed(const Duration(milliseconds: 200));
webview.evaluateJavaScript(
"getRSILauncherToken(\"$loginChannel\");");
}
} else if (url.startsWith(await _handleMirrorsUrl(
"https://www.erkul.games", appVersionData))) {
dPrint("load script");
await Future.delayed(const Duration(milliseconds: 100));
await webview.evaluateJavaScript(localizationScript);
dPrint("update replaceWords");
final replaceWords = _getLocalizationResource("DPS");
await webview.evaluateJavaScript(
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)");
} else if (url.startsWith(await _handleMirrorsUrl(
"https://uexcorp.space", appVersionData))) {
dPrint("load script");
await Future.delayed(const Duration(milliseconds: 100));
await webview.evaluateJavaScript(localizationScript);
dPrint("update replaceWords");
final replaceWords = _getLocalizationResource("UEX");
await webview.evaluateJavaScript(
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)");
}
}
});
webview.addOnUrlRequestCallback((url) {
dPrint("OnUrlRequestCallback === $url");
this.url = url;
});
webview.onClose.whenComplete(dispose);
if (loginMode) {
webview.addOnWebMessageReceivedCallback((messageString) {
final message = json.decode(messageString);
if (message["action"] == "webview_rsi_login_show_window") {
webview.setWebviewWindowVisibility(true);
} else if (message["action"] == "webview_rsi_login_success") {
_loginModeSuccess = true;
loginCallback?.call(message, true);
webview.close();
}
});
Future.delayed(const Duration(seconds: 1))
.then((value) => {webview.setWebviewWindowVisibility(false)});
}
} catch (e) {
showToast(context, S.current.app_init_failed_with_reason(e));
}
}
Future<String> _handleMirrorsUrl(
String url, AppVersionData appVersionData) async {
var finalUrl = url;
if (isEnableToolSiteMirrors) {
for (var kv in appVersionData.webMirrors!.entries) {
if (url.startsWith(kv.key)) {
finalUrl = url.replaceFirst(kv.key, kv.value);
AnalyticsApi.touch("webLocalization_with_boost_mirror");
}
}
}
return finalUrl;
}
launch(String url, AppVersionData appVersionData) async {
webview.launch(await _handleMirrorsUrl(url, appVersionData));
}
initLocalization(AppWebLocalizationVersionsData v) async {
localizationScript = await rootBundle.loadString('assets/web_script.js');
/// https://github.com/CxJuice/Uex_Chinese_Translate
// get versions
final hostUrl = URLConf.webTranslateHomeUrl;
dPrint("AppWebLocalizationVersionsData === ${v.toJson()}");
localizationResource["zh-CN"] = await _getJson("$hostUrl/zh-CN-rsi.json",
cacheKey: "rsi", version: v.rsi);
localizationResource["concierge"] = await _getJson(
"$hostUrl/concierge.json",
cacheKey: "concierge",
version: v.concierge);
localizationResource["orgs"] =
await _getJson("$hostUrl/orgs.json", cacheKey: "orgs", version: v.orgs);
localizationResource["address"] = await _getJson("$hostUrl/addresses.json",
cacheKey: "addresses", version: v.addresses);
localizationResource["hangar"] = await _getJson("$hostUrl/hangar.json",
cacheKey: "hangar", version: v.hangar);
localizationResource["UEX"] = await _getJson("$hostUrl/zh-CN-uex.json",
cacheKey: "uex", version: v.uex);
localizationResource["DPS"] = await _getJson("$hostUrl/zh-CN-dps.json",
cacheKey: "dps", version: v.dps);
}
List<Map<String, String>> _getLocalizationResource(String key) {
final List<Map<String, String>> localizations = [];
final dict = localizationResource[key];
if (dict is Map) {
for (var element in dict.entries) {
final k = element.key
.toString()
.trim()
.toLowerCase()
.replaceAll(RegExp("/\xa0/g"), ' ')
.replaceAll(RegExp("/s{2,}/g"), ' ');
localizations
.add({"word": k, "replacement": element.value.toString().trim()});
}
}
return localizations;
}
Future<Map> _getJson(String url,
{String cacheKey = "", String? version}) async {
final box = await Hive.openBox("web_localization_cache_data");
if (cacheKey.isNotEmpty) {
final localVersion = box.get("${cacheKey}_version}", defaultValue: "");
var data = box.get(cacheKey, defaultValue: {});
if (data is Map && data.isNotEmpty && localVersion == version) {
return data;
}
}
final startTime = DateTime.now();
final r = await RSHttp.getText(url);
final endTime = DateTime.now();
final data = json.decode(r);
if (cacheKey.isNotEmpty) {
dPrint(
"update $cacheKey v == $version time == ${(endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch) / 1000 / 1000}s");
await box.put(cacheKey, data);
await box.put("${cacheKey}_version}", version);
}
return data;
}
void addOnWebMessageReceivedCallback(OnWebMessageReceivedCallback callback) {
webview.addOnWebMessageReceivedCallback(callback);
}
void removeOnWebMessageReceivedCallback(
OnWebMessageReceivedCallback callback) {
webview.removeOnWebMessageReceivedCallback(callback);
}
FutureOr<void> dispose() {
if (loginMode && !_loginModeSuccess) {
loginCallback?.call(null, false);
}
_isClosed = true;
}
}

View File

@ -0,0 +1,37 @@
import 'dart:ui';
import 'package:flutter/material.dart';
class BlurOvalWidget extends StatelessWidget {
final Widget child;
final double padding;
final Color blurColor;
final BorderRadius borderRadius;
final ImageFilter? imageFilter;
const BlurOvalWidget(
{super.key,
required this.child,
this.padding = 0,
this.blurColor = Colors.white10,
this.borderRadius = BorderRadius.zero,
this.imageFilter});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: borderRadius,
child: BackdropFilter(
filter: imageFilter ??
ImageFilter.blur(
sigmaX: 10,
sigmaY: 10,
),
child: Container(
color: blurColor,
child: child,
),
),
);
}
}

View File

@ -7,7 +7,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/generated/l10n.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
import 'package:markdown_widget/config/all.dart';
import 'package:markdown_widget/widget/all.dart';
import 'package:extended_image/extended_image.dart';
@ -43,28 +42,24 @@ Widget makeDefaultPage(BuildContext context,
return NavigationView(
appBar: NavigationAppBar(
automaticallyImplyLeading: automaticallyImplyLeading,
title: DragToMoveArea(
child: titleRow ??
Column(
title: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: Row(
children: [
Text(title),
],
),
)
Text(title),
],
),
)
],
),
actions: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [...?actions, const WindowButtons()],
children: [...?actions],
)),
content: useBodyContainer
? Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(9),
),
child: content,
@ -73,23 +68,6 @@ Widget makeDefaultPage(BuildContext context,
);
}
class WindowButtons extends StatelessWidget {
const WindowButtons({super.key});
@override
Widget build(BuildContext context) {
final FluentThemeData theme = FluentTheme.of(context);
return SizedBox(
width: 138,
height: 50,
child: WindowCaption(
brightness: theme.brightness,
backgroundColor: Colors.transparent,
),
);
}
}
List<Widget> makeMarkdownView(String description, {String? attachmentsUrl}) {
return MarkdownGenerator().buildWidgets(description,
config: MarkdownConfig(configs: [