mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-01-18 06:00:28 +00:00
feat: Linux Path Basic Support
modified: lib/ui/settings/settings_ui_model.dart
This commit is contained in:
parent
062014f24a
commit
1a1f72a596
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
class ConstConf {
|
class ConstConf {
|
||||||
static const String appVersion = "3.0.0 Beta9";
|
static const String appVersion = "3.0.0 Beta9";
|
||||||
static const int appVersionCode = 79;
|
static const int appVersionCode = 79;
|
||||||
@ -18,5 +20,12 @@ class AppConf {
|
|||||||
_networkGameChannels = channels;
|
_networkGameChannels = channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<String> get gameChannels => _networkGameChannels ?? ConstConf._gameChannels;
|
static List<String> get gameChannels {
|
||||||
|
final baseChannels = _networkGameChannels ?? ConstConf._gameChannels;
|
||||||
|
// On Linux, add lowercase variants for case-sensitive filesystem
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
return [...baseChannels, ...baseChannels.map((c) => c.toLowerCase())];
|
||||||
|
}
|
||||||
|
return baseChannels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:hive_ce/hive.dart';
|
import 'package:hive_ce/hive.dart';
|
||||||
import 'package:starcitizen_doctor/common/conf/conf.dart';
|
import 'package:starcitizen_doctor/common/conf/conf.dart';
|
||||||
|
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||||
|
|
||||||
class SCLoggerHelper {
|
class SCLoggerHelper {
|
||||||
@ -43,20 +44,23 @@ class SCLoggerHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<String>> getGameInstallPath(List listData,
|
static Future<List<String>> getGameInstallPath(
|
||||||
{bool checkExists = true,
|
List listData, {
|
||||||
List<String> withVersion = const ["LIVE"]}) async {
|
bool checkExists = true,
|
||||||
|
List<String> withVersion = const ["LIVE"],
|
||||||
|
}) async {
|
||||||
List<String> scInstallPaths = [];
|
List<String> scInstallPaths = [];
|
||||||
|
|
||||||
checkAndAddPath(String path, bool checkExists) async {
|
checkAndAddPath(String path, bool checkExists) async {
|
||||||
// 将所有连续的 \\ 替换为 \
|
// Normalize path separators to current platform format
|
||||||
path = path.replaceAll(RegExp(r'\\+'), "\\");
|
path = path.platformPath;
|
||||||
if (path.isNotEmpty && !scInstallPaths.contains(path)) {
|
|
||||||
|
// Case-insensitive check for existing paths
|
||||||
|
if (path.isNotEmpty && !scInstallPaths.any((p) => p.toLowerCase() == path.toLowerCase())) {
|
||||||
if (!checkExists) {
|
if (!checkExists) {
|
||||||
dPrint("find installPath == $path");
|
dPrint("find installPath == $path");
|
||||||
scInstallPaths.add(path);
|
scInstallPaths.add(path);
|
||||||
} else if (await File("$path/Bin64/StarCitizen.exe").exists() &&
|
} else if (await File("$path/Bin64/StarCitizen.exe").exists() && await File("$path/Data.p4k").exists()) {
|
||||||
await File("$path/Data.p4k").exists()) {
|
|
||||||
dPrint("find installPath == $path");
|
dPrint("find installPath == $path");
|
||||||
scInstallPaths.add(path);
|
scInstallPaths.add(path);
|
||||||
}
|
}
|
||||||
@ -67,14 +71,14 @@ class SCLoggerHelper {
|
|||||||
final path = confBox.get("custom_game_path");
|
final path = confBox.get("custom_game_path");
|
||||||
if (path != null && path != "") {
|
if (path != null && path != "") {
|
||||||
for (var v in withVersion) {
|
for (var v in withVersion) {
|
||||||
await checkAndAddPath("$path\\$v", checkExists);
|
await checkAndAddPath("$path\\$v".platformPath, checkExists);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (var v in withVersion) {
|
for (var v in withVersion) {
|
||||||
String pattern =
|
// Match both Windows (\\) and Unix (/) path separators in log entries, case-insensitive
|
||||||
r'([a-zA-Z]:\\\\[^\\\\]*\\\\[^\\\\]*\\\\StarCitizen\\\\' + v + r')';
|
String pattern = r'([a-zA-Z]:[\\/][^\\/]*[\\/][^\\/]*[\\/]StarCitizen[\\/]' + v + r')';
|
||||||
RegExp regExp = RegExp(pattern, caseSensitive: false);
|
RegExp regExp = RegExp(pattern, caseSensitive: false);
|
||||||
for (var i = listData.length - 1; i > 0; i--) {
|
for (var i = listData.length - 1; i > 0; i--) {
|
||||||
final line = listData[i];
|
final line = listData[i];
|
||||||
@ -89,10 +93,14 @@ class SCLoggerHelper {
|
|||||||
// 动态检测更多位置
|
// 动态检测更多位置
|
||||||
for (var fileName in List.from(scInstallPaths)) {
|
for (var fileName in List.from(scInstallPaths)) {
|
||||||
for (var v in withVersion) {
|
for (var v in withVersion) {
|
||||||
if (fileName.toString().endsWith(v)) {
|
final suffix = '\\$v'.platformPath.toLowerCase();
|
||||||
|
if (fileName.toString().toLowerCase().endsWith(suffix)) {
|
||||||
for (var nv in withVersion) {
|
for (var nv in withVersion) {
|
||||||
final nextName =
|
final basePath = fileName.toString().replaceAll(
|
||||||
"${fileName.toString().replaceAll("\\$v", "")}\\$nv";
|
RegExp('${RegExp.escape(suffix)}\$', caseSensitive: false),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
final nextName = "$basePath\\$nv".platformPath;
|
||||||
await checkAndAddPath(nextName, true);
|
await checkAndAddPath(nextName, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,9 +116,10 @@ class SCLoggerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String getGameChannelID(String installPath) {
|
static String getGameChannelID(String installPath) {
|
||||||
|
final pathLower = installPath.platformPath.toLowerCase();
|
||||||
for (var value in AppConf.gameChannels) {
|
for (var value in AppConf.gameChannels) {
|
||||||
if (installPath.endsWith("\\$value")) {
|
if (pathLower.endsWith('\\${value.toLowerCase()}'.platformPath)) {
|
||||||
return value;
|
return value.toUpperCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
@ -121,8 +130,7 @@ class SCLoggerHelper {
|
|||||||
if (!await logFile.exists()) {
|
if (!await logFile.exists()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return await logFile.readAsLines(
|
return await logFile.readAsLines(encoding: const Utf8Codec(allowMalformed: true));
|
||||||
encoding: const Utf8Codec(allowMalformed: true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
|
static MapEntry<String, String>? getGameRunningLogInfo(List<String> logs) {
|
||||||
@ -138,47 +146,47 @@ class SCLoggerHelper {
|
|||||||
|
|
||||||
static MapEntry<String, String>? _checkRunningLine(String line) {
|
static MapEntry<String, String>? _checkRunningLine(String line) {
|
||||||
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
|
if (line.contains("STATUS_CRYENGINE_OUT_OF_SYSMEM")) {
|
||||||
return MapEntry(S.current.doctor_game_error_low_memory,
|
return MapEntry(S.current.doctor_game_error_low_memory, S.current.doctor_game_error_low_memory_info);
|
||||||
S.current.doctor_game_error_low_memory_info);
|
|
||||||
}
|
}
|
||||||
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
|
if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
|
||||||
return MapEntry(S.current.doctor_game_error_generic_info,
|
return MapEntry(S.current.doctor_game_error_generic_info, "https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
|
||||||
"https://docs.qq.com/doc/DUURxUVhzTmZoY09Z");
|
|
||||||
}
|
}
|
||||||
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
|
if (line.contains("DXGI_ERROR_DEVICE_REMOVED")) {
|
||||||
return MapEntry(S.current.doctor_game_error_gpu_crash,
|
return MapEntry(S.current.doctor_game_error_gpu_crash, "https://www.bilibili.com/read/cv19335199");
|
||||||
"https://www.bilibili.com/read/cv19335199");
|
|
||||||
}
|
}
|
||||||
if (line.contains("Wakeup socket sendto error")) {
|
if (line.contains("Wakeup socket sendto error")) {
|
||||||
return MapEntry(S.current.doctor_game_error_socket_error,
|
return MapEntry(S.current.doctor_game_error_socket_error, S.current.doctor_game_error_socket_error_info);
|
||||||
S.current.doctor_game_error_socket_error_info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.contains("The requested operation requires elevated")) {
|
if (line.contains("The requested operation requires elevated")) {
|
||||||
return MapEntry(S.current.doctor_game_error_permissions_error,
|
return MapEntry(
|
||||||
S.current.doctor_game_error_permissions_error_info);
|
S.current.doctor_game_error_permissions_error,
|
||||||
|
S.current.doctor_game_error_permissions_error_info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (line.contains(
|
if (line.contains("The process cannot access the file because is is being used by another process")) {
|
||||||
"The process cannot access the file because is is being used by another process")) {
|
return MapEntry(
|
||||||
return MapEntry(S.current.doctor_game_error_game_process_error,
|
S.current.doctor_game_error_game_process_error,
|
||||||
S.current.doctor_game_error_game_process_error_info);
|
S.current.doctor_game_error_game_process_error_info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (line.contains("0xc0000043")) {
|
if (line.contains("0xc0000043")) {
|
||||||
return MapEntry(S.current.doctor_game_error_game_damaged_file,
|
return MapEntry(
|
||||||
S.current.doctor_game_error_game_damaged_file_info);
|
S.current.doctor_game_error_game_damaged_file,
|
||||||
|
S.current.doctor_game_error_game_damaged_file_info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (line.contains("option to verify the content of the Data.p4k file")) {
|
if (line.contains("option to verify the content of the Data.p4k file")) {
|
||||||
return MapEntry(S.current.doctor_game_error_game_damaged_p4k_file,
|
return MapEntry(
|
||||||
S.current.doctor_game_error_game_damaged_p4k_file_info);
|
S.current.doctor_game_error_game_damaged_p4k_file,
|
||||||
|
S.current.doctor_game_error_game_damaged_p4k_file_info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
|
if (line.contains("OUTOFMEMORY Direct3D could not allocate")) {
|
||||||
return MapEntry(S.current.doctor_game_error_low_gpu_memory,
|
return MapEntry(S.current.doctor_game_error_low_gpu_memory, S.current.doctor_game_error_low_gpu_memory_info);
|
||||||
S.current.doctor_game_error_low_gpu_memory_info);
|
|
||||||
}
|
}
|
||||||
if (line.contains(
|
if (line.contains("try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
|
||||||
"try disabling with r_vulkanDisableLayers = 1 in your user.cfg")) {
|
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash, S.current.doctor_game_error_gpu_vulkan_crash_info);
|
||||||
return MapEntry(S.current.doctor_game_error_gpu_vulkan_crash,
|
|
||||||
S.current.doctor_game_error_gpu_vulkan_crash_info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unknown
|
/// Unknown
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -7,105 +8,118 @@ import 'dart:ui' as ui;
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:starcitizen_doctor/generated/l10n.dart';
|
import 'package:starcitizen_doctor/generated/l10n.dart';
|
||||||
|
|
||||||
Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async {
|
/// String extension for cross-platform path compatibility
|
||||||
return showBaseDialog(context,
|
extension PathStringExtension on String {
|
||||||
title: title ?? S.current.app_common_tip,
|
/// Converts path separators to the current platform's format.
|
||||||
content: Text(msg),
|
/// On Windows: / -> \
|
||||||
actions: [
|
/// On Linux/macOS: \ -> /
|
||||||
FilledButton(
|
String get platformPath {
|
||||||
child: Padding(
|
if (Platform.isWindows) {
|
||||||
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
|
return replaceAll('/', '\\');
|
||||||
child: Text(S.current.app_common_tip_i_know),
|
}
|
||||||
),
|
return replaceAll('\\', '/');
|
||||||
onPressed: () => Navigator.pop(context),
|
}
|
||||||
),
|
|
||||||
],
|
|
||||||
constraints: constraints);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> showConfirmDialogs(BuildContext context, String title, Widget content,
|
Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async {
|
||||||
{String confirm = "", String cancel = "", BoxConstraints? constraints}) async {
|
return showBaseDialog(
|
||||||
|
context,
|
||||||
|
title: title ?? S.current.app_common_tip,
|
||||||
|
content: Text(msg),
|
||||||
|
actions: [
|
||||||
|
FilledButton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
|
||||||
|
child: Text(S.current.app_common_tip_i_know),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
constraints: constraints,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> showConfirmDialogs(
|
||||||
|
BuildContext context,
|
||||||
|
String title,
|
||||||
|
Widget content, {
|
||||||
|
String confirm = "",
|
||||||
|
String cancel = "",
|
||||||
|
BoxConstraints? constraints,
|
||||||
|
}) async {
|
||||||
if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm;
|
if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm;
|
||||||
if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel;
|
if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel;
|
||||||
|
|
||||||
final r = await showBaseDialog(context,
|
final r = await showBaseDialog(
|
||||||
title: title,
|
context,
|
||||||
content: content,
|
title: title,
|
||||||
actions: [
|
content: content,
|
||||||
if (confirm.isNotEmpty)
|
actions: [
|
||||||
FilledButton(
|
if (confirm.isNotEmpty)
|
||||||
child: Padding(
|
FilledButton(
|
||||||
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
|
child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(confirm)),
|
||||||
child: Text(confirm),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.pop(context, true),
|
if (cancel.isNotEmpty)
|
||||||
),
|
Button(
|
||||||
if (cancel.isNotEmpty)
|
child: Padding(padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(cancel)),
|
||||||
Button(
|
onPressed: () => Navigator.pop(context, false),
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8),
|
],
|
||||||
child: Text(cancel),
|
constraints: constraints,
|
||||||
),
|
);
|
||||||
onPressed: () => Navigator.pop(context, false),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
constraints: constraints);
|
|
||||||
return r == true;
|
return r == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> showInputDialogs(BuildContext context,
|
Future<String?> showInputDialogs(
|
||||||
{required String title,
|
BuildContext context, {
|
||||||
required String content,
|
required String title,
|
||||||
BoxConstraints? constraints,
|
required String content,
|
||||||
String? initialValue,
|
BoxConstraints? constraints,
|
||||||
List<TextInputFormatter>? inputFormatters}) async {
|
String? initialValue,
|
||||||
|
List<TextInputFormatter>? inputFormatters,
|
||||||
|
}) async {
|
||||||
String? userInput;
|
String? userInput;
|
||||||
constraints ??= BoxConstraints(maxWidth: MediaQuery
|
constraints ??= BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .38);
|
||||||
.of(context)
|
|
||||||
.size
|
|
||||||
.width * .38);
|
|
||||||
final ok = await showConfirmDialogs(
|
final ok = await showConfirmDialogs(
|
||||||
context,
|
context,
|
||||||
title,
|
title,
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (content.isNotEmpty)
|
if (content.isNotEmpty) Text(content, style: TextStyle(color: Colors.white.withValues(alpha: .6))),
|
||||||
Text(
|
const SizedBox(height: 8),
|
||||||
content,
|
TextFormBox(
|
||||||
style: TextStyle(color: Colors.white.withValues(alpha: .6)),
|
initialValue: initialValue,
|
||||||
),
|
onChanged: (str) {
|
||||||
const SizedBox(height: 8),
|
userInput = str;
|
||||||
TextFormBox(
|
},
|
||||||
initialValue: initialValue,
|
inputFormatters: inputFormatters,
|
||||||
onChanged: (str) {
|
),
|
||||||
userInput = str;
|
],
|
||||||
},
|
),
|
||||||
inputFormatters: inputFormatters,
|
constraints: constraints,
|
||||||
),
|
);
|
||||||
],
|
|
||||||
),
|
|
||||||
constraints: constraints);
|
|
||||||
if (ok == true) return userInput;
|
if (ok == true) return userInput;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showBaseDialog(BuildContext context,
|
Future showBaseDialog(
|
||||||
{required String title, required Widget content, List<Widget>? actions, BoxConstraints? constraints}) async {
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required Widget content,
|
||||||
|
List<Widget>? actions,
|
||||||
|
BoxConstraints? constraints,
|
||||||
|
}) async {
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => ContentDialog(
|
||||||
ContentDialog(
|
title: Text(title),
|
||||||
title: Text(title),
|
content: content,
|
||||||
content: content,
|
constraints: constraints ?? const BoxConstraints(maxWidth: 512, maxHeight: 756.0),
|
||||||
constraints: constraints ??
|
actions: actions,
|
||||||
const BoxConstraints(
|
),
|
||||||
maxWidth: 512,
|
|
||||||
maxHeight: 756.0,
|
|
||||||
),
|
|
||||||
actions: actions,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,17 +36,9 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Image.asset("assets/app_logo_mini.png", width: 20, height: 20, fit: BoxFit.cover),
|
||||||
"assets/app_logo_mini.png",
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")),
|
||||||
S.current.app_index_version_info(
|
|
||||||
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -56,42 +48,34 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Image.asset("assets/app_logo.png", width: 192, height: 192),
|
Image.asset("assets/app_logo.png", width: 192, height: 192),
|
||||||
SizedBox(height: 12),
|
SizedBox(height: 12),
|
||||||
Text(
|
Text(S.current.guide_title_welcome, style: TextStyle(fontSize: 38)),
|
||||||
S.current.guide_title_welcome,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 38,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 24),
|
SizedBox(height: 24),
|
||||||
Text(S.current.guide_info_check_settings),
|
Text(S.current.guide_info_check_settings),
|
||||||
SizedBox(height: 32),
|
SizedBox(height: 32),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
makeGameLauncherPathSelect(
|
makeGameLauncherPathSelect(context, toolsModel, toolsState, settingModel),
|
||||||
context, toolsModel, toolsState, settingModel),
|
const SizedBox(height: 12),
|
||||||
const SizedBox(height: 12),
|
makeGamePathSelect(context, toolsModel, toolsState, settingModel),
|
||||||
makeGamePathSelect(
|
],
|
||||||
context, toolsModel, toolsState, settingModel),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(width: 12),
|
),
|
||||||
Button(
|
SizedBox(width: 12),
|
||||||
onPressed: () => toolsModel.reScanPath(context,
|
Button(
|
||||||
checkActive: true, skipToast: true),
|
onPressed: () => toolsModel.reScanPath(context, checkActive: true, skipToast: true),
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 30, bottom: 30, left: 12, right: 12),
|
||||||
top: 30, bottom: 30, left: 12, right: 12),
|
child: Icon(FluentIcons.refresh),
|
||||||
child: Icon(FluentIcons.refresh),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
)),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(height: 12),
|
SizedBox(height: 12),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 32, left: 32),
|
padding: const EdgeInsets.only(right: 32, left: 32),
|
||||||
@ -100,9 +84,7 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
S.current.guide_info_game_download_note,
|
S.current.guide_info_game_download_note,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white.withValues(alpha: .6)),
|
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -115,8 +97,7 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
Spacer(),
|
Spacer(),
|
||||||
Button(
|
Button(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: Text(S.current.guide_action_get_help),
|
child: Text(S.current.guide_action_get_help),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -126,26 +107,25 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
SizedBox(width: 24),
|
SizedBox(width: 24),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
child: Text(S.current.guide_action_complete_setup),
|
child: Text(S.current.guide_action_complete_setup),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (toolsState.rsiLauncherInstallPaths.isEmpty) {
|
if (toolsState.rsiLauncherInstallPaths.isEmpty) {
|
||||||
final ok = await showConfirmDialogs(
|
final ok = await showConfirmDialogs(
|
||||||
context,
|
context,
|
||||||
S.current.guide_dialog_confirm_complete_setup,
|
S.current.guide_dialog_confirm_complete_setup,
|
||||||
Text(S.current
|
Text(S.current.guide_action_info_no_launcher_path_warning),
|
||||||
.guide_action_info_no_launcher_path_warning));
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
if (toolsState.scInstallPaths.isEmpty) {
|
if (toolsState.scInstallPaths.isEmpty) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
final ok = await showConfirmDialogs(
|
final ok = await showConfirmDialogs(
|
||||||
context,
|
context,
|
||||||
S.current.guide_dialog_confirm_complete_setup,
|
S.current.guide_dialog_confirm_complete_setup,
|
||||||
Text(S
|
Text(S.current.guide_action_info_no_game_path_warning),
|
||||||
.current.guide_action_info_no_game_path_warning));
|
);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
}
|
}
|
||||||
final appConf = await Hive.openBox("app_conf");
|
final appConf = await Hive.openBox("app_conf");
|
||||||
@ -164,8 +144,12 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model,
|
Widget makeGameLauncherPathSelect(
|
||||||
ToolsUIState state, SettingsUIModel settingModel) {
|
BuildContext context,
|
||||||
|
ToolsUIModel model,
|
||||||
|
ToolsUIState state,
|
||||||
|
SettingsUIModel settingModel,
|
||||||
|
) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -177,13 +161,7 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
child: ComboBox<String>(
|
child: ComboBox<String>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: state.rsiLauncherInstalledPath,
|
value: state.rsiLauncherInstalledPath,
|
||||||
items: [
|
items: [for (final path in state.rsiLauncherInstallPaths) ComboBoxItem(value: path, child: Text(path))],
|
||||||
for (final path in state.rsiLauncherInstallPaths)
|
|
||||||
ComboBoxItem(
|
|
||||||
value: path,
|
|
||||||
child: Text(path),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
model.onChangeLauncherPath(v!);
|
model.onChangeLauncherPath(v!);
|
||||||
},
|
},
|
||||||
@ -192,10 +170,7 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Button(
|
Button(
|
||||||
child: const Padding(
|
child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
|
||||||
padding: EdgeInsets.all(6),
|
|
||||||
child: Icon(FluentIcons.folder_search),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await settingModel.setLauncherPath(context);
|
await settingModel.setLauncherPath(context);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@ -206,8 +181,12 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model,
|
Widget makeGamePathSelect(
|
||||||
ToolsUIState state, SettingsUIModel settingModel) {
|
BuildContext context,
|
||||||
|
ToolsUIModel model,
|
||||||
|
ToolsUIState state,
|
||||||
|
SettingsUIModel settingModel,
|
||||||
|
) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -219,13 +198,7 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
child: ComboBox<String>(
|
child: ComboBox<String>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: state.scInstalledPath,
|
value: state.scInstalledPath,
|
||||||
items: [
|
items: [for (final path in state.scInstallPaths) ComboBoxItem(value: path, child: Text(path))],
|
||||||
for (final path in state.scInstallPaths)
|
|
||||||
ComboBoxItem(
|
|
||||||
value: path,
|
|
||||||
child: Text(path),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
model.onChangeGamePath(v!);
|
model.onChangeGamePath(v!);
|
||||||
},
|
},
|
||||||
@ -234,15 +207,13 @@ class GuideUI extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Button(
|
Button(
|
||||||
child: const Padding(
|
child: const Padding(padding: EdgeInsets.all(6), child: Icon(FluentIcons.folder_search)),
|
||||||
padding: EdgeInsets.all(6),
|
onPressed: () async {
|
||||||
child: Icon(FluentIcons.folder_search),
|
await settingModel.setGamePath(context);
|
||||||
),
|
if (!context.mounted) return;
|
||||||
onPressed: () async {
|
model.reScanPath(context, checkActive: true, skipToast: true);
|
||||||
await settingModel.setGamePath(context);
|
},
|
||||||
if (!context.mounted) return;
|
),
|
||||||
model.reScanPath(context, checkActive: true, skipToast: true);
|
|
||||||
})
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -216,7 +216,8 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getChannelID(String installPath) {
|
String getChannelID(String installPath) {
|
||||||
if (installPath.endsWith("\\LIVE")) {
|
final pathLower = installPath.platformPath.toLowerCase();
|
||||||
|
if (pathLower.endsWith('\\live'.platformPath)) {
|
||||||
return "LIVE";
|
return "LIVE";
|
||||||
}
|
}
|
||||||
return "PTU";
|
return "PTU";
|
||||||
|
|||||||
@ -80,8 +80,8 @@ class SettingsUIModel extends _$SettingsUIModel {
|
|||||||
lockParentWindow: true,
|
lockParentWindow: true,
|
||||||
);
|
);
|
||||||
if (r == null || r.files.firstOrNull?.path == null) return;
|
if (r == null || r.files.firstOrNull?.path == null) return;
|
||||||
final fileName = r.files.first.path!;
|
final fileName = r.files.first.path!.platformPath;
|
||||||
if (fileName.endsWith("\\RSI Launcher.exe")) {
|
if (fileName.toLowerCase().endsWith('\\rsi launcher.exe'.platformPath)) {
|
||||||
await _saveCustomPath("custom_launcher_path", fileName);
|
await _saveCustomPath("custom_launcher_path", fileName);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
showToast(context, S.current.setting_action_info_setting_success);
|
showToast(context, S.current.setting_action_info_setting_success);
|
||||||
@ -101,11 +101,14 @@ class SettingsUIModel extends _$SettingsUIModel {
|
|||||||
lockParentWindow: true,
|
lockParentWindow: true,
|
||||||
);
|
);
|
||||||
if (r == null || r.files.firstOrNull?.path == null) return;
|
if (r == null || r.files.firstOrNull?.path == null) return;
|
||||||
final fileName = r.files.first.path!;
|
final fileName = r.files.first.path!.platformPath;
|
||||||
dPrint(fileName);
|
dPrint(fileName);
|
||||||
final fileNameRegExp = RegExp(r"^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$", caseSensitive: false);
|
final fileNameRegExp = RegExp(
|
||||||
|
r'^(.*\\StarCitizen\\.*\\)Bin64\\StarCitizen\.exe$'.platformPath,
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
if (fileNameRegExp.hasMatch(fileName)) {
|
if (fileNameRegExp.hasMatch(fileName)) {
|
||||||
RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$");
|
RegExp pathRegex = RegExp(r'\\[^\\]+\\Bin64\\StarCitizen\.exe$'.platformPath, caseSensitive: false);
|
||||||
String extractedPath = fileName.replaceFirst(pathRegex, '');
|
String extractedPath = fileName.replaceFirst(pathRegex, '');
|
||||||
await _saveCustomPath("custom_game_path", extractedPath);
|
await _saveCustomPath("custom_game_path", extractedPath);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user