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