feat: download Localization

This commit is contained in:
xkeyC 2024-09-04 20:50:23 +08:00
parent 308480095b
commit 07f2ab03cb
12 changed files with 185 additions and 422 deletions

View File

@ -215,7 +215,7 @@ class _$AppGlobalStateImpl implements _AppGlobalState {
this.appLocale, this.appLocale,
this.appConfBox, this.appConfBox,
this.backgroundImageAssetsPath = this.backgroundImageAssetsPath =
"assets/backgrounds/SC_01_Wallpaper_3840x2160.jpg"}); "assets/backgrounds/SC_01_Wallpaper_3840x2160.webp"});
@override @override
final String? deviceUUID; final String? deviceUUID;

View File

@ -788,15 +788,10 @@ class HomeUI extends HookConsumerWidget {
_onMenuTap(BuildContext context, String key, HomeUIModelState homeState, _onMenuTap(BuildContext context, String key, HomeUIModelState homeState,
WidgetRef ref) async { WidgetRef ref) async {
String gameInstallReqInfo = // String gameInstallReqInfo =
S.current.home_action_info_valid_install_location_required; // S.current.home_action_info_valid_install_location_required;
switch (key) { switch (key) {
case "localization": case "localization":
if (homeState.scInstalledPath == "not_install") {
// TODO
// ToolsUIModel.English(context, showNotGameInstallMsg: true);
break;
}
final model = ref.watch(homeUIModelProvider.notifier); final model = ref.watch(homeUIModelProvider.notifier);
model.checkLocalizationUpdate(); model.checkLocalizationUpdate();
await showDialog( await showDialog(
@ -807,14 +802,18 @@ class HomeUI extends HookConsumerWidget {
model.checkLocalizationUpdate(skipReload: true); model.checkLocalizationUpdate(skipReload: true);
break; break;
case "performance": case "performance":
if (homeState.scInstalledPath == "not_install") { return;
showToast(context, gameInstallReqInfo); // if (homeState.scInstalledPath == "not_install") {
break; // showToast(context, gameInstallReqInfo);
} // break;
context.push("/index/$key"); // }
break; // context.push("/index/$key");
// break;
case "game_doctor":
return;
default: default:
context.push("/index/$key"); return;
// context.push("/index/$key");
} }
} }

View File

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

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
@ -9,7 +8,6 @@ import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart'; import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart'; import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import '../home_ui_model.dart';
import 'advanced_localization_ui.json.dart'; import 'advanced_localization_ui.json.dart';
import 'localization_ui_model.dart'; import 'localization_ui_model.dart';
@ -178,40 +176,7 @@ class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
Future<(String, String)> _readIni(LocalizationUIState localizationUIState, Future<(String, String)> _readIni(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async { LocalizationUIModel localizationUIModel) async {
final homeUIState = ref.read(homeUIModelProvider); return ("", "");
final gameDir = homeUIState.scInstalledPath;
if (gameDir == null) return ("", "");
state = state.copyWith(
workingText: S.current.home_localization_advanced_msg_reading_p4k);
final p4kGlobalIni = await readEnglishInI(gameDir);
dPrint("read p4kGlobalIni => ${p4kGlobalIni.length}");
state = state.copyWith(
workingText: S.current
.home_localization_advanced_msg_reading_server_localization_text);
if (state.customizeGlobalIni != null) {
final apiLocalizationData = ScLocalizationData(
versionName: S.current.localization_info_custom_files,
info: "Customize");
state = state.copyWith(apiLocalizationData: apiLocalizationData);
return (p4kGlobalIni, state.customizeGlobalIni!);
} else {
final apiLocalizationData =
localizationUIState.apiLocalizationData?.values.firstOrNull;
if (apiLocalizationData == null) return ("", "");
final file = File(
"${localizationUIModel.getDownloadDir().absolute.path}\\${apiLocalizationData.versionName}.sclang");
if (!await file.exists()) {
await localizationUIModel.downloadLocalizationFile(
file, apiLocalizationData);
}
state = state.copyWith(apiLocalizationData: apiLocalizationData);
final serverGlobalIni =
(await compute(LocalizationUIModel.readArchive, file.absolute.path))
.toString();
dPrint("read serverGlobalIni => ${serverGlobalIni.length}");
return (p4kGlobalIni, serverGlobalIni);
}
} }
Future<String> readEnglishInI(String gameDir) async { Future<String> readEnglishInI(String gameDir) async {

View File

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

View File

@ -21,127 +21,19 @@ class LocalizationDialogUI extends HookConsumerWidget {
useEffect(() { useEffect(() {
addPostFrameCallback(() { addPostFrameCallback(() {
model.checkUserCfg(context); // model.checkUserCfg(context);
}); });
return null; return null;
}, []); }, []);
return ContentDialog( return ContentDialog(
title: makeTitle(context, model, state), title: makeTitle(context, model, state),
constraints: BoxConstraints( constraints: const BoxConstraints(maxWidth: 1080, minHeight: 920),
maxWidth: MediaQuery.of(context).size.width * .7,
minHeight: MediaQuery.of(context).size.height * .9),
content: Padding( content: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
AnimatedSize(
duration: const Duration(milliseconds: 130),
child: state.patchStatus?.key == true &&
state.patchStatus?.value ==
S.current.home_action_info_game_built_in
? Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InfoBar(
title: Text(S.current.home_action_info_warning),
content: Text(S.current
.localization_info_machine_translation_warning),
severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) {
return const BoxDecoration(
color: Color.fromRGBO(155, 7, 7, 1.0));
}, iconColor: (severity) {
return Colors.white;
}),
),
)
: SizedBox(
width: MediaQuery.of(context).size.width,
),
),
if (!(model.getScInstallPath() ?? "").contains("LIVE"))
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InfoBar(
title: Text(S.current
.home_localization_ptu_advanced_localization_tip_title),
content: Text(S.current
.home_localization_ptu_advanced_localization_tip_title_info),
severity: InfoBarSeverity.info,
style: InfoBarThemeData(decoration: (severity) {
return BoxDecoration(color: Colors.orange);
}, iconColor: (severity) {
return Colors.white;
}),
),
),
makeListContainer(
S.current.localization_info_translation_status,
[
if (state.patchStatus == null)
makeLoading(context)
else ...[
const SizedBox(height: 6),
Row(
children: [
Center(
child: Text(S.current.localization_info_enabled(
LocalizationUIModel.languageSupport[
state.selectedLanguage] ??
"")),
),
const Spacer(),
ToggleSwitch(
checked: state.patchStatus?.key == true,
onChanged: model.updateLangCfg,
)
],
),
const SizedBox(height: 12),
Row(
children: [
Text(S.current.localization_info_installed_version(
"${state.patchStatus?.value ?? ""} ${(state.isInstalledAdvanced ?? false) ? S.current.home_localization_msg_version_advanced : ""}")),
const Spacer(),
if (state.patchStatus?.value !=
S.current.home_action_info_game_built_in)
Row(
children: [
Button(
onPressed: model.goFeedback,
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.feedback),
const SizedBox(width: 6),
Text(S.current
.localization_action_translation_feedback),
],
),
)),
const SizedBox(width: 16),
Button(
onPressed: model.doDelIniFile(),
child: Padding(
padding: const EdgeInsets.all(4),
child: Row(
children: [
const Icon(FluentIcons.delete),
const SizedBox(width: 6),
Text(S.current
.localization_action_uninstall_translation),
],
),
)),
],
),
],
),
],
],
context),
makeListContainer( makeListContainer(
S.current.localization_info_community_translation, S.current.localization_info_community_translation,
[ [
@ -352,15 +244,37 @@ class LocalizationDialogUI extends HookConsumerWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Text(S.current.home_action_localization_management), Text(S.current.home_action_localization_management),
const SizedBox(width: 24), const SizedBox(width: 24),
Text( // Text(
"${model.getScInstallPath()}", // "${model.getScInstallPath()}",
style: const TextStyle(fontSize: 13), // style: const TextStyle(fontSize: 13),
), // ),
const Spacer(), const Spacer(),
SizedBox( SizedBox(
height: 36, height: 36,
child: Row( child: Row(
children: [ children: [
Text(
S.current.localization_info_channel(""),
style: const TextStyle(fontSize: 16),
),
const SizedBox(width: 12),
ComboBox<String>(
value: state.selectedChannel,
items: [
for (final channel in ["LIVE", "PTU"])
ComboBoxItem(
value: channel,
child: Text(channel),
)
],
onChanged: state.workingVersion.isNotEmpty
? null
: (v) {
if (v == null) return;
model.selectChannel(v);
},
),
const SizedBox(width: 24),
Text( Text(
S.current.localization_info_language, S.current.localization_info_language,
style: const TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
@ -453,8 +367,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
), ),
confirm: S.current.localization_action_install, confirm: S.current.localization_action_install,
cancel: S.current.home_action_cancel, cancel: S.current.home_action_cancel,
constraints: constraints: const BoxConstraints(maxWidth: 720),
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .45),
); );
if (userOK) { if (userOK) {
if (!context.mounted) return; if (!context.mounted) return;

View File

@ -1,26 +1,24 @@
// ignore_for_file: avoid_build_context_in_providers // ignore_for_file: avoid_build_context_in_providers
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'package:archive/archive.dart';
import 'dart:js_interop';
import 'package:archive/archive_io.dart';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.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/api.dart';
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/io/rs_http.dart'; import 'package:starcitizen_doctor/common/io/rs_http.dart';
import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart'; import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/generated/no_l10n_strings.dart'; import 'package:starcitizen_doctor/generated/no_l10n_strings.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart'; import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:web/web.dart' as web;
part 'localization_ui_model.g.dart'; part 'localization_ui_model.g.dart';
part 'localization_ui_model.freezed.dart'; part 'localization_ui_model.freezed.dart';
@ -29,6 +27,7 @@ part 'localization_ui_model.freezed.dart';
class LocalizationUIState with _$LocalizationUIState { class LocalizationUIState with _$LocalizationUIState {
factory LocalizationUIState({ factory LocalizationUIState({
String? selectedLanguage, String? selectedLanguage,
@Default("LIVE") String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData, Map<String, ScLocalizationData>? apiLocalizationData,
@Default("") String workingVersion, @Default("") String workingVersion,
MapEntry<bool, String>? patchStatus, MapEntry<bool, String>? patchStatus,
@ -44,16 +43,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
"chinese_(traditional)": NoL10n.langZHT, "chinese_(traditional)": NoL10n.langZHT,
}; };
Directory get _downloadDir =>
Directory("${appGlobalState.applicationSupportDir}\\Localizations");
Directory getDownloadDir() => _downloadDir;
Directory get _scDataDir =>
Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data");
File get _cfgFile => File("${_scDataDir.absolute.path}\\system.cfg");
StreamSubscription? _customizeDirListenSub; StreamSubscription? _customizeDirListenSub;
String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!; String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!;
@ -66,9 +55,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
Future<void> _init() async { Future<void> _init() async {
if (_scInstallPath == "not_install") {
return;
}
ref.onDispose(() { ref.onDispose(() {
_customizeDirListenSub?.cancel(); _customizeDirListenSub?.cancel();
_customizeDirListenSub = null; _customizeDirListenSub = null;
@ -85,14 +71,13 @@ class LocalizationUIModel extends _$LocalizationUIModel {
Future<void> _loadData() async { Future<void> _loadData() async {
_allVersionLocalizationData.clear(); _allVersionLocalizationData.clear();
await _updateStatus();
for (var lang in languageSupport.keys) { for (var lang in languageSupport.keys) {
final l = await Api.getScLocalizationData(lang).unwrap(); final l = await Api.getScLocalizationData(lang).unwrap();
if (l != null) { if (l != null) {
if (lang == state.selectedLanguage) { if (lang == state.selectedLanguage) {
final apiLocalizationData = <String, ScLocalizationData>{}; final apiLocalizationData = <String, ScLocalizationData>{};
for (var element in l) { for (var element in l) {
final isPTU = !_scInstallPath.contains("LIVE"); final isPTU = !state.selectedChannel.contains("LIVE");
if (isPTU && element.gameChannel == "PTU") { if (isPTU && element.gameChannel == "PTU") {
apiLocalizationData[element.versionName ?? ""] = element; apiLocalizationData[element.versionName ?? ""] = element;
} else if (!isPTU && element.gameChannel == "PU") { } else if (!isPTU && element.gameChannel == "PU") {
@ -110,62 +95,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
} }
void checkUserCfg(BuildContext context) async { Future<String> genLangCfg() async {
final userCfgFile = File("$_scInstallPath\\USER.cfg");
if (await userCfgFile.exists()) {
final cfgString = await userCfgFile.readAsString();
if (cfgString.contains("g_language") &&
!cfgString.contains("g_language=${state.selectedLanguage}")) {
if (!context.mounted) return;
final ok = await showConfirmDialogs(
context,
S.current.localization_info_remove_incompatible_translation_params,
Text(S.current
.localization_info_incompatible_translation_params_warning),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .35));
if (ok == true) {
var finalString = "";
for (var item in cfgString.split("\n")) {
if (!item.trim().startsWith("g_language")) {
finalString = "$finalString$item\n";
}
}
await userCfgFile.delete();
await userCfgFile.create();
await userCfgFile.writeAsString(finalString, flush: true);
_loadData();
}
}
}
}
Future<void> updateLangCfg(bool enable) async {
final selectedLanguage = state.selectedLanguage!; final selectedLanguage = state.selectedLanguage!;
final status = await _getLangCfgEnableLang(lang: selectedLanguage);
final exists = await _cfgFile.exists();
if (status == enable) {
await _updateStatus();
return;
}
StringBuffer newStr = StringBuffer(); StringBuffer newStr = StringBuffer();
var str = <String>[];
if (exists) {
str = (await _cfgFile.readAsString()).replaceAll(" ", "").split("\n");
}
if (enable) {
if (exists) {
for (var value in str) {
if (value.contains("sys_languages")) {
value = "sys_languages=$selectedLanguage";
} else if (value.contains("g_language")) {
value = "g_language=$selectedLanguage";
} else if (value.contains("g_languageAudio")) {
value = "g_language=english";
}
if (value.trim().isNotEmpty) newStr.writeln(value);
}
}
if (!newStr.toString().contains("sys_languages=$selectedLanguage")) { if (!newStr.toString().contains("sys_languages=$selectedLanguage")) {
newStr.writeln("sys_languages=$selectedLanguage"); newStr.writeln("sys_languages=$selectedLanguage");
} }
@ -175,22 +107,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (!newStr.toString().contains("g_languageAudio")) { if (!newStr.toString().contains("g_languageAudio")) {
newStr.writeln("g_languageAudio=english"); newStr.writeln("g_languageAudio=english");
} }
} else { return newStr.toString();
if (exists) {
for (var value in str) {
if (value.contains("sys_languages=")) {
continue;
} else if (value.contains("g_language")) {
continue;
}
newStr.writeln(value);
}
}
}
if (exists) await _cfgFile.delete(recursive: true);
await _cfgFile.create(recursive: true);
await _cfgFile.writeAsString(newStr.toString());
await _updateStatus();
} }
void goFeedback() { void goFeedback() {
@ -198,13 +115,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
} }
VoidCallback? doDelIniFile() { VoidCallback? doDelIniFile() {
return () async { return () async {};
final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
if (await iniFile.exists()) await iniFile.delete();
await updateLangCfg(false);
await _updateStatus();
};
} }
String getCustomizeFileName(String path) { String getCustomizeFileName(String path) {
@ -214,8 +125,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
installFormString(StringBuffer globalIni, String versionName, installFormString(StringBuffer globalIni, String versionName,
{bool? advanced}) async { {bool? advanced}) async {
dPrint("LocalizationUIModel -> installFormString $versionName"); dPrint("LocalizationUIModel -> installFormString $versionName");
final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
if (versionName.isNotEmpty) { if (versionName.isNotEmpty) {
if (!globalIni.toString().endsWith("\n")) { if (!globalIni.toString().endsWith("\n")) {
globalIni.write("\n"); globalIni.write("\n");
@ -226,39 +136,40 @@ class LocalizationUIModel extends _$LocalizationUIModel {
globalIni globalIni
.write("_starcitizen_doctor_localization_version=$versionName\n"); .write("_starcitizen_doctor_localization_version=$versionName\n");
} }
final selectedLanguage = state.selectedLanguage!;
/// write cfg final iniFileString = "\uFEFF${globalIni.toString().trim()}";
if (await _cfgFile.exists()) {} final cfg = await genLangCfg();
final archive = Archive();
/// write ini archive.addFile(ArchiveFile(
if (await iniFile.exists()) { "data/Localization/$selectedLanguage/global.ini", 0, iniFileString));
await iniFile.delete(); archive.addFile(ArchiveFile("data/system.cfg", 0, cfg));
final zip = await compute(_encodeZipFile, archive);
if (zip == null) return;
final blob = Blob.fromBytes(zip, opt: {
"type": "application/zip",
});
final url = web.URL.createObjectURL(blob);
jsDownloadBlobFile(url, "Localization_$versionName.zip");
} }
await iniFile.create(recursive: true);
await iniFile.writeAsString("\uFEFF${globalIni.toString().trim()}", List<int>? _encodeZipFile(Archive archive) {
flush: true); final zip = ZipEncoder().encode(archive);
await updateLangCfg(true); return zip;
await _updateStatus();
} }
VoidCallback? doRemoteInstall( VoidCallback? doRemoteInstall(
BuildContext context, ScLocalizationData value) { BuildContext context, ScLocalizationData value) {
return () async { return () async {
AnalyticsApi.touch("install_localization"); // AnalyticsApi.touch("install_localization");
final savePath = // final savePath =
File("${_downloadDir.absolute.path}\\${value.versionName}.sclang"); // File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
try { try {
state = state.copyWith(workingVersion: value.versionName!); state = state.copyWith(workingVersion: value.versionName!);
if (!await savePath.exists()) { final data = await downloadLocalizationFile(value);
// download
await downloadLocalizationFile(savePath, value);
} else {
dPrint("use cache $savePath");
}
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
// check file // check file
final globalIni = await compute(readArchive, savePath.absolute.path); final globalIni = await compute(readArchive, data);
if (globalIni.isEmpty) { if (globalIni.isEmpty) {
throw S.current.localization_info_corrupted_file; throw S.current.localization_info_corrupted_file;
} }
@ -267,30 +178,25 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (!context.mounted) return; if (!context.mounted) return;
await showToast( await showToast(
context, S.current.localization_info_installation_error(e)); context, S.current.localization_info_installation_error(e));
if (await savePath.exists()) await savePath.delete();
} }
state = state.copyWith(workingVersion: ""); state = state.copyWith(workingVersion: "");
}; };
} }
Future<void> downloadLocalizationFile( Future<Uint8List> downloadLocalizationFile(ScLocalizationData value) async {
File savePath, ScLocalizationData value) async { dPrint("downloading downloadLocalizationFile ...");
dPrint("downloading file to $savePath");
final downloadUrl = final downloadUrl =
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz"; "${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
final r = await RSHttp.get(downloadUrl); final r = await RSHttp.get(downloadUrl);
if (r.statusCode == 200 && r.data != null) { if (r.statusCode == 200 && r.data != null) {
await savePath.create(recursive: true); return r.data!;
await savePath.writeAsBytes(r.data!, flush: true);
} else { } else {
throw "statusCode Error : ${r.statusCode}"; throw "statusCode Error : ${r.statusCode}";
} }
} }
static StringBuffer readArchive(String savePath) { static StringBuffer readArchive(Uint8List data) {
final inputStream = InputFileStream(savePath); final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(data));
final archive =
TarDecoder().decodeBytes(GZipDecoder().decodeBuffer(inputStream));
StringBuffer globalIni = StringBuffer(""); StringBuffer globalIni = StringBuffer("");
for (var element in archive.files) { for (var element in archive.files) {
if (element.name.contains("global.ini")) { if (element.name.contains("global.ini")) {
@ -331,57 +237,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}; };
} }
_updateStatus() async {
final patchStatus = MapEntry(
await _getLangCfgEnableLang(lang: state.selectedLanguage!),
await _getInstalledIniVersion(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"));
final isInstalledAdvanced = await _checkAdvancedStatus(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
state = state.copyWith(
patchStatus: patchStatus, isInstalledAdvanced: isInstalledAdvanced);
}
Future<bool> _checkAdvancedStatus(String path) async {
final iniFile = File(path);
if (!await iniFile.exists()) {
return false;
}
final iniString = (await iniFile.readAsString());
return iniString.contains("_starcitizen_doctor_localization_advanced=true");
}
Future<bool> _getLangCfgEnableLang(
{String lang = "", String gamePath = ""}) async {
if (gamePath.isEmpty) {
gamePath = _scInstallPath;
}
final cfgFile = File("${_scDataDir.absolute.path}\\system.cfg");
if (!await cfgFile.exists()) return false;
final str = (await cfgFile.readAsString()).replaceAll(" ", "");
return str.contains("sys_languages=$lang") &&
str.contains("g_language=$lang") &&
str.contains("g_languageAudio=english");
}
static Future<String> _getInstalledIniVersion(String iniPath) async {
final iniFile = File(iniPath);
if (!await iniFile.exists()) {
return S.current.home_action_info_game_built_in;
}
final iniStringSplit = (await iniFile.readAsString()).split("\n");
for (var i = iniStringSplit.length - 1; i > 0; i--) {
if (iniStringSplit[i]
.contains("_starcitizen_doctor_localization_version=")) {
final v = iniStringSplit[i]
.trim()
.split("_starcitizen_doctor_localization_version=")[1];
return v;
}
}
return S.current.localization_info_custom_files;
}
Future<List<String>> checkLangUpdate({bool skipReload = false}) async { Future<List<String>> checkLangUpdate({bool skipReload = false}) async {
if (_scInstallPath == "not_install") { if (_scInstallPath == "not_install") {
return []; return [];
@ -394,40 +249,29 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (homeState.scInstallPaths.isEmpty) return []; if (homeState.scInstallPaths.isEmpty) return [];
List<String> updates = []; List<String> updates = [];
for (var scInstallPath in homeState.scInstallPaths) {
//
final scDataDir = Directory("$scInstallPath\\data\\Localization");
//
final dirList = await scDataDir.list().toList();
for (var element in dirList) {
for (var lang in languageSupport.keys) {
if (element.path.contains(lang) &&
await _getLangCfgEnableLang(
lang: lang, gamePath: scInstallPath)) {
final installedVersion =
await _getInstalledIniVersion("${element.path}\\global.ini");
if (installedVersion == S.current.home_action_info_game_built_in ||
installedVersion == S.current.localization_info_custom_files) {
continue;
}
final curData = _allVersionLocalizationData[lang];
dPrint("check Localization update $scInstallPath");
if (!(curData?.keys.contains(installedVersion) ?? false)) {
// has update
for (var channel in ConstConf.gameChannels) {
if (scInstallPath.contains(channel)) {
dPrint("check Localization update: has update -> $channel");
updates.add(channel);
}
}
} else {
dPrint("check Localization update: up to date");
}
}
}
}
}
return updates; return updates;
} }
void selectChannel(String v) {
state = state.copyWith(selectedChannel: v);
_loadData();
}
} }
@JS("Blob")
extension type Blob._(JSObject _) implements JSObject {
external factory Blob(JSArray<JSArrayBuffer> blobParts, JSAny? options);
factory Blob.fromBytes(List<int> bytes, {Map? opt}) {
final data = Uint8List.fromList(bytes).buffer.toJS;
return Blob([data].toJS, opt?.jsify());
}
external JSArrayBuffer? get blobParts;
external JSObject? get options;
}
@JS()
external void jsDownloadBlobFile(String blobUrl, String filename);

View File

@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc /// @nodoc
mixin _$LocalizationUIState { mixin _$LocalizationUIState {
String? get selectedLanguage => throw _privateConstructorUsedError; String? get selectedLanguage => throw _privateConstructorUsedError;
String get selectedChannel => throw _privateConstructorUsedError;
Map<String, ScLocalizationData>? get apiLocalizationData => Map<String, ScLocalizationData>? get apiLocalizationData =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
String get workingVersion => throw _privateConstructorUsedError; String get workingVersion => throw _privateConstructorUsedError;
@ -39,6 +40,7 @@ abstract class $LocalizationUIStateCopyWith<$Res> {
@useResult @useResult
$Res call( $Res call(
{String? selectedLanguage, {String? selectedLanguage,
String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData, Map<String, ScLocalizationData>? apiLocalizationData,
String workingVersion, String workingVersion,
MapEntry<bool, String>? patchStatus, MapEntry<bool, String>? patchStatus,
@ -62,6 +64,7 @@ class _$LocalizationUIStateCopyWithImpl<$Res, $Val extends LocalizationUIState>
@override @override
$Res call({ $Res call({
Object? selectedLanguage = freezed, Object? selectedLanguage = freezed,
Object? selectedChannel = null,
Object? apiLocalizationData = freezed, Object? apiLocalizationData = freezed,
Object? workingVersion = null, Object? workingVersion = null,
Object? patchStatus = freezed, Object? patchStatus = freezed,
@ -73,6 +76,10 @@ class _$LocalizationUIStateCopyWithImpl<$Res, $Val extends LocalizationUIState>
? _value.selectedLanguage ? _value.selectedLanguage
: selectedLanguage // ignore: cast_nullable_to_non_nullable : selectedLanguage // ignore: cast_nullable_to_non_nullable
as String?, as String?,
selectedChannel: null == selectedChannel
? _value.selectedChannel
: selectedChannel // ignore: cast_nullable_to_non_nullable
as String,
apiLocalizationData: freezed == apiLocalizationData apiLocalizationData: freezed == apiLocalizationData
? _value.apiLocalizationData ? _value.apiLocalizationData
: apiLocalizationData // ignore: cast_nullable_to_non_nullable : apiLocalizationData // ignore: cast_nullable_to_non_nullable
@ -107,6 +114,7 @@ abstract class _$$LocalizationUIStateImplCopyWith<$Res>
@useResult @useResult
$Res call( $Res call(
{String? selectedLanguage, {String? selectedLanguage,
String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData, Map<String, ScLocalizationData>? apiLocalizationData,
String workingVersion, String workingVersion,
MapEntry<bool, String>? patchStatus, MapEntry<bool, String>? patchStatus,
@ -128,6 +136,7 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? selectedLanguage = freezed, Object? selectedLanguage = freezed,
Object? selectedChannel = null,
Object? apiLocalizationData = freezed, Object? apiLocalizationData = freezed,
Object? workingVersion = null, Object? workingVersion = null,
Object? patchStatus = freezed, Object? patchStatus = freezed,
@ -139,6 +148,10 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
? _value.selectedLanguage ? _value.selectedLanguage
: selectedLanguage // ignore: cast_nullable_to_non_nullable : selectedLanguage // ignore: cast_nullable_to_non_nullable
as String?, as String?,
selectedChannel: null == selectedChannel
? _value.selectedChannel
: selectedChannel // ignore: cast_nullable_to_non_nullable
as String,
apiLocalizationData: freezed == apiLocalizationData apiLocalizationData: freezed == apiLocalizationData
? _value._apiLocalizationData ? _value._apiLocalizationData
: apiLocalizationData // ignore: cast_nullable_to_non_nullable : apiLocalizationData // ignore: cast_nullable_to_non_nullable
@ -168,6 +181,7 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
class _$LocalizationUIStateImpl implements _LocalizationUIState { class _$LocalizationUIStateImpl implements _LocalizationUIState {
_$LocalizationUIStateImpl( _$LocalizationUIStateImpl(
{this.selectedLanguage, {this.selectedLanguage,
this.selectedChannel = "LIVE",
final Map<String, ScLocalizationData>? apiLocalizationData, final Map<String, ScLocalizationData>? apiLocalizationData,
this.workingVersion = "", this.workingVersion = "",
this.patchStatus, this.patchStatus,
@ -178,6 +192,9 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
@override @override
final String? selectedLanguage; final String? selectedLanguage;
@override
@JsonKey()
final String selectedChannel;
final Map<String, ScLocalizationData>? _apiLocalizationData; final Map<String, ScLocalizationData>? _apiLocalizationData;
@override @override
Map<String, ScLocalizationData>? get apiLocalizationData { Map<String, ScLocalizationData>? get apiLocalizationData {
@ -208,7 +225,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
@override @override
String toString() { String toString() {
return 'LocalizationUIState(selectedLanguage: $selectedLanguage, apiLocalizationData: $apiLocalizationData, workingVersion: $workingVersion, patchStatus: $patchStatus, isInstalledAdvanced: $isInstalledAdvanced, customizeList: $customizeList)'; return 'LocalizationUIState(selectedLanguage: $selectedLanguage, selectedChannel: $selectedChannel, apiLocalizationData: $apiLocalizationData, workingVersion: $workingVersion, patchStatus: $patchStatus, isInstalledAdvanced: $isInstalledAdvanced, customizeList: $customizeList)';
} }
@override @override
@ -218,6 +235,8 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
other is _$LocalizationUIStateImpl && other is _$LocalizationUIStateImpl &&
(identical(other.selectedLanguage, selectedLanguage) || (identical(other.selectedLanguage, selectedLanguage) ||
other.selectedLanguage == selectedLanguage) && other.selectedLanguage == selectedLanguage) &&
(identical(other.selectedChannel, selectedChannel) ||
other.selectedChannel == selectedChannel) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._apiLocalizationData, _apiLocalizationData) && .equals(other._apiLocalizationData, _apiLocalizationData) &&
(identical(other.workingVersion, workingVersion) || (identical(other.workingVersion, workingVersion) ||
@ -234,6 +253,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
selectedLanguage, selectedLanguage,
selectedChannel,
const DeepCollectionEquality().hash(_apiLocalizationData), const DeepCollectionEquality().hash(_apiLocalizationData),
workingVersion, workingVersion,
patchStatus, patchStatus,
@ -253,6 +273,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
abstract class _LocalizationUIState implements LocalizationUIState { abstract class _LocalizationUIState implements LocalizationUIState {
factory _LocalizationUIState( factory _LocalizationUIState(
{final String? selectedLanguage, {final String? selectedLanguage,
final String selectedChannel,
final Map<String, ScLocalizationData>? apiLocalizationData, final Map<String, ScLocalizationData>? apiLocalizationData,
final String workingVersion, final String workingVersion,
final MapEntry<bool, String>? patchStatus, final MapEntry<bool, String>? patchStatus,
@ -262,6 +283,8 @@ abstract class _LocalizationUIState implements LocalizationUIState {
@override @override
String? get selectedLanguage; String? get selectedLanguage;
@override @override
String get selectedChannel;
@override
Map<String, ScLocalizationData>? get apiLocalizationData; Map<String, ScLocalizationData>? get apiLocalizationData;
@override @override
String get workingVersion; String get workingVersion;

View File

@ -7,7 +7,7 @@ part of 'localization_ui_model.dart';
// ************************************************************************** // **************************************************************************
String _$localizationUIModelHash() => String _$localizationUIModelHash() =>
r'd08a3d100c72b3f1f4a6c96944d4a73bb3c21808'; r'404f40f1f2c04494ce570173262ee0d8579b30b4';
/// See also [LocalizationUIModel]. /// See also [LocalizationUIModel].
@ProviderFor(LocalizationUIModel) @ProviderFor(LocalizationUIModel)

View File

@ -14,6 +14,8 @@ import 'package:starcitizen_doctor/common/conf/url_conf.dart';
import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:web/web.dart' as web;
class SplashUI extends HookConsumerWidget { class SplashUI extends HookConsumerWidget {
const SplashUI({super.key}); const SplashUI({super.key});
@ -102,7 +104,7 @@ class SplashUI extends HookConsumerWidget {
if (userOk) { if (userOk) {
await appConf.put("splash_alert_info_version", _alertInfoVersion); await appConf.put("splash_alert_info_version", _alertInfoVersion);
} else { } else {
exit(0); web.window.close();
} }
} }
} }

View File

@ -53,6 +53,8 @@ dependencies:
super_sliver_list: ^0.4.1 super_sliver_list: ^0.4.1
re_editor: ^0.3.0 re_editor: ^0.3.0
re_highlight: ^0.0.3 re_highlight: ^0.0.3
cross_file: ^0.3.4+2
web: ^1.0.0
dependency_overrides: dependency_overrides:
http: ^1.1.2 http: ^1.1.2

View File

@ -31,10 +31,25 @@
<title>starcitizentoolbox_web</title> <title>starcitizentoolbox_web</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script type="application/javascript" src="/assets/packages/flutter_inappwebview_web/assets/web/web_support.js" defer></script>
<script>
function jsDownloadBlobFile(blobUrl, filename) {
const a = document.createElement("a");
document.body.appendChild(a);
a.href = blobUrl;
a.download = filename;
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(blobUrl);
document.body.removeChild(a);
}, 0);
}
</script>
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>
</body> </body>
</html> </html>