diff --git a/analysis_options.yaml b/analysis_options.yaml index 61b6c4d..3a2332d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -24,6 +24,7 @@ linter: rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + dangling_library_doc_comments: false # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/assets/localization_web_script.js b/assets/localization_web_script.js index a01bfc6..4b34d37 100644 --- a/assets/localization_web_script.js +++ b/assets/localization_web_script.js @@ -1,6 +1,6 @@ /// https://github.com/CxJuice/Uex_Chinese_Translate - +/// ------- WebLocalization Script -------------- let SCLocalizationReplaceLocalesMap = {}; let enable_webview_localization_capture = false; let SCLocalizationEnableSplitMode = false; @@ -216,4 +216,68 @@ function ReportUnTranslate(k, v) { } } -InitWebLocalization(); \ No newline at end of file +InitWebLocalization(); + + +/// ----- Login Script ---- +async function getRSILauncherToken() { + // check login + let r = await fetch("api/launcher/v3/account/check", { + method: 'POST', headers: { + 'x-rsi-token': $.cookie('Rsi-Token'), + }, + }); + if (r.status !== 200) { + // wait login + return; + } + + // get claims + let claimsR = await fetch("api/launcher/v3/games/claims", { + method: 'POST', headers: { + 'x-rsi-token': $.cookie('Rsi-Token'), + }, + }); + if (claimsR.status !== 200) return; + let claimsData = (await claimsR.json())["data"]; + + let tokenFormData = new FormData(); + tokenFormData.append('claims', claimsData); + tokenFormData.append('gameId', 'SC'); + let tokenR = await fetch("api/launcher/v3/games/token", { + method: 'POST', headers: { + 'x-rsi-token': $.cookie('Rsi-Token'), + }, + body: tokenFormData + }); + + if (tokenR.status !== 200) return; + let TokenData = (await tokenR.json())["data"]; + console.log(TokenData); + + // get release Data + let releaseFormData = new FormData(); + releaseFormData.append("channelId", "LIVE"); + releaseFormData.append("claims", claimsData); + releaseFormData.append("gameId", "SC"); + releaseFormData.append("platformId", "prod"); + let releaseR = await fetch("api/launcher/v3/games/release", { + method: 'POST', headers: { + 'x-rsi-token': $.cookie('Rsi-Token'), + }, + body: releaseFormData + }); + if (releaseR.status !== 200) return; + let releaseDataJson = await releaseR.json(); + console.log(releaseDataJson); + + // post message + window.chrome.webview.postMessage({ + action: 'webview_rsi_login_success', data: { + 'webToken': $.cookie('Rsi-Token'), + 'claims': claimsData, + 'authToken': TokenData, + 'releaseInfo': releaseDataJson + } + }); +} diff --git a/lib/base/ui.dart b/lib/base/ui.dart index 5d79f8a..a57aba8 100644 --- a/lib/base/ui.dart +++ b/lib/base/ui.dart @@ -16,8 +16,7 @@ class BaseUIContainer extends ConsumerStatefulWidget { final dynamic Function() modelCreate; const BaseUIContainer( - {Key? key, required this.uiCreate, required this.modelCreate}) - : super(key: key); + {super.key, required this.uiCreate, required this.modelCreate}); @override // ignore: no_logic_in_create_state diff --git a/lib/main.dart b/lib/main.dart index db9ebc6..203420e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -51,7 +51,7 @@ class AppUI extends BaseUI { } class WindowButtons extends StatelessWidget { - const WindowButtons({Key? key}) : super(key: key); + const WindowButtons({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/ui/home/home_ui_model.dart b/lib/ui/home/home_ui_model.dart index 5197236..7dfd2ed 100644 --- a/lib/ui/home/home_ui_model.dart +++ b/lib/ui/home/home_ui_model.dart @@ -388,9 +388,12 @@ class HomeUIModel extends BaseUIModel { notifyListeners(); } - goWebView(String title, String url, {bool useLocalization = false}) async { + goWebView(String title, String url, + {bool useLocalization = false, + bool loginMode = false, + RsiLoginCallback? rsiLoginCallback}) async { if (useLocalization) { - const tipVersion = 1; + const tipVersion = 2; final box = await Hive.openBox("app_conf"); final skip = await box.get("skip_web_localization_tip_version", defaultValue: 0); @@ -399,15 +402,18 @@ class HomeUIModel extends BaseUIModel { context!, "星际公民官网汉化", const Text( - "该汉化功能移植自星际公民汉化组的 Tampermonkey 浏览器插件(https://greasyfork.org/zh-CN/scripts/459084),文本内容由星际公民汉化组进行更新。" - "\n\n移植后的脚本源代码随 StarCitizenDoctor 项目一起分发(https://jihulab.com/StarCitizenCN_Community/StarCitizenDoctor)。" "\n\n\n本插功能件仅供大致浏览使用,不对任何有关本功能产生的问题负责!在涉及账号操作前请注意确认网站的原本内容!" "\n\n\n使用此功能登录账号时请确保您的 StarCitizenDoctor 是从可信任的来源下载。", style: TextStyle(fontSize: 16), ), constraints: BoxConstraints( maxWidth: MediaQuery.of(context!).size.width * .6)); - if (!ok) return; + if (!ok) { + if (loginMode) { + rsiLoginCallback?.call(null, false); + } + return; + } await box.put("skip_web_localization_tip_version", tipVersion); } } @@ -417,7 +423,8 @@ class HomeUIModel extends BaseUIModel { "https://developer.microsoft.com/en-us/microsoft-edge/webview2/"); return; } - final webViewModel = WebViewModel(context!); + final webViewModel = WebViewModel(context!, + loginMode: loginMode, loginCallback: rsiLoginCallback); if (useLocalization) { isFixingString = "正在初始化汉化资源..."; isFixing = true; @@ -431,7 +438,9 @@ class HomeUIModel extends BaseUIModel { isFixing = false; } - await webViewModel.initWebView(title: title); + await webViewModel.initWebView( + title: title, + ); if (await File( "${AppConf.applicationSupportDir}\\webview_data\\enable_webview_localization_capture") .exists()) { @@ -453,17 +462,19 @@ class HomeUIModel extends BaseUIModel { launchRSI() async { isRsiLauncherStarting = true; notifyListeners(); - final rsiLauncherInstalledPath = await SystemHelper.getRSILauncherPath(); - if (rsiLauncherInstalledPath.isEmpty) { + // final rsiLauncherInstalledPath = await SystemHelper.getRSILauncherPath(); + // if (rsiLauncherInstalledPath.isEmpty) { + // isRsiLauncherStarting = false; + // notifyListeners(); + // showToast(context!, "未找到 RSI 启动器目录"); + // return; + // } + // SystemHelper.checkAndLaunchRSILauncher(rsiLauncherInstalledPath); + goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect", + loginMode: true, rsiLoginCallback: (data, ok) { isRsiLauncherStarting = false; notifyListeners(); - showToast(context!, "未找到 RSI 启动器目录"); - return; - } - SystemHelper.checkAndLaunchRSILauncher(rsiLauncherInstalledPath); - await Future.delayed(const Duration(seconds: 3)); - isRsiLauncherStarting = false; - notifyListeners(); + }, useLocalization: true); } bool isRSIServerStatusOK(Map map) { diff --git a/lib/ui/home/webview/webview.dart b/lib/ui/home/webview/webview.dart index cb278be..120944d 100644 --- a/lib/ui/home/webview/webview.dart +++ b/lib/ui/home/webview/webview.dart @@ -1,5 +1,6 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:async'; import 'dart:convert'; import 'package:desktop_webview_window/desktop_webview_window.dart'; @@ -12,6 +13,8 @@ import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart' import '../../../api/api.dart'; import '../../../base/ui.dart'; +typedef RsiLoginCallback = void Function(Map? data, bool success); + class WebViewModel { late Webview webview; final BuildContext context; @@ -20,7 +23,7 @@ class WebViewModel { bool get isClosed => _isClosed; - WebViewModel(this.context); + WebViewModel(this.context, {this.loginMode = false, this.loginCallback}); String url = ""; bool canGoBack = false; @@ -35,19 +38,27 @@ class WebViewModel { Map? get curReplaceWords => _curReplaceWords; + final bool loginMode; + + bool _loginModeSuccess = false; + + final RsiLoginCallback? loginCallback; + initWebView({String title = ""}) async { try { webview = await WebviewWindow.create( configuration: CreateConfiguration( - windowWidth: 1920, - windowHeight: 1080, + windowWidth: loginMode ? 960 : 1920, + windowHeight: loginMode ? 720 : 1080, userDataFolderWindows: "${AppConf.applicationSupportDir}/webview_data", title: title)); + // webview.openDevToolsWindow(); webview.isNavigating.addListener(() async { if (!webview.isNavigating.value && localizationResource.isNotEmpty) { final uri = Uri.parse(url); + dPrint("webview Navigating uri === $uri"); if (uri.host.contains("robertsspaceindustries.com")) { // SC 官网 dPrint("load script"); @@ -103,6 +114,11 @@ class WebViewModel { await Future.delayed(const Duration(milliseconds: 100)); await webview.evaluateJavaScript( "WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)"); + if (loginMode) { + dPrint("--- do rsi login ---"); + await Future.delayed(const Duration(milliseconds: 200)); + webview.evaluateJavaScript("getRSILauncherToken();"); + } } else if (uri.host.contains("www.erkul.games") || uri.host.contains("uexcorp.space") || uri.host.contains("ccugame.app")) { @@ -121,9 +137,17 @@ class WebViewModel { dPrint("OnUrlRequestCallback === $url"); this.url = url; }); - webview.onClose.whenComplete(() { - _isClosed = true; - }); + webview.onClose.whenComplete(dispose); + if (loginMode) { + webview.addOnWebMessageReceivedCallback((messageString) { + final message = json.decode(messageString); + if (message["action"] == "webview_rsi_login_success") { + _loginModeSuccess = true; + loginCallback?.call(message, true); + webview.close(); + } + }); + } } catch (e) { showToast(context, "初始化失败:$e"); } @@ -211,4 +235,11 @@ class WebViewModel { OnWebMessageReceivedCallback callback) { webview.removeOnWebMessageReceivedCallback(callback); } + + FutureOr dispose() { + if (loginMode && !_loginModeSuccess) { + loginCallback?.call(null, false); + } + _isClosed = true; + } } diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index bbab9af..ba2e17d 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -60,45 +60,45 @@ fastPadding( } List makeMarkdownView(String description) { - return MarkdownGenerator( + return MarkdownGenerator().buildWidgets(description, config: MarkdownConfig(configs: [ - LinkConfig(onTap: (url) { - if (url.startsWith("/")) { - url = "${AppConf.gitlabHomeUrl}/$url"; - } - launchUrlString(url); - }), - ImgConfig(builder: (String url, Map attributes) { - return ExtendedImage.network( - url, - loadStateChanged: (ExtendedImageState state) { - switch (state.extendedImageLoadState) { - case LoadState.loading: - return const Center( - child: Padding( - padding: EdgeInsets.all(8.0), - child: Column( - children: [ - ProgressRing(), - SizedBox( - height: 12, - ), - Text("加载图片...") - ], - ), - ), - ); - case LoadState.completed: - return ExtendedRawImage( - image: state.extendedImageInfo?.image, - ); - case LoadState.failed: - return const Text("Loading Image error"); + LinkConfig(onTap: (url) { + if (url.startsWith("/")) { + url = "${AppConf.gitlabHomeUrl}/$url"; } - }, - ); - }) - ])).buildWidgets(description); + launchUrlString(url); + }), + ImgConfig(builder: (String url, Map attributes) { + return ExtendedImage.network( + url, + loadStateChanged: (ExtendedImageState state) { + switch (state.extendedImageLoadState) { + case LoadState.loading: + return const Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + children: [ + ProgressRing(), + SizedBox( + height: 12, + ), + Text("加载图片...") + ], + ), + ), + ); + case LoadState.completed: + return ExtendedRawImage( + image: state.extendedImageInfo?.image, + ); + case LoadState.failed: + return const Text("Loading Image error"); + } + }, + ); + }) + ])); } class NoScrollBehavior extends ScrollBehavior { diff --git a/pubspec.yaml b/pubspec.yaml index b8b3c3e..8a5a6d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,7 +44,7 @@ dependencies: markdown_widget: ^2.2.0 extended_image: ^8.1.1 device_info_plus: ^9.0.3 - file_picker: ^5.5.0 + file_picker: ^6.0.0 file_sizes: ^1.0.6 desktop_webview_window: ^0.2.3 flutter_svg: ^2.0.7 @@ -59,7 +59,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec