app/lib/ui/home/home_ui_model.dart

468 lines
16 KiB
Dart
Raw Normal View History

import 'dart:async';
2023-10-09 09:32:07 +08:00
import 'dart:convert';
import 'dart:io';
import 'package:dart_rss/dart_rss.dart';
2023-10-09 09:32:07 +08:00
import 'package:desktop_webview_window/desktop_webview_window.dart';
2024-02-07 19:32:36 +08:00
import 'package:starcitizen_doctor/common/io/rs_http.dart';
2023-10-09 09:32:07 +08:00
import 'package:hive/hive.dart';
2023-10-30 20:39:31 +08:00
import 'package:starcitizen_doctor/api/analytics.dart';
2023-10-09 09:32:07 +08:00
import 'package:starcitizen_doctor/api/api.dart';
import 'package:starcitizen_doctor/api/rss.dart';
2023-10-09 09:32:07 +08:00
import 'package:starcitizen_doctor/base/ui_model.dart';
2024-01-29 20:44:00 +08:00
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
2023-10-09 09:32:07 +08:00
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
import 'package:starcitizen_doctor/data/app_placard_data.dart';
2023-10-28 19:58:26 +08:00
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
2023-11-03 00:18:45 +08:00
import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart';
2023-11-03 23:03:19 +08:00
import 'package:starcitizen_doctor/ui/home/countdown/countdown_dialog_ui_model.dart';
2023-10-09 09:32:07 +08:00
import 'package:starcitizen_doctor/ui/home/dialogs/md_content_dialog_ui.dart';
import 'package:starcitizen_doctor/ui/home/dialogs/md_content_dialog_ui_model.dart';
import 'package:starcitizen_doctor/ui/home/localization/localization_ui_model.dart';
2023-10-28 18:19:18 +08:00
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui.dart';
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui_model.dart';
2023-10-09 09:32:07 +08:00
import 'package:starcitizen_doctor/ui/home/performance/performance_ui_model.dart';
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
import 'package:starcitizen_doctor/ui/home/webview/webview_localization_capture_ui_model.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:html/parser.dart' as html;
2023-12-02 12:35:40 +08:00
import 'package:html/dom.dart' as html_dom;
import 'package:windows_ui/windows_ui.dart';
2023-11-03 23:03:19 +08:00
import 'countdown/countdown_dialog_ui.dart';
import 'game_doctor/game_doctor_ui.dart';
import 'game_doctor/game_doctor_ui_model.dart';
2023-10-09 09:32:07 +08:00
import 'localization/localization_ui.dart';
import 'performance/performance_ui.dart';
import 'webview/webview_localization_capture_ui.dart';
class HomeUIModel extends BaseUIModel {
var scInstalledPath = "not_install";
List<String> scInstallPaths = [];
String lastScreenInfo = "";
2023-10-09 09:32:07 +08:00
bool isFixing = false;
String isFixingString = "";
2023-10-28 18:19:18 +08:00
final Map<String, bool> _isGameRunning = {};
bool get isCurGameRunning => _isGameRunning[scInstalledPath] ?? false;
List<RssItem>? rssVideoItems;
List<RssItem>? rssTextItems;
2023-10-28 19:58:26 +08:00
AppWebLocalizationVersionsData? appWebLocalizationVersionsData;
2023-11-03 00:18:45 +08:00
List<CountdownFestivalItemData>? countdownFestivalListData;
MapEntry<String, bool>? localizationUpdateInfo;
bool _isSendLocalizationUpdateNotification = false;
2023-10-09 09:32:07 +08:00
AppPlacardData? appPlacardData;
List? scServerStatus;
Timer? serverUpdateTimer;
Timer? appUpdateTimer;
final statusCnName = const {
"Platform": "平台",
"Persistent Universe": "持续宇宙",
"Electronic Access": "电子访问",
2023-10-18 22:34:29 +08:00
"Arena Commander": "竞技场指挥官"
};
bool isRsiLauncherStarting = false;
2023-10-09 09:32:07 +08:00
@override
Future loadData() async {
if (AppConf.networkVersionData == null) return;
try {
final r = await Api.getAppPlacard();
final box = await Hive.openBox("app_conf");
final version = box.get("close_placard", defaultValue: "");
if (r.enable == true) {
if (r.alwaysShow != true && version == r.version) {
} else {
appPlacardData = r;
}
}
2023-10-28 20:37:45 +08:00
updateSCServerStatus();
notifyListeners();
2023-10-28 19:58:26 +08:00
appWebLocalizationVersionsData = AppWebLocalizationVersionsData.fromJson(
2024-02-07 19:32:36 +08:00
json.decode((await RSHttp.getText(
"${URLConf.webTranslateHomeUrl}/versions.json"))));
2023-11-03 00:18:45 +08:00
countdownFestivalListData = await Api.getFestivalCountdownList();
notifyListeners();
2024-02-03 12:28:15 +08:00
_loadRRS();
2023-10-09 09:32:07 +08:00
} catch (e) {
dPrint(e);
}
// check Localization update
_checkLocalizationUpdate();
notifyListeners();
2023-10-09 09:32:07 +08:00
}
@override
void initModel() {
reScanPath();
serverUpdateTimer = Timer.periodic(
const Duration(minutes: 10),
(timer) {
updateSCServerStatus();
},
);
appUpdateTimer = Timer.periodic(const Duration(minutes: 30), (timer) {
_checkLocalizationUpdate();
});
2023-10-09 09:32:07 +08:00
super.initModel();
}
@override
void dispose() {
serverUpdateTimer?.cancel();
serverUpdateTimer = null;
appUpdateTimer?.cancel();
appUpdateTimer = null;
super.dispose();
}
2023-10-09 09:32:07 +08:00
Future<void> reScanPath() async {
scInstallPaths.clear();
scInstalledPath = "not_install";
lastScreenInfo = "正在扫描 ...";
try {
final listData = await SCLoggerHelper.getLauncherLogList();
if (listData == null) {
lastScreenInfo = "获取log失败";
return;
}
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
2023-11-21 01:05:20 +08:00
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
2023-10-09 09:32:07 +08:00
if (scInstallPaths.isNotEmpty) {
scInstalledPath = scInstallPaths.first;
}
lastScreenInfo = "扫描完毕,共找到 ${scInstallPaths.length} 个有效安装目录";
} catch (e) {
lastScreenInfo = "解析 log 文件失败!";
2023-10-30 20:39:31 +08:00
AnalyticsApi.touch("error_launchLogs");
2023-10-09 09:32:07 +08:00
showToast(context!,
"解析 log 文件失败! \n请关闭游戏退出RSI启动器后重试若仍有问题请使用工具箱中的 RSI Launcher log 修复。");
}
}
updateSCServerStatus() async {
try {
final s = await Api.getScServerStatus();
dPrint("updateSCServerStatus===$s");
scServerStatus = s;
notifyListeners();
} catch (e) {
dPrint(e);
}
}
Future _loadRRS() async {
2024-02-03 12:28:15 +08:00
try {
final v = await RSSApi.getRssVideo();
rssVideoItems = v;
notifyListeners();
final t = await RSSApi.getRssText();
rssTextItems = t;
notifyListeners();
dPrint("RSS update Success !");
} catch (e) {
dPrint("_loadRRS Error:$e");
}
}
2023-10-09 09:32:07 +08:00
openDir(rsiLauncherInstalledPath) async {
2023-11-07 22:35:25 +08:00
await Process.run(SystemHelper.powershellPath,
2023-10-09 09:32:07 +08:00
["explorer.exe", "/select,\"$rsiLauncherInstalledPath\""]);
}
2023-11-10 22:10:27 +08:00
onMenuTap(String key) async {
const String gameInstallReqInfo =
"该功能需要一个有效的安装位置\n\n如果您的游戏未下载完成,请等待下载完毕后使用此功能。\n\n如果您的游戏已下载完毕但未识别,请启动一次游戏后重新打开盒子 或 在设置选项中手动设置安装位置。";
2023-10-09 09:32:07 +08:00
switch (key) {
case "auto_check":
BaseUIContainer(
uiCreate: () => GameDoctorUI(),
modelCreate: () => GameDoctorUIModel(scInstalledPath))
.push(context!);
2023-10-09 09:32:07 +08:00
return;
case "localization":
if (scInstalledPath == "not_install") {
showToast(context!, gameInstallReqInfo);
2023-10-09 09:32:07 +08:00
return;
}
await showDialog(
2023-10-09 09:32:07 +08:00
context: context!,
dismissWithEsc: false,
builder: (BuildContext context) {
return BaseUIContainer(
uiCreate: () => LocalizationUI(),
modelCreate: () => LocalizationUIModel(scInstalledPath));
});
_checkLocalizationUpdate();
2023-10-09 09:32:07 +08:00
return;
case "performance":
if (scInstalledPath == "not_install") {
showToast(context!, gameInstallReqInfo);
2023-10-09 09:32:07 +08:00
return;
}
2023-10-30 20:39:31 +08:00
AnalyticsApi.touch("performance_launch");
2023-10-09 09:32:07 +08:00
BaseUIContainer(
uiCreate: () => PerformanceUI(),
modelCreate: () => PerformanceUIModel(scInstalledPath))
.push(context!);
return;
}
}
showPlacard() {
switch (appPlacardData?.linkType) {
case "external":
launchUrlString(appPlacardData?.link);
return;
case "doc":
showDialog(
context: context!,
builder: (context) {
return BaseUIContainer(
uiCreate: () => MDContentDialogUI(),
modelCreate: () => MDContentDialogUIModel(
appPlacardData?.title ?? "公告详情", appPlacardData?.link));
});
return;
}
}
closePlacard() async {
final box = await Hive.openBox("app_conf");
await box.put("close_placard", appPlacardData?.version);
appPlacardData = null;
notifyListeners();
}
2023-10-28 13:00:10 +08:00
goWebView(String title, String url,
{bool useLocalization = false,
bool loginMode = false,
RsiLoginCallback? rsiLoginCallback}) async {
2023-10-09 09:32:07 +08:00
if (useLocalization) {
2023-10-28 13:00:10 +08:00
const tipVersion = 2;
2023-10-09 09:32:07 +08:00
final box = await Hive.openBox("app_conf");
final skip =
await box.get("skip_web_localization_tip_version", defaultValue: 0);
if (skip != tipVersion) {
final ok = await showConfirmDialogs(
context!,
2023-10-30 20:39:31 +08:00
"星际公民网站汉化",
2023-10-09 09:32:07 +08:00
const Text(
2023-10-28 18:19:18 +08:00
"本插功能件仅供大致浏览使用,不对任何有关本功能产生的问题负责!在涉及账号操作前请注意确认网站的原本内容!"
2023-12-11 23:56:17 +08:00
"\n\n\n使用此功能登录账号时请确保您的 SC汉化盒子 是从可信任的来源下载。",
2023-10-09 09:32:07 +08:00
style: TextStyle(fontSize: 16),
),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context!).size.width * .6));
2023-10-28 13:00:10 +08:00
if (!ok) {
if (loginMode) {
rsiLoginCallback?.call(null, false);
}
return;
}
2023-10-09 09:32:07 +08:00
await box.put("skip_web_localization_tip_version", tipVersion);
}
}
if (!await WebviewWindow.isWebviewAvailable()) {
showToast(context!, "需要安装 WebView2 Runtime");
launchUrlString(
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
return;
}
2023-10-28 13:00:10 +08:00
final webViewModel = WebViewModel(context!,
loginMode: loginMode, loginCallback: rsiLoginCallback);
2023-10-09 09:32:07 +08:00
if (useLocalization) {
isFixingString = "正在初始化汉化资源...";
isFixing = true;
notifyListeners();
try {
2023-10-28 19:58:26 +08:00
await webViewModel.initLocalization(appWebLocalizationVersionsData!);
2023-10-09 09:32:07 +08:00
} catch (e) {
showToast(context!, "初始化网页汉化资源失败!$e");
}
isFixingString = "";
isFixing = false;
}
2023-10-28 13:00:10 +08:00
await webViewModel.initWebView(
title: title,
);
2023-10-09 09:32:07 +08:00
if (await File(
"${AppConf.applicationSupportDir}\\webview_data\\enable_webview_localization_capture")
.exists()) {
webViewModel.enableCapture = true;
BaseUIContainer(
uiCreate: () => WebviewLocalizationCaptureUI(),
modelCreate: () =>
WebviewLocalizationCaptureUIModel(webViewModel))
.push(context!)
.then((_) {
webViewModel.enableCapture = false;
});
}
2023-10-28 20:37:45 +08:00
await Future.delayed(const Duration(milliseconds: 500));
2023-10-09 09:32:07 +08:00
await webViewModel.launch(url);
notifyListeners();
}
launchRSI() async {
2023-10-28 18:19:18 +08:00
if (scInstalledPath == "not_install") {
showToast(context!, "该功能需要一个有效的安装位置");
return;
}
if (AppConf.isMSE) {
if (isCurGameRunning) {
await Process.run(
SystemHelper.powershellPath, ["ps \"StarCitizen\" | kill"]);
return;
}
AnalyticsApi.touch("gameLaunch");
showDialog(
context: context!,
dismissWithEsc: false,
builder: (context) {
return BaseUIContainer(
uiCreate: () => LoginDialog(),
modelCreate: () => LoginDialogModel(scInstalledPath, this));
});
} else {
2023-11-16 20:53:27 +08:00
final ok = await showConfirmDialogs(
context!,
"一键启动功能提示",
const Text("为确保账户安全,一键启动功能已在开发版中禁用,我们将在微软商店版本中提供此功能。"
2023-11-16 20:59:11 +08:00
"\n\n微软商店版由微软提供可靠的分发下载与数字签名,可有效防止软件被恶意篡改。\n\n提示:您无需使用盒子启动游戏也可使用汉化。"),
2023-11-16 20:53:27 +08:00
confirm: "安装微软商店版本",
cancel: "取消");
if (ok == true) {
await launchUrlString(
"https://apps.microsoft.com/detail/9NF3SWFWNKL1?launch=true");
await Future.delayed(const Duration(seconds: 2));
exit(0);
}
2023-10-28 18:19:18 +08:00
}
}
2023-10-19 19:44:23 +08:00
bool isRSIServerStatusOK(Map map) {
return (map["status"] == "ok" || map["status"] == "operational");
}
2023-10-28 18:19:18 +08:00
2023-11-21 21:25:59 +08:00
doLaunchGame(String launchExe, List<String> args, String installPath,
2023-11-23 22:30:00 +08:00
String? processorAffinity) async {
2023-10-28 18:19:18 +08:00
_isGameRunning[installPath] = true;
notifyListeners();
try {
late ProcessResult result;
2023-11-21 21:25:59 +08:00
if (processorAffinity == null) {
result = await Process.run(launchExe, args);
2023-11-21 21:25:59 +08:00
} else {
dPrint("set Affinity === $processorAffinity launchExe === $launchExe");
result = await Process.run("cmd.exe", [
2023-11-21 21:25:59 +08:00
'/C',
'Start',
'"StarCitizen"',
'/High',
'/Affinity',
2023-11-23 22:30:00 +08:00
processorAffinity,
2023-11-21 21:25:59 +08:00
launchExe,
...args
]);
}
dPrint('Exit code: ${result.exitCode}');
dPrint('stdout: ${result.stdout}');
dPrint('stderr: ${result.stderr}');
if (result.exitCode != 0) {
showToast(context!,
"游戏非正常退出\nexitCode=${result.exitCode}\nstdout=${result.stdout}\nstderr=${result.stderr}\n\n诊断信息:");
2023-11-21 21:25:59 +08:00
}
2023-10-28 18:19:18 +08:00
final launchFile = File("$installPath\\loginData.json");
if (await launchFile.exists()) {
await launchFile.delete();
}
} catch (_) {}
_isGameRunning[installPath] = false;
notifyListeners();
}
2023-11-03 23:03:19 +08:00
onTapFestival() {
if (countdownFestivalListData == null) return;
showDialog(
context: context!,
builder: (context) {
return BaseUIContainer(
uiCreate: () => CountdownDialogUI(),
modelCreate: () =>
CountdownDialogUIModel(countdownFestivalListData!));
});
}
getRssImage(RssItem item) {
final h = html.parse(item.description ?? "");
if (h.body == null) return "";
for (var node in h.body!.nodes) {
2023-12-02 12:35:40 +08:00
if (node is html_dom.Element) {
if (node.localName == "img") {
return node.attributes["src"]?.trim() ?? "";
}
}
}
return "";
}
2023-11-30 20:28:03 +08:00
handleTitle(String? title) {
if (title == null) return "";
title = title.replaceAll("", "[ ");
title = title.replaceAll("", " ] ");
return title;
}
Future<void> _checkLocalizationUpdate() async {
final info = await handleError(
() => LocalizationUIModel.checkLocalizationUpdates(scInstallPaths));
dPrint("lUpdateInfo === $info");
localizationUpdateInfo = info;
notifyListeners();
if (info?.value == true && !_isSendLocalizationUpdateNotification) {
final toastNotifier =
ToastNotificationManager.createToastNotifierWithId("SC汉化盒子");
if (toastNotifier != null) {
final toastContent = ToastNotificationManager.getTemplateContent(
ToastTemplateType.toastText02);
if (toastContent != null) {
final xmlNodeList = toastContent.getElementsByTagName('text');
const title = '汉化有新版本!';
final content = '您在 ${info?.key} 安装的汉化有新版本啦!';
xmlNodeList.item(0)?.appendChild(toastContent.createTextNode(title));
xmlNodeList
.item(1)
?.appendChild(toastContent.createTextNode(content));
final toastNotification =
ToastNotification.createToastNotification(toastContent);
toastNotifier.show(toastNotification);
_isSendLocalizationUpdateNotification = true;
}
}
}
}
2023-10-09 09:32:07 +08:00
}