mirror of
https://ghfast.top/https://github.com/StarCitizenToolBox/app.git
synced 2025-08-14 12:19:21 +08:00
feat: Guide UI
This commit is contained in:
@ -99,6 +99,23 @@ class AboutUI extends HookConsumerWidget {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Row(
|
||||
children: [
|
||||
const Icon(FontAwesomeIcons.question),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
S.current.about_action_btn_faq,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
launchUrlString(URLConf.feedbackFAQUrl);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
IconButton(
|
||||
icon: Row(
|
||||
children: [
|
||||
@ -262,4 +279,4 @@ class AboutUI extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
248
lib/ui/guide/guide_ui.dart
Normal file
248
lib/ui/guide/guide_ui.dart
Normal file
@ -0,0 +1,248 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class GuideUI extends HookConsumerWidget {
|
||||
const GuideUI({super.key});
|
||||
|
||||
static const version = 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final toolsState = ref.watch(toolsUIModelProvider);
|
||||
final toolsModel = ref.read(toolsUIModelProvider.notifier);
|
||||
|
||||
final settingModel = ref.read(settingsUIModelProvider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
addPostFrameCallback(() {
|
||||
toolsModel.reScanPath(context, checkActive: true, skipToast: true);
|
||||
});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return makeDefaultPage(
|
||||
context,
|
||||
automaticallyImplyLeading: false,
|
||||
titleRow: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
"assets/app_logo_mini.png",
|
||||
width: 20,
|
||||
height: 20,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
S.current.app_index_version_info(
|
||||
ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
content: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset("assets/app_logo.png", width: 192, height: 192),
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
S.current.guide_title_welcome,
|
||||
style: TextStyle(
|
||||
fontSize: 38,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24),
|
||||
Text(S.current.guide_info_check_settings),
|
||||
SizedBox(height: 32),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
makeGameLauncherPathSelect(
|
||||
context, toolsModel, toolsState, settingModel),
|
||||
const SizedBox(height: 12),
|
||||
makeGamePathSelect(
|
||||
context, toolsModel, toolsState, settingModel),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: () => toolsModel.reScanPath(context,
|
||||
checkActive: true, skipToast: true),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 30, bottom: 30, left: 12, right: 12),
|
||||
child: Icon(FluentIcons.refresh),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 32, left: 32),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.current.guide_info_game_download_note,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Colors.white.withOpacity(.6)),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 38),
|
||||
Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
Button(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Text(S.current.guide_action_get_help),
|
||||
),
|
||||
onPressed: () {
|
||||
launchUrlString(URLConf.feedbackFAQUrl);
|
||||
},
|
||||
),
|
||||
SizedBox(width: 24),
|
||||
FilledButton(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Text(S.current.guide_action_complete_setup),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (toolsState.rsiLauncherInstallPaths.isEmpty) {
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.guide_dialog_confirm_complete_setup,
|
||||
Text(S.current
|
||||
.guide_action_info_no_launcher_path_warning));
|
||||
if (!ok) return;
|
||||
}
|
||||
if (toolsState.scInstallPaths.isEmpty) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
S.current.guide_dialog_confirm_complete_setup,
|
||||
Text(S
|
||||
.current.guide_action_info_no_game_path_warning));
|
||||
if (!ok) return;
|
||||
}
|
||||
final appConf = await Hive.openBox("app_conf");
|
||||
await appConf.put("guide_version", version);
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
SizedBox(width: 32),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 128),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model,
|
||||
ToolsUIState state, SettingsUIModel settingModel) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(S.current.tools_info_rsi_launcher_location),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<String>(
|
||||
isExpanded: true,
|
||||
value: state.rsiLauncherInstalledPath,
|
||||
items: [
|
||||
for (final path in state.rsiLauncherInstallPaths)
|
||||
ComboBoxItem(
|
||||
value: path,
|
||||
child: Text(path),
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.onChangeLauncherPath(v!);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Button(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.folder_search),
|
||||
),
|
||||
onPressed: () async {
|
||||
await settingModel.setLauncherPath(context);
|
||||
if (!context.mounted) return;
|
||||
model.reScanPath(context, checkActive: true, skipToast: true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model,
|
||||
ToolsUIState state, SettingsUIModel settingModel) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(S.current.tools_info_game_install_location),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<String>(
|
||||
isExpanded: true,
|
||||
value: state.scInstalledPath,
|
||||
items: [
|
||||
for (final path in state.scInstallPaths)
|
||||
ComboBoxItem(
|
||||
value: path,
|
||||
child: Text(path),
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.onChangeGamePath(v!);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Button(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.folder_search),
|
||||
),
|
||||
onPressed: () async {
|
||||
await settingModel.setGamePath(context);
|
||||
if (!context.mounted) return;
|
||||
model.reScanPath(context, checkActive: true, skipToast: true);
|
||||
})
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
import 'package:card_swiper/card_swiper.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/ui/guide/guide_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@ -25,6 +28,12 @@ class HomeUI extends HookConsumerWidget {
|
||||
final homeState = ref.watch(homeUIModelProvider);
|
||||
final model = ref.watch(homeUIModelProvider.notifier);
|
||||
ref.watch(localizationUIModelProvider);
|
||||
|
||||
useEffect(() {
|
||||
_checkGuide(context, model);
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Center(
|
||||
@ -819,6 +828,19 @@ class HomeUI extends HookConsumerWidget {
|
||||
}
|
||||
return const Color.fromRGBO(49, 227, 88, .8);
|
||||
}
|
||||
|
||||
Future _checkGuide(BuildContext context, HomeUIModel model) async {
|
||||
final appConf = await Hive.openBox("app_conf");
|
||||
final guideVersion = appConf.get("guide_version", defaultValue: 0);
|
||||
if (guideVersion < GuideUI.version) {
|
||||
await Future.delayed(Duration(milliseconds: 200));
|
||||
if (!context.mounted) return;
|
||||
await context.push("/guide");
|
||||
await model.reScanPath();
|
||||
await model.checkLocalizationUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeItemData {
|
||||
|
@ -16,11 +16,6 @@ class SettingsUI extends HookConsumerWidget {
|
||||
final appGlobalModel = ref.read(appGlobalModelProvider.notifier);
|
||||
return ListView(padding: const EdgeInsets.all(16), children: [
|
||||
makeTitle(S.current.settings_title_general),
|
||||
makeSettingsItem(const Icon(FluentIcons.link, size: 20),
|
||||
S.current.setting_action_create_settings_shortcut,
|
||||
subTitle: S.current.setting_action_create_desktop_shortcut,
|
||||
onTap: () => model.addShortCut(context)),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(
|
||||
const Icon(FontAwesomeIcons.language, size: 20),
|
||||
S.current.settings_app_language,
|
||||
@ -32,8 +27,13 @@ class SettingsUI extends HookConsumerWidget {
|
||||
showGoIcon: false,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(
|
||||
const Icon(FontAwesomeIcons.networkWired, size: 20), S.current.settings_item_dns,
|
||||
makeSettingsItem(const Icon(FluentIcons.link, size: 20),
|
||||
S.current.setting_action_create_settings_shortcut,
|
||||
subTitle: S.current.setting_action_create_desktop_shortcut,
|
||||
onTap: () => model.addShortCut(context)),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(const Icon(FontAwesomeIcons.networkWired, size: 20),
|
||||
S.current.settings_item_dns,
|
||||
subTitle: S.current.settings_item_dns_info,
|
||||
switchStatus: sate.isUseInternalDNS,
|
||||
onSwitch: model.onChangeUseInternalDNS,
|
||||
@ -177,4 +177,4 @@ class SettingsUI extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,10 +69,13 @@ class SettingsUIModel extends _$SettingsUIModel {
|
||||
}
|
||||
|
||||
Future<void> setLauncherPath(BuildContext context) async {
|
||||
await showToast(context, "${S.current.setting_toast_select_launcher_exe}");
|
||||
final r = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: S.current.setting_action_info_select_rsi_launcher_location,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["exe"]);
|
||||
dialogTitle: S.current.setting_action_info_select_rsi_launcher_location,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["exe"],
|
||||
lockParentWindow: true,
|
||||
);
|
||||
if (r == null || r.files.firstOrNull?.path == null) return;
|
||||
final fileName = r.files.first.path!;
|
||||
if (fileName.endsWith("\\RSI Launcher.exe")) {
|
||||
@ -87,10 +90,13 @@ class SettingsUIModel extends _$SettingsUIModel {
|
||||
}
|
||||
|
||||
Future<void> setGamePath(BuildContext context) async {
|
||||
await showToast(context, "${S.current.setting_toast_select_game_file}");
|
||||
final r = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: S.current.setting_action_info_select_game_install_location,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["exe"]);
|
||||
dialogTitle: S.current.setting_action_info_select_game_install_location,
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ["exe"],
|
||||
lockParentWindow: true,
|
||||
);
|
||||
if (r == null || r.files.firstOrNull?.path == null) return;
|
||||
final fileName = r.files.first.path!;
|
||||
dPrint(fileName);
|
||||
@ -211,7 +217,8 @@ class SettingsUIModel extends _$SettingsUIModel {
|
||||
|
||||
Future _loadUseInternalDNS() async {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
final isUseInternalDNS = userBox.get("isUseInternalDNS", defaultValue: false);
|
||||
final isUseInternalDNS =
|
||||
userBox.get("isUseInternalDNS", defaultValue: false);
|
||||
state = state.copyWith(isUseInternalDNS: isUseInternalDNS);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ part of 'settings_ui_model.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$settingsUIModelHash() => r'de58885742e29aae6b1226c16c03655a6a6b018d';
|
||||
String _$settingsUIModelHash() => r'27193efaa8964e8a097daf8dbabf93bb1fdcab3c';
|
||||
|
||||
/// See also [SettingsUIModel].
|
||||
@ProviderFor(SettingsUIModel)
|
||||
|
@ -75,7 +75,7 @@ class SplashUI extends HookConsumerWidget {
|
||||
await _showAlert(context, appConf);
|
||||
}
|
||||
try {
|
||||
await URLConf.checkHost();
|
||||
await URLConf.checkHost();
|
||||
} catch (e) {
|
||||
dPrint("checkHost Error:$e");
|
||||
}
|
||||
|
@ -208,8 +208,8 @@ class ToolsUI extends HookConsumerWidget {
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
model.onChangeGamePath(v!);
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -253,8 +253,8 @@ class ToolsUI extends HookConsumerWidget {
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
model.onChangeLauncherPath(v!);
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -11,6 +11,7 @@ import 'package:go_router/go_router.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/helper/log_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
@ -245,7 +246,8 @@ class ToolsUIModel extends _$ToolsUIModel {
|
||||
/// -----------------------------------------------------------------------------------------
|
||||
/// -----------------------------------------------------------------------------------------
|
||||
|
||||
Future<void> reScanPath(BuildContext context) async {
|
||||
Future<void> reScanPath(BuildContext context,
|
||||
{bool checkActive = false, bool skipToast = false}) async {
|
||||
var scInstallPaths = <String>[];
|
||||
var rsiLauncherInstallPaths = <String>[];
|
||||
var scInstalledPath = "";
|
||||
@ -266,7 +268,7 @@ class ToolsUIModel extends _$ToolsUIModel {
|
||||
return;
|
||||
}
|
||||
scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
|
||||
checkExists: false, withVersion: ["LIVE", "PTU", "EPTU"]);
|
||||
checkExists: checkActive, withVersion: ConstConf.gameChannels);
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
@ -282,13 +284,15 @@ class ToolsUIModel extends _$ToolsUIModel {
|
||||
showToast(context, S.current.tools_action_info_log_file_parse_failed);
|
||||
}
|
||||
|
||||
if (rsiLauncherInstalledPath == "") {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.tools_action_info_rsi_launcher_not_found);
|
||||
}
|
||||
if (scInstalledPath == "") {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.tools_action_info_star_citizen_not_found);
|
||||
if (!skipToast) {
|
||||
if (rsiLauncherInstalledPath == "") {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.tools_action_info_rsi_launcher_not_found);
|
||||
}
|
||||
if (scInstalledPath == "") {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, S.current.tools_action_info_star_citizen_not_found);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ part of 'tools_ui_model.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$toolsUIModelHash() => r'cc75badc7ad810d2a8cef3d2a5188b1b68079aa3';
|
||||
String _$toolsUIModelHash() => r'b0f314abe69c56d4e5e1ac0e89d848b55daabb4f';
|
||||
|
||||
/// See also [ToolsUIModel].
|
||||
@ProviderFor(ToolsUIModel)
|
||||
|
Reference in New Issue
Block a user