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.appConfBox,
this.backgroundImageAssetsPath =
"assets/backgrounds/SC_01_Wallpaper_3840x2160.jpg"});
"assets/backgrounds/SC_01_Wallpaper_3840x2160.webp"});
@override
final String? deviceUUID;

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.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/sc_localization_data.dart';
import '../home_ui_model.dart';
import 'advanced_localization_ui.json.dart';
import 'localization_ui_model.dart';
@ -178,40 +176,7 @@ class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
Future<(String, String)> _readIni(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async {
final homeUIState = ref.read(homeUIModelProvider);
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);
}
return ("", "");
}
Future<String> readEnglishInI(String gameDir) async {

View File

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

View File

@ -21,127 +21,19 @@ class LocalizationDialogUI extends HookConsumerWidget {
useEffect(() {
addPostFrameCallback(() {
model.checkUserCfg(context);
// model.checkUserCfg(context);
});
return null;
}, []);
return ContentDialog(
title: makeTitle(context, model, state),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * .7,
minHeight: MediaQuery.of(context).size.height * .9),
constraints: const BoxConstraints(maxWidth: 1080, minHeight: 920),
content: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
child: SingleChildScrollView(
child: Column(
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(
S.current.localization_info_community_translation,
[
@ -352,15 +244,37 @@ class LocalizationDialogUI extends HookConsumerWidget {
const SizedBox(width: 12),
Text(S.current.home_action_localization_management),
const SizedBox(width: 24),
Text(
"${model.getScInstallPath()}",
style: const TextStyle(fontSize: 13),
),
// Text(
// "${model.getScInstallPath()}",
// style: const TextStyle(fontSize: 13),
// ),
const Spacer(),
SizedBox(
height: 36,
child: Row(
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(
S.current.localization_info_language,
style: const TextStyle(fontSize: 16),
@ -453,8 +367,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
),
confirm: S.current.localization_action_install,
cancel: S.current.home_action_cancel,
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .45),
constraints: const BoxConstraints(maxWidth: 720),
);
if (userOK) {
if (!context.mounted) return;

View File

@ -1,26 +1,24 @@
// ignore_for_file: avoid_build_context_in_providers
import 'dart:async';
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:archive/archive.dart';
import 'dart:js_interop';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.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/common/conf/const_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/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/generated/no_l10n_strings.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 'package:web/web.dart' as web;
part 'localization_ui_model.g.dart';
part 'localization_ui_model.freezed.dart';
@ -29,6 +27,7 @@ part 'localization_ui_model.freezed.dart';
class LocalizationUIState with _$LocalizationUIState {
factory LocalizationUIState({
String? selectedLanguage,
@Default("LIVE") String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData,
@Default("") String workingVersion,
MapEntry<bool, String>? patchStatus,
@ -44,16 +43,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
"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;
String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!;
@ -66,9 +55,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
Future<void> _init() async {
if (_scInstallPath == "not_install") {
return;
}
ref.onDispose(() {
_customizeDirListenSub?.cancel();
_customizeDirListenSub = null;
@ -85,14 +71,13 @@ class LocalizationUIModel extends _$LocalizationUIModel {
Future<void> _loadData() async {
_allVersionLocalizationData.clear();
await _updateStatus();
for (var lang in languageSupport.keys) {
final l = await Api.getScLocalizationData(lang).unwrap();
if (l != null) {
if (lang == state.selectedLanguage) {
final apiLocalizationData = <String, ScLocalizationData>{};
for (var element in l) {
final isPTU = !_scInstallPath.contains("LIVE");
final isPTU = !state.selectedChannel.contains("LIVE");
if (isPTU && element.gameChannel == "PTU") {
apiLocalizationData[element.versionName ?? ""] = element;
} else if (!isPTU && element.gameChannel == "PU") {
@ -110,62 +95,9 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
}
void checkUserCfg(BuildContext context) 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 {
Future<String> genLangCfg() async {
final selectedLanguage = state.selectedLanguage!;
final status = await _getLangCfgEnableLang(lang: selectedLanguage);
final exists = await _cfgFile.exists();
if (status == enable) {
await _updateStatus();
return;
}
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")) {
newStr.writeln("sys_languages=$selectedLanguage");
}
@ -175,22 +107,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (!newStr.toString().contains("g_languageAudio")) {
newStr.writeln("g_languageAudio=english");
}
} else {
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();
return newStr.toString();
}
void goFeedback() {
@ -198,13 +115,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
}
VoidCallback? doDelIniFile() {
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();
};
return () async {};
}
String getCustomizeFileName(String path) {
@ -214,8 +125,7 @@ class LocalizationUIModel extends _$LocalizationUIModel {
installFormString(StringBuffer globalIni, String versionName,
{bool? advanced}) async {
dPrint("LocalizationUIModel -> installFormString $versionName");
final iniFile = File(
"${_scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
if (versionName.isNotEmpty) {
if (!globalIni.toString().endsWith("\n")) {
globalIni.write("\n");
@ -226,39 +136,40 @@ class LocalizationUIModel extends _$LocalizationUIModel {
globalIni
.write("_starcitizen_doctor_localization_version=$versionName\n");
}
/// write cfg
if (await _cfgFile.exists()) {}
/// write ini
if (await iniFile.exists()) {
await iniFile.delete();
final selectedLanguage = state.selectedLanguage!;
final iniFileString = "\uFEFF${globalIni.toString().trim()}";
final cfg = await genLangCfg();
final archive = Archive();
archive.addFile(ArchiveFile(
"data/Localization/$selectedLanguage/global.ini", 0, iniFileString));
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()}",
flush: true);
await updateLangCfg(true);
await _updateStatus();
List<int>? _encodeZipFile(Archive archive) {
final zip = ZipEncoder().encode(archive);
return zip;
}
VoidCallback? doRemoteInstall(
BuildContext context, ScLocalizationData value) {
return () async {
AnalyticsApi.touch("install_localization");
// AnalyticsApi.touch("install_localization");
final savePath =
File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
// final savePath =
// File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
try {
state = state.copyWith(workingVersion: value.versionName!);
if (!await savePath.exists()) {
// download
await downloadLocalizationFile(savePath, value);
} else {
dPrint("use cache $savePath");
}
final data = await downloadLocalizationFile(value);
await Future.delayed(const Duration(milliseconds: 300));
// check file
final globalIni = await compute(readArchive, savePath.absolute.path);
final globalIni = await compute(readArchive, data);
if (globalIni.isEmpty) {
throw S.current.localization_info_corrupted_file;
}
@ -267,30 +178,25 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (!context.mounted) return;
await showToast(
context, S.current.localization_info_installation_error(e));
if (await savePath.exists()) await savePath.delete();
}
state = state.copyWith(workingVersion: "");
};
}
Future<void> downloadLocalizationFile(
File savePath, ScLocalizationData value) async {
dPrint("downloading file to $savePath");
Future<Uint8List> downloadLocalizationFile(ScLocalizationData value) async {
dPrint("downloading downloadLocalizationFile ...");
final downloadUrl =
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
final r = await RSHttp.get(downloadUrl);
if (r.statusCode == 200 && r.data != null) {
await savePath.create(recursive: true);
await savePath.writeAsBytes(r.data!, flush: true);
return r.data!;
} else {
throw "statusCode Error : ${r.statusCode}";
}
}
static StringBuffer readArchive(String savePath) {
final inputStream = InputFileStream(savePath);
final archive =
TarDecoder().decodeBytes(GZipDecoder().decodeBuffer(inputStream));
static StringBuffer readArchive(Uint8List data) {
final archive = TarDecoder().decodeBytes(GZipDecoder().decodeBytes(data));
StringBuffer globalIni = StringBuffer("");
for (var element in archive.files) {
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 {
if (_scInstallPath == "not_install") {
return [];
@ -394,40 +249,29 @@ class LocalizationUIModel extends _$LocalizationUIModel {
if (homeState.scInstallPaths.isEmpty) return [];
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;
}
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
mixin _$LocalizationUIState {
String? get selectedLanguage => throw _privateConstructorUsedError;
String get selectedChannel => throw _privateConstructorUsedError;
Map<String, ScLocalizationData>? get apiLocalizationData =>
throw _privateConstructorUsedError;
String get workingVersion => throw _privateConstructorUsedError;
@ -39,6 +40,7 @@ abstract class $LocalizationUIStateCopyWith<$Res> {
@useResult
$Res call(
{String? selectedLanguage,
String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData,
String workingVersion,
MapEntry<bool, String>? patchStatus,
@ -62,6 +64,7 @@ class _$LocalizationUIStateCopyWithImpl<$Res, $Val extends LocalizationUIState>
@override
$Res call({
Object? selectedLanguage = freezed,
Object? selectedChannel = null,
Object? apiLocalizationData = freezed,
Object? workingVersion = null,
Object? patchStatus = freezed,
@ -73,6 +76,10 @@ class _$LocalizationUIStateCopyWithImpl<$Res, $Val extends LocalizationUIState>
? _value.selectedLanguage
: selectedLanguage // ignore: cast_nullable_to_non_nullable
as String?,
selectedChannel: null == selectedChannel
? _value.selectedChannel
: selectedChannel // ignore: cast_nullable_to_non_nullable
as String,
apiLocalizationData: freezed == apiLocalizationData
? _value.apiLocalizationData
: apiLocalizationData // ignore: cast_nullable_to_non_nullable
@ -107,6 +114,7 @@ abstract class _$$LocalizationUIStateImplCopyWith<$Res>
@useResult
$Res call(
{String? selectedLanguage,
String selectedChannel,
Map<String, ScLocalizationData>? apiLocalizationData,
String workingVersion,
MapEntry<bool, String>? patchStatus,
@ -128,6 +136,7 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? selectedLanguage = freezed,
Object? selectedChannel = null,
Object? apiLocalizationData = freezed,
Object? workingVersion = null,
Object? patchStatus = freezed,
@ -139,6 +148,10 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
? _value.selectedLanguage
: selectedLanguage // ignore: cast_nullable_to_non_nullable
as String?,
selectedChannel: null == selectedChannel
? _value.selectedChannel
: selectedChannel // ignore: cast_nullable_to_non_nullable
as String,
apiLocalizationData: freezed == apiLocalizationData
? _value._apiLocalizationData
: apiLocalizationData // ignore: cast_nullable_to_non_nullable
@ -168,6 +181,7 @@ class __$$LocalizationUIStateImplCopyWithImpl<$Res>
class _$LocalizationUIStateImpl implements _LocalizationUIState {
_$LocalizationUIStateImpl(
{this.selectedLanguage,
this.selectedChannel = "LIVE",
final Map<String, ScLocalizationData>? apiLocalizationData,
this.workingVersion = "",
this.patchStatus,
@ -178,6 +192,9 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
@override
final String? selectedLanguage;
@override
@JsonKey()
final String selectedChannel;
final Map<String, ScLocalizationData>? _apiLocalizationData;
@override
Map<String, ScLocalizationData>? get apiLocalizationData {
@ -208,7 +225,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
@override
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
@ -218,6 +235,8 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
other is _$LocalizationUIStateImpl &&
(identical(other.selectedLanguage, selectedLanguage) ||
other.selectedLanguage == selectedLanguage) &&
(identical(other.selectedChannel, selectedChannel) ||
other.selectedChannel == selectedChannel) &&
const DeepCollectionEquality()
.equals(other._apiLocalizationData, _apiLocalizationData) &&
(identical(other.workingVersion, workingVersion) ||
@ -234,6 +253,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
int get hashCode => Object.hash(
runtimeType,
selectedLanguage,
selectedChannel,
const DeepCollectionEquality().hash(_apiLocalizationData),
workingVersion,
patchStatus,
@ -253,6 +273,7 @@ class _$LocalizationUIStateImpl implements _LocalizationUIState {
abstract class _LocalizationUIState implements LocalizationUIState {
factory _LocalizationUIState(
{final String? selectedLanguage,
final String selectedChannel,
final Map<String, ScLocalizationData>? apiLocalizationData,
final String workingVersion,
final MapEntry<bool, String>? patchStatus,
@ -262,6 +283,8 @@ abstract class _LocalizationUIState implements LocalizationUIState {
@override
String? get selectedLanguage;
@override
String get selectedChannel;
@override
Map<String, ScLocalizationData>? get apiLocalizationData;
@override
String get workingVersion;

View File

@ -7,7 +7,7 @@ part of 'localization_ui_model.dart';
// **************************************************************************
String _$localizationUIModelHash() =>
r'd08a3d100c72b3f1f4a6c96944d4a73bb3c21808';
r'404f40f1f2c04494ce570173262ee0d8579b30b4';
/// See also [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/widgets/widgets.dart';
import 'package:web/web.dart' as web;
class SplashUI extends HookConsumerWidget {
const SplashUI({super.key});
@ -102,7 +104,7 @@ class SplashUI extends HookConsumerWidget {
if (userOk) {
await appConf.put("splash_alert_info_version", _alertInfoVersion);
} else {
exit(0);
web.window.close();
}
}
}

View File

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

View File

@ -31,10 +31,25 @@
<title>starcitizentoolbox_web</title>
<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>
<body>
<script src="flutter_bootstrap.js" async></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>