feat:riverpod 迁移 HomeGameDoctorUI

This commit is contained in:
xkeyC 2024-03-10 14:18:30 +08:00
parent 2aa4fb6c09
commit c290e832a0
10 changed files with 875 additions and 18 deletions

View File

@ -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()),
),
],
),
],
);

View File

@ -6,7 +6,7 @@ part of 'app.dart';
// RiverpodGenerator
// **************************************************************************
String _$routerHash() => r'6e1ddfd4fb004ca0c2262ab17a4b0882f12bcc3b';
String _$routerHash() => r'd8cf08526e81cdcda0203c32fc89140f2ffb0058';
/// See also [router].
@ProviderFor(router)

View 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;
}
}
}

View 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);
}
}
}

View 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;
}

View 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

View File

@ -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 {

View File

@ -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,

View File

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

View File

@ -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(
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0),
).animate(CurvedAnimation(
parent: animation,
curve: FluentTheme.of(context).animationCurve,
),
curve: Curves.easeInOut,
)),
child: child,
),
);
});
}