mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-22 23:23:43 +08:00
feat:riverpod 迁移 HomeGameDoctorUI
This commit is contained in:
parent
2aa4fb6c09
commit
c290e832a0
@ -23,6 +23,7 @@ import 'common/helper/system_helper.dart';
|
||||
import 'common/io/rs_http.dart';
|
||||
import 'common/rust/frb_generated.dart';
|
||||
import 'data/app_version_data.dart';
|
||||
import 'ui/home/game_doctor/game_doctor_ui.dart';
|
||||
import 'ui/index_ui.dart';
|
||||
import 'ui/settings/upgrade_dialog.dart';
|
||||
|
||||
@ -43,6 +44,13 @@ GoRouter router(RouterRef ref) {
|
||||
path: '/index',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const IndexUI()),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'game_doctor',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const HomeGameDoctorUI()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ part of 'app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$routerHash() => r'6e1ddfd4fb004ca0c2262ab17a4b0882f12bcc3b';
|
||||
String _$routerHash() => r'd8cf08526e81cdcda0203c32fc89140f2ffb0058';
|
||||
|
||||
/// See also [router].
|
||||
@ProviderFor(router)
|
||||
|
294
lib/ui/home/game_doctor/game_doctor_ui.dart
Normal file
294
lib/ui/home/game_doctor/game_doctor_ui.dart
Normal file
@ -0,0 +1,294 @@
|
||||
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/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(() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
dPrint("HomeGameDoctorUI useEffect doCheck timeStamp === $timeStamp");
|
||||
model.doCheck(context);
|
||||
});
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
return makeDefaultPage(context,
|
||||
title: "一键诊断",
|
||||
useBodyContainer: true,
|
||||
content: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
for (final item in const {
|
||||
"rsi_log": "RSI启动器log",
|
||||
"game_log": "游戏运行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) ...[
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 12),
|
||||
Text("扫描完毕,没有找到问题!", maxLines: 1),
|
||||
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
|
||||
: "正在处理..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: makeRescueBanner(context),
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget makeRescueBanner(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await showToast(context,
|
||||
"您即将前往由 深空治疗中心(QQ群号:536454632 ) 提供的游戏异常救援服务,主要解决游戏安装失败与频繁闪退,如游戏玩法问题,请勿加群。");
|
||||
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),
|
||||
const Text("需要帮助? 点击加群寻求免费人工支援!"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> makeResult(BuildContext context, HomeGameDoctorState state,
|
||||
HomeGameDoctorUIModel model) {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Text(state.lastScreenInfo, maxLines: 1),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"注意:本工具检测结果仅供参考,若您不理解以下操作,请提供截图给有经验的玩家!",
|
||||
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("不支持的操作系统,游戏可能无法运行", "请升级您的系统 (${item.value})"),
|
||||
"no_live_path": MapEntry("安装目录缺少LIVE文件夹,可能导致安装失败",
|
||||
"点击修复为您创建 LIVE 文件夹,完成后重试安装。(${item.value})"),
|
||||
"nvme_PhysicalBytes": MapEntry("新型 NVME 设备,与 RSI 启动器暂不兼容,可能导致安装失败",
|
||||
"为注册表项添加 ForcedPhysicalSectorSizeInBytes 值 模拟旧设备。硬盘分区(${item.value})"),
|
||||
"eac_file_miss": const MapEntry("EasyAntiCheat 文件丢失",
|
||||
"未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件"),
|
||||
"eac_not_install": const MapEntry("EasyAntiCheat 未安装 或 未正常退出",
|
||||
"EasyAntiCheat 未安装,请点击修复为您一键安装。(在游戏正常启动并结束前,该问题会一直出现,若您因为其他原因游戏闪退,可忽略此条目)"),
|
||||
"cn_user_name":
|
||||
const MapEntry("中文用户名!", "中文用户名可能会导致游戏启动/安装错误! 点击修复按钮查看修改教程!"),
|
||||
"cn_install_path": MapEntry("中文安装路径!",
|
||||
"中文安装路径!这可能会导致游戏 启动/安装 错误!(${item.value}),请在RSI启动器更换安装路径。"),
|
||||
"low_ram": MapEntry(
|
||||
"物理内存过低", "您至少需要 16GB 的物理内存(Memory)才可运行此游戏。(当前大小:${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(
|
||||
"修复建议: ${errorNames[item.key]?.value ?? "暂无解决方法,请截图反馈"}",
|
||||
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: const Padding(
|
||||
padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
|
||||
child: Text("修复"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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: const Padding(
|
||||
padding:
|
||||
EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
|
||||
child: Text("查看解决方案"),
|
||||
),
|
||||
)
|
||||
: 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, "请在首页选择游戏安装目录。");
|
||||
return;
|
||||
}
|
||||
SystemHelper.openDir("${homeState.scInstalledPath}\\Game.log");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
273
lib/ui/home/game_doctor/game_doctor_ui_model.dart
Normal file
273
lib/ui/home/game_doctor/game_doctor_ui_model.dart
Normal file
@ -0,0 +1,273 @@
|
||||
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 {
|
||||
const 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 = const 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, "若您的硬件达标,请尝试安装最新的 Windows 系统。");
|
||||
break;
|
||||
case "no_live_path":
|
||||
try {
|
||||
await Directory(item.value).create(recursive: true);
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "创建文件夹成功,请尝试继续下载游戏!");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} catch (e) {
|
||||
showToast(context, "创建文件夹失败,请尝试手动创建。\n目录:${item.value} \n错误:$e");
|
||||
}
|
||||
break;
|
||||
case "nvme_PhysicalBytes":
|
||||
final r = await SystemHelper.addNvmePatch();
|
||||
if (r == "") {
|
||||
if (!context.mounted) break;
|
||||
showToast(context,
|
||||
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} else {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,$r");
|
||||
}
|
||||
break;
|
||||
case "eac_file_miss":
|
||||
showToast(
|
||||
context, "未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件");
|
||||
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, "修复成功,请尝试启动游戏。(若问题无法解决,请使用工具箱的 《重装 EAC》)");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} else {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,${result.stderr}");
|
||||
}
|
||||
} catch (e) {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,$e");
|
||||
}
|
||||
break;
|
||||
case "cn_user_name":
|
||||
showToast(context, "即将跳转,教程来自互联网,请谨慎操作...");
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
launchUrlString(
|
||||
"https://btfy.eu.org/?q=5L+u5pS5d2luZG93c+eUqOaIt+WQjeS7juS4reaWh+WIsOiLseaWhw==");
|
||||
break;
|
||||
default:
|
||||
showToast(context, "该问题暂不支持自动处理,请提供截图寻求帮助");
|
||||
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: "正在分析...");
|
||||
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(const MapEntry("unSupport_system", "android"));
|
||||
// checkResult?.add(const MapEntry("nvme_PhysicalBytes", "C"));
|
||||
// checkResult?.add(const 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) {
|
||||
const lastScreenInfo = "分析完毕,没有发现问题";
|
||||
state = state.copyWith(checkResult: null, lastScreenInfo: lastScreenInfo);
|
||||
} else {
|
||||
final lastScreenInfo = "分析完毕,发现 ${checkResult.length} 个问题";
|
||||
state = state.copyWith(
|
||||
checkResult: checkResult, lastScreenInfo: lastScreenInfo);
|
||||
}
|
||||
|
||||
if (scInstalledPath == "not_install" && (checkResult.isEmpty)) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "扫描完毕,没有发现问题,若仍然安装失败,请尝试使用工具箱中的 RSI启动器管理员模式。");
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future _checkGameRunningLog(BuildContext context, String scInstalledPath,
|
||||
List<MapEntry<String, String>> checkResult) async {
|
||||
if (scInstalledPath == "not_install") return;
|
||||
const lastScreenInfo = "正在检查: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("游戏异常退出:${info.key}", info.value));
|
||||
} else {
|
||||
checkResult
|
||||
.add(MapEntry("游戏异常退出:未知异常", "info:${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;
|
||||
const lastScreenInfo = "正在检查: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 {
|
||||
const lastScreenInfo = "正在检查:运行环境";
|
||||
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 = "不支持的操作系统:${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: "正在检查:安装信息");
|
||||
// 检查安装分区
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
242
lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart
Normal file
242
lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart
Normal file
@ -0,0 +1,242 @@
|
||||
// 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;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$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;
|
||||
|
||||
@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);
|
||||
|
||||
@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 {
|
||||
const _$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));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
|
||||
__$$HomeGameDoctorStateImplCopyWithImpl<_$HomeGameDoctorStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _HomeGameDoctorState implements HomeGameDoctorState {
|
||||
const 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;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/game_doctor/game_doctor_ui_model.g.dart
Normal file
27
lib/ui/home/game_doctor/game_doctor_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'game_doctor_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeGameDoctorUIModelHash() =>
|
||||
r'59cbd6f866bacbc24eb0c0eb0ad88fe7f2dac4a7';
|
||||
|
||||
/// 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
|
@ -4,6 +4,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
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';
|
||||
@ -427,7 +428,7 @@ class HomeUI extends HookConsumerWidget {
|
||||
Widget makeIndexActionLists(
|
||||
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
|
||||
final items = [
|
||||
_HomeItemData("auto_check", "一键诊断", "一键诊断星际公民常见问题",
|
||||
_HomeItemData("game_doctor", "一键诊断", "一键诊断星际公民常见问题",
|
||||
FluentIcons.auto_deploy_settings),
|
||||
_HomeItemData(
|
||||
"localization", "汉化管理", "快捷安装汉化资源", FluentIcons.locale_language),
|
||||
@ -445,7 +446,7 @@ class HomeUI extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
final item = items.elementAt(index);
|
||||
return HoverButton(
|
||||
onPressed: () => model.onMenuTap(item.key),
|
||||
onPressed: () => _onMenuTap(context, item.key),
|
||||
builder: (BuildContext context, Set<ButtonStates> states) {
|
||||
return Container(
|
||||
width: 300,
|
||||
@ -746,6 +747,10 @@ class HomeUI extends HookConsumerWidget {
|
||||
showDialog(
|
||||
context: context, builder: (context) => const HomeCountdownDialogUI());
|
||||
}
|
||||
|
||||
_onMenuTap(BuildContext context, String key) {
|
||||
context.push("/index/$key");
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeItemData {
|
||||
|
@ -121,8 +121,6 @@ class HomeUIModel extends _$HomeUIModel {
|
||||
return title;
|
||||
}
|
||||
|
||||
onMenuTap(String key) {}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> goWebView(BuildContext context, String title, String url,
|
||||
{bool useLocalization = false,
|
||||
|
@ -6,7 +6,7 @@ part of 'home_ui_model.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeUIModelHash() => r'3094d9ab828a578670e11f3eaffa57bdb95a004b';
|
||||
String _$homeUIModelHash() => r'4ef5c2bdf2d9a506b2e139d321c52c97cf4bc30d';
|
||||
|
||||
/// See also [HomeUIModel].
|
||||
@ProviderFor(HomeUIModel)
|
||||
|
@ -32,7 +32,8 @@ Widget makeDefaultPage(BuildContext context,
|
||||
List<Widget>? actions,
|
||||
Widget? content,
|
||||
bool automaticallyImplyLeading = true,
|
||||
String title = ""}) {
|
||||
String title = "",
|
||||
bool useBodyContainer = false}) {
|
||||
return NavigationView(
|
||||
appBar: NavigationAppBar(
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
@ -54,7 +55,15 @@ Widget makeDefaultPage(BuildContext context,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [...?actions, const WindowButtons()],
|
||||
)),
|
||||
content: content,
|
||||
content: useBodyContainer
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
child: content,
|
||||
)
|
||||
: content,
|
||||
);
|
||||
}
|
||||
|
||||
@ -128,18 +137,19 @@ CustomTransitionPage<T> myPageBuilder<T>(
|
||||
BuildContext context, GoRouterState state, Widget child) {
|
||||
return CustomTransitionPage(
|
||||
child: child,
|
||||
transitionDuration: const Duration(milliseconds: 150),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
transitionsBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
return Semantics(
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
child: EntrancePageTransition(
|
||||
animation: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: FluentTheme.of(context).animationCurve,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
)),
|
||||
child: child,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user