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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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