app/lib/ui/home/localization/advanced_localization_ui_model.dart

312 lines
11 KiB
Dart
Raw Normal View History

2024-05-03 22:35:31 +08:00
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
2024-05-07 21:08:16 +08:00
import 'package:starcitizen_doctor/api/analytics.dart';
2024-05-03 22:35:31 +08:00
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';
2024-05-05 14:59:07 +08:00
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
2024-05-03 22:35:31 +08:00
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import '../home_ui_model.dart';
2024-05-05 15:05:47 +08:00
import 'advanced_localization_ui.json.dart';
2024-05-03 22:35:31 +08:00
import 'localization_ui_model.dart';
part 'advanced_localization_ui_model.g.dart';
part 'advanced_localization_ui_model.freezed.dart';
@freezed
class AdvancedLocalizationUIState with _$AdvancedLocalizationUIState {
factory AdvancedLocalizationUIState({
@Default("") String workingText,
Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
String? p4kGlobalIni,
String? serverGlobalIni,
2024-05-05 20:58:58 +08:00
String? customizeGlobalIni,
2024-05-05 14:59:07 +08:00
ScLocalizationData? apiLocalizationData,
@Default(0) int p4kGlobalIniLines,
@Default(0) int serverGlobalIniLines,
@Default("") String errorMessage,
2024-05-03 22:35:31 +08:00
}) = _AdvancedLocalizationUIState;
}
2024-05-05 14:59:07 +08:00
extension AdvancedLocalizationUIStateEx on AdvancedLocalizationUIState {
Map<AppAdvancedLocalizationClassKeysDataMode, String> get typeNames => {
2024-05-05 16:34:38 +08:00
AppAdvancedLocalizationClassKeysDataMode.localization:
S.current.home_localization_advanced_action_mod_change_localization,
AppAdvancedLocalizationClassKeysDataMode.unLocalization: S.current
.home_localization_advanced_action_mod_change_un_localization,
AppAdvancedLocalizationClassKeysDataMode.mixed:
S.current.home_localization_advanced_action_mod_change_mixed,
AppAdvancedLocalizationClassKeysDataMode.mixedNewline: S
.current.home_localization_advanced_action_mod_change_mixed_newline,
2024-05-05 14:59:07 +08:00
};
}
2024-05-03 22:35:31 +08:00
@riverpod
class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
@override
AdvancedLocalizationUIState build() {
final localizationUIState = ref.read(localizationUIModelProvider);
final localizationUIModel = ref.read(localizationUIModelProvider.notifier);
state = AdvancedLocalizationUIState(classMap: {});
_init(localizationUIState, localizationUIModel);
return state;
}
Future<void> _init(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async {
final (p4kGlobalIni, serverGlobalIni) =
await _readIni(localizationUIState, localizationUIModel);
final ald = await _readClassJson();
if (ald.classKeys == null) return;
2024-05-05 16:34:38 +08:00
state = state.copyWith(
workingText: S.current.home_localization_advanced_msg_classifying);
final m = await compute(_doClassIni, (
ald,
p4kGlobalIni,
serverGlobalIni,
S.current.home_localization_advanced_json_text_un_localization,
S.current.home_localization_advanced_json_text_others
));
2024-05-05 14:59:07 +08:00
final p4kGlobalIniLines = p4kGlobalIni.split("\n").length;
final serverGlobalIniLines = serverGlobalIni.split("\n").length;
2024-05-03 22:35:31 +08:00
state = state.copyWith(
workingText: "",
p4kGlobalIni: p4kGlobalIni,
serverGlobalIni: serverGlobalIni,
2024-05-05 14:59:07 +08:00
p4kGlobalIniLines: p4kGlobalIniLines,
serverGlobalIniLines: serverGlobalIniLines,
2024-05-03 22:35:31 +08:00
classMap: m);
}
2024-05-05 20:58:58 +08:00
void setCustomizeGlobalIni(String? data) async {
state = state.copyWith(customizeGlobalIni: data);
final localizationUIState = ref.read(localizationUIModelProvider);
final localizationUIModel = ref.read(localizationUIModelProvider.notifier);
await _init(localizationUIState, localizationUIModel);
}
2024-05-03 22:35:31 +08:00
static Map<String, AppAdvancedLocalizationClassKeysData> _doClassIni(
(
AppAdvancedLocalizationData ald,
String p4kGlobalIni,
2024-05-05 16:34:38 +08:00
String serverGlobalIni,
String unLocalizationClassName,
String othersClassName,
2024-05-03 22:35:31 +08:00
) v,
) {
final (
AppAdvancedLocalizationData ald,
String p4kGlobalIni,
String serverGlobalIni,
2024-05-05 16:34:38 +08:00
String unLocalizationClassName,
String othersClassName,
2024-05-03 22:35:31 +08:00
) = v;
final unLocalization = AppAdvancedLocalizationClassKeysData(
id: "un_localization",
2024-05-05 16:34:38 +08:00
className: unLocalizationClassName,
2024-05-03 22:35:31 +08:00
keys: [],
2024-05-05 14:59:07 +08:00
)
..mode = AppAdvancedLocalizationClassKeysDataMode.unLocalization
..lockMod = true;
2024-05-03 22:35:31 +08:00
final unClass = AppAdvancedLocalizationClassKeysData(
id: "un_class",
2024-05-05 16:34:38 +08:00
className: othersClassName,
2024-05-03 22:35:31 +08:00
keys: [],
);
final classMap = <String, AppAdvancedLocalizationClassKeysData>{
for (final keys in ald.classKeys!) keys.id ?? "": keys,
};
final p4kIniMap = readIniAsMap(p4kGlobalIni);
final serverIniMap = readIniAsMap(serverGlobalIni);
var regexList = classMap.values
2024-05-05 14:59:07 +08:00
.expand((c) =>
c.keys!.map((k) => MapEntry(c, RegExp(k, caseSensitive: false))))
2024-05-03 22:35:31 +08:00
.toList();
iniKeysLoop:
for (var p4kIniKey in p4kIniMap.keys) {
final serverValue = serverIniMap[p4kIniKey];
2024-05-05 14:59:07 +08:00
if (serverValue == null || serverValue.trim().isEmpty) {
final p4kValue = p4kIniMap[p4kIniKey] ?? "";
if (p4kValue.trim().isNotEmpty) {
unLocalization.valuesMap[p4kIniKey] = p4kValue;
}
2024-05-03 22:35:31 +08:00
continue iniKeysLoop;
} else {
for (var item in regexList) {
2024-05-05 14:59:07 +08:00
if (p4kIniKey.startsWith(item.value)) {
2024-05-03 22:35:31 +08:00
item.key.valuesMap[p4kIniKey] = serverValue;
serverIniMap.remove(p4kIniKey);
continue iniKeysLoop;
}
}
}
}
if (serverIniMap.isNotEmpty) {
for (var element in serverIniMap.keys) {
unClass.valuesMap[element] = serverIniMap[element] ?? "";
}
classMap[unClass.id!] = unClass;
}
2024-05-05 14:59:07 +08:00
if (unLocalization.valuesMap.isNotEmpty) {
classMap[unLocalization.id!] = unLocalization;
}
2024-05-03 22:35:31 +08:00
return classMap;
}
static Map<String, String> readIniAsMap(String iniString) {
final iniMap = <String, String>{};
for (final line in iniString.split("\n")) {
final index = line.indexOf("=");
if (index == -1) continue;
final key = line.substring(0, index).trim();
final value = line.substring(index + 1).trim();
iniMap[key] = value;
}
return iniMap;
}
Future<AppAdvancedLocalizationData> _readClassJson() async {
2024-05-05 15:05:47 +08:00
return AppAdvancedLocalizationData.fromJson(advancedLocalizationJsonData);
2024-05-03 22:35:31 +08:00
}
Future<(String, String)> _readIni(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async {
final homeUIState = ref.read(homeUIModelProvider);
final gameDir = homeUIState.scInstalledPath;
if (gameDir == null) return ("", "");
2024-05-05 16:34:38 +08:00
state = state.copyWith(
workingText: S.current.home_localization_advanced_msg_reading_p4k);
2024-05-03 22:35:31 +08:00
final p4kGlobalIni = await readEnglishInI(gameDir);
dPrint("read p4kGlobalIni => ${p4kGlobalIni.length}");
2024-05-05 16:34:38 +08:00
state = state.copyWith(
workingText: S.current
.home_localization_advanced_msg_reading_server_localization_text);
2024-05-05 20:58:58 +08:00
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);
2024-05-03 22:35:31 +08:00
}
}
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) {
2024-05-07 21:08:16 +08:00
final errorMessage = e.toString();
if (Unp4kCModel.checkRunTimeError(errorMessage)) {
2024-05-07 21:08:16 +08:00
AnalyticsApi.touch("advanced_localization_no_runtime");
}
state = state.copyWith(
2024-05-07 21:08:16 +08:00
errorMessage: errorMessage,
);
// rethrow;
2024-05-05 14:59:07 +08:00
}
return "";
2024-05-03 22:35:31 +08:00
}
2024-05-05 14:59:07 +08:00
onChangeMod(AppAdvancedLocalizationClassKeysData item,
AppAdvancedLocalizationClassKeysDataMode mode) async {
if (item.lockMod) return;
item.mode = mode;
item.isWorking = true;
final classMap =
Map<String, AppAdvancedLocalizationClassKeysData>.from(state.classMap!);
classMap[item.id!] = item;
state = state.copyWith(classMap: classMap);
final p4kIniMap = readIniAsMap(state.p4kGlobalIni!);
final serverIniMap = readIniAsMap(state.serverGlobalIni!);
final newValuesMap = <String, String>{};
for (var kv in item.valuesMap.entries) {
switch (mode) {
case AppAdvancedLocalizationClassKeysDataMode.localization:
newValuesMap[kv.key] = serverIniMap[kv.key] ?? "";
break;
case AppAdvancedLocalizationClassKeysDataMode.unLocalization:
newValuesMap[kv.key] = p4kIniMap[kv.key] ?? "";
break;
case AppAdvancedLocalizationClassKeysDataMode.mixed:
newValuesMap[kv.key] =
"${serverIniMap[kv.key]} [${p4kIniMap[kv.key]}]";
break;
case AppAdvancedLocalizationClassKeysDataMode.mixedNewline:
newValuesMap[kv.key] =
"${serverIniMap[kv.key]}\\n${p4kIniMap[kv.key]}";
break;
}
await Future.delayed(Duration.zero);
}
item.valuesMap = newValuesMap;
item.isWorking = false;
classMap[item.id!] = item;
state = state.copyWith(classMap: classMap);
}
Future<bool> doInstall() async {
2024-05-07 21:08:16 +08:00
AnalyticsApi.touch("advanced_localization_apply");
2024-05-05 16:34:38 +08:00
state = state.copyWith(
workingText:
S.current.home_localization_advanced_msg_gen_localization_text);
2024-05-05 14:59:07 +08:00
final classMap = state.classMap!;
final globalIni = StringBuffer();
for (var item in classMap.values) {
for (var kv in item.valuesMap.entries) {
globalIni.write("${kv.key}=${kv.value}\n");
await Future.delayed(Duration.zero);
}
}
2024-05-05 16:34:38 +08:00
state = state.copyWith(
workingText:
S.current.home_localization_advanced_msg_gen_localization_install);
2024-05-05 14:59:07 +08:00
final localizationUIModel = ref.read(localizationUIModelProvider.notifier);
await localizationUIModel.installFormString(
globalIni, state.apiLocalizationData?.versionName ?? "-",
advanced: true);
state = state.copyWith(workingText: "");
return true;
}
2024-05-03 22:35:31 +08:00
}