mirror of
https://mirror.ghproxy.com/https://github.com/StarCitizenToolBox/app.git
synced 2024-12-23 05:23:44 +08:00
Merge pull request #8 from StarCitizenToolBox/feat/full_riverpod
Feat: full riverpod
This commit is contained in:
commit
00e5f7545b
@ -26,5 +26,9 @@ linter:
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
dangling_library_doc_comments: false
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
279
lib/app.dart
Normal file
279
lib/app.dart
Normal file
@ -0,0 +1,279 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_acrylic/flutter_acrylic.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hexcolor/hexcolor.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/performance/performance_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/splash_ui.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'api/analytics.dart';
|
||||
import 'api/api.dart';
|
||||
import 'common/helper/system_helper.dart';
|
||||
import 'common/io/rs_http.dart';
|
||||
import 'common/rust/frb_generated.dart';
|
||||
import 'data/app_version_data.dart';
|
||||
import 'ui/home/downloader/home_downloader_ui.dart';
|
||||
import 'ui/home/game_doctor/game_doctor_ui.dart';
|
||||
import 'ui/index_ui.dart';
|
||||
import 'ui/settings/upgrade_dialog.dart';
|
||||
|
||||
part 'app.g.dart';
|
||||
|
||||
part 'app.freezed.dart';
|
||||
|
||||
@riverpod
|
||||
GoRouter router(RouterRef ref) {
|
||||
return GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const SplashUI()),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/index',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const IndexUI()),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: "downloader",
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const HomeDownloaderUI())),
|
||||
GoRoute(
|
||||
path: 'game_doctor',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const HomeGameDoctorUI()),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'performance',
|
||||
pageBuilder: (context, state) =>
|
||||
myPageBuilder(context, state, const HomePerformanceUI()),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AppGlobalModel extends _$AppGlobalModel {
|
||||
@override
|
||||
AppGlobalState build() {
|
||||
return const AppGlobalState();
|
||||
}
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
Future<void> initApp() async {
|
||||
if (_initialized) return;
|
||||
// init Data
|
||||
final userProfileDir = Platform.environment["USERPROFILE"];
|
||||
final applicationSupportDir =
|
||||
(await getApplicationSupportDirectory()).absolute.path;
|
||||
String? applicationBinaryModuleDir;
|
||||
final logFile = File(
|
||||
"$applicationSupportDir\\logs\\${DateTime.now().millisecondsSinceEpoch}.log");
|
||||
await logFile.create(recursive: true);
|
||||
setDPrintFile(logFile);
|
||||
if (ConstConf.isMSE && userProfileDir != null) {
|
||||
applicationBinaryModuleDir =
|
||||
"$userProfileDir\\AppData\\Local\\Temp\\SCToolbox\\modules";
|
||||
} else {
|
||||
applicationBinaryModuleDir = "$applicationSupportDir\\modules";
|
||||
}
|
||||
dPrint("applicationSupportDir == $applicationSupportDir");
|
||||
dPrint("applicationBinaryModuleDir == $applicationBinaryModuleDir");
|
||||
state = state.copyWith(
|
||||
applicationSupportDir: applicationSupportDir,
|
||||
applicationBinaryModuleDir: applicationBinaryModuleDir,
|
||||
);
|
||||
|
||||
// init Hive
|
||||
try {
|
||||
Hive.init("$applicationSupportDir/db");
|
||||
final box = await Hive.openBox("app_conf");
|
||||
if (box.get("install_id", defaultValue: "") == "") {
|
||||
await box.put("install_id", const Uuid().v4());
|
||||
AnalyticsApi.touch("firstLaunch");
|
||||
}
|
||||
final deviceUUID = box.get("install_id", defaultValue: "");
|
||||
state = state.copyWith(deviceUUID: deviceUUID);
|
||||
} catch (e) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// init Rust bridge
|
||||
await RustLib.init();
|
||||
await RSHttp.init();
|
||||
dPrint("---- rust bridge inited -----");
|
||||
|
||||
// init powershell
|
||||
await SystemHelper.initPowershellPath();
|
||||
|
||||
// get windows info
|
||||
WindowsDeviceInfo? windowsDeviceInfo;
|
||||
try {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
windowsDeviceInfo = await deviceInfo.windowsInfo;
|
||||
} catch (e) {
|
||||
dPrint("DeviceInfo.windowsInfo error: $e");
|
||||
}
|
||||
|
||||
// init windows
|
||||
|
||||
windowManager.waitUntilReadyToShow().then((_) async {
|
||||
await windowManager.setSize(const Size(1280, 810));
|
||||
await windowManager.setMinimumSize(const Size(1280, 810));
|
||||
await windowManager.setSkipTaskbar(false);
|
||||
await windowManager.show();
|
||||
await Window.initialize();
|
||||
await Window.hideWindowControls();
|
||||
if (windowsDeviceInfo?.productName.contains("Windows 11") ?? false) {
|
||||
await Window.setEffect(
|
||||
effect: WindowEffect.acrylic,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
_initialized = true;
|
||||
ref.keepAlive();
|
||||
}
|
||||
|
||||
String getUpgradePath() {
|
||||
return "${state.applicationSupportDir}/._upgrade";
|
||||
}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<bool> checkUpdate(BuildContext context) async {
|
||||
if (!ConstConf.isMSE) {
|
||||
final dir = Directory(getUpgradePath());
|
||||
if (await dir.exists()) {
|
||||
dir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
dynamic checkUpdateError;
|
||||
|
||||
try {
|
||||
final networkVersionData = await Api.getAppVersion();
|
||||
checkActivityThemeColor(networkVersionData);
|
||||
if (ConstConf.isMSE) {
|
||||
dPrint(
|
||||
"lastVersion=${networkVersionData.mSELastVersion} ${networkVersionData.mSELastVersionCode}");
|
||||
} else {
|
||||
dPrint(
|
||||
"lastVersion=${networkVersionData.lastVersion} ${networkVersionData.lastVersionCode}");
|
||||
}
|
||||
state = state.copyWith(networkVersionData: networkVersionData);
|
||||
} catch (e) {
|
||||
checkUpdateError = e;
|
||||
dPrint("_checkUpdate Error:$e");
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (state.networkVersionData == null) {
|
||||
if (!context.mounted) return false;
|
||||
await showToast(context,
|
||||
"网络异常!\n这可能是您的网络环境存在DNS污染,请尝试更换DNS。\n或服务器正在维护或遭受攻击,稍后再试。 \n进入离线模式... \n\n请谨慎在离线模式中使用。 \n当前版本构建日期:${ConstConf.appVersionDate}\n QQ群:940696487 \n错误信息:$checkUpdateError");
|
||||
return false;
|
||||
}
|
||||
final lastVersion = ConstConf.isMSE
|
||||
? state.networkVersionData?.mSELastVersionCode
|
||||
: state.networkVersionData?.lastVersionCode;
|
||||
if ((lastVersion ?? 0) > ConstConf.appVersionCode) {
|
||||
// need update
|
||||
if (!context.mounted) return false;
|
||||
|
||||
final r = await showDialog(
|
||||
dismissWithEsc: false,
|
||||
context: context,
|
||||
builder: (context) => const UpgradeDialogUI());
|
||||
|
||||
if (r != true) {
|
||||
if (!context.mounted) return false;
|
||||
await showToast(context, "获取更新信息失败,请稍后重试。");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Timer? _activityThemeColorTimer;
|
||||
|
||||
checkActivityThemeColor(AppVersionData networkVersionData) {
|
||||
if (_activityThemeColorTimer != null) {
|
||||
_activityThemeColorTimer?.cancel();
|
||||
_activityThemeColorTimer = null;
|
||||
}
|
||||
|
||||
final startTime = networkVersionData.activityColors?.startTime;
|
||||
final endTime = networkVersionData.activityColors?.endTime;
|
||||
if (startTime == null || endTime == null) return;
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
dPrint("now == $now start == $startTime end == $endTime");
|
||||
if (now < startTime) {
|
||||
_activityThemeColorTimer = Timer(Duration(milliseconds: startTime - now),
|
||||
() => checkActivityThemeColor(networkVersionData));
|
||||
dPrint("start Timer ....");
|
||||
} else if (now >= startTime && now <= endTime) {
|
||||
dPrint("update Color ....");
|
||||
// update Color
|
||||
final colorCfg = networkVersionData.activityColors;
|
||||
state = state.copyWith(
|
||||
themeConf: ThemeConf(
|
||||
backgroundColor:
|
||||
HexColor(colorCfg?.background ?? "#132431").withOpacity(.75),
|
||||
menuColor: HexColor(colorCfg?.menu ?? "#132431").withOpacity(.95),
|
||||
micaColor: HexColor(colorCfg?.mica ?? "#0A3142"),
|
||||
),
|
||||
);
|
||||
|
||||
// wait for end
|
||||
_activityThemeColorTimer = Timer(Duration(milliseconds: endTime - now),
|
||||
() => checkActivityThemeColor(networkVersionData));
|
||||
} else {
|
||||
dPrint("reset Color ....");
|
||||
state = state.copyWith(
|
||||
themeConf: ThemeConf(
|
||||
backgroundColor: HexColor("#132431").withOpacity(.75),
|
||||
menuColor: HexColor("#132431").withOpacity(.95),
|
||||
micaColor: HexColor("#0A3142"),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class AppGlobalState with _$AppGlobalState {
|
||||
const factory AppGlobalState({
|
||||
String? deviceUUID,
|
||||
String? applicationSupportDir,
|
||||
String? applicationBinaryModuleDir,
|
||||
AppVersionData? networkVersionData,
|
||||
@Default(ThemeConf()) ThemeConf themeConf,
|
||||
}) = _AppGlobalState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ThemeConf with _$ThemeConf {
|
||||
const factory ThemeConf({
|
||||
@Default(Color(0xbf132431)) Color backgroundColor,
|
||||
@Default(Color(0xf2132431)) Color menuColor,
|
||||
@Default(Color(0xff0a3142)) Color micaColor,
|
||||
}) = _ThemeConf;
|
||||
}
|
405
lib/app.freezed.dart
Normal file
405
lib/app.freezed.dart
Normal file
@ -0,0 +1,405 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'app.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AppGlobalState {
|
||||
String? get deviceUUID => throw _privateConstructorUsedError;
|
||||
String? get applicationSupportDir => throw _privateConstructorUsedError;
|
||||
String? get applicationBinaryModuleDir => throw _privateConstructorUsedError;
|
||||
AppVersionData? get networkVersionData => throw _privateConstructorUsedError;
|
||||
ThemeConf get themeConf => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$AppGlobalStateCopyWith<AppGlobalState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $AppGlobalStateCopyWith<$Res> {
|
||||
factory $AppGlobalStateCopyWith(
|
||||
AppGlobalState value, $Res Function(AppGlobalState) then) =
|
||||
_$AppGlobalStateCopyWithImpl<$Res, AppGlobalState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? deviceUUID,
|
||||
String? applicationSupportDir,
|
||||
String? applicationBinaryModuleDir,
|
||||
AppVersionData? networkVersionData,
|
||||
ThemeConf themeConf});
|
||||
|
||||
$ThemeConfCopyWith<$Res> get themeConf;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$AppGlobalStateCopyWithImpl<$Res, $Val extends AppGlobalState>
|
||||
implements $AppGlobalStateCopyWith<$Res> {
|
||||
_$AppGlobalStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? deviceUUID = freezed,
|
||||
Object? applicationSupportDir = freezed,
|
||||
Object? applicationBinaryModuleDir = freezed,
|
||||
Object? networkVersionData = freezed,
|
||||
Object? themeConf = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
deviceUUID: freezed == deviceUUID
|
||||
? _value.deviceUUID
|
||||
: deviceUUID // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
applicationSupportDir: freezed == applicationSupportDir
|
||||
? _value.applicationSupportDir
|
||||
: applicationSupportDir // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
applicationBinaryModuleDir: freezed == applicationBinaryModuleDir
|
||||
? _value.applicationBinaryModuleDir
|
||||
: applicationBinaryModuleDir // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
networkVersionData: freezed == networkVersionData
|
||||
? _value.networkVersionData
|
||||
: networkVersionData // ignore: cast_nullable_to_non_nullable
|
||||
as AppVersionData?,
|
||||
themeConf: null == themeConf
|
||||
? _value.themeConf
|
||||
: themeConf // ignore: cast_nullable_to_non_nullable
|
||||
as ThemeConf,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ThemeConfCopyWith<$Res> get themeConf {
|
||||
return $ThemeConfCopyWith<$Res>(_value.themeConf, (value) {
|
||||
return _then(_value.copyWith(themeConf: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$AppGlobalStateImplCopyWith<$Res>
|
||||
implements $AppGlobalStateCopyWith<$Res> {
|
||||
factory _$$AppGlobalStateImplCopyWith(_$AppGlobalStateImpl value,
|
||||
$Res Function(_$AppGlobalStateImpl) then) =
|
||||
__$$AppGlobalStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? deviceUUID,
|
||||
String? applicationSupportDir,
|
||||
String? applicationBinaryModuleDir,
|
||||
AppVersionData? networkVersionData,
|
||||
ThemeConf themeConf});
|
||||
|
||||
@override
|
||||
$ThemeConfCopyWith<$Res> get themeConf;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$AppGlobalStateImplCopyWithImpl<$Res>
|
||||
extends _$AppGlobalStateCopyWithImpl<$Res, _$AppGlobalStateImpl>
|
||||
implements _$$AppGlobalStateImplCopyWith<$Res> {
|
||||
__$$AppGlobalStateImplCopyWithImpl(
|
||||
_$AppGlobalStateImpl _value, $Res Function(_$AppGlobalStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? deviceUUID = freezed,
|
||||
Object? applicationSupportDir = freezed,
|
||||
Object? applicationBinaryModuleDir = freezed,
|
||||
Object? networkVersionData = freezed,
|
||||
Object? themeConf = null,
|
||||
}) {
|
||||
return _then(_$AppGlobalStateImpl(
|
||||
deviceUUID: freezed == deviceUUID
|
||||
? _value.deviceUUID
|
||||
: deviceUUID // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
applicationSupportDir: freezed == applicationSupportDir
|
||||
? _value.applicationSupportDir
|
||||
: applicationSupportDir // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
applicationBinaryModuleDir: freezed == applicationBinaryModuleDir
|
||||
? _value.applicationBinaryModuleDir
|
||||
: applicationBinaryModuleDir // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
networkVersionData: freezed == networkVersionData
|
||||
? _value.networkVersionData
|
||||
: networkVersionData // ignore: cast_nullable_to_non_nullable
|
||||
as AppVersionData?,
|
||||
themeConf: null == themeConf
|
||||
? _value.themeConf
|
||||
: themeConf // ignore: cast_nullable_to_non_nullable
|
||||
as ThemeConf,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$AppGlobalStateImpl implements _AppGlobalState {
|
||||
const _$AppGlobalStateImpl(
|
||||
{this.deviceUUID,
|
||||
this.applicationSupportDir,
|
||||
this.applicationBinaryModuleDir,
|
||||
this.networkVersionData,
|
||||
this.themeConf = const ThemeConf()});
|
||||
|
||||
@override
|
||||
final String? deviceUUID;
|
||||
@override
|
||||
final String? applicationSupportDir;
|
||||
@override
|
||||
final String? applicationBinaryModuleDir;
|
||||
@override
|
||||
final AppVersionData? networkVersionData;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ThemeConf themeConf;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AppGlobalState(deviceUUID: $deviceUUID, applicationSupportDir: $applicationSupportDir, applicationBinaryModuleDir: $applicationBinaryModuleDir, networkVersionData: $networkVersionData, themeConf: $themeConf)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$AppGlobalStateImpl &&
|
||||
(identical(other.deviceUUID, deviceUUID) ||
|
||||
other.deviceUUID == deviceUUID) &&
|
||||
(identical(other.applicationSupportDir, applicationSupportDir) ||
|
||||
other.applicationSupportDir == applicationSupportDir) &&
|
||||
(identical(other.applicationBinaryModuleDir,
|
||||
applicationBinaryModuleDir) ||
|
||||
other.applicationBinaryModuleDir ==
|
||||
applicationBinaryModuleDir) &&
|
||||
(identical(other.networkVersionData, networkVersionData) ||
|
||||
other.networkVersionData == networkVersionData) &&
|
||||
(identical(other.themeConf, themeConf) ||
|
||||
other.themeConf == themeConf));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
deviceUUID,
|
||||
applicationSupportDir,
|
||||
applicationBinaryModuleDir,
|
||||
networkVersionData,
|
||||
themeConf);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AppGlobalStateImplCopyWith<_$AppGlobalStateImpl> get copyWith =>
|
||||
__$$AppGlobalStateImplCopyWithImpl<_$AppGlobalStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _AppGlobalState implements AppGlobalState {
|
||||
const factory _AppGlobalState(
|
||||
{final String? deviceUUID,
|
||||
final String? applicationSupportDir,
|
||||
final String? applicationBinaryModuleDir,
|
||||
final AppVersionData? networkVersionData,
|
||||
final ThemeConf themeConf}) = _$AppGlobalStateImpl;
|
||||
|
||||
@override
|
||||
String? get deviceUUID;
|
||||
@override
|
||||
String? get applicationSupportDir;
|
||||
@override
|
||||
String? get applicationBinaryModuleDir;
|
||||
@override
|
||||
AppVersionData? get networkVersionData;
|
||||
@override
|
||||
ThemeConf get themeConf;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$AppGlobalStateImplCopyWith<_$AppGlobalStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ThemeConf {
|
||||
Color get backgroundColor => throw _privateConstructorUsedError;
|
||||
Color get menuColor => throw _privateConstructorUsedError;
|
||||
Color get micaColor => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ThemeConfCopyWith<ThemeConf> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ThemeConfCopyWith<$Res> {
|
||||
factory $ThemeConfCopyWith(ThemeConf value, $Res Function(ThemeConf) then) =
|
||||
_$ThemeConfCopyWithImpl<$Res, ThemeConf>;
|
||||
@useResult
|
||||
$Res call({Color backgroundColor, Color menuColor, Color micaColor});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ThemeConfCopyWithImpl<$Res, $Val extends ThemeConf>
|
||||
implements $ThemeConfCopyWith<$Res> {
|
||||
_$ThemeConfCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? backgroundColor = null,
|
||||
Object? menuColor = null,
|
||||
Object? micaColor = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
backgroundColor: null == backgroundColor
|
||||
? _value.backgroundColor
|
||||
: backgroundColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
menuColor: null == menuColor
|
||||
? _value.menuColor
|
||||
: menuColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
micaColor: null == micaColor
|
||||
? _value.micaColor
|
||||
: micaColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ThemeConfImplCopyWith<$Res>
|
||||
implements $ThemeConfCopyWith<$Res> {
|
||||
factory _$$ThemeConfImplCopyWith(
|
||||
_$ThemeConfImpl value, $Res Function(_$ThemeConfImpl) then) =
|
||||
__$$ThemeConfImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({Color backgroundColor, Color menuColor, Color micaColor});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ThemeConfImplCopyWithImpl<$Res>
|
||||
extends _$ThemeConfCopyWithImpl<$Res, _$ThemeConfImpl>
|
||||
implements _$$ThemeConfImplCopyWith<$Res> {
|
||||
__$$ThemeConfImplCopyWithImpl(
|
||||
_$ThemeConfImpl _value, $Res Function(_$ThemeConfImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? backgroundColor = null,
|
||||
Object? menuColor = null,
|
||||
Object? micaColor = null,
|
||||
}) {
|
||||
return _then(_$ThemeConfImpl(
|
||||
backgroundColor: null == backgroundColor
|
||||
? _value.backgroundColor
|
||||
: backgroundColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
menuColor: null == menuColor
|
||||
? _value.menuColor
|
||||
: menuColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
micaColor: null == micaColor
|
||||
? _value.micaColor
|
||||
: micaColor // ignore: cast_nullable_to_non_nullable
|
||||
as Color,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ThemeConfImpl implements _ThemeConf {
|
||||
const _$ThemeConfImpl(
|
||||
{this.backgroundColor = const Color(0xbf132431),
|
||||
this.menuColor = const Color(0xf2132431),
|
||||
this.micaColor = const Color(0xff0a3142)});
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Color menuColor;
|
||||
@override
|
||||
@JsonKey()
|
||||
final Color micaColor;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ThemeConf(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ThemeConfImpl &&
|
||||
(identical(other.backgroundColor, backgroundColor) ||
|
||||
other.backgroundColor == backgroundColor) &&
|
||||
(identical(other.menuColor, menuColor) ||
|
||||
other.menuColor == menuColor) &&
|
||||
(identical(other.micaColor, micaColor) ||
|
||||
other.micaColor == micaColor));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, backgroundColor, menuColor, micaColor);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ThemeConfImplCopyWith<_$ThemeConfImpl> get copyWith =>
|
||||
__$$ThemeConfImplCopyWithImpl<_$ThemeConfImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ThemeConf implements ThemeConf {
|
||||
const factory _ThemeConf(
|
||||
{final Color backgroundColor,
|
||||
final Color menuColor,
|
||||
final Color micaColor}) = _$ThemeConfImpl;
|
||||
|
||||
@override
|
||||
Color get backgroundColor;
|
||||
@override
|
||||
Color get menuColor;
|
||||
@override
|
||||
Color get micaColor;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ThemeConfImplCopyWith<_$ThemeConfImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
40
lib/app.g.dart
Normal file
40
lib/app.g.dart
Normal file
@ -0,0 +1,40 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'app.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$routerHash() => r'e7b1e3a9fd74b4f00e3d71017615d7fb82bd649d';
|
||||
|
||||
/// See also [router].
|
||||
@ProviderFor(router)
|
||||
final routerProvider = AutoDisposeProvider<GoRouter>.internal(
|
||||
router,
|
||||
name: r'routerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$routerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
|
||||
String _$appGlobalModelHash() => r'9c114910aed546bfd469c8bbfa50cdd4a5be5028';
|
||||
|
||||
/// See also [AppGlobalModel].
|
||||
@ProviderFor(AppGlobalModel)
|
||||
final appGlobalModelProvider =
|
||||
AutoDisposeNotifierProvider<AppGlobalModel, AppGlobalState>.internal(
|
||||
AppGlobalModel.new,
|
||||
name: r'appGlobalModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$appGlobalModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AppGlobalModel = AutoDisposeNotifier<AppGlobalState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
187
lib/base/ui.dart
187
lib/base/ui.dart
@ -1,187 +0,0 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/main.dart';
|
||||
import 'package:starcitizen_doctor/widgets/my_page_route.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import '../common/utils/log.dart' as log_utils;
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'ui_model.dart';
|
||||
|
||||
export '../common/utils/base_utils.dart';
|
||||
export '../widgets/widgets.dart';
|
||||
export 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
class BaseUIContainer extends ConsumerStatefulWidget {
|
||||
final ConsumerState<BaseUIContainer> Function() uiCreate;
|
||||
final dynamic Function() modelCreate;
|
||||
|
||||
const BaseUIContainer(
|
||||
{super.key, required this.uiCreate, required this.modelCreate});
|
||||
|
||||
@override
|
||||
// ignore: no_logic_in_create_state
|
||||
ConsumerState<BaseUIContainer> createState() => uiCreate();
|
||||
|
||||
Future push(BuildContext context) {
|
||||
return Navigator.push(context, makeRoute(context));
|
||||
}
|
||||
|
||||
// Future pushShowModalBottomSheet(BuildContext context) {
|
||||
// return showModalBottomSheet(
|
||||
// context: context,
|
||||
// isScrollControlled: true,
|
||||
// builder: (BuildContext context) {
|
||||
// return this;
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
/// 获取路由
|
||||
FluentPageRoute makeRoute(BuildContext context) {
|
||||
return MyPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return this;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Future pushAndRemoveUntil(BuildContext context) {
|
||||
// return Navigator.pushAndRemoveUntil(context,
|
||||
// MaterialPageRoute(builder: (BuildContext context) {
|
||||
// return this;
|
||||
// }), (_) => false);
|
||||
// }
|
||||
}
|
||||
|
||||
abstract class BaseUI<T extends BaseUIModel>
|
||||
extends ConsumerState<BaseUIContainer> {
|
||||
BaseUIModel? _needDisposeModel;
|
||||
late final ChangeNotifierProvider<T> provider = bindUIModel();
|
||||
|
||||
// final GlobalKey<ScaffoldState> scaffoldState = GlobalKey();
|
||||
// RefreshController? refreshController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// get model
|
||||
final model = ref.watch(provider);
|
||||
return buildBody(context, model)!;
|
||||
}
|
||||
|
||||
String getUITitle(BuildContext context, T model);
|
||||
|
||||
Widget? buildBody(
|
||||
BuildContext context,
|
||||
T model,
|
||||
);
|
||||
|
||||
Widget? getBottomNavigationBar(BuildContext context, T model) => null;
|
||||
|
||||
Color? getBackgroundColor(BuildContext context, T model) => null;
|
||||
|
||||
Widget? getFloatingActionButton(BuildContext context, T model) => null;
|
||||
|
||||
bool getDrawerEnableOpenDragGesture(BuildContext context, T model) => true;
|
||||
|
||||
Widget? getDrawer(BuildContext context, T model) => null;
|
||||
|
||||
Widget makeDefaultPage(BuildContext context, T model,
|
||||
{Widget? titleRow,
|
||||
List<Widget>? actions,
|
||||
Widget? content,
|
||||
bool automaticallyImplyLeading = true}) {
|
||||
return NavigationView(
|
||||
pane: NavigationPane(
|
||||
size: const NavigationPaneSize(openWidth: 0),
|
||||
),
|
||||
appBar: NavigationAppBar(
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
title: DragToMoveArea(
|
||||
child: titleRow ??
|
||||
Column(children: [Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(getUITitle(context, model)),
|
||||
],
|
||||
),
|
||||
)],),
|
||||
),
|
||||
actions: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [...?actions, const WindowButtons()],
|
||||
)),
|
||||
paneBodyBuilder: (
|
||||
PaneItem? item,
|
||||
Widget? body,
|
||||
) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: content ?? makeLoading(context),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void initState() {
|
||||
dPrint("[base] <$runtimeType> UI Init");
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void dispose() {
|
||||
dPrint("[base] <$runtimeType> UI Disposed");
|
||||
_needDisposeModel?.dispose();
|
||||
_needDisposeModel = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 关闭键盘
|
||||
dismissKeyBoard() {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
|
||||
|
||||
// void updateStatusBarIconColor(BuildContext context) {
|
||||
// SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
// statusBarBrightness: Theme.of(context).brightness,
|
||||
// statusBarIconBrightness: getAndroidIconBrightness(context),
|
||||
// ));
|
||||
// }
|
||||
|
||||
ChangeNotifierProvider<T> bindUIModel() {
|
||||
final createdModel = widget.modelCreate();
|
||||
if (createdModel is T) {
|
||||
_needDisposeModel = createdModel;
|
||||
return ChangeNotifierProvider<T>((ref) {
|
||||
return createdModel..context = context;
|
||||
});
|
||||
}
|
||||
return createdModel;
|
||||
}
|
||||
|
||||
// Widget pullToRefreshBody(
|
||||
// {required BaseUIModel model, required Widget child}) {
|
||||
// refreshController ??= RefreshController();
|
||||
// return AppSmartRefresher(
|
||||
// enablePullUp: false,
|
||||
// controller: refreshController,
|
||||
// onRefresh: () async {
|
||||
// await model.reloadData();
|
||||
// refreshController?.refreshCompleted();
|
||||
// },
|
||||
// child: child,
|
||||
// );
|
||||
// }
|
||||
|
||||
makeSvgColor(Color color, {BlendMode blendMode = BlendMode.color}) {
|
||||
return ui.ColorFilter.mode(color, blendMode);
|
||||
}
|
||||
|
||||
dPrint(src) {
|
||||
log_utils.dPrint("<$runtimeType> $src");
|
||||
}
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
import 'ui.dart';
|
||||
import '../common/utils/log.dart' as log_utils;
|
||||
|
||||
export '../common/utils/base_utils.dart';
|
||||
export 'ui.dart';
|
||||
|
||||
class BaseUIModel extends ChangeNotifier {
|
||||
String uiErrorMsg = "";
|
||||
bool _isDisposed = false;
|
||||
|
||||
bool get mounted => !_isDisposed;
|
||||
|
||||
BuildContext? context;
|
||||
|
||||
BaseUIModel() {
|
||||
initModel();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
void initModel() {
|
||||
dPrint("[base] <$runtimeType> Model Init");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
_childUIModels?.forEach((k, value) {
|
||||
(value as BaseUIModel).dispose();
|
||||
_childUIModels?[k] = null;
|
||||
});
|
||||
dPrint("[base] <$runtimeType> Model Disposed");
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future loadData() async {}
|
||||
|
||||
Future reloadData() async {
|
||||
return loadData();
|
||||
}
|
||||
|
||||
Future onErrorReloadData() async {
|
||||
return loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
if (!mounted) return;
|
||||
super.notifyListeners();
|
||||
}
|
||||
|
||||
Future<T?> handleError<T>(Future<T> Function() requestFunc,
|
||||
{bool showFullScreenError = false,
|
||||
String? errorOverride,
|
||||
bool noAlert = false}) async {
|
||||
uiErrorMsg = "";
|
||||
if (mounted) notifyListeners();
|
||||
try {
|
||||
return await requestFunc();
|
||||
} catch (e) {
|
||||
dPrint("$runtimeType.handleError Error:$e");
|
||||
String errorMsg = "Unknown Error";
|
||||
if (e is GrpcError) {
|
||||
errorMsg = "远程服务器消息: ${e.message ?? "Unknown Error"}";
|
||||
} else {
|
||||
errorMsg = e.toString();
|
||||
}
|
||||
if (showFullScreenError) {
|
||||
uiErrorMsg = errorMsg;
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
if (!noAlert) {
|
||||
showToast(context!, errorOverride ?? errorMsg,
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .6,
|
||||
),
|
||||
title: "出现错误!");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<dynamic, dynamic>? _childUIModels;
|
||||
Map<dynamic, dynamic>? _childUIProviders;
|
||||
|
||||
BaseUIModel? onCreateChildUIModel(modelKey) => null;
|
||||
|
||||
dynamic _getChildUIModel(modelKey) {
|
||||
_childUIModels ??= {};
|
||||
final cachedModel = _childUIModels![modelKey];
|
||||
if (cachedModel != null) {
|
||||
return (cachedModel);
|
||||
}
|
||||
final newModel = onCreateChildUIModel(modelKey);
|
||||
_childUIModels![modelKey] = newModel!;
|
||||
return newModel;
|
||||
}
|
||||
|
||||
ChangeNotifierProvider<M> getChildUIModelProviders<M extends BaseUIModel>(
|
||||
modelKey) {
|
||||
_childUIProviders ??= {};
|
||||
if (_childUIProviders![modelKey] == null) {
|
||||
_childUIProviders![modelKey] = ChangeNotifierProvider<M>((ref) {
|
||||
final c = (_getChildUIModel(modelKey) as M);
|
||||
return c..context = context;
|
||||
});
|
||||
}
|
||||
return _childUIProviders![modelKey]!;
|
||||
}
|
||||
|
||||
T? getCreatedChildUIModel<T extends BaseUIModel>(String modelKey,
|
||||
{bool create = false}) {
|
||||
if (create && _childUIModels?[modelKey] == null) {
|
||||
_getChildUIModel(modelKey);
|
||||
}
|
||||
return _childUIModels?[modelKey] as T?;
|
||||
}
|
||||
|
||||
Future<void> reloadAllChildModels() async {
|
||||
if (_childUIModels == null) return;
|
||||
final futureList = <Future>[];
|
||||
for (var value in _childUIModels!.entries) {
|
||||
futureList.add(value.value.reloadData());
|
||||
}
|
||||
await Future.wait(futureList);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
dismissKeyBoard() {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
|
||||
dPrint(src) {
|
||||
log_utils.dPrint("<$runtimeType> $src");
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter_acrylic/flutter_acrylic.dart';
|
||||
import 'package:hexcolor/hexcolor.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/api/api.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/frb_generated.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/data/app_version_data.dart';
|
||||
import 'package:starcitizen_doctor/global_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class AppConf {
|
||||
static const String appVersion = "2.10.7 Beta";
|
||||
static const int appVersionCode = 42;
|
||||
static const String appVersionDate = "2024-03-01";
|
||||
|
||||
static const gameChannels = ["LIVE", "PTU", "EPTU"];
|
||||
|
||||
static String deviceUUID = "";
|
||||
|
||||
static late final String applicationSupportDir;
|
||||
|
||||
static late final String applicationBinaryModuleDir;
|
||||
|
||||
static File? appLogFile;
|
||||
|
||||
static AppVersionData? networkVersionData;
|
||||
|
||||
static bool offlineMode = false;
|
||||
|
||||
static late final WindowsDeviceInfo windowsDeviceInfo;
|
||||
|
||||
static Color? colorBackground;
|
||||
static Color? colorMenu;
|
||||
static Color? colorMica;
|
||||
|
||||
static const isMSE =
|
||||
String.fromEnvironment("MSE", defaultValue: "false") == "true";
|
||||
|
||||
static init(List<String> args) async {
|
||||
dPrint("launch args == $args");
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
/// init device info
|
||||
try {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
windowsDeviceInfo = await deviceInfo.windowsInfo;
|
||||
} catch (_) {}
|
||||
|
||||
/// init Data
|
||||
final userProfileDir = Platform.environment["USERPROFILE"];
|
||||
applicationSupportDir =
|
||||
(await getApplicationSupportDirectory()).absolute.path;
|
||||
final logFile = File(
|
||||
"$applicationSupportDir\\logs\\${DateTime.now().millisecondsSinceEpoch}.log");
|
||||
await logFile.create(recursive: true);
|
||||
appLogFile = logFile;
|
||||
if (AppConf.isMSE && userProfileDir != null) {
|
||||
applicationBinaryModuleDir =
|
||||
"$userProfileDir\\AppData\\Local\\Temp\\SCToolbox\\modules";
|
||||
} else {
|
||||
applicationBinaryModuleDir = "$applicationSupportDir\\modules";
|
||||
}
|
||||
dPrint("applicationSupportDir == $applicationSupportDir");
|
||||
dPrint("applicationBinaryModuleDir == $applicationBinaryModuleDir");
|
||||
try {
|
||||
Hive.init("$applicationSupportDir/db");
|
||||
final box = await Hive.openBox("app_conf");
|
||||
if (box.get("install_id", defaultValue: "") == "") {
|
||||
await box.put("install_id", const Uuid().v4());
|
||||
AnalyticsApi.touch("firstLaunch");
|
||||
}
|
||||
deviceUUID = box.get("install_id", defaultValue: "");
|
||||
} catch (e) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/// check Rust bridge
|
||||
await RustLib.init();
|
||||
await RSHttp.init();
|
||||
dPrint("---- rust bridge inited -----");
|
||||
await SystemHelper.initPowershellPath();
|
||||
|
||||
/// init defaultColor
|
||||
colorBackground = HexColor("#132431").withOpacity(.75);
|
||||
colorMenu = HexColor("#132431").withOpacity(.95);
|
||||
colorMica = HexColor("#0A3142");
|
||||
|
||||
/// init windows
|
||||
await windowManager.ensureInitialized();
|
||||
windowManager.waitUntilReadyToShow().then((_) async {
|
||||
await windowManager.setSize(const Size(1280, 810));
|
||||
await windowManager.setMinimumSize(const Size(1280, 810));
|
||||
await windowManager.center(animate: true);
|
||||
await windowManager.setSkipTaskbar(false);
|
||||
await windowManager.setTitleBarStyle(
|
||||
TitleBarStyle.hidden,
|
||||
windowButtonVisibility: false,
|
||||
);
|
||||
await windowManager.show();
|
||||
await Window.initialize();
|
||||
await Window.hideWindowControls();
|
||||
if (windowsDeviceInfo.productName.contains("Windows 11")) {
|
||||
await Window.setEffect(
|
||||
effect: WindowEffect.acrylic,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static String getUpgradePath() {
|
||||
return "${AppConf.applicationSupportDir}/._upgrade";
|
||||
}
|
||||
|
||||
static Future<void> checkUpdate() async {
|
||||
// clean path
|
||||
if (!isMSE) {
|
||||
final dir = Directory(getUpgradePath());
|
||||
if (await dir.exists()) {
|
||||
dir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
try {
|
||||
networkVersionData = await Api.getAppVersion();
|
||||
globalUIModel.checkActivityThemeColor();
|
||||
if (isMSE) {
|
||||
dPrint(
|
||||
"lastVersion=${networkVersionData?.mSELastVersion} ${networkVersionData?.mSELastVersionCode}");
|
||||
} else {
|
||||
dPrint(
|
||||
"lastVersion=${networkVersionData?.lastVersion} ${networkVersionData?.lastVersionCode}");
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("_checkUpdate Error:$e");
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
|
||||
class BinaryModuleConf {
|
||||
@ -10,8 +9,7 @@ class BinaryModuleConf {
|
||||
"aria2c": "0",
|
||||
};
|
||||
|
||||
static Future extractModule(List<String> modules) async {
|
||||
final workingDir = AppConf.applicationBinaryModuleDir;
|
||||
static Future extractModule(List<String> modules, String workingDir) async {
|
||||
for (var m in _modules.entries) {
|
||||
if (!modules.contains(m.key)) continue;
|
||||
final name = m.key;
|
||||
|
8
lib/common/conf/const_conf.dart
Normal file
8
lib/common/conf/const_conf.dart
Normal file
@ -0,0 +1,8 @@
|
||||
class ConstConf {
|
||||
static const String appVersion = "2.10.7 Beta";
|
||||
static const int appVersionCode = 42;
|
||||
static const String appVersionDate = "2024-03-01";
|
||||
static const gameChannels = ["LIVE", "PTU", "EPTU"];
|
||||
static const isMSE =
|
||||
String.fromEnvironment("MSE", defaultValue: "false") == "true";
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pbgrpc.dart';
|
||||
|
||||
class PartyRoomGrpcServer {
|
||||
static const clientVersion = 0;
|
||||
static final _channel = ClientChannel(
|
||||
"127.0.0.1",
|
||||
port: 39399,
|
||||
options: ChannelOptions(
|
||||
credentials: const ChannelCredentials.insecure(),
|
||||
codecRegistry:
|
||||
CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),
|
||||
),
|
||||
);
|
||||
|
||||
static final _indexService = IndexServiceClient(_channel);
|
||||
|
||||
static Future<PingData> pingServer() async {
|
||||
final r = await _indexService.pingServer(PingData(
|
||||
data: "PING", clientVersion: Int64.parseInt(clientVersion.toString())));
|
||||
return r;
|
||||
}
|
||||
|
||||
static Future<RoomTypesData> getRoomTypes() async {
|
||||
final r = await _indexService.getRoomTypes(Empty());
|
||||
return r;
|
||||
}
|
||||
|
||||
static Future<RoomListData> getRoomList(RoomListPageReqData req) async {
|
||||
return await _indexService.getRoomList(req);
|
||||
}
|
||||
|
||||
static Future<RoomData> createRoom(RoomData roomData) async {
|
||||
return await _indexService.createRoom(roomData);
|
||||
}
|
||||
|
||||
static Future<RoomData?> touchUserRoom(String userName, String deviceUUID) {
|
||||
return _indexService
|
||||
.touchUser(PreUser(userName: userName, deviceUUID: deviceUUID));
|
||||
}
|
||||
|
||||
static ResponseStream<RoomUpdateMessage> joinRoom(
|
||||
String roomID, String userName, String deviceUUID) {
|
||||
return _indexService.joinRoom(
|
||||
PreUser(roomID: roomID, userName: userName, deviceUUID: deviceUUID));
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/api/http_api.dart' as rust_http;
|
||||
import 'package:starcitizen_doctor/common/rust/api/http_api.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/http_package.dart';
|
||||
@ -10,7 +10,7 @@ class RSHttp {
|
||||
static init() async {
|
||||
await rust_http.setDefaultHeader(headers: {
|
||||
"User-Agent":
|
||||
"SCToolBox/${AppConf.appVersion} (${AppConf.appVersionCode})${AppConf.isMSE ? "" : " DEV"} RSHttp"
|
||||
"SCToolBox/${ConstConf.appVersion} (${ConstConf.appVersionCode})${ConstConf.isMSE ? "" : " DEV"} RSHttp"
|
||||
});
|
||||
}
|
||||
|
||||
|
17
lib/common/utils/async.dart
Normal file
17
lib/common/utils/async.dart
Normal file
@ -0,0 +1,17 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
|
||||
extension AsyncError on Future {
|
||||
Future<T?> unwrap<T>({BuildContext? context}) async {
|
||||
try {
|
||||
return await this;
|
||||
} catch (e) {
|
||||
dPrint("unwrap error:$e");
|
||||
if (context != null) {
|
||||
showToast(context, "出现错误: $e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,8 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
import '../conf/app_conf.dart';
|
||||
|
||||
var _logLock = Lock();
|
||||
File? _logFile;
|
||||
|
||||
void dPrint(src) async {
|
||||
if (kDebugMode) {
|
||||
@ -13,7 +12,15 @@ void dPrint(src) async {
|
||||
}
|
||||
try {
|
||||
await _logLock.synchronized(() async {
|
||||
await AppConf.appLogFile?.writeAsString("$src\n", mode: FileMode.append);
|
||||
_logFile?.writeAsString("$src\n", mode: FileMode.append);
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void setDPrintFile(File file) {
|
||||
_logFile = file;
|
||||
}
|
||||
|
||||
File? getDPrintFile() {
|
||||
return _logFile;
|
||||
}
|
||||
|
9
lib/common/utils/provider.dart
Normal file
9
lib/common/utils/provider.dart
Normal file
@ -0,0 +1,9 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/app.dart';
|
||||
|
||||
extension ProviderExtension on AutoDisposeNotifier {
|
||||
AppGlobalModel get appGlobalModel =>
|
||||
ref.read(appGlobalModelProvider.notifier);
|
||||
|
||||
AppGlobalState get appGlobalState => ref.read(appGlobalModelProvider);
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: chat.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'chat.pbenum.dart';
|
||||
|
||||
export 'chat.pbenum.dart';
|
||||
|
||||
class ChatMessage extends $pb.GeneratedMessage {
|
||||
factory ChatMessage({
|
||||
$core.String? senderID,
|
||||
$core.String? receiverID,
|
||||
ReceiverType? receiverType,
|
||||
MessageType? messageType,
|
||||
$core.String? data,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (senderID != null) {
|
||||
$result.senderID = senderID;
|
||||
}
|
||||
if (receiverID != null) {
|
||||
$result.receiverID = receiverID;
|
||||
}
|
||||
if (receiverType != null) {
|
||||
$result.receiverType = receiverType;
|
||||
}
|
||||
if (messageType != null) {
|
||||
$result.messageType = messageType;
|
||||
}
|
||||
if (data != null) {
|
||||
$result.data = data;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ChatMessage._() : super();
|
||||
factory ChatMessage.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory ChatMessage.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'ChatMessage',
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'senderID', protoName: 'senderID')
|
||||
..aOS(2, _omitFieldNames ? '' : 'receiverID', protoName: 'receiverID')
|
||||
..e<ReceiverType>(
|
||||
3, _omitFieldNames ? '' : 'receiverType', $pb.PbFieldType.OE,
|
||||
protoName: 'receiverType',
|
||||
defaultOrMaker: ReceiverType.RoomMsg,
|
||||
valueOf: ReceiverType.valueOf,
|
||||
enumValues: ReceiverType.values)
|
||||
..e<MessageType>(
|
||||
4, _omitFieldNames ? '' : 'messageType', $pb.PbFieldType.OE,
|
||||
protoName: 'messageType',
|
||||
defaultOrMaker: MessageType.System,
|
||||
valueOf: MessageType.valueOf,
|
||||
enumValues: MessageType.values)
|
||||
..aOS(5, _omitFieldNames ? '' : 'data')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ChatMessage clone() => ChatMessage()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ChatMessage copyWith(void Function(ChatMessage) updates) =>
|
||||
super.copyWith((message) => updates(message as ChatMessage))
|
||||
as ChatMessage;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChatMessage create() => ChatMessage._();
|
||||
ChatMessage createEmptyInstance() => create();
|
||||
static $pb.PbList<ChatMessage> createRepeated() => $pb.PbList<ChatMessage>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChatMessage getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<ChatMessage>(create);
|
||||
static ChatMessage? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get senderID => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set senderID($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasSenderID() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearSenderID() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get receiverID => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set receiverID($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasReceiverID() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearReceiverID() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
ReceiverType get receiverType => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set receiverType(ReceiverType v) {
|
||||
setField(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasReceiverType() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearReceiverType() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
MessageType get messageType => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set messageType(MessageType v) {
|
||||
setField(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasMessageType() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearMessageType() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.String get data => $_getSZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set data($core.String v) {
|
||||
$_setString(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasData() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearData() => clearField(5);
|
||||
}
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
@ -1,58 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: chat.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class ReceiverType extends $pb.ProtobufEnum {
|
||||
static const ReceiverType RoomMsg =
|
||||
ReceiverType._(0, _omitEnumNames ? '' : 'RoomMsg');
|
||||
static const ReceiverType PrivateMsg =
|
||||
ReceiverType._(1, _omitEnumNames ? '' : 'PrivateMsg');
|
||||
|
||||
static const $core.List<ReceiverType> values = <ReceiverType>[
|
||||
RoomMsg,
|
||||
PrivateMsg,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ReceiverType> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static ReceiverType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ReceiverType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class MessageType extends $pb.ProtobufEnum {
|
||||
static const MessageType System =
|
||||
MessageType._(0, _omitEnumNames ? '' : 'System');
|
||||
static const MessageType Text =
|
||||
MessageType._(1, _omitEnumNames ? '' : 'Text');
|
||||
static const MessageType Image =
|
||||
MessageType._(2, _omitEnumNames ? '' : 'Image');
|
||||
static const MessageType Markdown =
|
||||
MessageType._(3, _omitEnumNames ? '' : 'Markdown');
|
||||
|
||||
static const $core.List<MessageType> values = <MessageType>[
|
||||
System,
|
||||
Text,
|
||||
Image,
|
||||
Markdown,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, MessageType> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static MessageType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const MessageType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
@ -1,88 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: chat.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'chat.pb.dart' as $1;
|
||||
import 'index.pb.dart' as $0;
|
||||
|
||||
export 'chat.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('ChatService')
|
||||
class ChatServiceClient extends $grpc.Client {
|
||||
static final _$listenMessage = $grpc.ClientMethod<$0.PreUser, $1.ChatMessage>(
|
||||
'/ChatService/ListenMessage',
|
||||
($0.PreUser value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $1.ChatMessage.fromBuffer(value));
|
||||
static final _$sendMessage =
|
||||
$grpc.ClientMethod<$1.ChatMessage, $0.BaseRespData>(
|
||||
'/ChatService/SendMessage',
|
||||
($1.ChatMessage value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.BaseRespData.fromBuffer(value));
|
||||
|
||||
ChatServiceClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options, interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseStream<$1.ChatMessage> listenMessage($0.PreUser request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createStreamingCall(
|
||||
_$listenMessage, $async.Stream.fromIterable([request]),
|
||||
options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.BaseRespData> sendMessage($1.ChatMessage request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$sendMessage, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('ChatService')
|
||||
abstract class ChatServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'ChatService';
|
||||
|
||||
ChatServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.PreUser, $1.ChatMessage>(
|
||||
'ListenMessage',
|
||||
listenMessage_Pre,
|
||||
false,
|
||||
true,
|
||||
($core.List<$core.int> value) => $0.PreUser.fromBuffer(value),
|
||||
($1.ChatMessage value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$1.ChatMessage, $0.BaseRespData>(
|
||||
'SendMessage',
|
||||
sendMessage_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $1.ChatMessage.fromBuffer(value),
|
||||
($0.BaseRespData value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Stream<$1.ChatMessage> listenMessage_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.PreUser> request) async* {
|
||||
yield* listenMessage(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.BaseRespData> sendMessage_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$1.ChatMessage> request) async {
|
||||
return sendMessage(call, await request);
|
||||
}
|
||||
|
||||
$async.Stream<$1.ChatMessage> listenMessage(
|
||||
$grpc.ServiceCall call, $0.PreUser request);
|
||||
$async.Future<$0.BaseRespData> sendMessage(
|
||||
$grpc.ServiceCall call, $1.ChatMessage request);
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: chat.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use receiverTypeDescriptor instead')
|
||||
const ReceiverType$json = {
|
||||
'1': 'ReceiverType',
|
||||
'2': [
|
||||
{'1': 'RoomMsg', '2': 0},
|
||||
{'1': 'PrivateMsg', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ReceiverType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List receiverTypeDescriptor = $convert.base64Decode(
|
||||
'CgxSZWNlaXZlclR5cGUSCwoHUm9vbU1zZxAAEg4KClByaXZhdGVNc2cQAQ==');
|
||||
|
||||
@$core.Deprecated('Use messageTypeDescriptor instead')
|
||||
const MessageType$json = {
|
||||
'1': 'MessageType',
|
||||
'2': [
|
||||
{'1': 'System', '2': 0},
|
||||
{'1': 'Text', '2': 1},
|
||||
{'1': 'Image', '2': 2},
|
||||
{'1': 'Markdown', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `MessageType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List messageTypeDescriptor = $convert.base64Decode(
|
||||
'CgtNZXNzYWdlVHlwZRIKCgZTeXN0ZW0QABIICgRUZXh0EAESCQoFSW1hZ2UQAhIMCghNYXJrZG'
|
||||
'93bhAD');
|
||||
|
||||
@$core.Deprecated('Use chatMessageDescriptor instead')
|
||||
const ChatMessage$json = {
|
||||
'1': 'ChatMessage',
|
||||
'2': [
|
||||
{'1': 'senderID', '3': 1, '4': 1, '5': 9, '10': 'senderID'},
|
||||
{'1': 'receiverID', '3': 2, '4': 1, '5': 9, '10': 'receiverID'},
|
||||
{
|
||||
'1': 'receiverType',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.ReceiverType',
|
||||
'10': 'receiverType'
|
||||
},
|
||||
{
|
||||
'1': 'messageType',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.MessageType',
|
||||
'10': 'messageType'
|
||||
},
|
||||
{'1': 'data', '3': 5, '4': 1, '5': 9, '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ChatMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List chatMessageDescriptor = $convert.base64Decode(
|
||||
'CgtDaGF0TWVzc2FnZRIaCghzZW5kZXJJRBgBIAEoCVIIc2VuZGVySUQSHgoKcmVjZWl2ZXJJRB'
|
||||
'gCIAEoCVIKcmVjZWl2ZXJJRBIxCgxyZWNlaXZlclR5cGUYAyABKA4yDS5SZWNlaXZlclR5cGVS'
|
||||
'DHJlY2VpdmVyVHlwZRIuCgttZXNzYWdlVHlwZRgEIAEoDjIMLk1lc3NhZ2VUeXBlUgttZXNzYW'
|
||||
'dlVHlwZRISCgRkYXRhGAUgASgJUgRkYXRh');
|
File diff suppressed because it is too large
Load Diff
@ -1,115 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: index.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class RoomStatus extends $pb.ProtobufEnum {
|
||||
static const RoomStatus All = RoomStatus._(0, _omitEnumNames ? '' : 'All');
|
||||
static const RoomStatus Open = RoomStatus._(1, _omitEnumNames ? '' : 'Open');
|
||||
static const RoomStatus Private =
|
||||
RoomStatus._(2, _omitEnumNames ? '' : 'Private');
|
||||
static const RoomStatus Full = RoomStatus._(3, _omitEnumNames ? '' : 'Full');
|
||||
static const RoomStatus Closed =
|
||||
RoomStatus._(4, _omitEnumNames ? '' : 'Closed');
|
||||
static const RoomStatus WillOffline =
|
||||
RoomStatus._(5, _omitEnumNames ? '' : 'WillOffline');
|
||||
static const RoomStatus Offline =
|
||||
RoomStatus._(6, _omitEnumNames ? '' : 'Offline');
|
||||
|
||||
static const $core.List<RoomStatus> values = <RoomStatus>[
|
||||
All,
|
||||
Open,
|
||||
Private,
|
||||
Full,
|
||||
Closed,
|
||||
WillOffline,
|
||||
Offline,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RoomStatus> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static RoomStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RoomStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class RoomSortType extends $pb.ProtobufEnum {
|
||||
static const RoomSortType Default =
|
||||
RoomSortType._(0, _omitEnumNames ? '' : 'Default');
|
||||
static const RoomSortType MostPlayerNumber =
|
||||
RoomSortType._(1, _omitEnumNames ? '' : 'MostPlayerNumber');
|
||||
static const RoomSortType MinimumPlayerNumber =
|
||||
RoomSortType._(2, _omitEnumNames ? '' : 'MinimumPlayerNumber');
|
||||
static const RoomSortType RecentlyCreated =
|
||||
RoomSortType._(3, _omitEnumNames ? '' : 'RecentlyCreated');
|
||||
static const RoomSortType OldestCreated =
|
||||
RoomSortType._(4, _omitEnumNames ? '' : 'OldestCreated');
|
||||
|
||||
static const $core.List<RoomSortType> values = <RoomSortType>[
|
||||
Default,
|
||||
MostPlayerNumber,
|
||||
MinimumPlayerNumber,
|
||||
RecentlyCreated,
|
||||
OldestCreated,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RoomSortType> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static RoomSortType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RoomSortType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class RoomUserStatus extends $pb.ProtobufEnum {
|
||||
static const RoomUserStatus RoomUserStatusJoin =
|
||||
RoomUserStatus._(0, _omitEnumNames ? '' : 'RoomUserStatusJoin');
|
||||
static const RoomUserStatus RoomUserStatusLostOffline =
|
||||
RoomUserStatus._(1, _omitEnumNames ? '' : 'RoomUserStatusLostOffline');
|
||||
static const RoomUserStatus RoomUserStatusLeave =
|
||||
RoomUserStatus._(2, _omitEnumNames ? '' : 'RoomUserStatusLeave');
|
||||
static const RoomUserStatus RoomUserStatusWaitingConnect =
|
||||
RoomUserStatus._(3, _omitEnumNames ? '' : 'RoomUserStatusWaitingConnect');
|
||||
|
||||
static const $core.List<RoomUserStatus> values = <RoomUserStatus>[
|
||||
RoomUserStatusJoin,
|
||||
RoomUserStatusLostOffline,
|
||||
RoomUserStatusLeave,
|
||||
RoomUserStatusWaitingConnect,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RoomUserStatus> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static RoomUserStatus? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RoomUserStatus._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class RoomUpdateType extends $pb.ProtobufEnum {
|
||||
static const RoomUpdateType RoomUpdateData =
|
||||
RoomUpdateType._(0, _omitEnumNames ? '' : 'RoomUpdateData');
|
||||
static const RoomUpdateType RoomClose =
|
||||
RoomUpdateType._(1, _omitEnumNames ? '' : 'RoomClose');
|
||||
|
||||
static const $core.List<RoomUpdateType> values = <RoomUpdateType>[
|
||||
RoomUpdateData,
|
||||
RoomClose,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, RoomUpdateType> _byValue =
|
||||
$pb.ProtobufEnum.initByValue(values);
|
||||
static RoomUpdateType? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const RoomUpdateType._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
@ -1,206 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: index.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'index.pb.dart' as $0;
|
||||
|
||||
export 'index.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('IndexService')
|
||||
class IndexServiceClient extends $grpc.Client {
|
||||
static final _$pingServer = $grpc.ClientMethod<$0.PingData, $0.PingData>(
|
||||
'/IndexService/PingServer',
|
||||
($0.PingData value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.PingData.fromBuffer(value));
|
||||
static final _$getRoomTypes = $grpc.ClientMethod<$0.Empty, $0.RoomTypesData>(
|
||||
'/IndexService/GetRoomTypes',
|
||||
($0.Empty value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.RoomTypesData.fromBuffer(value));
|
||||
static final _$createRoom = $grpc.ClientMethod<$0.RoomData, $0.RoomData>(
|
||||
'/IndexService/CreateRoom',
|
||||
($0.RoomData value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.RoomData.fromBuffer(value));
|
||||
static final _$getRoomList =
|
||||
$grpc.ClientMethod<$0.RoomListPageReqData, $0.RoomListData>(
|
||||
'/IndexService/GetRoomList',
|
||||
($0.RoomListPageReqData value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.RoomListData.fromBuffer(value));
|
||||
static final _$touchUser = $grpc.ClientMethod<$0.PreUser, $0.RoomData>(
|
||||
'/IndexService/TouchUser',
|
||||
($0.PreUser value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.RoomData.fromBuffer(value));
|
||||
static final _$joinRoom =
|
||||
$grpc.ClientMethod<$0.PreUser, $0.RoomUpdateMessage>(
|
||||
'/IndexService/JoinRoom',
|
||||
($0.PreUser value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) =>
|
||||
$0.RoomUpdateMessage.fromBuffer(value));
|
||||
static final _$leaveRoom = $grpc.ClientMethod<$0.PreUser, $0.BaseRespData>(
|
||||
'/IndexService/LeaveRoom',
|
||||
($0.PreUser value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.BaseRespData.fromBuffer(value));
|
||||
|
||||
IndexServiceClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options, interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.PingData> pingServer($0.PingData request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$pingServer, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.RoomTypesData> getRoomTypes($0.Empty request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$getRoomTypes, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.RoomData> createRoom($0.RoomData request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$createRoom, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.RoomListData> getRoomList(
|
||||
$0.RoomListPageReqData request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$getRoomList, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.RoomData> touchUser($0.PreUser request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$touchUser, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseStream<$0.RoomUpdateMessage> joinRoom($0.PreUser request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createStreamingCall(
|
||||
_$joinRoom, $async.Stream.fromIterable([request]),
|
||||
options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.BaseRespData> leaveRoom($0.PreUser request,
|
||||
{$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$leaveRoom, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('IndexService')
|
||||
abstract class IndexServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'IndexService';
|
||||
|
||||
IndexServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.PingData, $0.PingData>(
|
||||
'PingServer',
|
||||
pingServer_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PingData.fromBuffer(value),
|
||||
($0.PingData value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.Empty, $0.RoomTypesData>(
|
||||
'GetRoomTypes',
|
||||
getRoomTypes_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.Empty.fromBuffer(value),
|
||||
($0.RoomTypesData value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.RoomData, $0.RoomData>(
|
||||
'CreateRoom',
|
||||
createRoom_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.RoomData.fromBuffer(value),
|
||||
($0.RoomData value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.RoomListPageReqData, $0.RoomListData>(
|
||||
'GetRoomList',
|
||||
getRoomList_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) =>
|
||||
$0.RoomListPageReqData.fromBuffer(value),
|
||||
($0.RoomListData value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.PreUser, $0.RoomData>(
|
||||
'TouchUser',
|
||||
touchUser_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PreUser.fromBuffer(value),
|
||||
($0.RoomData value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.PreUser, $0.RoomUpdateMessage>(
|
||||
'JoinRoom',
|
||||
joinRoom_Pre,
|
||||
false,
|
||||
true,
|
||||
($core.List<$core.int> value) => $0.PreUser.fromBuffer(value),
|
||||
($0.RoomUpdateMessage value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.PreUser, $0.BaseRespData>(
|
||||
'LeaveRoom',
|
||||
leaveRoom_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.PreUser.fromBuffer(value),
|
||||
($0.BaseRespData value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.PingData> pingServer_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.PingData> request) async {
|
||||
return pingServer(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.RoomTypesData> getRoomTypes_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.Empty> request) async {
|
||||
return getRoomTypes(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.RoomData> createRoom_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.RoomData> request) async {
|
||||
return createRoom(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.RoomListData> getRoomList_Pre($grpc.ServiceCall call,
|
||||
$async.Future<$0.RoomListPageReqData> request) async {
|
||||
return getRoomList(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.RoomData> touchUser_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.PreUser> request) async {
|
||||
return touchUser(call, await request);
|
||||
}
|
||||
|
||||
$async.Stream<$0.RoomUpdateMessage> joinRoom_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.PreUser> request) async* {
|
||||
yield* joinRoom(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.BaseRespData> leaveRoom_Pre(
|
||||
$grpc.ServiceCall call, $async.Future<$0.PreUser> request) async {
|
||||
return leaveRoom(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.PingData> pingServer(
|
||||
$grpc.ServiceCall call, $0.PingData request);
|
||||
$async.Future<$0.RoomTypesData> getRoomTypes(
|
||||
$grpc.ServiceCall call, $0.Empty request);
|
||||
$async.Future<$0.RoomData> createRoom(
|
||||
$grpc.ServiceCall call, $0.RoomData request);
|
||||
$async.Future<$0.RoomListData> getRoomList(
|
||||
$grpc.ServiceCall call, $0.RoomListPageReqData request);
|
||||
$async.Future<$0.RoomData> touchUser(
|
||||
$grpc.ServiceCall call, $0.PreUser request);
|
||||
$async.Stream<$0.RoomUpdateMessage> joinRoom(
|
||||
$grpc.ServiceCall call, $0.PreUser request);
|
||||
$async.Future<$0.BaseRespData> leaveRoom(
|
||||
$grpc.ServiceCall call, $0.PreUser request);
|
||||
}
|
@ -1,354 +0,0 @@
|
||||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: index.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use roomStatusDescriptor instead')
|
||||
const RoomStatus$json = {
|
||||
'1': 'RoomStatus',
|
||||
'2': [
|
||||
{'1': 'All', '2': 0},
|
||||
{'1': 'Open', '2': 1},
|
||||
{'1': 'Private', '2': 2},
|
||||
{'1': 'Full', '2': 3},
|
||||
{'1': 'Closed', '2': 4},
|
||||
{'1': 'WillOffline', '2': 5},
|
||||
{'1': 'Offline', '2': 6},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomStatus`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List roomStatusDescriptor = $convert.base64Decode(
|
||||
'CgpSb29tU3RhdHVzEgcKA0FsbBAAEggKBE9wZW4QARILCgdQcml2YXRlEAISCAoERnVsbBADEg'
|
||||
'oKBkNsb3NlZBAEEg8KC1dpbGxPZmZsaW5lEAUSCwoHT2ZmbGluZRAG');
|
||||
|
||||
@$core.Deprecated('Use roomSortTypeDescriptor instead')
|
||||
const RoomSortType$json = {
|
||||
'1': 'RoomSortType',
|
||||
'2': [
|
||||
{'1': 'Default', '2': 0},
|
||||
{'1': 'MostPlayerNumber', '2': 1},
|
||||
{'1': 'MinimumPlayerNumber', '2': 2},
|
||||
{'1': 'RecentlyCreated', '2': 3},
|
||||
{'1': 'OldestCreated', '2': 4},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomSortType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List roomSortTypeDescriptor = $convert.base64Decode(
|
||||
'CgxSb29tU29ydFR5cGUSCwoHRGVmYXVsdBAAEhQKEE1vc3RQbGF5ZXJOdW1iZXIQARIXChNNaW'
|
||||
'5pbXVtUGxheWVyTnVtYmVyEAISEwoPUmVjZW50bHlDcmVhdGVkEAMSEQoNT2xkZXN0Q3JlYXRl'
|
||||
'ZBAE');
|
||||
|
||||
@$core.Deprecated('Use roomUserStatusDescriptor instead')
|
||||
const RoomUserStatus$json = {
|
||||
'1': 'RoomUserStatus',
|
||||
'2': [
|
||||
{'1': 'RoomUserStatusJoin', '2': 0},
|
||||
{'1': 'RoomUserStatusLostOffline', '2': 1},
|
||||
{'1': 'RoomUserStatusLeave', '2': 2},
|
||||
{'1': 'RoomUserStatusWaitingConnect', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomUserStatus`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List roomUserStatusDescriptor = $convert.base64Decode(
|
||||
'Cg5Sb29tVXNlclN0YXR1cxIWChJSb29tVXNlclN0YXR1c0pvaW4QABIdChlSb29tVXNlclN0YX'
|
||||
'R1c0xvc3RPZmZsaW5lEAESFwoTUm9vbVVzZXJTdGF0dXNMZWF2ZRACEiAKHFJvb21Vc2VyU3Rh'
|
||||
'dHVzV2FpdGluZ0Nvbm5lY3QQAw==');
|
||||
|
||||
@$core.Deprecated('Use roomUpdateTypeDescriptor instead')
|
||||
const RoomUpdateType$json = {
|
||||
'1': 'RoomUpdateType',
|
||||
'2': [
|
||||
{'1': 'RoomUpdateData', '2': 0},
|
||||
{'1': 'RoomClose', '2': 1},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomUpdateType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List roomUpdateTypeDescriptor = $convert.base64Decode(
|
||||
'Cg5Sb29tVXBkYXRlVHlwZRISCg5Sb29tVXBkYXRlRGF0YRAAEg0KCVJvb21DbG9zZRAB');
|
||||
|
||||
@$core.Deprecated('Use emptyDescriptor instead')
|
||||
const Empty$json = {
|
||||
'1': 'Empty',
|
||||
};
|
||||
|
||||
/// Descriptor for `Empty`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List emptyDescriptor =
|
||||
$convert.base64Decode('CgVFbXB0eQ==');
|
||||
|
||||
@$core.Deprecated('Use baseRespDataDescriptor instead')
|
||||
const BaseRespData$json = {
|
||||
'1': 'BaseRespData',
|
||||
'2': [
|
||||
{'1': 'code', '3': 1, '4': 1, '5': 5, '10': 'code'},
|
||||
{'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `BaseRespData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List baseRespDataDescriptor = $convert.base64Decode(
|
||||
'CgxCYXNlUmVzcERhdGESEgoEY29kZRgBIAEoBVIEY29kZRIYCgdtZXNzYWdlGAIgASgJUgdtZX'
|
||||
'NzYWdl');
|
||||
|
||||
@$core.Deprecated('Use basePageRespDataDescriptor instead')
|
||||
const BasePageRespData$json = {
|
||||
'1': 'BasePageRespData',
|
||||
'2': [
|
||||
{'1': 'code', '3': 1, '4': 1, '5': 5, '10': 'code'},
|
||||
{'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'},
|
||||
{'1': 'hasNext', '3': 3, '4': 1, '5': 8, '10': 'hasNext'},
|
||||
{'1': 'curPageNum', '3': 4, '4': 1, '5': 4, '10': 'curPageNum'},
|
||||
{'1': 'pageSize', '3': 5, '4': 1, '5': 3, '10': 'pageSize'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `BasePageRespData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List basePageRespDataDescriptor = $convert.base64Decode(
|
||||
'ChBCYXNlUGFnZVJlc3BEYXRhEhIKBGNvZGUYASABKAVSBGNvZGUSGAoHbWVzc2FnZRgCIAEoCV'
|
||||
'IHbWVzc2FnZRIYCgdoYXNOZXh0GAMgASgIUgdoYXNOZXh0Eh4KCmN1clBhZ2VOdW0YBCABKARS'
|
||||
'CmN1clBhZ2VOdW0SGgoIcGFnZVNpemUYBSABKANSCHBhZ2VTaXpl');
|
||||
|
||||
@$core.Deprecated('Use pingDataDescriptor instead')
|
||||
const PingData$json = {
|
||||
'1': 'PingData',
|
||||
'2': [
|
||||
{'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'},
|
||||
{'1': 'clientVersion', '3': 2, '4': 1, '5': 18, '10': 'clientVersion'},
|
||||
{'1': 'serverVersion', '3': 3, '4': 1, '5': 18, '10': 'serverVersion'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PingData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pingDataDescriptor = $convert.base64Decode(
|
||||
'CghQaW5nRGF0YRISCgRkYXRhGAEgASgJUgRkYXRhEiQKDWNsaWVudFZlcnNpb24YAiABKBJSDW'
|
||||
'NsaWVudFZlcnNpb24SJAoNc2VydmVyVmVyc2lvbhgDIAEoElINc2VydmVyVmVyc2lvbg==');
|
||||
|
||||
@$core.Deprecated('Use roomTypesDataDescriptor instead')
|
||||
const RoomTypesData$json = {
|
||||
'1': 'RoomTypesData',
|
||||
'2': [
|
||||
{
|
||||
'1': 'roomTypes',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.RoomType',
|
||||
'10': 'roomTypes'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomTypesData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomTypesDataDescriptor = $convert.base64Decode(
|
||||
'Cg1Sb29tVHlwZXNEYXRhEicKCXJvb21UeXBlcxgBIAMoCzIJLlJvb21UeXBlUglyb29tVHlwZX'
|
||||
'M=');
|
||||
|
||||
@$core.Deprecated('Use roomTypeDescriptor instead')
|
||||
const RoomType$json = {
|
||||
'1': 'RoomType',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
||||
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'icon', '3': 3, '4': 1, '5': 9, '10': 'icon'},
|
||||
{'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
|
||||
{
|
||||
'1': 'subTypes',
|
||||
'3': 5,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.RoomSubtype',
|
||||
'10': 'subTypes'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomType`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomTypeDescriptor = $convert.base64Decode(
|
||||
'CghSb29tVHlwZRIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRpY29uGA'
|
||||
'MgASgJUgRpY29uEhIKBGRlc2MYBCABKAlSBGRlc2MSKAoIc3ViVHlwZXMYBSADKAsyDC5Sb29t'
|
||||
'U3VidHlwZVIIc3ViVHlwZXM=');
|
||||
|
||||
@$core.Deprecated('Use roomSubtypeDescriptor instead')
|
||||
const RoomSubtype$json = {
|
||||
'1': 'RoomSubtype',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
||||
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomSubtype`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomSubtypeDescriptor = $convert.base64Decode(
|
||||
'CgtSb29tU3VidHlwZRIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZQ==');
|
||||
|
||||
@$core.Deprecated('Use roomDataDescriptor instead')
|
||||
const RoomData$json = {
|
||||
'1': 'RoomData',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
||||
{'1': 'roomTypeID', '3': 2, '4': 1, '5': 9, '10': 'roomTypeID'},
|
||||
{'1': 'roomSubTypeIds', '3': 3, '4': 3, '5': 9, '10': 'roomSubTypeIds'},
|
||||
{'1': 'owner', '3': 4, '4': 1, '5': 9, '10': 'owner'},
|
||||
{'1': 'maxPlayer', '3': 5, '4': 1, '5': 5, '10': 'maxPlayer'},
|
||||
{'1': 'createTime', '3': 6, '4': 1, '5': 3, '10': 'createTime'},
|
||||
{'1': 'curPlayer', '3': 7, '4': 1, '5': 5, '10': 'curPlayer'},
|
||||
{
|
||||
'1': 'status',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.RoomStatus',
|
||||
'10': 'status'
|
||||
},
|
||||
{'1': 'deviceUUID', '3': 9, '4': 1, '5': 9, '10': 'deviceUUID'},
|
||||
{'1': 'announcement', '3': 10, '4': 1, '5': 9, '10': 'announcement'},
|
||||
{'1': 'avatar', '3': 11, '4': 1, '5': 9, '10': 'avatar'},
|
||||
{'1': 'updateTime', '3': 12, '4': 1, '5': 3, '10': 'updateTime'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomDataDescriptor = $convert.base64Decode(
|
||||
'CghSb29tRGF0YRIOCgJpZBgBIAEoCVICaWQSHgoKcm9vbVR5cGVJRBgCIAEoCVIKcm9vbVR5cG'
|
||||
'VJRBImCg5yb29tU3ViVHlwZUlkcxgDIAMoCVIOcm9vbVN1YlR5cGVJZHMSFAoFb3duZXIYBCAB'
|
||||
'KAlSBW93bmVyEhwKCW1heFBsYXllchgFIAEoBVIJbWF4UGxheWVyEh4KCmNyZWF0ZVRpbWUYBi'
|
||||
'ABKANSCmNyZWF0ZVRpbWUSHAoJY3VyUGxheWVyGAcgASgFUgljdXJQbGF5ZXISIwoGc3RhdHVz'
|
||||
'GAggASgOMgsuUm9vbVN0YXR1c1IGc3RhdHVzEh4KCmRldmljZVVVSUQYCSABKAlSCmRldmljZV'
|
||||
'VVSUQSIgoMYW5ub3VuY2VtZW50GAogASgJUgxhbm5vdW5jZW1lbnQSFgoGYXZhdGFyGAsgASgJ'
|
||||
'UgZhdmF0YXISHgoKdXBkYXRlVGltZRgMIAEoA1IKdXBkYXRlVGltZQ==');
|
||||
|
||||
@$core.Deprecated('Use roomListPageReqDataDescriptor instead')
|
||||
const RoomListPageReqData$json = {
|
||||
'1': 'RoomListPageReqData',
|
||||
'2': [
|
||||
{'1': 'typeID', '3': 1, '4': 1, '5': 9, '10': 'typeID'},
|
||||
{'1': 'subTypeID', '3': 2, '4': 1, '5': 9, '10': 'subTypeID'},
|
||||
{
|
||||
'1': 'status',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.RoomStatus',
|
||||
'10': 'status'
|
||||
},
|
||||
{'1': 'sort', '3': 4, '4': 1, '5': 14, '6': '.RoomSortType', '10': 'sort'},
|
||||
{'1': 'pageNum', '3': 5, '4': 1, '5': 4, '10': 'pageNum'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomListPageReqData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomListPageReqDataDescriptor = $convert.base64Decode(
|
||||
'ChNSb29tTGlzdFBhZ2VSZXFEYXRhEhYKBnR5cGVJRBgBIAEoCVIGdHlwZUlEEhwKCXN1YlR5cG'
|
||||
'VJRBgCIAEoCVIJc3ViVHlwZUlEEiMKBnN0YXR1cxgDIAEoDjILLlJvb21TdGF0dXNSBnN0YXR1'
|
||||
'cxIhCgRzb3J0GAQgASgOMg0uUm9vbVNvcnRUeXBlUgRzb3J0EhgKB3BhZ2VOdW0YBSABKARSB3'
|
||||
'BhZ2VOdW0=');
|
||||
|
||||
@$core.Deprecated('Use roomListDataDescriptor instead')
|
||||
const RoomListData$json = {
|
||||
'1': 'RoomListData',
|
||||
'2': [
|
||||
{
|
||||
'1': 'pageData',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.BasePageRespData',
|
||||
'10': 'pageData'
|
||||
},
|
||||
{'1': 'rooms', '3': 2, '4': 3, '5': 11, '6': '.RoomData', '10': 'rooms'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomListData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomListDataDescriptor = $convert.base64Decode(
|
||||
'CgxSb29tTGlzdERhdGESLQoIcGFnZURhdGEYASABKAsyES5CYXNlUGFnZVJlc3BEYXRhUghwYW'
|
||||
'dlRGF0YRIfCgVyb29tcxgCIAMoCzIJLlJvb21EYXRhUgVyb29tcw==');
|
||||
|
||||
@$core.Deprecated('Use preUserDescriptor instead')
|
||||
const PreUser$json = {
|
||||
'1': 'PreUser',
|
||||
'2': [
|
||||
{'1': 'userName', '3': 1, '4': 1, '5': 9, '10': 'userName'},
|
||||
{'1': 'deviceUUID', '3': 2, '4': 1, '5': 9, '10': 'deviceUUID'},
|
||||
{'1': 'roomID', '3': 3, '4': 1, '5': 9, '10': 'roomID'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PreUser`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List preUserDescriptor = $convert.base64Decode(
|
||||
'CgdQcmVVc2VyEhoKCHVzZXJOYW1lGAEgASgJUgh1c2VyTmFtZRIeCgpkZXZpY2VVVUlEGAIgAS'
|
||||
'gJUgpkZXZpY2VVVUlEEhYKBnJvb21JRBgDIAEoCVIGcm9vbUlE');
|
||||
|
||||
@$core.Deprecated('Use roomUserDataDescriptor instead')
|
||||
const RoomUserData$json = {
|
||||
'1': 'RoomUserData',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
|
||||
{'1': 'playerName', '3': 2, '4': 1, '5': 9, '10': 'playerName'},
|
||||
{'1': 'Avatar', '3': 3, '4': 1, '5': 9, '10': 'Avatar'},
|
||||
{
|
||||
'1': 'status',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.RoomUserStatus',
|
||||
'10': 'status'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomUserData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomUserDataDescriptor = $convert.base64Decode(
|
||||
'CgxSb29tVXNlckRhdGESDgoCaWQYASABKAlSAmlkEh4KCnBsYXllck5hbWUYAiABKAlSCnBsYX'
|
||||
'llck5hbWUSFgoGQXZhdGFyGAMgASgJUgZBdmF0YXISJwoGc3RhdHVzGAQgASgOMg8uUm9vbVVz'
|
||||
'ZXJTdGF0dXNSBnN0YXR1cw==');
|
||||
|
||||
@$core.Deprecated('Use roomUpdateMessageDescriptor instead')
|
||||
const RoomUpdateMessage$json = {
|
||||
'1': 'RoomUpdateMessage',
|
||||
'2': [
|
||||
{
|
||||
'1': 'roomData',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.RoomData',
|
||||
'10': 'roomData'
|
||||
},
|
||||
{
|
||||
'1': 'usersData',
|
||||
'3': 2,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.RoomUserData',
|
||||
'10': 'usersData'
|
||||
},
|
||||
{
|
||||
'1': 'roomUpdateType',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.RoomUpdateType',
|
||||
'10': 'roomUpdateType'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RoomUpdateMessage`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List roomUpdateMessageDescriptor = $convert.base64Decode(
|
||||
'ChFSb29tVXBkYXRlTWVzc2FnZRIlCghyb29tRGF0YRgBIAEoCzIJLlJvb21EYXRhUghyb29tRG'
|
||||
'F0YRIrCgl1c2Vyc0RhdGEYAiADKAsyDS5Sb29tVXNlckRhdGFSCXVzZXJzRGF0YRI3Cg5yb29t'
|
||||
'VXBkYXRlVHlwZRgDIAEoDjIPLlJvb21VcGRhdGVUeXBlUg5yb29tVXBkYXRlVHlwZQ==');
|
@ -1,101 +0,0 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hexcolor/hexcolor.dart';
|
||||
|
||||
import 'base/ui_model.dart';
|
||||
import 'common/conf/app_conf.dart';
|
||||
import 'ui/settings/upgrade_dialog_ui.dart';
|
||||
import 'ui/settings/upgrade_dialog_ui_model.dart';
|
||||
|
||||
final globalUIModel = AppGlobalUIModel();
|
||||
final globalUIModelProvider = ChangeNotifierProvider((ref) => globalUIModel);
|
||||
|
||||
class AppGlobalUIModel extends BaseUIModel {
|
||||
Timer? activityThemeColorTimer;
|
||||
|
||||
Future<String?> getRunningGameUser() async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
///TODO 实现获取运行中用户名
|
||||
return "xkeyC";
|
||||
}
|
||||
|
||||
Future<bool> doCheckUpdate(BuildContext context, {bool init = true}) async {
|
||||
dynamic checkUpdateError;
|
||||
if (!init) {
|
||||
try {
|
||||
await AppConf.checkUpdate();
|
||||
} catch (e) {
|
||||
checkUpdateError = e;
|
||||
}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (AppConf.networkVersionData == null) {
|
||||
showToast(context,
|
||||
"网络异常!\n这可能是您的网络环境存在DNS污染,请尝试更换DNS。\n或服务器正在维护或遭受攻击,稍后再试。 \n进入离线模式... \n\n请谨慎在离线模式中使用。 \n当前版本构建日期:${AppConf.appVersionDate}\n QQ群:940696487 \n错误信息:$checkUpdateError");
|
||||
return false;
|
||||
}
|
||||
final lastVersion = AppConf.isMSE
|
||||
? AppConf.networkVersionData?.mSELastVersionCode
|
||||
: AppConf.networkVersionData?.lastVersionCode;
|
||||
if ((lastVersion ?? 0) > AppConf.appVersionCode) {
|
||||
// need update
|
||||
final r = await showDialog(
|
||||
dismissWithEsc: false,
|
||||
context: context,
|
||||
builder: (context) => BaseUIContainer(
|
||||
uiCreate: () => UpgradeDialogUI(),
|
||||
modelCreate: () => UpgradeDialogUIModel()));
|
||||
if (r != true) {
|
||||
showToast(context, "获取更新信息失败,请稍后重试。");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
checkActivityThemeColor() {
|
||||
if (activityThemeColorTimer != null) {
|
||||
activityThemeColorTimer?.cancel();
|
||||
activityThemeColorTimer = null;
|
||||
}
|
||||
if (AppConf.networkVersionData == null ||
|
||||
AppConf.networkVersionData?.activityColors?.enable != true) return;
|
||||
|
||||
final startTime = AppConf.networkVersionData!.activityColors?.startTime;
|
||||
final endTime = AppConf.networkVersionData!.activityColors?.endTime;
|
||||
if (startTime == null || endTime == null) return;
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
dPrint("now == $now start == $startTime end == $endTime");
|
||||
if (now < startTime) {
|
||||
activityThemeColorTimer = Timer(
|
||||
Duration(milliseconds: startTime - now), checkActivityThemeColor);
|
||||
dPrint("start Timer ....");
|
||||
} else if (now >= startTime && now <= endTime) {
|
||||
dPrint("update Color ....");
|
||||
// update Color
|
||||
final colorCfg = AppConf.networkVersionData!.activityColors;
|
||||
AppConf.colorBackground =
|
||||
HexColor(colorCfg?.background ?? "#132431").withOpacity(.75);
|
||||
AppConf.colorMenu =
|
||||
HexColor(colorCfg?.menu ?? "#132431").withOpacity(.95);
|
||||
AppConf.colorMica = HexColor(colorCfg?.mica ?? "#0A3142");
|
||||
notifyListeners();
|
||||
// wait for end
|
||||
activityThemeColorTimer =
|
||||
Timer(Duration(milliseconds: endTime - now), checkActivityThemeColor);
|
||||
} else {
|
||||
dPrint("reset Color ....");
|
||||
AppConf.colorBackground = HexColor("#132431").withOpacity(.75);
|
||||
AppConf.colorMenu = HexColor("#132431").withOpacity(.95);
|
||||
AppConf.colorMica = HexColor("#0A3142");
|
||||
notifyListeners();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,34 +1,42 @@
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'base/ui_model.dart';
|
||||
import 'common/conf/app_conf.dart';
|
||||
import 'global_ui_model.dart';
|
||||
import 'ui/splash_ui.dart';
|
||||
import 'ui/splash_ui_model.dart';
|
||||
import 'app.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
// webview window
|
||||
if (runWebViewTitleBarWidget(args,
|
||||
backgroundColor: const Color.fromRGBO(19, 36, 49, 1),
|
||||
builder: _defaultWebviewTitleBar)) {
|
||||
return;
|
||||
}
|
||||
await AppConf.init(args);
|
||||
runApp(ProviderScope(
|
||||
child: BaseUIContainer(
|
||||
uiCreate: () => AppUI(),
|
||||
modelCreate: () => globalUIModelProvider,
|
||||
),
|
||||
));
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await _initWindow();
|
||||
// run app
|
||||
runApp(const ProviderScope(child: App()));
|
||||
}
|
||||
|
||||
class AppUI extends BaseUI {
|
||||
_initWindow() async {
|
||||
await windowManager.ensureInitialized();
|
||||
await windowManager.setTitleBarStyle(
|
||||
TitleBarStyle.hidden,
|
||||
windowButtonVisibility: false,
|
||||
);
|
||||
await windowManager.center(animate: true);
|
||||
}
|
||||
|
||||
class App extends HookConsumerWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, BaseUIModel model) {
|
||||
return FluentApp(
|
||||
title: "StarCitizen Doctor",
|
||||
restorationScopeId: "Doctor",
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(routerProvider);
|
||||
final appState = ref.watch(appGlobalModelProvider);
|
||||
return FluentApp.router(
|
||||
title: "StarCitizenToolBox",
|
||||
restorationScopeId: "StarCitizenToolBox",
|
||||
themeMode: ThemeMode.dark,
|
||||
builder: (context, child) {
|
||||
return MediaQuery(
|
||||
@ -41,10 +49,10 @@ class AppUI extends BaseUI {
|
||||
brightness: Brightness.dark,
|
||||
fontFamily: "SourceHanSansCN-Regular",
|
||||
navigationPaneTheme: NavigationPaneThemeData(
|
||||
backgroundColor: AppConf.colorBackground,
|
||||
backgroundColor: appState.themeConf.backgroundColor,
|
||||
),
|
||||
menuColor: AppConf.colorMenu,
|
||||
micaBackgroundColor: AppConf.colorMica,
|
||||
menuColor: appState.themeConf.menuColor,
|
||||
micaBackgroundColor: appState.themeConf.micaColor,
|
||||
buttonTheme: ButtonThemeData(
|
||||
defaultButtonStyle: ButtonStyle(
|
||||
shape: ButtonState.all(RoundedRectangleBorder(
|
||||
@ -52,28 +60,9 @@ class AppUI extends BaseUI {
|
||||
side: BorderSide(color: Colors.white.withOpacity(.01)))),
|
||||
))),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: BaseUIContainer(
|
||||
uiCreate: () => SplashUI(), modelCreate: () => SplashUIModel()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, BaseUIModel model) => "";
|
||||
}
|
||||
|
||||
class WindowButtons extends StatelessWidget {
|
||||
const WindowButtons({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final FluentThemeData theme = FluentTheme.of(context);
|
||||
return SizedBox(
|
||||
width: 138,
|
||||
height: 50,
|
||||
child: WindowCaption(
|
||||
brightness: theme.brightness,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
routeInformationParser: router.routeInformationParser,
|
||||
routerDelegate: router.routerDelegate,
|
||||
routeInformationProvider: router.routeInformationProvider,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,81 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/rust/api/process_api.dart'
|
||||
as rs_process;
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aria2/aria2.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:starcitizen_doctor/api/api.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/binary_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
|
||||
import 'package:starcitizen_doctor/common/rust/api/process_api.dart'
|
||||
as rs_process;
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
|
||||
class Aria2cManager {
|
||||
static bool _isDaemonRunning = false;
|
||||
part 'aria2c.g.dart';
|
||||
|
||||
static final String _aria2cDir =
|
||||
"${AppConf.applicationBinaryModuleDir}\\aria2c";
|
||||
part 'aria2c.freezed.dart';
|
||||
|
||||
static Aria2c? _aria2c;
|
||||
|
||||
static Aria2c getClient() {
|
||||
if (_aria2c != null) return _aria2c!;
|
||||
throw "not connect!";
|
||||
@freezed
|
||||
class Aria2cModelState with _$Aria2cModelState {
|
||||
const factory Aria2cModelState({
|
||||
required String aria2cDir,
|
||||
Aria2c? aria2c,
|
||||
Aria2GlobalStat? aria2globalStat,
|
||||
}) = _Aria2cModelState;
|
||||
}
|
||||
|
||||
static bool get isAvailable => _isDaemonRunning && _aria2c != null;
|
||||
extension Aria2cModelExt on Aria2cModelState {
|
||||
bool get isRunning => aria2c != null;
|
||||
|
||||
static Future checkLazyLoad() async {
|
||||
bool get hasDownloadTask => aria2globalStat != null && aria2TotalTaskNum > 0;
|
||||
|
||||
int get aria2TotalTaskNum => aria2globalStat == null
|
||||
? 0
|
||||
: ((aria2globalStat!.numActive ?? 0) +
|
||||
(aria2globalStat!.numWaiting ?? 0));
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class Aria2cModel extends _$Aria2cModel {
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
Aria2cModelState build() {
|
||||
if (appGlobalState.applicationBinaryModuleDir == null) {
|
||||
throw Exception("applicationBinaryModuleDir is null");
|
||||
}
|
||||
ref.onDispose(() {
|
||||
_disposed = true;
|
||||
});
|
||||
ref.keepAlive();
|
||||
final aria2cDir = "${appGlobalState.applicationBinaryModuleDir}\\aria2c";
|
||||
// LazyLoad init
|
||||
() async {
|
||||
try {
|
||||
final sessionFile = File("$_aria2cDir\\aria2.session");
|
||||
final sessionFile = File("$aria2cDir\\aria2.session");
|
||||
// 有下载任务则第一时间初始化
|
||||
if (await sessionFile.exists() &&
|
||||
(await sessionFile.readAsString()).trim().isNotEmpty) {
|
||||
await launchDaemon();
|
||||
dPrint("launch Aria2c daemon");
|
||||
await launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
} else {
|
||||
dPrint("LazyLoad Aria2c daemon");
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("Aria2cManager.checkLazyLoad Error:$e");
|
||||
}
|
||||
}();
|
||||
|
||||
return Aria2cModelState(aria2cDir: aria2cDir);
|
||||
}
|
||||
|
||||
static Future launchDaemon() async {
|
||||
if (_isDaemonRunning) return;
|
||||
await BinaryModuleConf.extractModule(["aria2c"]);
|
||||
Future launchDaemon(String applicationBinaryModuleDir) async {
|
||||
if (state.aria2c != null) return;
|
||||
await BinaryModuleConf.extractModule(
|
||||
["aria2c"], applicationBinaryModuleDir);
|
||||
|
||||
/// skip for debug hot reload
|
||||
if (kDebugMode) {
|
||||
@ -53,12 +85,12 @@ class Aria2cManager {
|
||||
}
|
||||
}
|
||||
|
||||
final sessionFile = File("$_aria2cDir\\aria2.session");
|
||||
final sessionFile = File("${state.aria2cDir}\\aria2.session");
|
||||
if (!await sessionFile.exists()) {
|
||||
await sessionFile.create(recursive: true);
|
||||
}
|
||||
|
||||
final exePath = "$_aria2cDir\\aria2c.exe";
|
||||
final exePath = "${state.aria2cDir}\\aria2c.exe";
|
||||
final port = await getFreePort();
|
||||
final pwd = generateRandomPassword(16);
|
||||
dPrint("pwd === $pwd");
|
||||
@ -72,7 +104,7 @@ class Aria2cManager {
|
||||
"-V",
|
||||
"-c",
|
||||
"-x 10",
|
||||
"--dir=$_aria2cDir\\downloads",
|
||||
"--dir=${state.aria2cDir}\\downloads",
|
||||
"--disable-ipv6",
|
||||
"--enable-rpc",
|
||||
"--pause",
|
||||
@ -84,7 +116,7 @@ class Aria2cManager {
|
||||
"--file-allocation=trunc",
|
||||
"--seed-time=0",
|
||||
],
|
||||
workingDirectory: _aria2cDir);
|
||||
workingDirectory: state.aria2cDir);
|
||||
|
||||
String launchError = "";
|
||||
|
||||
@ -95,31 +127,29 @@ class Aria2cManager {
|
||||
_onLaunch(port, pwd, trackerList);
|
||||
}
|
||||
} else if (event.startsWith("error:")) {
|
||||
_isDaemonRunning = false;
|
||||
_aria2c = null;
|
||||
state = state.copyWith(aria2c: null);
|
||||
launchError = event;
|
||||
} else if (event.startsWith("exit:")) {
|
||||
_isDaemonRunning = false;
|
||||
_aria2c = null;
|
||||
state = state.copyWith(aria2c: null);
|
||||
launchError = event;
|
||||
}
|
||||
});
|
||||
|
||||
while (true) {
|
||||
if (_isDaemonRunning) return;
|
||||
if (state.aria2c != null) return;
|
||||
if (launchError.isNotEmpty) throw launchError;
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> getFreePort() async {
|
||||
Future<int> getFreePort() async {
|
||||
final serverSocket = await ServerSocket.bind("127.0.0.1", 0);
|
||||
final port = serverSocket.port;
|
||||
await serverSocket.close();
|
||||
return port;
|
||||
}
|
||||
|
||||
static String generateRandomPassword(int length) {
|
||||
String generateRandomPassword(int length) {
|
||||
const String charset =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
Random random = Random();
|
||||
@ -131,7 +161,7 @@ class Aria2cManager {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
static int textToByte(String text) {
|
||||
int textToByte(String text) {
|
||||
if (text.length == 1) {
|
||||
return 0;
|
||||
}
|
||||
@ -147,19 +177,36 @@ class Aria2cManager {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Future<void> _onLaunch(
|
||||
int port, String pwd, String trackerList) async {
|
||||
_isDaemonRunning = true;
|
||||
_aria2c = Aria2c("ws://127.0.0.1:$port/jsonrpc", "websocket", pwd);
|
||||
_aria2c!.getVersion().then((value) {
|
||||
Future<void> _onLaunch(int port, String pwd, String trackerList) async {
|
||||
final aria2c = Aria2c("ws://127.0.0.1:$port/jsonrpc", "websocket", pwd);
|
||||
state = state.copyWith(aria2c: aria2c);
|
||||
aria2c.getVersion().then((value) {
|
||||
dPrint("Aria2cManager.connected! version == ${value.version}");
|
||||
_listenState(aria2c);
|
||||
});
|
||||
final box = await Hive.openBox("app_conf");
|
||||
_aria2c!.changeGlobalOption(Aria2Option()
|
||||
aria2c.changeGlobalOption(Aria2Option()
|
||||
..maxOverallUploadLimit =
|
||||
textToByte(box.get("downloader_up_limit", defaultValue: "0"))
|
||||
..maxOverallDownloadLimit =
|
||||
textToByte(box.get("downloader_down_limit", defaultValue: "0"))
|
||||
..btTracker = trackerList);
|
||||
}
|
||||
|
||||
Future<void> _listenState(Aria2c aria2c) async {
|
||||
dPrint("Aria2cModel._listenState start");
|
||||
while (true) {
|
||||
if (_disposed || state.aria2c == null) {
|
||||
dPrint("Aria2cModel._listenState end");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final aria2globalStat = await aria2c.getGlobalStat();
|
||||
state = state.copyWith(aria2globalStat: aria2globalStat);
|
||||
} catch (e) {
|
||||
dPrint("aria2globalStat update error:$e");
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
}
|
||||
}
|
186
lib/provider/aria2c.freezed.dart
Normal file
186
lib/provider/aria2c.freezed.dart
Normal file
@ -0,0 +1,186 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'aria2c.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Aria2cModelState {
|
||||
String get aria2cDir => throw _privateConstructorUsedError;
|
||||
Aria2c? get aria2c => throw _privateConstructorUsedError;
|
||||
Aria2GlobalStat? get aria2globalStat => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$Aria2cModelStateCopyWith<Aria2cModelState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $Aria2cModelStateCopyWith<$Res> {
|
||||
factory $Aria2cModelStateCopyWith(
|
||||
Aria2cModelState value, $Res Function(Aria2cModelState) then) =
|
||||
_$Aria2cModelStateCopyWithImpl<$Res, Aria2cModelState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$Aria2cModelStateCopyWithImpl<$Res, $Val extends Aria2cModelState>
|
||||
implements $Aria2cModelStateCopyWith<$Res> {
|
||||
_$Aria2cModelStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? aria2cDir = null,
|
||||
Object? aria2c = freezed,
|
||||
Object? aria2globalStat = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
aria2cDir: null == aria2cDir
|
||||
? _value.aria2cDir
|
||||
: aria2cDir // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
aria2c: freezed == aria2c
|
||||
? _value.aria2c
|
||||
: aria2c // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2c?,
|
||||
aria2globalStat: freezed == aria2globalStat
|
||||
? _value.aria2globalStat
|
||||
: aria2globalStat // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2GlobalStat?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$Aria2cModelStateImplCopyWith<$Res>
|
||||
implements $Aria2cModelStateCopyWith<$Res> {
|
||||
factory _$$Aria2cModelStateImplCopyWith(_$Aria2cModelStateImpl value,
|
||||
$Res Function(_$Aria2cModelStateImpl) then) =
|
||||
__$$Aria2cModelStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String aria2cDir, Aria2c? aria2c, Aria2GlobalStat? aria2globalStat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$Aria2cModelStateImplCopyWithImpl<$Res>
|
||||
extends _$Aria2cModelStateCopyWithImpl<$Res, _$Aria2cModelStateImpl>
|
||||
implements _$$Aria2cModelStateImplCopyWith<$Res> {
|
||||
__$$Aria2cModelStateImplCopyWithImpl(_$Aria2cModelStateImpl _value,
|
||||
$Res Function(_$Aria2cModelStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? aria2cDir = null,
|
||||
Object? aria2c = freezed,
|
||||
Object? aria2globalStat = freezed,
|
||||
}) {
|
||||
return _then(_$Aria2cModelStateImpl(
|
||||
aria2cDir: null == aria2cDir
|
||||
? _value.aria2cDir
|
||||
: aria2cDir // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
aria2c: freezed == aria2c
|
||||
? _value.aria2c
|
||||
: aria2c // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2c?,
|
||||
aria2globalStat: freezed == aria2globalStat
|
||||
? _value.aria2globalStat
|
||||
: aria2globalStat // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2GlobalStat?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$Aria2cModelStateImpl
|
||||
with DiagnosticableTreeMixin
|
||||
implements _Aria2cModelState {
|
||||
const _$Aria2cModelStateImpl(
|
||||
{required this.aria2cDir, this.aria2c, this.aria2globalStat});
|
||||
|
||||
@override
|
||||
final String aria2cDir;
|
||||
@override
|
||||
final Aria2c? aria2c;
|
||||
@override
|
||||
final Aria2GlobalStat? aria2globalStat;
|
||||
|
||||
@override
|
||||
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||
return 'Aria2cModelState(aria2cDir: $aria2cDir, aria2c: $aria2c, aria2globalStat: $aria2globalStat)';
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('type', 'Aria2cModelState'))
|
||||
..add(DiagnosticsProperty('aria2cDir', aria2cDir))
|
||||
..add(DiagnosticsProperty('aria2c', aria2c))
|
||||
..add(DiagnosticsProperty('aria2globalStat', aria2globalStat));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$Aria2cModelStateImpl &&
|
||||
(identical(other.aria2cDir, aria2cDir) ||
|
||||
other.aria2cDir == aria2cDir) &&
|
||||
(identical(other.aria2c, aria2c) || other.aria2c == aria2c) &&
|
||||
(identical(other.aria2globalStat, aria2globalStat) ||
|
||||
other.aria2globalStat == aria2globalStat));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, aria2cDir, aria2c, aria2globalStat);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$Aria2cModelStateImplCopyWith<_$Aria2cModelStateImpl> get copyWith =>
|
||||
__$$Aria2cModelStateImplCopyWithImpl<_$Aria2cModelStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _Aria2cModelState implements Aria2cModelState {
|
||||
const factory _Aria2cModelState(
|
||||
{required final String aria2cDir,
|
||||
final Aria2c? aria2c,
|
||||
final Aria2GlobalStat? aria2globalStat}) = _$Aria2cModelStateImpl;
|
||||
|
||||
@override
|
||||
String get aria2cDir;
|
||||
@override
|
||||
Aria2c? get aria2c;
|
||||
@override
|
||||
Aria2GlobalStat? get aria2globalStat;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$Aria2cModelStateImplCopyWith<_$Aria2cModelStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
25
lib/provider/aria2c.g.dart
Normal file
25
lib/provider/aria2c.g.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'aria2c.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$aria2cModelHash() => r'6685f6a716016113487de190a61f71196094526e';
|
||||
|
||||
/// See also [Aria2cModel].
|
||||
@ProviderFor(Aria2cModel)
|
||||
final aria2cModelProvider =
|
||||
AutoDisposeNotifierProvider<Aria2cModel, Aria2cModelState>.internal(
|
||||
Aria2cModel.new,
|
||||
name: r'aria2cModelProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$aria2cModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$Aria2cModel = AutoDisposeNotifier<Aria2cModelState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,16 +1,20 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/app.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'about_ui_model.dart';
|
||||
|
||||
class AboutUI extends BaseUI<AboutUIModel> {
|
||||
bool isTipTextCn = false;
|
||||
class AboutUI extends HookConsumerWidget {
|
||||
const AboutUI({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, AboutUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isTipTextCn = useState(false);
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -20,11 +24,11 @@ class AboutUI extends BaseUI<AboutUIModel> {
|
||||
Image.asset("assets/app_logo.png", width: 128, height: 128),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
"SC汉化盒子 V${AppConf.appVersion} ${AppConf.isMSE ? "" : " +Dev"}",
|
||||
"SC汉化盒子 V${ConstConf.appVersion} ${ConstConf.isMSE ? "" : " +Dev"}",
|
||||
style: TextStyle(fontSize: 18)),
|
||||
const SizedBox(height: 12),
|
||||
Button(
|
||||
onPressed: model.checkUpdate,
|
||||
onPressed: () => _onCheckUpdate(context, ref),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Text("检查更新"),
|
||||
@ -131,15 +135,14 @@ class AboutUI extends BaseUI<AboutUIModel> {
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: Text(
|
||||
isTipTextCn ? tipTextCN : tipTextEN,
|
||||
isTipTextCn.value ? tipTextCN : tipTextEN,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Colors.white.withOpacity(.9)),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
isTipTextCn = !isTipTextCn;
|
||||
setState(() {});
|
||||
isTipTextCn.value = !isTipTextCn.value;
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -158,6 +161,17 @@ class AboutUI extends BaseUI<AboutUIModel> {
|
||||
static const tipTextCN =
|
||||
"这是一个非官方的星际公民工具,不隶属于 Cloud Imperium 公司集团。 本软件中非由其主机或用户创作的所有内容均为其各自所有者的财产。 \nStar Citizen®、Roberts Space Industries® 和 Cloud Imperium® 是 Cloud Imperium Rights LLC 的注册商标。";
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, AboutUIModel model) => "";
|
||||
_onCheckUpdate(BuildContext context, WidgetRef ref) async {
|
||||
if (ConstConf.isMSE) {
|
||||
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
|
||||
return;
|
||||
} else {
|
||||
final hasUpdate =
|
||||
await ref.read(appGlobalModelProvider.notifier).checkUpdate(context);
|
||||
if (!hasUpdate) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "已经是最新版本!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/global_ui_model.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class AboutUIModel extends BaseUIModel {
|
||||
Future<void> checkUpdate() async {
|
||||
if (AppConf.isMSE) {
|
||||
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
|
||||
return;
|
||||
}
|
||||
final hasUpdate = await globalUIModel.doCheckUpdate(context!);
|
||||
if (!hasUpdate) {
|
||||
if (mounted) showToast(context!, "已是最新版本");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/widgets/countdown_time_text.dart';
|
||||
|
||||
import 'countdown_dialog_ui_model.dart';
|
||||
|
||||
class CountdownDialogUI extends BaseUI<CountdownDialogUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, CountdownDialogUIModel model) {
|
||||
return ContentDialog(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .65),
|
||||
title: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: model.onBack),
|
||||
const SizedBox(width: 12),
|
||||
const Text("节日倒计时"),
|
||||
],
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
AlignedGridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: model.countdownFestivalListData.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.countdownFestivalListData[index];
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (item.icon != null && item.icon != "") ...[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: Image.asset(
|
||||
"assets/countdown/${item.icon}",
|
||||
width: 38,
|
||||
height: 38,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
] else
|
||||
const SizedBox(width: 50),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.name}",
|
||||
),
|
||||
CountdownTimeText(
|
||||
targetTime: DateTime.fromMillisecondsSinceEpoch(
|
||||
item.time ?? 0),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"* 以上节日日期由人工收录、维护,可能存在错误,欢迎反馈!",
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withOpacity(.3)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, CountdownDialogUIModel model) => "";
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart';
|
||||
|
||||
class CountdownDialogUIModel extends BaseUIModel {
|
||||
final List<CountdownFestivalItemData> countdownFestivalListData;
|
||||
|
||||
CountdownDialogUIModel(this.countdownFestivalListData);
|
||||
|
||||
onBack() {
|
||||
Navigator.pop(context!);
|
||||
}
|
||||
}
|
98
lib/ui/home/dialogs/home_countdown_dialog_ui.dart
Normal file
98
lib/ui/home/dialogs/home_countdown_dialog_ui.dart
Normal file
@ -0,0 +1,98 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
class HomeCountdownDialogUI extends HookConsumerWidget {
|
||||
const HomeCountdownDialogUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final homeState = ref.watch(homeUIModelProvider);
|
||||
return ContentDialog(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .65),
|
||||
title: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
const SizedBox(width: 12),
|
||||
const Text("节日倒计时"),
|
||||
],
|
||||
),
|
||||
content: homeState.countdownFestivalListData == null
|
||||
? makeLoading(context)
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
AlignedGridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: homeState.countdownFestivalListData!.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item =
|
||||
homeState.countdownFestivalListData![index];
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (item.icon != null && item.icon != "") ...[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: Image.asset(
|
||||
"assets/countdown/${item.icon}",
|
||||
width: 38,
|
||||
height: 38,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
] else
|
||||
const SizedBox(width: 50),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.name}",
|
||||
),
|
||||
CountdownTimeText(
|
||||
targetTime:
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
item.time ?? 0),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"* 以上节日日期由人工收录、维护,可能存在错误,欢迎反馈!",
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withOpacity(.3)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
107
lib/ui/home/dialogs/home_game_login_dialog_ui.dart
Normal file
107
lib/ui/home/dialogs/home_game_login_dialog_ui.dart
Normal file
@ -0,0 +1,107 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'home_game_login_dialog_ui_model.dart';
|
||||
|
||||
class HomeGameLoginDialogUI extends HookConsumerWidget {
|
||||
const HomeGameLoginDialogUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final loginState = ref.watch(homeGameLoginUIModelProvider);
|
||||
useEffect(() {
|
||||
ref.read(homeGameLoginUIModelProvider.notifier).launchWebLogin(context);
|
||||
return null;
|
||||
}, const []);
|
||||
return ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .56,
|
||||
),
|
||||
title: (loginState.loginStatus == 2) ? null : const Text("一键启动"),
|
||||
content: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 230),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Row(),
|
||||
if (loginState.loginStatus == 0) ...[
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("登录中..."),
|
||||
const SizedBox(height: 12),
|
||||
const ProgressRing(),
|
||||
if (loginState.isDeviceSupportWinHello ?? false)
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
"* 若开启了自动填充,请留意弹出的 Windows Hello 窗口",
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withOpacity(.6)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
] else if (loginState.loginStatus == 1) ...[
|
||||
Text(
|
||||
"请输入RSI账户 [${loginState.nickname}] 的邮箱,以保存登录状态(输入错误会导致无法进入游戏!)"),
|
||||
const SizedBox(height: 12),
|
||||
TextFormBox(
|
||||
// controller: model.emailCtrl,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"*该操作同一账号只需执行一次,输入错误请在盒子设置中清理,切换账号请在汉化浏览器中操作。",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.6),
|
||||
),
|
||||
)
|
||||
] else if (loginState.loginStatus == 2 ||
|
||||
loginState.loginStatus == 3) ...[
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"欢迎回来!",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (loginState.avatarUrl != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: loginState.avatarUrl!,
|
||||
width: 128,
|
||||
height: 128,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
loginState.nickname ?? "",
|
||||
style: const TextStyle(
|
||||
fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(loginState.loginStatus == 2
|
||||
? "正在为您启动游戏..."
|
||||
: "正在等待优化CPU参数..."),
|
||||
const SizedBox(height: 12),
|
||||
const ProgressRing(),
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,68 +1,78 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/common/win32/credentials.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
|
||||
import 'package:starcitizen_doctor/ui/webview/webview.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class LoginDialogModel extends BaseUIModel {
|
||||
int loginStatus = 0;
|
||||
part 'home_game_login_dialog_ui_model.freezed.dart';
|
||||
|
||||
String nickname = "";
|
||||
String? avatarUrl;
|
||||
String? authToken;
|
||||
String? webToken;
|
||||
Map? releaseInfo;
|
||||
part 'home_game_login_dialog_ui_model.g.dart';
|
||||
|
||||
final String installPath;
|
||||
|
||||
final HomeUIModel homeUIModel;
|
||||
|
||||
// TextEditingController emailCtrl = TextEditingController();
|
||||
|
||||
LoginDialogModel(this.installPath, this.homeUIModel);
|
||||
|
||||
final LocalAuthentication localAuth = LocalAuthentication();
|
||||
|
||||
var isDeviceSupportWinHello = false;
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
_launchWebLogin();
|
||||
super.initModel();
|
||||
@freezed
|
||||
class HomeGameLoginState with _$HomeGameLoginState {
|
||||
const factory HomeGameLoginState({
|
||||
required int loginStatus,
|
||||
String? nickname,
|
||||
String? avatarUrl,
|
||||
String? authToken,
|
||||
String? webToken,
|
||||
Map? releaseInfo,
|
||||
String? installPath,
|
||||
bool? isDeviceSupportWinHello,
|
||||
}) = _LoginStatus;
|
||||
}
|
||||
|
||||
Future<void> _launchWebLogin() async {
|
||||
isDeviceSupportWinHello = await localAuth.isDeviceSupported();
|
||||
notifyListeners();
|
||||
goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect",
|
||||
@riverpod
|
||||
class HomeGameLoginUIModel extends _$HomeGameLoginUIModel {
|
||||
@override
|
||||
HomeGameLoginState build() {
|
||||
return const HomeGameLoginState(loginStatus: 0);
|
||||
}
|
||||
|
||||
final LocalAuthentication _localAuth = LocalAuthentication();
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> launchWebLogin(BuildContext context) async {
|
||||
final homeState = ref.read(homeUIModelProvider);
|
||||
final isDeviceSupportWinHello = await _localAuth.isDeviceSupported();
|
||||
state = state.copyWith(isDeviceSupportWinHello: isDeviceSupportWinHello);
|
||||
|
||||
if (!context.mounted) return;
|
||||
goWebView(
|
||||
context, "登录 RSI 账户", "https://robertsspaceindustries.com/connect",
|
||||
loginMode: true, rsiLoginCallback: (message, ok) async {
|
||||
// dPrint(
|
||||
// "======rsiLoginCallback=== $ok ===== data==\n${json.encode(message)}");
|
||||
if (message == null || !ok) {
|
||||
Navigator.pop(context!);
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
// final emailBox = await Hive.openBox("quick_login_email");
|
||||
final data = message["data"];
|
||||
authToken = data["authToken"];
|
||||
webToken = data["webToken"];
|
||||
releaseInfo = data["releaseInfo"];
|
||||
avatarUrl = data["avatar"]
|
||||
final authToken = data["authToken"];
|
||||
final webToken = data["webToken"];
|
||||
final releaseInfo = data["releaseInfo"];
|
||||
final avatarUrl = data["avatar"]
|
||||
?.toString()
|
||||
.replaceAll("url(\"", "")
|
||||
.replaceAll("\")", "");
|
||||
Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
||||
nickname = payload["nickname"] ?? "";
|
||||
final Map<String, dynamic> payload = Jwt.parseJwt(authToken!);
|
||||
final nickname = payload["nickname"] ?? "";
|
||||
|
||||
final inputEmail = data["inputEmail"];
|
||||
final inputPassword = data["inputPassword"];
|
||||
@ -71,19 +81,29 @@ class LoginDialogModel extends BaseUIModel {
|
||||
if (inputEmail != null && inputEmail != "") {
|
||||
await userBox.put("account_email", inputEmail);
|
||||
}
|
||||
state = state.copyWith(
|
||||
nickname: nickname,
|
||||
avatarUrl: avatarUrl,
|
||||
authToken: authToken,
|
||||
webToken: webToken,
|
||||
releaseInfo: releaseInfo,
|
||||
);
|
||||
|
||||
if (isDeviceSupportWinHello) {
|
||||
if (await userBox.get("enable", defaultValue: true)) {
|
||||
if (inputEmail != null &&
|
||||
inputEmail != "" &&
|
||||
inputPassword != null &&
|
||||
inputPassword != "") {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"是否开启自动密码填充?",
|
||||
const Text(
|
||||
"盒子将使用 PIN 与 Windows 凭据加密保存您的密码,密码只存储在您的设备中。\n\n当下次登录需要输入密码时,您只需授权PIN即可自动填充登录。"));
|
||||
if (ok == true) {
|
||||
if (await localAuth.authenticate(localizedReason: "输入PIN以启用加密") ==
|
||||
if (await _localAuth.authenticate(
|
||||
localizedReason: "输入PIN以启用加密") ==
|
||||
true) {
|
||||
await _savePwd(inputEmail, inputPassword);
|
||||
}
|
||||
@ -94,7 +114,8 @@ class LoginDialogModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
|
||||
final buildInfoFile = File("$installPath\\build_manifest.id");
|
||||
final buildInfoFile =
|
||||
File("${homeState.scInstalledPath}\\build_manifest.id");
|
||||
if (await buildInfoFile.exists()) {
|
||||
final buildInfo =
|
||||
json.decode(await buildInfoFile.readAsString())["Data"];
|
||||
@ -105,37 +126,43 @@ class LoginDialogModel extends BaseUIModel {
|
||||
if (!(releaseInfo!["versionLabel"]!
|
||||
.toString()
|
||||
.endsWith(buildInfo["RequestedP4ChangeNum"]!.toString()))) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"游戏版本过期",
|
||||
Text(
|
||||
"RSI 服务器报告版本号:${releaseInfo?["versionLabel"]} \n\n本地版本号:${buildInfo["RequestedP4ChangeNum"]} \n\n建议使用 RSI Launcher 更新游戏!"),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .4),
|
||||
maxWidth: MediaQuery.of(context).size.width * .4),
|
||||
cancel: "忽略");
|
||||
if (ok == true) {
|
||||
Navigator.pop(context!);
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_readyForLaunch();
|
||||
}, useLocalization: true);
|
||||
if (!context.mounted) return;
|
||||
_readyForLaunch(homeState, context);
|
||||
}, useLocalization: true, homeState: homeState);
|
||||
}
|
||||
|
||||
goWebView(String title, String url,
|
||||
// ignore: avoid_build_context_in_providers
|
||||
goWebView(BuildContext context, String title, String url,
|
||||
{bool useLocalization = false,
|
||||
bool loginMode = false,
|
||||
RsiLoginCallback? rsiLoginCallback}) async {
|
||||
RsiLoginCallback? rsiLoginCallback,
|
||||
required HomeUIModelState homeState}) async {
|
||||
if (useLocalization) {
|
||||
const tipVersion = 2;
|
||||
final box = await Hive.openBox("app_conf");
|
||||
final skip = await box.get("skip_web_login_version", defaultValue: 0);
|
||||
if (skip != tipVersion) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"盒子一键启动",
|
||||
const Text(
|
||||
"本功能可以帮您更加便利的启动游戏。\n\n为确保账户安全 ,本功能使用汉化浏览器保留登录状态,且不会保存您的密码信息(除非你启用了自动填充功能)。"
|
||||
@ -143,7 +170,7 @@ class LoginDialogModel extends BaseUIModel {
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .6));
|
||||
maxWidth: MediaQuery.of(context).size.width * .6));
|
||||
if (!ok) {
|
||||
if (loginMode) {
|
||||
rsiLoginCallback?.call(null, false);
|
||||
@ -154,84 +181,81 @@ class LoginDialogModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
||||
await showToast(context!, "需要安装 WebView2 Runtime");
|
||||
if (!context.mounted) return;
|
||||
await showToast(context, "需要安装 WebView2 Runtime");
|
||||
if (!context.mounted) return;
|
||||
await launchUrlString(
|
||||
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
||||
Navigator.pop(context!);
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
final webViewModel = WebViewModel(context!,
|
||||
if (!context.mounted) return;
|
||||
final webViewModel = WebViewModel(context,
|
||||
loginMode: loginMode,
|
||||
loginCallback: rsiLoginCallback,
|
||||
loginChannel: getChannelID());
|
||||
loginChannel: getChannelID(homeState.scInstalledPath!));
|
||||
if (useLocalization) {
|
||||
try {
|
||||
await webViewModel
|
||||
.initLocalization(homeUIModel.appWebLocalizationVersionsData!);
|
||||
.initLocalization(homeState.webLocalizationVersionsData!);
|
||||
} catch (_) {}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await webViewModel.initWebView(
|
||||
title: title,
|
||||
applicationSupportDir: appGlobalState.applicationSupportDir!,
|
||||
appVersionData: appGlobalState.networkVersionData!,
|
||||
);
|
||||
await webViewModel.launch(url);
|
||||
notifyListeners();
|
||||
await webViewModel.launch(url, appGlobalState.networkVersionData!);
|
||||
}
|
||||
|
||||
// onSaveEmail() async {
|
||||
// final RegExp emailRegex = RegExp(r'^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$');
|
||||
// if (!emailRegex.hasMatch(emailCtrl.text.trim())) {
|
||||
// showToast(context!, "邮箱输入有误!");
|
||||
// return;
|
||||
// }
|
||||
// final emailBox = await Hive.openBox("quick_login_email");
|
||||
// await emailBox.put(nickname, emailCtrl.text.trim());
|
||||
// _readyForLaunch();
|
||||
// notifyListeners();
|
||||
// }
|
||||
|
||||
Future<void> _readyForLaunch() async {
|
||||
Future<void> _readyForLaunch(
|
||||
HomeUIModelState homeState,
|
||||
// ignore: avoid_build_context_in_providers
|
||||
BuildContext context) async {
|
||||
final userBox = await Hive.openBox("rsi_account_data");
|
||||
loginStatus = 2;
|
||||
notifyListeners();
|
||||
state = state.copyWith(loginStatus: 2);
|
||||
final launchData = {
|
||||
"username": userBox.get("account_email", defaultValue: ""),
|
||||
"token": webToken,
|
||||
"auth_token": authToken,
|
||||
"token": state.webToken,
|
||||
"auth_token": state.authToken,
|
||||
"star_network": {
|
||||
"services_endpoint": releaseInfo?["servicesEndpoint"],
|
||||
"hostname": releaseInfo?["universeHost"],
|
||||
"port": releaseInfo?["universePort"],
|
||||
"services_endpoint": state.releaseInfo?["servicesEndpoint"],
|
||||
"hostname": state.releaseInfo?["universeHost"],
|
||||
"port": state.releaseInfo?["universePort"],
|
||||
},
|
||||
"TMid": const Uuid().v4(),
|
||||
};
|
||||
final executable = releaseInfo?["executable"];
|
||||
final launchOptions = releaseInfo?["launchOptions"];
|
||||
final executable = state.releaseInfo?["executable"];
|
||||
final launchOptions = state.releaseInfo?["launchOptions"];
|
||||
dPrint("----------launch data ====== -----------\n$launchData");
|
||||
dPrint(
|
||||
"----------executable data ====== -----------\n$installPath\\$executable $launchOptions");
|
||||
final launchFile = File("$installPath\\loginData.json");
|
||||
"----------executable data ====== -----------\n${homeState.scInstalledPath}\\$executable $launchOptions");
|
||||
final launchFile = File("${homeState.scInstalledPath}\\loginData.json");
|
||||
if (await launchFile.exists()) {
|
||||
await launchFile.delete();
|
||||
}
|
||||
await launchFile.create();
|
||||
await launchFile.writeAsString(json.encode(launchData));
|
||||
notifyListeners();
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
final processorAffinity = await SystemHelper.getCpuAffinity();
|
||||
|
||||
final homeUIModel = ref.read(homeUIModelProvider.notifier);
|
||||
if (!context.mounted) return;
|
||||
homeUIModel.doLaunchGame(
|
||||
'$installPath\\$executable',
|
||||
context,
|
||||
'${homeState.scInstalledPath}\\$executable',
|
||||
["-no_login_dialog", ...launchOptions.toString().split(" ")],
|
||||
installPath,
|
||||
homeState.scInstalledPath!,
|
||||
processorAffinity);
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
Navigator.pop(context!);
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
String getChannelID() {
|
||||
String getChannelID(String installPath) {
|
||||
if (installPath.endsWith("\\LIVE")) {
|
||||
return "LIVE";
|
||||
} else if (installPath.endsWith("\\PTU")) {
|
303
lib/ui/home/dialogs/home_game_login_dialog_ui_model.freezed.dart
Normal file
303
lib/ui/home/dialogs/home_game_login_dialog_ui_model.freezed.dart
Normal file
@ -0,0 +1,303 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'home_game_login_dialog_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HomeGameLoginState {
|
||||
int get loginStatus => throw _privateConstructorUsedError;
|
||||
String? get nickname => throw _privateConstructorUsedError;
|
||||
String? get avatarUrl => throw _privateConstructorUsedError;
|
||||
String? get authToken => throw _privateConstructorUsedError;
|
||||
String? get webToken => throw _privateConstructorUsedError;
|
||||
Map<dynamic, dynamic>? get releaseInfo => throw _privateConstructorUsedError;
|
||||
String? get installPath => throw _privateConstructorUsedError;
|
||||
bool? get isDeviceSupportWinHello => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$HomeGameLoginStateCopyWith<HomeGameLoginState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HomeGameLoginStateCopyWith<$Res> {
|
||||
factory $HomeGameLoginStateCopyWith(
|
||||
HomeGameLoginState value, $Res Function(HomeGameLoginState) then) =
|
||||
_$HomeGameLoginStateCopyWithImpl<$Res, HomeGameLoginState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{int loginStatus,
|
||||
String? nickname,
|
||||
String? avatarUrl,
|
||||
String? authToken,
|
||||
String? webToken,
|
||||
Map<dynamic, dynamic>? releaseInfo,
|
||||
String? installPath,
|
||||
bool? isDeviceSupportWinHello});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HomeGameLoginStateCopyWithImpl<$Res, $Val extends HomeGameLoginState>
|
||||
implements $HomeGameLoginStateCopyWith<$Res> {
|
||||
_$HomeGameLoginStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? loginStatus = null,
|
||||
Object? nickname = freezed,
|
||||
Object? avatarUrl = freezed,
|
||||
Object? authToken = freezed,
|
||||
Object? webToken = freezed,
|
||||
Object? releaseInfo = freezed,
|
||||
Object? installPath = freezed,
|
||||
Object? isDeviceSupportWinHello = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
loginStatus: null == loginStatus
|
||||
? _value.loginStatus
|
||||
: loginStatus // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
nickname: freezed == nickname
|
||||
? _value.nickname
|
||||
: nickname // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
avatarUrl: freezed == avatarUrl
|
||||
? _value.avatarUrl
|
||||
: avatarUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
authToken: freezed == authToken
|
||||
? _value.authToken
|
||||
: authToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
webToken: freezed == webToken
|
||||
? _value.webToken
|
||||
: webToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
releaseInfo: freezed == releaseInfo
|
||||
? _value.releaseInfo
|
||||
: releaseInfo // ignore: cast_nullable_to_non_nullable
|
||||
as Map<dynamic, dynamic>?,
|
||||
installPath: freezed == installPath
|
||||
? _value.installPath
|
||||
: installPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
|
||||
? _value.isDeviceSupportWinHello
|
||||
: isDeviceSupportWinHello // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LoginStatusImplCopyWith<$Res>
|
||||
implements $HomeGameLoginStateCopyWith<$Res> {
|
||||
factory _$$LoginStatusImplCopyWith(
|
||||
_$LoginStatusImpl value, $Res Function(_$LoginStatusImpl) then) =
|
||||
__$$LoginStatusImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{int loginStatus,
|
||||
String? nickname,
|
||||
String? avatarUrl,
|
||||
String? authToken,
|
||||
String? webToken,
|
||||
Map<dynamic, dynamic>? releaseInfo,
|
||||
String? installPath,
|
||||
bool? isDeviceSupportWinHello});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LoginStatusImplCopyWithImpl<$Res>
|
||||
extends _$HomeGameLoginStateCopyWithImpl<$Res, _$LoginStatusImpl>
|
||||
implements _$$LoginStatusImplCopyWith<$Res> {
|
||||
__$$LoginStatusImplCopyWithImpl(
|
||||
_$LoginStatusImpl _value, $Res Function(_$LoginStatusImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? loginStatus = null,
|
||||
Object? nickname = freezed,
|
||||
Object? avatarUrl = freezed,
|
||||
Object? authToken = freezed,
|
||||
Object? webToken = freezed,
|
||||
Object? releaseInfo = freezed,
|
||||
Object? installPath = freezed,
|
||||
Object? isDeviceSupportWinHello = freezed,
|
||||
}) {
|
||||
return _then(_$LoginStatusImpl(
|
||||
loginStatus: null == loginStatus
|
||||
? _value.loginStatus
|
||||
: loginStatus // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
nickname: freezed == nickname
|
||||
? _value.nickname
|
||||
: nickname // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
avatarUrl: freezed == avatarUrl
|
||||
? _value.avatarUrl
|
||||
: avatarUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
authToken: freezed == authToken
|
||||
? _value.authToken
|
||||
: authToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
webToken: freezed == webToken
|
||||
? _value.webToken
|
||||
: webToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
releaseInfo: freezed == releaseInfo
|
||||
? _value._releaseInfo
|
||||
: releaseInfo // ignore: cast_nullable_to_non_nullable
|
||||
as Map<dynamic, dynamic>?,
|
||||
installPath: freezed == installPath
|
||||
? _value.installPath
|
||||
: installPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
|
||||
? _value.isDeviceSupportWinHello
|
||||
: isDeviceSupportWinHello // ignore: cast_nullable_to_non_nullable
|
||||
as bool?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$LoginStatusImpl implements _LoginStatus {
|
||||
const _$LoginStatusImpl(
|
||||
{required this.loginStatus,
|
||||
this.nickname,
|
||||
this.avatarUrl,
|
||||
this.authToken,
|
||||
this.webToken,
|
||||
final Map<dynamic, dynamic>? releaseInfo,
|
||||
this.installPath,
|
||||
this.isDeviceSupportWinHello})
|
||||
: _releaseInfo = releaseInfo;
|
||||
|
||||
@override
|
||||
final int loginStatus;
|
||||
@override
|
||||
final String? nickname;
|
||||
@override
|
||||
final String? avatarUrl;
|
||||
@override
|
||||
final String? authToken;
|
||||
@override
|
||||
final String? webToken;
|
||||
final Map<dynamic, dynamic>? _releaseInfo;
|
||||
@override
|
||||
Map<dynamic, dynamic>? get releaseInfo {
|
||||
final value = _releaseInfo;
|
||||
if (value == null) return null;
|
||||
if (_releaseInfo is EqualUnmodifiableMapView) return _releaseInfo;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final String? installPath;
|
||||
@override
|
||||
final bool? isDeviceSupportWinHello;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeGameLoginState(loginStatus: $loginStatus, nickname: $nickname, avatarUrl: $avatarUrl, authToken: $authToken, webToken: $webToken, releaseInfo: $releaseInfo, installPath: $installPath, isDeviceSupportWinHello: $isDeviceSupportWinHello)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LoginStatusImpl &&
|
||||
(identical(other.loginStatus, loginStatus) ||
|
||||
other.loginStatus == loginStatus) &&
|
||||
(identical(other.nickname, nickname) ||
|
||||
other.nickname == nickname) &&
|
||||
(identical(other.avatarUrl, avatarUrl) ||
|
||||
other.avatarUrl == avatarUrl) &&
|
||||
(identical(other.authToken, authToken) ||
|
||||
other.authToken == authToken) &&
|
||||
(identical(other.webToken, webToken) ||
|
||||
other.webToken == webToken) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._releaseInfo, _releaseInfo) &&
|
||||
(identical(other.installPath, installPath) ||
|
||||
other.installPath == installPath) &&
|
||||
(identical(
|
||||
other.isDeviceSupportWinHello, isDeviceSupportWinHello) ||
|
||||
other.isDeviceSupportWinHello == isDeviceSupportWinHello));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
loginStatus,
|
||||
nickname,
|
||||
avatarUrl,
|
||||
authToken,
|
||||
webToken,
|
||||
const DeepCollectionEquality().hash(_releaseInfo),
|
||||
installPath,
|
||||
isDeviceSupportWinHello);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LoginStatusImplCopyWith<_$LoginStatusImpl> get copyWith =>
|
||||
__$$LoginStatusImplCopyWithImpl<_$LoginStatusImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _LoginStatus implements HomeGameLoginState {
|
||||
const factory _LoginStatus(
|
||||
{required final int loginStatus,
|
||||
final String? nickname,
|
||||
final String? avatarUrl,
|
||||
final String? authToken,
|
||||
final String? webToken,
|
||||
final Map<dynamic, dynamic>? releaseInfo,
|
||||
final String? installPath,
|
||||
final bool? isDeviceSupportWinHello}) = _$LoginStatusImpl;
|
||||
|
||||
@override
|
||||
int get loginStatus;
|
||||
@override
|
||||
String? get nickname;
|
||||
@override
|
||||
String? get avatarUrl;
|
||||
@override
|
||||
String? get authToken;
|
||||
@override
|
||||
String? get webToken;
|
||||
@override
|
||||
Map<dynamic, dynamic>? get releaseInfo;
|
||||
@override
|
||||
String? get installPath;
|
||||
@override
|
||||
bool? get isDeviceSupportWinHello;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$LoginStatusImplCopyWith<_$LoginStatusImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart
Normal file
27
lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'home_game_login_dialog_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeGameLoginUIModelHash() =>
|
||||
r'c26dfd89985ff9246104135c288b673b7f15acf0';
|
||||
|
||||
/// See also [HomeGameLoginUIModel].
|
||||
@ProviderFor(HomeGameLoginUIModel)
|
||||
final homeGameLoginUIModelProvider = AutoDisposeNotifierProvider<
|
||||
HomeGameLoginUIModel, HomeGameLoginState>.internal(
|
||||
HomeGameLoginUIModel.new,
|
||||
name: r'homeGameLoginUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$homeGameLoginUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HomeGameLoginUIModel = AutoDisposeNotifier<HomeGameLoginState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
55
lib/ui/home/dialogs/home_md_content_dialog_ui.dart
Normal file
55
lib/ui/home/dialogs/home_md_content_dialog_ui.dart
Normal file
@ -0,0 +1,55 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Material;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
class HomeMdContentDialogUI extends HookConsumerWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
const HomeMdContentDialogUI(
|
||||
{super.key, required this.title, required this.url});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Material(
|
||||
child: ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .6,
|
||||
),
|
||||
title: Text(title),
|
||||
content: LoadingWidget(
|
||||
onLoadData: _getContent,
|
||||
childBuilder: (BuildContext context, String data) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: makeMarkdownView(data),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2),
|
||||
child: Text("关闭"),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getContent() async {
|
||||
final r = await RSHttp.getText(url);
|
||||
return r;
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import 'package:flutter/material.dart' show Material;
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/dialogs/md_content_dialog_ui_model.dart';
|
||||
|
||||
class MDContentDialogUI extends BaseUI<MDContentDialogUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, MDContentDialogUIModel model) {
|
||||
return Material(
|
||||
child: ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .6,
|
||||
),
|
||||
title: Text(getUITitle(context, model)),
|
||||
content: model.data == null
|
||||
? makeLoading(context)
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: makeMarkdownView(model.data ?? ""),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
FilledButton(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2),
|
||||
child: Text("关闭"),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, MDContentDialogUIModel model) =>
|
||||
model.title;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
|
||||
class MDContentDialogUIModel extends BaseUIModel {
|
||||
String title;
|
||||
String url;
|
||||
|
||||
MDContentDialogUIModel(this.title, this.url);
|
||||
|
||||
String? data;
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
final r = await handleError(() => RSHttp.getText(url));
|
||||
if (r == null) return;
|
||||
data = r;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:file_sizes/file_sizes.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/io/aria2c.dart';
|
||||
|
||||
import 'downloader_ui_model.dart';
|
||||
import 'home_downloader_ui_model.dart';
|
||||
|
||||
class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
final DateFormat formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
class HomeDownloaderUI extends HookConsumerWidget {
|
||||
const HomeDownloaderUI({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, DownloaderUIModel model) {
|
||||
return makeDefaultPage(context, model,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(homeDownloaderUIModelProvider);
|
||||
final model = ref.read(homeDownloaderUIModelProvider.notifier);
|
||||
|
||||
return makeDefaultPage(context,
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -23,11 +25,11 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
const SizedBox(width: 12),
|
||||
for (final item in <MapEntry<String, IconData>, String>{
|
||||
const MapEntry("settings", FluentIcons.settings): "限速设置",
|
||||
if (model.tasks.isNotEmpty)
|
||||
if (state.tasks.isNotEmpty)
|
||||
const MapEntry("pause_all", FluentIcons.pause): "全部暂停",
|
||||
if (model.waitingTasks.isNotEmpty)
|
||||
if (state.waitingTasks.isNotEmpty)
|
||||
const MapEntry("resume_all", FluentIcons.download): "恢复全部",
|
||||
if (model.tasks.isNotEmpty || model.waitingTasks.isNotEmpty)
|
||||
if (state.tasks.isNotEmpty || state.waitingTasks.isNotEmpty)
|
||||
const MapEntry("cancel_all", FluentIcons.cancel): "全部取消",
|
||||
}.entries)
|
||||
Padding(
|
||||
@ -43,7 +45,8 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () => model.onTapButton(item.key.key)),
|
||||
onPressed: () =>
|
||||
model.onTapButton(context, item.key.key)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
@ -58,7 +61,7 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
child: ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final (task, type, isFirstType) = model.getTaskAndType(index);
|
||||
final nt = DownloaderUIModel.getTaskTypeAndName(task);
|
||||
final nt = HomeDownloaderUIModel.getTaskTypeAndName(task);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -139,7 +142,7 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
if (task.status == "active" &&
|
||||
task.verifiedLength == null)
|
||||
Text(
|
||||
"ETA: ${formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}"),
|
||||
"ETA: ${model.formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}"),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -194,7 +197,7 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
),
|
||||
text: const Text('取消下载'),
|
||||
onPressed: () =>
|
||||
model.cancelTask(task.gid)),
|
||||
model.cancelTask(context, task.gid)),
|
||||
MenuFlyoutItem(
|
||||
leading: const Icon(
|
||||
FluentIcons.folder_open,
|
||||
@ -223,15 +226,13 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: Aria2cManager.isAvailable
|
||||
? Colors.green
|
||||
: Colors.white,
|
||||
color: state.isAvailable ? Colors.green : Colors.white,
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
"下载: ${FileSize.getSize(model.globalStat?.downloadSpeed ?? 0)}/s 上传:${FileSize.getSize(model.globalStat?.uploadSpeed ?? 0)}/s",
|
||||
"下载: ${FileSize.getSize(state.globalStat?.downloadSpeed ?? 0)}/s 上传:${FileSize.getSize(state.globalStat?.uploadSpeed ?? 0)}/s",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
)
|
||||
],
|
||||
@ -239,9 +240,7 @@ class DownloaderUI extends BaseUI<DownloaderUIModel> {
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
),
|
||||
useBodyContainer: true);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, DownloaderUIModel model) => "下载管理";
|
||||
}
|
@ -1,17 +1,43 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aria2/aria2.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/io/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/provider/aria2c.dart';
|
||||
|
||||
class DownloaderUIModel extends BaseUIModel {
|
||||
List<Aria2Task> tasks = [];
|
||||
List<Aria2Task> waitingTasks = [];
|
||||
List<Aria2Task> stoppedTasks = [];
|
||||
Aria2GlobalStat? globalStat;
|
||||
import '../../../widgets/widgets.dart';
|
||||
|
||||
part 'home_downloader_ui_model.g.dart';
|
||||
|
||||
part 'home_downloader_ui_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class HomeDownloaderUIState with _$HomeDownloaderUIState {
|
||||
const factory HomeDownloaderUIState({
|
||||
@Default([]) List<Aria2Task> tasks,
|
||||
@Default([]) List<Aria2Task> waitingTasks,
|
||||
@Default([]) List<Aria2Task> stoppedTasks,
|
||||
Aria2GlobalStat? globalStat,
|
||||
}) = _HomeDownloaderUIState;
|
||||
}
|
||||
|
||||
extension HomeDownloaderUIStateExtension on HomeDownloaderUIState {
|
||||
bool get isAvailable => globalStat != null;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class HomeDownloaderUIModel extends _$HomeDownloaderUIModel {
|
||||
final DateFormat formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
|
||||
|
||||
bool _disposed = false;
|
||||
|
||||
final statusMap = {
|
||||
"active": "下载中...",
|
||||
@ -29,82 +55,74 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
};
|
||||
|
||||
@override
|
||||
initModel() {
|
||||
super.initModel();
|
||||
HomeDownloaderUIState build() {
|
||||
state = const HomeDownloaderUIState();
|
||||
_listenDownloader();
|
||||
ref.onDispose(() {
|
||||
_disposed = true;
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
onTapButton(String key) async {
|
||||
onTapButton(BuildContext context, String key) async {
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
switch (key) {
|
||||
case "pause_all":
|
||||
if (!Aria2cManager.isAvailable) return;
|
||||
await Aria2cManager.getClient().pauseAll();
|
||||
await Aria2cManager.getClient().saveSession();
|
||||
if (!aria2cState.isRunning) return;
|
||||
await aria2cState.aria2c?.pauseAll();
|
||||
await aria2cState.aria2c?.saveSession();
|
||||
return;
|
||||
case "resume_all":
|
||||
if (!Aria2cManager.isAvailable) return;
|
||||
await Aria2cManager.getClient().unpauseAll();
|
||||
await Aria2cManager.getClient().saveSession();
|
||||
if (!aria2cState.isRunning) return;
|
||||
await aria2cState.aria2c?.unpauseAll();
|
||||
await aria2cState.aria2c?.saveSession();
|
||||
return;
|
||||
case "cancel_all":
|
||||
final userOK = await showConfirmDialogs(
|
||||
context!, "确认取消全部任务?", const Text("如果文件不再需要,你可能需要手动删除下载文件。"));
|
||||
context, "确认取消全部任务?", const Text("如果文件不再需要,你可能需要手动删除下载文件。"));
|
||||
if (userOK == true) {
|
||||
if (!Aria2cManager.isAvailable) return;
|
||||
if (!aria2cState.isRunning) return;
|
||||
try {
|
||||
for (var value in [...tasks, ...waitingTasks]) {
|
||||
await Aria2cManager.getClient().remove(value.gid!);
|
||||
for (var value in [...state.tasks, ...state.waitingTasks]) {
|
||||
await aria2cState.aria2c?.remove(value.gid!);
|
||||
}
|
||||
await Aria2cManager.getClient().saveSession();
|
||||
await aria2cState.aria2c?.saveSession();
|
||||
} catch (e) {
|
||||
dPrint("DownloadsUIModel cancel_all Error: $e");
|
||||
}
|
||||
}
|
||||
return;
|
||||
case "settings":
|
||||
_showDownloadSpeedSettings();
|
||||
_showDownloadSpeedSettings(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_listenDownloader() async {
|
||||
try {
|
||||
while (true) {
|
||||
if (!mounted) return;
|
||||
if (Aria2cManager.isAvailable) {
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
tasks.clear();
|
||||
tasks = await aria2c.tellActive();
|
||||
waitingTasks = await aria2c.tellWaiting(0, 1000000);
|
||||
stoppedTasks = await aria2c.tellStopped(0, 1000000);
|
||||
globalStat = await aria2c.getGlobalStat();
|
||||
notifyListeners();
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("[DownloadsUIModel]._listenDownloader Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
int getTasksLen() {
|
||||
return tasks.length + waitingTasks.length + stoppedTasks.length;
|
||||
return state.tasks.length +
|
||||
state.waitingTasks.length +
|
||||
state.stoppedTasks.length;
|
||||
}
|
||||
|
||||
(Aria2Task, String, bool) getTaskAndType(int index) {
|
||||
final tempList = <Aria2Task>[...tasks, ...waitingTasks, ...stoppedTasks];
|
||||
if (index >= 0 && index < tasks.length) {
|
||||
final tempList = <Aria2Task>[
|
||||
...state.tasks,
|
||||
...state.waitingTasks,
|
||||
...state.stoppedTasks
|
||||
];
|
||||
if (index >= 0 && index < state.tasks.length) {
|
||||
return (tempList[index], "active", index == 0);
|
||||
}
|
||||
if (index >= tasks.length && index < tasks.length + waitingTasks.length) {
|
||||
return (tempList[index], "waiting", index == tasks.length);
|
||||
if (index >= state.tasks.length &&
|
||||
index < state.tasks.length + state.waitingTasks.length) {
|
||||
return (tempList[index], "waiting", index == state.tasks.length);
|
||||
}
|
||||
if (index >= tasks.length + waitingTasks.length &&
|
||||
if (index >= state.tasks.length + state.waitingTasks.length &&
|
||||
index < tempList.length) {
|
||||
return (
|
||||
tempList[index],
|
||||
"stopped",
|
||||
index == tasks.length + waitingTasks.length
|
||||
index == state.tasks.length + state.waitingTasks.length
|
||||
);
|
||||
}
|
||||
throw Exception("Index out of range or element is null");
|
||||
@ -126,6 +144,41 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
|
||||
int getETA(Aria2Task task) {
|
||||
if (task.downloadSpeed == null || task.downloadSpeed == 0) return 0;
|
||||
final remainingBytes =
|
||||
(task.totalLength ?? 0) - (task.completedLength ?? 0);
|
||||
return remainingBytes ~/ (task.downloadSpeed!);
|
||||
}
|
||||
|
||||
Future<void> resumeTask(String? gid) async {
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c;
|
||||
if (gid != null) {
|
||||
await aria2c?.unpause(gid);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pauseTask(String? gid) async {
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c;
|
||||
if (gid != null) {
|
||||
await aria2c?.pause(gid);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> cancelTask(BuildContext context, String? gid) async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (gid != null) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context, "确认取消下载?", const Text("如果文件不再需要,你可能需要手动删除下载文件。"));
|
||||
if (ok == true) {
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c;
|
||||
await aria2c?.remove(gid);
|
||||
await aria2c?.saveSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Aria2File> getFilesFormTask(Aria2Task task) {
|
||||
List<Aria2File> l = [];
|
||||
if (task.files != null) {
|
||||
@ -137,41 +190,6 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
return l;
|
||||
}
|
||||
|
||||
int getETA(Aria2Task task) {
|
||||
if (task.downloadSpeed == null || task.downloadSpeed == 0) return 0;
|
||||
final remainingBytes =
|
||||
(task.totalLength ?? 0) - (task.completedLength ?? 0);
|
||||
return remainingBytes ~/ (task.downloadSpeed!);
|
||||
}
|
||||
|
||||
Future<void> resumeTask(String? gid) async {
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
if (gid != null) {
|
||||
await aria2c.unpause(gid);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pauseTask(String? gid) async {
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
|
||||
if (gid != null) {
|
||||
await aria2c.pause(gid);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> cancelTask(String? gid) async {
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (gid != null) {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!, "确认取消下载?", const Text("如果文件不再需要,你可能需要手动删除下载文件。"));
|
||||
if (ok == true) {
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
await aria2c.remove(gid);
|
||||
await Aria2cManager.getClient().saveSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openFolder(Aria2Task task) {
|
||||
final f = getFilesFormTask(task).firstOrNull;
|
||||
if (f != null) {
|
||||
@ -179,7 +197,39 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showDownloadSpeedSettings() async {
|
||||
_listenDownloader() async {
|
||||
try {
|
||||
while (true) {
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
if (_disposed) return;
|
||||
if (aria2cState.isRunning) {
|
||||
final aria2c = aria2cState.aria2c!;
|
||||
final tasks = await aria2c.tellActive();
|
||||
final waitingTasks = await aria2c.tellWaiting(0, 1000000);
|
||||
final stoppedTasks = await aria2c.tellStopped(0, 1000000);
|
||||
final globalStat = await aria2c.getGlobalStat();
|
||||
state = state.copyWith(
|
||||
tasks: tasks,
|
||||
waitingTasks: waitingTasks,
|
||||
stoppedTasks: stoppedTasks,
|
||||
globalStat: globalStat,
|
||||
);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
tasks: [],
|
||||
waitingTasks: [],
|
||||
stoppedTasks: [],
|
||||
globalStat: null,
|
||||
);
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("[DownloadsUIModel]._listenDownloader Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showDownloadSpeedSettings(BuildContext context) async {
|
||||
final box = await Hive.openBox("app_conf");
|
||||
|
||||
final upCtrl = TextEditingController(
|
||||
@ -189,8 +239,9 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
|
||||
final ifr = FilteringTextInputFormatter.allow(RegExp(r'^\d*[km]?$'));
|
||||
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"限速设置",
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -234,17 +285,21 @@ class DownloaderUIModel extends BaseUIModel {
|
||||
],
|
||||
));
|
||||
if (ok == true) {
|
||||
await handleError(() => Aria2cManager.launchDaemon());
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
final upByte = Aria2cManager.textToByte(upCtrl.text.trim());
|
||||
final downByte = Aria2cManager.textToByte(downCtrl.text.trim());
|
||||
final r = await handleError(() => aria2c.changeGlobalOption(Aria2Option()
|
||||
final aria2cState = ref.read(aria2cModelProvider);
|
||||
final aria2cModel = ref.read(aria2cModelProvider.notifier);
|
||||
await aria2cModel
|
||||
.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
final aria2c = aria2cState.aria2c!;
|
||||
final upByte = aria2cModel.textToByte(upCtrl.text.trim());
|
||||
final downByte = aria2cModel.textToByte(downCtrl.text.trim());
|
||||
final r = await aria2c
|
||||
.changeGlobalOption(Aria2Option()
|
||||
..maxOverallUploadLimit = upByte
|
||||
..maxOverallDownloadLimit = downByte));
|
||||
..maxOverallDownloadLimit = downByte)
|
||||
.unwrap();
|
||||
if (r != null) {
|
||||
await box.put('downloader_up_limit', upCtrl.text.trim());
|
||||
await box.put('downloader_down_limit', downCtrl.text.trim());
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
232
lib/ui/home/downloader/home_downloader_ui_model.freezed.dart
Normal file
232
lib/ui/home/downloader/home_downloader_ui_model.freezed.dart
Normal file
@ -0,0 +1,232 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'home_downloader_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HomeDownloaderUIState {
|
||||
List<Aria2Task> get tasks => throw _privateConstructorUsedError;
|
||||
List<Aria2Task> get waitingTasks => throw _privateConstructorUsedError;
|
||||
List<Aria2Task> get stoppedTasks => throw _privateConstructorUsedError;
|
||||
Aria2GlobalStat? get globalStat => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$HomeDownloaderUIStateCopyWith<HomeDownloaderUIState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HomeDownloaderUIStateCopyWith<$Res> {
|
||||
factory $HomeDownloaderUIStateCopyWith(HomeDownloaderUIState value,
|
||||
$Res Function(HomeDownloaderUIState) then) =
|
||||
_$HomeDownloaderUIStateCopyWithImpl<$Res, HomeDownloaderUIState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Aria2Task> tasks,
|
||||
List<Aria2Task> waitingTasks,
|
||||
List<Aria2Task> stoppedTasks,
|
||||
Aria2GlobalStat? globalStat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HomeDownloaderUIStateCopyWithImpl<$Res,
|
||||
$Val extends HomeDownloaderUIState>
|
||||
implements $HomeDownloaderUIStateCopyWith<$Res> {
|
||||
_$HomeDownloaderUIStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? tasks = null,
|
||||
Object? waitingTasks = null,
|
||||
Object? stoppedTasks = null,
|
||||
Object? globalStat = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
tasks: null == tasks
|
||||
? _value.tasks
|
||||
: tasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
waitingTasks: null == waitingTasks
|
||||
? _value.waitingTasks
|
||||
: waitingTasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
stoppedTasks: null == stoppedTasks
|
||||
? _value.stoppedTasks
|
||||
: stoppedTasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
globalStat: freezed == globalStat
|
||||
? _value.globalStat
|
||||
: globalStat // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2GlobalStat?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HomeDownloaderUIStateImplCopyWith<$Res>
|
||||
implements $HomeDownloaderUIStateCopyWith<$Res> {
|
||||
factory _$$HomeDownloaderUIStateImplCopyWith(
|
||||
_$HomeDownloaderUIStateImpl value,
|
||||
$Res Function(_$HomeDownloaderUIStateImpl) then) =
|
||||
__$$HomeDownloaderUIStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{List<Aria2Task> tasks,
|
||||
List<Aria2Task> waitingTasks,
|
||||
List<Aria2Task> stoppedTasks,
|
||||
Aria2GlobalStat? globalStat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HomeDownloaderUIStateImplCopyWithImpl<$Res>
|
||||
extends _$HomeDownloaderUIStateCopyWithImpl<$Res,
|
||||
_$HomeDownloaderUIStateImpl>
|
||||
implements _$$HomeDownloaderUIStateImplCopyWith<$Res> {
|
||||
__$$HomeDownloaderUIStateImplCopyWithImpl(_$HomeDownloaderUIStateImpl _value,
|
||||
$Res Function(_$HomeDownloaderUIStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? tasks = null,
|
||||
Object? waitingTasks = null,
|
||||
Object? stoppedTasks = null,
|
||||
Object? globalStat = freezed,
|
||||
}) {
|
||||
return _then(_$HomeDownloaderUIStateImpl(
|
||||
tasks: null == tasks
|
||||
? _value._tasks
|
||||
: tasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
waitingTasks: null == waitingTasks
|
||||
? _value._waitingTasks
|
||||
: waitingTasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
stoppedTasks: null == stoppedTasks
|
||||
? _value._stoppedTasks
|
||||
: stoppedTasks // ignore: cast_nullable_to_non_nullable
|
||||
as List<Aria2Task>,
|
||||
globalStat: freezed == globalStat
|
||||
? _value.globalStat
|
||||
: globalStat // ignore: cast_nullable_to_non_nullable
|
||||
as Aria2GlobalStat?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$HomeDownloaderUIStateImpl implements _HomeDownloaderUIState {
|
||||
const _$HomeDownloaderUIStateImpl(
|
||||
{final List<Aria2Task> tasks = const [],
|
||||
final List<Aria2Task> waitingTasks = const [],
|
||||
final List<Aria2Task> stoppedTasks = const [],
|
||||
this.globalStat})
|
||||
: _tasks = tasks,
|
||||
_waitingTasks = waitingTasks,
|
||||
_stoppedTasks = stoppedTasks;
|
||||
|
||||
final List<Aria2Task> _tasks;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Aria2Task> get tasks {
|
||||
if (_tasks is EqualUnmodifiableListView) return _tasks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tasks);
|
||||
}
|
||||
|
||||
final List<Aria2Task> _waitingTasks;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Aria2Task> get waitingTasks {
|
||||
if (_waitingTasks is EqualUnmodifiableListView) return _waitingTasks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_waitingTasks);
|
||||
}
|
||||
|
||||
final List<Aria2Task> _stoppedTasks;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<Aria2Task> get stoppedTasks {
|
||||
if (_stoppedTasks is EqualUnmodifiableListView) return _stoppedTasks;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_stoppedTasks);
|
||||
}
|
||||
|
||||
@override
|
||||
final Aria2GlobalStat? globalStat;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeDownloaderUIState(tasks: $tasks, waitingTasks: $waitingTasks, stoppedTasks: $stoppedTasks, globalStat: $globalStat)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HomeDownloaderUIStateImpl &&
|
||||
const DeepCollectionEquality().equals(other._tasks, _tasks) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._waitingTasks, _waitingTasks) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._stoppedTasks, _stoppedTasks) &&
|
||||
(identical(other.globalStat, globalStat) ||
|
||||
other.globalStat == globalStat));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(_tasks),
|
||||
const DeepCollectionEquality().hash(_waitingTasks),
|
||||
const DeepCollectionEquality().hash(_stoppedTasks),
|
||||
globalStat);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HomeDownloaderUIStateImplCopyWith<_$HomeDownloaderUIStateImpl>
|
||||
get copyWith => __$$HomeDownloaderUIStateImplCopyWithImpl<
|
||||
_$HomeDownloaderUIStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _HomeDownloaderUIState implements HomeDownloaderUIState {
|
||||
const factory _HomeDownloaderUIState(
|
||||
{final List<Aria2Task> tasks,
|
||||
final List<Aria2Task> waitingTasks,
|
||||
final List<Aria2Task> stoppedTasks,
|
||||
final Aria2GlobalStat? globalStat}) = _$HomeDownloaderUIStateImpl;
|
||||
|
||||
@override
|
||||
List<Aria2Task> get tasks;
|
||||
@override
|
||||
List<Aria2Task> get waitingTasks;
|
||||
@override
|
||||
List<Aria2Task> get stoppedTasks;
|
||||
@override
|
||||
Aria2GlobalStat? get globalStat;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HomeDownloaderUIStateImplCopyWith<_$HomeDownloaderUIStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/downloader/home_downloader_ui_model.g.dart
Normal file
27
lib/ui/home/downloader/home_downloader_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'home_downloader_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeDownloaderUIModelHash() =>
|
||||
r'947ebb9abb262aea6121c74481753da0eebb9a79';
|
||||
|
||||
/// See also [HomeDownloaderUIModel].
|
||||
@ProviderFor(HomeDownloaderUIModel)
|
||||
final homeDownloaderUIModelProvider = AutoDisposeNotifierProvider<
|
||||
HomeDownloaderUIModel, HomeDownloaderUIState>.internal(
|
||||
HomeDownloaderUIModel.new,
|
||||
name: r'homeDownloaderUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$homeDownloaderUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HomeDownloaderUIModel = AutoDisposeNotifier<HomeDownloaderUIState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,13 +1,37 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'game_doctor_ui_model.dart';
|
||||
|
||||
class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
class HomeGameDoctorUI extends HookConsumerWidget {
|
||||
const HomeGameDoctorUI({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, GameDoctorUIModel model) {
|
||||
return makeDefaultPage(context, model,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(homeGameDoctorUIModelProvider);
|
||||
final homeState = ref.watch(homeUIModelProvider);
|
||||
final model = ref.read(homeGameDoctorUIModelProvider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
dPrint("HomeGameDoctorUI useEffect doCheck timeStamp === $timeStamp");
|
||||
model.doCheck(context);
|
||||
});
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
return makeDefaultPage(context,
|
||||
title: "一键诊断 -> ${homeState.scInstalledPath}",
|
||||
useBodyContainer: true,
|
||||
content: Stack(
|
||||
children: [
|
||||
Column(
|
||||
@ -33,11 +57,12 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () => model.onTapButton(item.key)),
|
||||
onPressed: () =>
|
||||
_onTapButton(context, item.key, homeState)),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (model.isChecking)
|
||||
if (state.isChecking)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
@ -45,12 +70,12 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
children: [
|
||||
const ProgressRing(),
|
||||
const SizedBox(height: 12),
|
||||
Text(model.lastScreenInfo)
|
||||
Text(state.lastScreenInfo)
|
||||
],
|
||||
),
|
||||
))
|
||||
else if (model.checkResult == null ||
|
||||
model.checkResult!.isEmpty) ...[
|
||||
else if (state.checkResult == null ||
|
||||
state.checkResult!.isEmpty) ...[
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
@ -63,10 +88,10 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
),
|
||||
))
|
||||
] else
|
||||
...makeResult(context, model),
|
||||
...makeResult(context, state, model),
|
||||
],
|
||||
),
|
||||
if (model.isFixing)
|
||||
if (state.isFixing)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(150),
|
||||
@ -77,8 +102,8 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
children: [
|
||||
const ProgressRing(),
|
||||
const SizedBox(height: 12),
|
||||
Text(model.isFixingString.isNotEmpty
|
||||
? model.isFixingString
|
||||
Text(state.isFixingString.isNotEmpty
|
||||
? state.isFixingString
|
||||
: "正在处理..."),
|
||||
],
|
||||
),
|
||||
@ -93,29 +118,6 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> makeResult(BuildContext context, GameDoctorUIModel model) {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Text(model.lastScreenInfo, maxLines: 1),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"注意:本工具检测结果仅供参考,若您不理解以下操作,请提供截图给有经验的玩家!",
|
||||
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ListView.builder(
|
||||
itemCount: model.checkResult!.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.checkResult![index];
|
||||
return makeResultItem(item, model);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 64),
|
||||
];
|
||||
}
|
||||
|
||||
Widget makeRescueBanner(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
@ -146,8 +148,32 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeResultItem(
|
||||
MapEntry<String, String> item, GameDoctorUIModel model) {
|
||||
List<Widget> makeResult(BuildContext context, HomeGameDoctorState state,
|
||||
HomeGameDoctorUIModel model) {
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
Text(state.lastScreenInfo, maxLines: 1),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"注意:本工具检测结果仅供参考,若您不理解以下操作,请提供截图给有经验的玩家!",
|
||||
style: TextStyle(color: Colors.red, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ListView.builder(
|
||||
itemCount: state.checkResult!.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = state.checkResult![index];
|
||||
return makeResultItem(context, item, state, model);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 64),
|
||||
];
|
||||
}
|
||||
|
||||
Widget makeResultItem(BuildContext context, MapEntry<String, String> item,
|
||||
HomeGameDoctorState state, HomeGameDoctorUIModel model) {
|
||||
final errorNames = {
|
||||
"unSupport_system":
|
||||
MapEntry("不支持的操作系统,游戏可能无法运行", "请升级您的系统 (${item.value})"),
|
||||
@ -193,12 +219,10 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
),
|
||||
),
|
||||
trailing: Button(
|
||||
onPressed: (errorNames[item.key]?.value == null || model.isFixing)
|
||||
onPressed: (errorNames[item.key]?.value == null || state.isFixing)
|
||||
? null
|
||||
: () async {
|
||||
await model.doFix(item);
|
||||
model.isFixing = false;
|
||||
model.notifyListeners();
|
||||
await model.doFix(context, item);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 4),
|
||||
@ -249,7 +273,22 @@ class GameDoctorUI extends BaseUI<GameDoctorUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, GameDoctorUIModel model) =>
|
||||
"一键诊断 > ${model.scInstalledPath}";
|
||||
_onTapButton(
|
||||
BuildContext context, String key, HomeUIModelState homeState) async {
|
||||
switch (key) {
|
||||
case "rsi_log":
|
||||
final path = await SCLoggerHelper.getLogFilePath();
|
||||
if (path == null) return;
|
||||
SystemHelper.openDir(path);
|
||||
return;
|
||||
case "game_log":
|
||||
if (homeState.scInstalledPath == "not_install" ||
|
||||
homeState.scInstalledPath == null) {
|
||||
showToast(context, "请在首页选择游戏安装目录。");
|
||||
return;
|
||||
}
|
||||
SystemHelper.openDir("${homeState.scInstalledPath}\\Game.log");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +1,187 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class GameDoctorUIModel extends BaseUIModel {
|
||||
String scInstalledPath = "";
|
||||
part 'game_doctor_ui_model.g.dart';
|
||||
|
||||
GameDoctorUIModel(this.scInstalledPath);
|
||||
part 'game_doctor_ui_model.freezed.dart';
|
||||
|
||||
String _lastScreenInfo = "";
|
||||
|
||||
String get lastScreenInfo => _lastScreenInfo;
|
||||
|
||||
List<MapEntry<String, String>>? checkResult;
|
||||
|
||||
set lastScreenInfo(String info) {
|
||||
_lastScreenInfo = info;
|
||||
notifyListeners();
|
||||
@freezed
|
||||
class HomeGameDoctorState with _$HomeGameDoctorState {
|
||||
const factory HomeGameDoctorState({
|
||||
@Default(false) bool isChecking,
|
||||
@Default(false) bool isFixing,
|
||||
@Default("") String lastScreenInfo,
|
||||
@Default("") String isFixingString,
|
||||
List<MapEntry<String, String>>? checkResult,
|
||||
}) = _HomeGameDoctorState;
|
||||
}
|
||||
|
||||
bool isChecking = false;
|
||||
|
||||
bool isFixing = false;
|
||||
String isFixingString = "";
|
||||
|
||||
final cnExp = RegExp(r"[^\x00-\xff]");
|
||||
|
||||
@riverpod
|
||||
class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel {
|
||||
@override
|
||||
void initModel() {
|
||||
doCheck()?.call();
|
||||
super.initModel();
|
||||
HomeGameDoctorState build() {
|
||||
state = const HomeGameDoctorState();
|
||||
return state;
|
||||
}
|
||||
|
||||
VoidCallback? doCheck() {
|
||||
if (isChecking) return null;
|
||||
return () async {
|
||||
isChecking = true;
|
||||
lastScreenInfo = "正在分析...";
|
||||
await _statCheck();
|
||||
isChecking = false;
|
||||
notifyListeners();
|
||||
};
|
||||
Future<void> doFix(
|
||||
// ignore: avoid_build_context_in_providers
|
||||
BuildContext context,
|
||||
MapEntry<String, String> item) async {
|
||||
final checkResult =
|
||||
List<MapEntry<String, String>>.from(state.checkResult ?? []);
|
||||
state = state.copyWith(isFixing: true, isFixingString: "");
|
||||
switch (item.key) {
|
||||
case "unSupport_system":
|
||||
showToast(context, "若您的硬件达标,请尝试安装最新的 Windows 系统。");
|
||||
break;
|
||||
case "no_live_path":
|
||||
try {
|
||||
await Directory(item.value).create(recursive: true);
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "创建文件夹成功,请尝试继续下载游戏!");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} catch (e) {
|
||||
showToast(context, "创建文件夹失败,请尝试手动创建。\n目录:${item.value} \n错误:$e");
|
||||
}
|
||||
break;
|
||||
case "nvme_PhysicalBytes":
|
||||
final r = await SystemHelper.addNvmePatch();
|
||||
if (r == "") {
|
||||
if (!context.mounted) break;
|
||||
showToast(context,
|
||||
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} else {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,$r");
|
||||
}
|
||||
break;
|
||||
case "eac_file_miss":
|
||||
showToast(
|
||||
context, "未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件");
|
||||
break;
|
||||
case "eac_not_install":
|
||||
final eacJsonPath = "${item.value}\\Settings.json";
|
||||
final eacJsonData = await File(eacJsonPath).readAsBytes();
|
||||
final Map eacJson = json.decode(utf8.decode(eacJsonData));
|
||||
final eacID = eacJson["productid"];
|
||||
try {
|
||||
var result = await Process.run(
|
||||
"${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]);
|
||||
dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID");
|
||||
if (result.stderr == "") {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复成功,请尝试启动游戏。(若问题无法解决,请使用工具箱的 《重装 EAC》)");
|
||||
checkResult.remove(item);
|
||||
state = state.copyWith(checkResult: checkResult);
|
||||
} else {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,${result.stderr}");
|
||||
}
|
||||
} catch (e) {
|
||||
if (!context.mounted) break;
|
||||
showToast(context, "修复失败,$e");
|
||||
}
|
||||
break;
|
||||
case "cn_user_name":
|
||||
showToast(context, "即将跳转,教程来自互联网,请谨慎操作...");
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
launchUrlString(
|
||||
"https://btfy.eu.org/?q=5L+u5pS5d2luZG93c+eUqOaIt+WQjeS7juS4reaWh+WIsOiLseaWhw==");
|
||||
break;
|
||||
default:
|
||||
showToast(context, "该问题暂不支持自动处理,请提供截图寻求帮助");
|
||||
break;
|
||||
}
|
||||
state = state.copyWith(isFixing: false, isFixingString: "");
|
||||
}
|
||||
|
||||
Future _statCheck() async {
|
||||
checkResult = [];
|
||||
// ignore: avoid_build_context_in_providers
|
||||
doCheck(BuildContext context) async {
|
||||
if (state.isChecking) return;
|
||||
state = state.copyWith(isChecking: true, lastScreenInfo: "正在分析...");
|
||||
dPrint("-------- start docker check -----");
|
||||
if (!context.mounted) return;
|
||||
await _statCheck(context);
|
||||
state = state.copyWith(isChecking: false);
|
||||
}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
_statCheck(BuildContext context) async {
|
||||
final homeState = ref.read(homeUIModelProvider);
|
||||
final scInstalledPath = homeState.scInstalledPath!;
|
||||
|
||||
final checkResult = <MapEntry<String, String>>[];
|
||||
// TODO for debug
|
||||
// checkResult?.add(const MapEntry("unSupport_system", "android"));
|
||||
// checkResult?.add(const MapEntry("nvme_PhysicalBytes", "C"));
|
||||
// checkResult?.add(const MapEntry("no_live_path", ""));
|
||||
|
||||
await _checkPreInstall();
|
||||
await _checkEAC();
|
||||
await _checkGameRunningLog();
|
||||
await _checkPreInstall(context, scInstalledPath, checkResult);
|
||||
if (!context.mounted) return;
|
||||
await _checkEAC(context, scInstalledPath, checkResult);
|
||||
if (!context.mounted) return;
|
||||
await _checkGameRunningLog(context, scInstalledPath, checkResult);
|
||||
|
||||
if (checkResult!.isEmpty) {
|
||||
checkResult = null;
|
||||
lastScreenInfo = "分析完毕,没有发现问题";
|
||||
if (checkResult.isEmpty) {
|
||||
const lastScreenInfo = "分析完毕,没有发现问题";
|
||||
state = state.copyWith(checkResult: null, lastScreenInfo: lastScreenInfo);
|
||||
} else {
|
||||
lastScreenInfo = "分析完毕,发现 ${checkResult!.length} 个问题";
|
||||
final lastScreenInfo = "分析完毕,发现 ${checkResult.length} 个问题";
|
||||
state = state.copyWith(
|
||||
checkResult: checkResult, lastScreenInfo: lastScreenInfo);
|
||||
}
|
||||
|
||||
if (scInstalledPath == "not_install" && (checkResult?.isEmpty ?? true)) {
|
||||
showToast(context!, "扫描完毕,没有发现问题,若仍然安装失败,请尝试使用工具箱中的 RSI启动器管理员模式。");
|
||||
if (scInstalledPath == "not_install" && (checkResult.isEmpty)) {
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "扫描完毕,没有发现问题,若仍然安装失败,请尝试使用工具箱中的 RSI启动器管理员模式。");
|
||||
}
|
||||
}
|
||||
|
||||
Future _checkGameRunningLog() async {
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future _checkGameRunningLog(BuildContext context, String scInstalledPath,
|
||||
List<MapEntry<String, String>> checkResult) async {
|
||||
if (scInstalledPath == "not_install") return;
|
||||
lastScreenInfo = "正在检查:Game.log";
|
||||
const lastScreenInfo = "正在检查:Game.log";
|
||||
state = state.copyWith(lastScreenInfo: lastScreenInfo);
|
||||
final logs = await SCLoggerHelper.getGameRunningLogs(scInstalledPath);
|
||||
if (logs == null) return;
|
||||
final info = SCLoggerHelper.getGameRunningLogInfo(logs);
|
||||
if (info != null) {
|
||||
if (info.key != "_") {
|
||||
checkResult?.add(MapEntry("游戏异常退出:${info.key}", info.value));
|
||||
checkResult.add(MapEntry("游戏异常退出:${info.key}", info.value));
|
||||
} else {
|
||||
checkResult
|
||||
?.add(MapEntry("游戏异常退出:未知异常", "info:${info.value},请点击右下角加群反馈。"));
|
||||
.add(MapEntry("游戏异常退出:未知异常", "info:${info.value},请点击右下角加群反馈。"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future _checkEAC() async {
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future _checkEAC(BuildContext context, String scInstalledPath,
|
||||
List<MapEntry<String, String>> checkResult) async {
|
||||
if (scInstalledPath == "not_install") return;
|
||||
lastScreenInfo = "正在检查:EAC";
|
||||
const lastScreenInfo = "正在检查:EAC";
|
||||
state = state.copyWith(lastScreenInfo: lastScreenInfo);
|
||||
|
||||
final eacPath = "$scInstalledPath\\EasyAntiCheat";
|
||||
final eacJsonPath = "$eacPath\\Settings.json";
|
||||
if (!await Directory(eacPath).exists() ||
|
||||
!await File(eacJsonPath).exists()) {
|
||||
checkResult?.add(const MapEntry("eac_file_miss", ""));
|
||||
checkResult.add(const MapEntry("eac_file_miss", ""));
|
||||
return;
|
||||
}
|
||||
final eacJsonData = await File(eacJsonPath).readAsBytes();
|
||||
@ -101,38 +189,44 @@ class GameDoctorUIModel extends BaseUIModel {
|
||||
final eacID = eacJson["productid"];
|
||||
final eacDeploymentId = eacJson["deploymentid"];
|
||||
if (eacID == null || eacDeploymentId == null) {
|
||||
checkResult?.add(const MapEntry("eac_file_miss", ""));
|
||||
checkResult.add(const MapEntry("eac_file_miss", ""));
|
||||
return;
|
||||
}
|
||||
final eacFilePath =
|
||||
"${Platform.environment["appdata"]}\\EasyAntiCheat\\$eacID\\$eacDeploymentId\\anticheatlauncher.log";
|
||||
if (!await File(eacFilePath).exists()) {
|
||||
checkResult?.add(MapEntry("eac_not_install", eacPath));
|
||||
checkResult.add(MapEntry("eac_not_install", eacPath));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Future _checkPreInstall() async {
|
||||
lastScreenInfo = "正在检查:运行环境";
|
||||
final _cnExp = RegExp(r"[^\x00-\xff]");
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future _checkPreInstall(BuildContext context, String scInstalledPath,
|
||||
List<MapEntry<String, String>> checkResult) async {
|
||||
const lastScreenInfo = "正在检查:运行环境";
|
||||
state = state.copyWith(lastScreenInfo: lastScreenInfo);
|
||||
|
||||
if (!(Platform.operatingSystemVersion.contains("Windows 10") ||
|
||||
Platform.operatingSystemVersion.contains("Windows 11"))) {
|
||||
checkResult
|
||||
?.add(MapEntry("unSupport_system", Platform.operatingSystemVersion));
|
||||
lastScreenInfo = "不支持的操作系统:${Platform.operatingSystemVersion}";
|
||||
await showToast(context!, lastScreenInfo);
|
||||
.add(MapEntry("unSupport_system", Platform.operatingSystemVersion));
|
||||
final lastScreenInfo = "不支持的操作系统:${Platform.operatingSystemVersion}";
|
||||
state = state.copyWith(lastScreenInfo: lastScreenInfo);
|
||||
await showToast(context, lastScreenInfo);
|
||||
}
|
||||
|
||||
if (cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) {
|
||||
checkResult?.add(const MapEntry("cn_user_name", ""));
|
||||
if (_cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) {
|
||||
checkResult.add(const MapEntry("cn_user_name", ""));
|
||||
}
|
||||
|
||||
// 检查 RAM
|
||||
final ramSize = await SystemHelper.getSystemMemorySizeGB();
|
||||
if (ramSize < 16) {
|
||||
checkResult?.add(MapEntry("low_ram", "$ramSize"));
|
||||
checkResult.add(MapEntry("low_ram", "$ramSize"));
|
||||
}
|
||||
|
||||
lastScreenInfo = "正在检查:安装信息";
|
||||
state = state.copyWith(lastScreenInfo: "正在检查:安装信息");
|
||||
// 检查安装分区
|
||||
try {
|
||||
final listData = await SCLoggerHelper.getGameInstallPath(
|
||||
@ -141,13 +235,13 @@ class GameDoctorUIModel extends BaseUIModel {
|
||||
final checkedPath = [];
|
||||
for (var installPath in listData) {
|
||||
if (!checkedPath.contains(installPath)) {
|
||||
if (cnExp.hasMatch(installPath)) {
|
||||
checkResult?.add(MapEntry("cn_install_path", installPath));
|
||||
if (_cnExp.hasMatch(installPath)) {
|
||||
checkResult.add(MapEntry("cn_install_path", installPath));
|
||||
}
|
||||
if (scInstalledPath == "not_install") {
|
||||
checkedPath.add(installPath);
|
||||
if (!await Directory(installPath).exists()) {
|
||||
checkResult?.add(MapEntry("no_live_path", installPath));
|
||||
checkResult.add(MapEntry("no_live_path", installPath));
|
||||
}
|
||||
}
|
||||
final tp = installPath.split(":")[0];
|
||||
@ -168,7 +262,7 @@ class GameDoctorUIModel extends BaseUIModel {
|
||||
final rs = result.stdout.toString().trim();
|
||||
final physicalBytesPerSectorForPerformance = (int.tryParse(rs) ?? 0);
|
||||
if (physicalBytesPerSectorForPerformance > 4096) {
|
||||
checkResult?.add(MapEntry("nvme_PhysicalBytes", element));
|
||||
checkResult.add(MapEntry("nvme_PhysicalBytes", element));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,85 +270,4 @@ class GameDoctorUIModel extends BaseUIModel {
|
||||
dPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> doFix(MapEntry<String, String> item) async {
|
||||
isFixing = true;
|
||||
notifyListeners();
|
||||
switch (item.key) {
|
||||
case "unSupport_system":
|
||||
showToast(context!, "若您的硬件达标,请尝试安装最新的 Windows 系统。");
|
||||
return;
|
||||
case "no_live_path":
|
||||
try {
|
||||
await Directory(item.value).create(recursive: true);
|
||||
showToast(context!, "创建文件夹成功,请尝试继续下载游戏!");
|
||||
checkResult?.remove(item);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
showToast(context!, "创建文件夹失败,请尝试手动创建。\n目录:${item.value} \n错误:$e");
|
||||
}
|
||||
return;
|
||||
case "nvme_PhysicalBytes":
|
||||
final r = await SystemHelper.addNvmePatch();
|
||||
if (r == "") {
|
||||
showToast(context!,
|
||||
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
|
||||
checkResult?.remove(item);
|
||||
notifyListeners();
|
||||
} else {
|
||||
showToast(context!, "修复失败,$r");
|
||||
}
|
||||
return;
|
||||
case "eac_file_miss":
|
||||
showToast(
|
||||
context!, "未在 LIVE 文件夹找到 EasyAntiCheat 文件 或 文件不完整,请使用 RSI 启动器校验文件");
|
||||
return;
|
||||
case "eac_not_install":
|
||||
final eacJsonPath = "${item.value}\\Settings.json";
|
||||
final eacJsonData = await File(eacJsonPath).readAsBytes();
|
||||
final Map eacJson = json.decode(utf8.decode(eacJsonData));
|
||||
final eacID = eacJson["productid"];
|
||||
try {
|
||||
var result = await Process.run(
|
||||
"${item.value}\\EasyAntiCheat_EOS_Setup.exe", ["install", eacID]);
|
||||
dPrint("${item.value}\\EasyAntiCheat_EOS_Setup.exe install $eacID");
|
||||
if (result.stderr == "") {
|
||||
showToast(context!, "修复成功,请尝试启动游戏。(若问题无法解决,请使用工具箱的 《重装 EAC》)");
|
||||
checkResult?.remove(item);
|
||||
notifyListeners();
|
||||
} else {
|
||||
showToast(context!, "修复失败,${result.stderr}");
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(context!, "修复失败,$e");
|
||||
}
|
||||
return;
|
||||
case "cn_user_name":
|
||||
showToast(context!, "即将跳转,教程来自互联网,请谨慎操作...");
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
launchUrlString(
|
||||
"https://btfy.eu.org/?q=5L+u5pS5d2luZG93c+eUqOaIt+WQjeS7juS4reaWh+WIsOiLseaWhw==");
|
||||
return;
|
||||
default:
|
||||
showToast(context!, "该问题暂不支持自动处理,请提供截图寻求帮助");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onTapButton(String key) async {
|
||||
switch (key) {
|
||||
case "rsi_log":
|
||||
final path = await SCLoggerHelper.getLogFilePath();
|
||||
if (path == null) return;
|
||||
SystemHelper.openDir(path);
|
||||
return;
|
||||
case "game_log":
|
||||
if (scInstalledPath == "not_install") {
|
||||
showToast(context!, "请在首页选择游戏安装目录。");
|
||||
return;
|
||||
}
|
||||
SystemHelper.openDir("$scInstalledPath\\Game.log");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
242
lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart
Normal file
242
lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart
Normal file
@ -0,0 +1,242 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'game_doctor_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HomeGameDoctorState {
|
||||
bool get isChecking => throw _privateConstructorUsedError;
|
||||
bool get isFixing => throw _privateConstructorUsedError;
|
||||
String get lastScreenInfo => throw _privateConstructorUsedError;
|
||||
String get isFixingString => throw _privateConstructorUsedError;
|
||||
List<MapEntry<String, String>>? get checkResult =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$HomeGameDoctorStateCopyWith<HomeGameDoctorState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HomeGameDoctorStateCopyWith<$Res> {
|
||||
factory $HomeGameDoctorStateCopyWith(
|
||||
HomeGameDoctorState value, $Res Function(HomeGameDoctorState) then) =
|
||||
_$HomeGameDoctorStateCopyWithImpl<$Res, HomeGameDoctorState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool isChecking,
|
||||
bool isFixing,
|
||||
String lastScreenInfo,
|
||||
String isFixingString,
|
||||
List<MapEntry<String, String>>? checkResult});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HomeGameDoctorStateCopyWithImpl<$Res, $Val extends HomeGameDoctorState>
|
||||
implements $HomeGameDoctorStateCopyWith<$Res> {
|
||||
_$HomeGameDoctorStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isChecking = null,
|
||||
Object? isFixing = null,
|
||||
Object? lastScreenInfo = null,
|
||||
Object? isFixingString = null,
|
||||
Object? checkResult = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isChecking: null == isChecking
|
||||
? _value.isChecking
|
||||
: isChecking // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFixing: null == isFixing
|
||||
? _value.isFixing
|
||||
: isFixing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
lastScreenInfo: null == lastScreenInfo
|
||||
? _value.lastScreenInfo
|
||||
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isFixingString: null == isFixingString
|
||||
? _value.isFixingString
|
||||
: isFixingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
checkResult: freezed == checkResult
|
||||
? _value.checkResult
|
||||
: checkResult // ignore: cast_nullable_to_non_nullable
|
||||
as List<MapEntry<String, String>>?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HomeGameDoctorStateImplCopyWith<$Res>
|
||||
implements $HomeGameDoctorStateCopyWith<$Res> {
|
||||
factory _$$HomeGameDoctorStateImplCopyWith(_$HomeGameDoctorStateImpl value,
|
||||
$Res Function(_$HomeGameDoctorStateImpl) then) =
|
||||
__$$HomeGameDoctorStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool isChecking,
|
||||
bool isFixing,
|
||||
String lastScreenInfo,
|
||||
String isFixingString,
|
||||
List<MapEntry<String, String>>? checkResult});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HomeGameDoctorStateImplCopyWithImpl<$Res>
|
||||
extends _$HomeGameDoctorStateCopyWithImpl<$Res, _$HomeGameDoctorStateImpl>
|
||||
implements _$$HomeGameDoctorStateImplCopyWith<$Res> {
|
||||
__$$HomeGameDoctorStateImplCopyWithImpl(_$HomeGameDoctorStateImpl _value,
|
||||
$Res Function(_$HomeGameDoctorStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isChecking = null,
|
||||
Object? isFixing = null,
|
||||
Object? lastScreenInfo = null,
|
||||
Object? isFixingString = null,
|
||||
Object? checkResult = freezed,
|
||||
}) {
|
||||
return _then(_$HomeGameDoctorStateImpl(
|
||||
isChecking: null == isChecking
|
||||
? _value.isChecking
|
||||
: isChecking // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFixing: null == isFixing
|
||||
? _value.isFixing
|
||||
: isFixing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
lastScreenInfo: null == lastScreenInfo
|
||||
? _value.lastScreenInfo
|
||||
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isFixingString: null == isFixingString
|
||||
? _value.isFixingString
|
||||
: isFixingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
checkResult: freezed == checkResult
|
||||
? _value._checkResult
|
||||
: checkResult // ignore: cast_nullable_to_non_nullable
|
||||
as List<MapEntry<String, String>>?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$HomeGameDoctorStateImpl implements _HomeGameDoctorState {
|
||||
const _$HomeGameDoctorStateImpl(
|
||||
{this.isChecking = false,
|
||||
this.isFixing = false,
|
||||
this.lastScreenInfo = "",
|
||||
this.isFixingString = "",
|
||||
final List<MapEntry<String, String>>? checkResult})
|
||||
: _checkResult = checkResult;
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isChecking;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFixing;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String lastScreenInfo;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String isFixingString;
|
||||
final List<MapEntry<String, String>>? _checkResult;
|
||||
@override
|
||||
List<MapEntry<String, String>>? get checkResult {
|
||||
final value = _checkResult;
|
||||
if (value == null) return null;
|
||||
if (_checkResult is EqualUnmodifiableListView) return _checkResult;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeGameDoctorState(isChecking: $isChecking, isFixing: $isFixing, lastScreenInfo: $lastScreenInfo, isFixingString: $isFixingString, checkResult: $checkResult)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HomeGameDoctorStateImpl &&
|
||||
(identical(other.isChecking, isChecking) ||
|
||||
other.isChecking == isChecking) &&
|
||||
(identical(other.isFixing, isFixing) ||
|
||||
other.isFixing == isFixing) &&
|
||||
(identical(other.lastScreenInfo, lastScreenInfo) ||
|
||||
other.lastScreenInfo == lastScreenInfo) &&
|
||||
(identical(other.isFixingString, isFixingString) ||
|
||||
other.isFixingString == isFixingString) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._checkResult, _checkResult));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
isChecking,
|
||||
isFixing,
|
||||
lastScreenInfo,
|
||||
isFixingString,
|
||||
const DeepCollectionEquality().hash(_checkResult));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
|
||||
__$$HomeGameDoctorStateImplCopyWithImpl<_$HomeGameDoctorStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _HomeGameDoctorState implements HomeGameDoctorState {
|
||||
const factory _HomeGameDoctorState(
|
||||
{final bool isChecking,
|
||||
final bool isFixing,
|
||||
final String lastScreenInfo,
|
||||
final String isFixingString,
|
||||
final List<MapEntry<String, String>>? checkResult}) =
|
||||
_$HomeGameDoctorStateImpl;
|
||||
|
||||
@override
|
||||
bool get isChecking;
|
||||
@override
|
||||
bool get isFixing;
|
||||
@override
|
||||
String get lastScreenInfo;
|
||||
@override
|
||||
String get isFixingString;
|
||||
@override
|
||||
List<MapEntry<String, String>>? get checkResult;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/game_doctor/game_doctor_ui_model.g.dart
Normal file
27
lib/ui/home/game_doctor/game_doctor_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'game_doctor_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeGameDoctorUIModelHash() =>
|
||||
r'1e32d75095de065cf2cdedf444f74ffc753ce66f';
|
||||
|
||||
/// See also [HomeGameDoctorUIModel].
|
||||
@ProviderFor(HomeGameDoctorUIModel)
|
||||
final homeGameDoctorUIModelProvider = AutoDisposeNotifierProvider<
|
||||
HomeGameDoctorUIModel, HomeGameDoctorState>.internal(
|
||||
HomeGameDoctorUIModel.new,
|
||||
name: r'homeGameDoctorUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$homeGameDoctorUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HomeGameDoctorUIModel = AutoDisposeNotifier<HomeGameDoctorState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,19 +1,28 @@
|
||||
import 'package:card_swiper/card_swiper.dart';
|
||||
import 'package:fluent_ui/fluent_ui.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:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/widgets/cache_image.dart';
|
||||
import 'package:starcitizen_doctor/widgets/countdown_time_text.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'dialogs/home_countdown_dialog_ui.dart';
|
||||
import 'dialogs/home_md_content_dialog_ui.dart';
|
||||
import 'home_ui_model.dart';
|
||||
import 'localization/localization_dialog_ui.dart';
|
||||
|
||||
class HomeUI extends HookConsumerWidget {
|
||||
const HomeUI({super.key});
|
||||
|
||||
class HomeUI extends BaseUI<HomeUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, HomeUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final homeState = ref.watch(homeUIModelProvider);
|
||||
final model = ref.watch(homeUIModelProvider.notifier);
|
||||
return Stack(
|
||||
children: [
|
||||
Center(
|
||||
@ -22,29 +31,29 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (model.appPlacardData != null) ...[
|
||||
if (homeState.appPlacardData != null) ...[
|
||||
InfoBar(
|
||||
title: Text("${model.appPlacardData?.title}"),
|
||||
content: Text("${model.appPlacardData?.content}"),
|
||||
title: Text("${homeState.appPlacardData?.title}"),
|
||||
content: Text("${homeState.appPlacardData?.content}"),
|
||||
severity: InfoBarSeverity.info,
|
||||
action: model.appPlacardData?.link == null
|
||||
action: homeState.appPlacardData?.link == null
|
||||
? null
|
||||
: Button(
|
||||
child: const Text('查看详情'),
|
||||
onPressed: () => model.showPlacard(),
|
||||
onPressed: () => _showPlacard(context, homeState),
|
||||
),
|
||||
onClose: model.appPlacardData?.alwaysShow == true
|
||||
onClose: homeState.appPlacardData?.alwaysShow == true
|
||||
? null
|
||||
: () => model.closePlacard(),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
...makeIndex(context, model)
|
||||
...makeIndex(context, model, homeState)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (model.isFixing)
|
||||
if (homeState.isFixing)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(150),
|
||||
@ -55,8 +64,8 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
children: [
|
||||
const ProgressRing(),
|
||||
const SizedBox(height: 12),
|
||||
Text(model.isFixingString.isNotEmpty
|
||||
? model.isFixingString
|
||||
Text(homeState.isFixingString.isNotEmpty
|
||||
? homeState.isFixingString
|
||||
: "正在处理..."),
|
||||
],
|
||||
),
|
||||
@ -66,7 +75,8 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> makeIndex(BuildContext context, HomeUIModel model) {
|
||||
List<Widget> makeIndex(
|
||||
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
|
||||
const double width = 280;
|
||||
return [
|
||||
Stack(
|
||||
@ -86,7 +96,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
height: 260,
|
||||
),
|
||||
),
|
||||
makeGameStatusCard(context, model, 340)
|
||||
makeGameStatusCard(context, model, 340, homeState)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -95,12 +105,12 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 24,
|
||||
child: makeLeftColumn(context, model, width),
|
||||
child: makeLeftColumn(context, model, width, homeState),
|
||||
),
|
||||
Positioned(
|
||||
right: 24,
|
||||
top: 0,
|
||||
child: makeNewsCard(context, model),
|
||||
child: makeNewsCard(context, model, homeState),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -114,52 +124,45 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: ComboBox<String>(
|
||||
value: model.scInstalledPath,
|
||||
value: homeState.scInstalledPath,
|
||||
items: [
|
||||
const ComboBoxItem(
|
||||
value: "not_install",
|
||||
child: Text("未安装 或 安装失败"),
|
||||
),
|
||||
for (final path in model.scInstallPaths)
|
||||
for (final path in homeState.scInstallPaths)
|
||||
ComboBoxItem(
|
||||
value: path,
|
||||
child: Text(path),
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.scInstalledPath = v!;
|
||||
model.notifyListeners();
|
||||
},
|
||||
onChanged: model.onChangeInstallPath,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: model.isRsiLauncherStarting
|
||||
? const ProgressRing()
|
||||
: Button(
|
||||
onPressed: model.appWebLocalizationVersionsData == null
|
||||
Button(
|
||||
onPressed: homeState.webLocalizationVersionsData == null
|
||||
? null
|
||||
: () => model.launchRSI(),
|
||||
: () => model.launchRSI(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Icon(
|
||||
model.isCurGameRunning
|
||||
homeState.isCurGameRunning
|
||||
? FluentIcons.stop_solid
|
||||
: FluentIcons.play,
|
||||
color: model.isCurGameRunning
|
||||
color: homeState.isCurGameRunning
|
||||
? Colors.red.withOpacity(.8)
|
||||
: null,
|
||||
),
|
||||
)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.folder_open),
|
||||
),
|
||||
onPressed: () => model.openDir(model.scInstalledPath)),
|
||||
onPressed: () => SystemHelper.openDir(homeState.scInstalledPath),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: model.reScanPath,
|
||||
@ -172,12 +175,13 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(model.lastScreenInfo, maxLines: 1),
|
||||
makeIndexActionLists(context, model),
|
||||
Text(homeState.lastScreenInfo, maxLines: 1),
|
||||
makeIndexActionLists(context, model, homeState),
|
||||
];
|
||||
}
|
||||
|
||||
Widget makeLeftColumn(BuildContext context, HomeUIModel model, double width) {
|
||||
Widget makeLeftColumn(BuildContext context, HomeUIModel model, double width,
|
||||
HomeUIModelState homeState) {
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
@ -192,7 +196,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
makeWebViewButton(model,
|
||||
makeWebViewButton(context, model,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/rsi.svg",
|
||||
colorFilter: makeSvgColor(Colors.white),
|
||||
@ -206,7 +210,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
width: width,
|
||||
touchKey: "webLocalization_rsi"),
|
||||
const SizedBox(height: 12),
|
||||
makeWebViewButton(model,
|
||||
makeWebViewButton(context, model,
|
||||
icon: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
@ -224,7 +228,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
width: width,
|
||||
touchKey: "webLocalization_uex"),
|
||||
const SizedBox(height: 12),
|
||||
makeWebViewButton(model,
|
||||
makeWebViewButton(context, model,
|
||||
icon: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
@ -288,10 +292,10 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
makeActivityBanner(context, model, width),
|
||||
makeActivityBanner(context, model, width, homeState),
|
||||
],
|
||||
),
|
||||
if (model.appWebLocalizationVersionsData == null)
|
||||
if (homeState.webLocalizationVersionsData == null)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@ -305,7 +309,8 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeNewsCard(BuildContext context, HomeUIModel model) {
|
||||
Widget makeNewsCard(
|
||||
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
|
||||
return ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
child: Container(
|
||||
@ -326,16 +331,16 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
child: model.rssVideoItems == null
|
||||
child: homeState.rssVideoItems == null
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(.1)),
|
||||
child: makeLoading(context),
|
||||
)
|
||||
: Swiper(
|
||||
itemCount: model.rssVideoItems?.length ?? 0,
|
||||
itemCount: homeState.rssVideoItems?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final item = model.rssVideoItems![index];
|
||||
final item = homeState.rssVideoItems![index];
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (item.link != null) {
|
||||
@ -352,14 +357,14 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 1),
|
||||
if (model.rssTextItems == null)
|
||||
if (homeState.rssTextItems == null)
|
||||
makeLoading(context)
|
||||
else
|
||||
ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.rssTextItems![index];
|
||||
final item = homeState.rssTextItems![index];
|
||||
return Tilt(
|
||||
shadowConfig: const ShadowConfig(maxIntensity: .3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
@ -378,7 +383,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"${model.handleTitle(item.title)}",
|
||||
model.handleTitle(item.title),
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -396,7 +401,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
));
|
||||
},
|
||||
itemCount: model.rssTextItems?.length,
|
||||
itemCount: homeState.rssTextItems?.length,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
@ -421,9 +426,10 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
return const FaIcon(FontAwesomeIcons.rss, size: 14);
|
||||
}
|
||||
|
||||
Widget makeIndexActionLists(BuildContext context, HomeUIModel model) {
|
||||
Widget makeIndexActionLists(
|
||||
BuildContext context, HomeUIModel model, HomeUIModelState homeState) {
|
||||
final items = [
|
||||
_HomeItemData("auto_check", "一键诊断", "一键诊断星际公民常见问题",
|
||||
_HomeItemData("game_doctor", "一键诊断", "一键诊断星际公民常见问题",
|
||||
FluentIcons.auto_deploy_settings),
|
||||
_HomeItemData(
|
||||
"localization", "汉化管理", "快捷安装汉化资源", FluentIcons.locale_language),
|
||||
@ -441,7 +447,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
itemBuilder: (context, index) {
|
||||
final item = items.elementAt(index);
|
||||
return HoverButton(
|
||||
onPressed: () => model.onMenuTap(item.key),
|
||||
onPressed: () => _onMenuTap(context, item.key, homeState),
|
||||
builder: (BuildContext context, Set<ButtonStates> states) {
|
||||
return Container(
|
||||
width: 300,
|
||||
@ -483,15 +489,15 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
],
|
||||
)),
|
||||
if (item.key == "localization" &&
|
||||
model.localizationUpdateInfo != null)
|
||||
homeState.localizationUpdateInfo != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 3, bottom: 3, left: 8, right: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
child:
|
||||
Text(model.localizationUpdateInfo?.key ?? " "),
|
||||
child: Text(
|
||||
homeState.localizationUpdateInfo?.key ?? " "),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(
|
||||
@ -508,10 +514,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, HomeUIModel model) => "HOME";
|
||||
|
||||
Widget makeWebViewButton(HomeUIModel model,
|
||||
Widget makeWebViewButton(BuildContext context, HomeUIModel model,
|
||||
{required Widget icon,
|
||||
required String name,
|
||||
required String webTitle,
|
||||
@ -528,7 +531,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
if (touchKey != null) {
|
||||
AnalyticsApi.touch(touchKey);
|
||||
}
|
||||
model.goWebView(webTitle, webURL, useLocalization: true);
|
||||
model.goWebView(context, webTitle, webURL, useLocalization: true);
|
||||
},
|
||||
child: Container(
|
||||
width: width,
|
||||
@ -579,15 +582,22 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGameStatusCard(
|
||||
BuildContext context, HomeUIModel model, double width) {
|
||||
Widget makeGameStatusCard(BuildContext context, HomeUIModel model,
|
||||
double width, HomeUIModelState homeState) {
|
||||
const statusCnName = {
|
||||
"Platform": "平台",
|
||||
"Persistent Universe": "持续宇宙",
|
||||
"Electronic Access": "电子访问",
|
||||
"Arena Commander": "竞技场指挥官"
|
||||
};
|
||||
|
||||
return Tilt(
|
||||
shadowConfig: const ShadowConfig(maxIntensity: .2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
model.goWebView(
|
||||
"RSI 服务器状态", "https://status.robertsspaceindustries.com/",
|
||||
model.goWebView(context, "RSI 服务器状态",
|
||||
"https://status.robertsspaceindustries.com/",
|
||||
useLocalization: true);
|
||||
},
|
||||
child: Container(
|
||||
@ -598,14 +608,14 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(children: [
|
||||
if (model.scServerStatus == null)
|
||||
if (homeState.scServerStatus == null)
|
||||
makeLoading(context, width: 20)
|
||||
else
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text("状态:"),
|
||||
for (final item in model.scServerStatus ?? [])
|
||||
for (final item in homeState.scServerStatus ?? [])
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
@ -622,7 +632,7 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
"${model.statusCnName[item["name"]] ?? item["name"]}",
|
||||
"${statusCnName[item["name"]] ?? item["name"]}",
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
],
|
||||
@ -641,20 +651,20 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeActivityBanner(
|
||||
BuildContext context, HomeUIModel model, double width) {
|
||||
Widget makeActivityBanner(BuildContext context, HomeUIModel model,
|
||||
double width, HomeUIModelState homeState) {
|
||||
return Tilt(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
shadowConfig: const ShadowConfig(disable: true),
|
||||
child: GestureDetector(
|
||||
onTap: () => model.onTapFestival(),
|
||||
onTap: () => _onTapFestival(context),
|
||||
child: Container(
|
||||
width: width + 24,
|
||||
decoration: BoxDecoration(color: FluentTheme.of(context).cardColor),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 8),
|
||||
child: (model.countdownFestivalListData == null)
|
||||
child: (homeState.countdownFestivalListData == null)
|
||||
? SizedBox(
|
||||
width: width,
|
||||
height: 62,
|
||||
@ -666,11 +676,12 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
width: width,
|
||||
height: 62,
|
||||
child: Swiper(
|
||||
itemCount: model.countdownFestivalListData!.length,
|
||||
itemCount: homeState.countdownFestivalListData!.length,
|
||||
autoplay: true,
|
||||
autoplayDelay: 5000,
|
||||
itemBuilder: (context, index) {
|
||||
final item = model.countdownFestivalListData![index];
|
||||
final item =
|
||||
homeState.countdownFestivalListData![index];
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
@ -714,6 +725,49 @@ class HomeUI extends BaseUI<HomeUIModel> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showPlacard(BuildContext context, HomeUIModelState homeState) {
|
||||
switch (homeState.appPlacardData?.linkType) {
|
||||
case "external":
|
||||
launchUrlString(homeState.appPlacardData?.link);
|
||||
return;
|
||||
case "doc":
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return HomeMdContentDialogUI(
|
||||
title: homeState.appPlacardData?.title ?? "公告详情",
|
||||
url: homeState.appPlacardData?.link,
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_onTapFestival(BuildContext context) {
|
||||
showDialog(
|
||||
context: context, builder: (context) => const HomeCountdownDialogUI());
|
||||
}
|
||||
|
||||
_onMenuTap(
|
||||
BuildContext context, String key, HomeUIModelState homeState) async {
|
||||
const String gameInstallReqInfo =
|
||||
"该功能需要一个有效的安装位置\n\n如果您的游戏未下载完成,请等待下载完毕后使用此功能。\n\n如果您的游戏已下载完毕但未识别,请启动一次游戏后重新打开盒子 或 在设置选项中手动设置安装位置。";
|
||||
switch (key) {
|
||||
case "localization":
|
||||
if (homeState.scInstalledPath == "not_install") {
|
||||
showToast(context, gameInstallReqInfo);
|
||||
break;
|
||||
}
|
||||
await showDialog(
|
||||
context: context,
|
||||
dismissWithEsc: false,
|
||||
builder: (BuildContext context) => const LocalizationDialogUI());
|
||||
break;
|
||||
default:
|
||||
context.push("/index/$key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeItemData {
|
||||
|
@ -2,257 +2,127 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dart_rss/dart_rss.dart';
|
||||
import 'package:dart_rss/domain/rss_item.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.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/api/rss.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.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';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/data/app_placard_data.dart';
|
||||
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
|
||||
import 'package:starcitizen_doctor/data/countdown_festival_item_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/countdown/countdown_dialog_ui_model.dart';
|
||||
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';
|
||||
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/login/login_dialog_ui_model.dart';
|
||||
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:starcitizen_doctor/ui/home/dialogs/home_game_login_dialog_ui.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:html/parser.dart' as html;
|
||||
import 'package:html/dom.dart' as html_dom;
|
||||
import 'package:windows_ui/windows_ui.dart';
|
||||
|
||||
import 'countdown/countdown_dialog_ui.dart';
|
||||
import 'game_doctor/game_doctor_ui.dart';
|
||||
import 'game_doctor/game_doctor_ui_model.dart';
|
||||
import 'localization/localization_ui.dart';
|
||||
import 'performance/performance_ui.dart';
|
||||
import 'webview/webview_localization_capture_ui.dart';
|
||||
import '../webview/webview.dart';
|
||||
|
||||
class HomeUIModel extends BaseUIModel {
|
||||
var scInstalledPath = "not_install";
|
||||
part 'home_ui_model.freezed.dart';
|
||||
|
||||
List<String> scInstallPaths = [];
|
||||
part 'home_ui_model.g.dart';
|
||||
|
||||
String lastScreenInfo = "";
|
||||
@freezed
|
||||
class HomeUIModelState with _$HomeUIModelState {
|
||||
factory HomeUIModelState({
|
||||
AppPlacardData? appPlacardData,
|
||||
@Default(false) bool isFixing,
|
||||
@Default("") String isFixingString,
|
||||
String? scInstalledPath,
|
||||
@Default([]) List<String> scInstallPaths,
|
||||
AppWebLocalizationVersionsData? webLocalizationVersionsData,
|
||||
@Default("") String lastScreenInfo,
|
||||
List<RssItem>? rssVideoItems,
|
||||
List<RssItem>? rssTextItems,
|
||||
MapEntry<String, bool>? localizationUpdateInfo,
|
||||
List? scServerStatus,
|
||||
List<CountdownFestivalItemData>? countdownFestivalListData,
|
||||
@Default({}) Map<String, bool> isGameRunning,
|
||||
}) = _HomeUIModelState;
|
||||
}
|
||||
|
||||
bool isFixing = false;
|
||||
String isFixingString = "";
|
||||
|
||||
final Map<String, bool> _isGameRunning = {};
|
||||
|
||||
bool get isCurGameRunning => _isGameRunning[scInstalledPath] ?? false;
|
||||
|
||||
List<RssItem>? rssVideoItems;
|
||||
List<RssItem>? rssTextItems;
|
||||
|
||||
AppWebLocalizationVersionsData? appWebLocalizationVersionsData;
|
||||
|
||||
List<CountdownFestivalItemData>? countdownFestivalListData;
|
||||
|
||||
MapEntry<String, bool>? localizationUpdateInfo;
|
||||
|
||||
bool _isSendLocalizationUpdateNotification = false;
|
||||
|
||||
AppPlacardData? appPlacardData;
|
||||
|
||||
List? scServerStatus;
|
||||
|
||||
Timer? serverUpdateTimer;
|
||||
Timer? appUpdateTimer;
|
||||
|
||||
final statusCnName = const {
|
||||
"Platform": "平台",
|
||||
"Persistent Universe": "持续宇宙",
|
||||
"Electronic Access": "电子访问",
|
||||
"Arena Commander": "竞技场指挥官"
|
||||
};
|
||||
|
||||
bool isRsiLauncherStarting = false;
|
||||
extension HomeUIModelStateEx on HomeUIModelState {
|
||||
bool get isCurGameRunning => isGameRunning[scInstalledPath] ?? false;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class HomeUIModel extends _$HomeUIModel {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
updateSCServerStatus();
|
||||
notifyListeners();
|
||||
appWebLocalizationVersionsData = AppWebLocalizationVersionsData.fromJson(
|
||||
json.decode((await RSHttp.getText(
|
||||
"${URLConf.webTranslateHomeUrl}/versions.json"))));
|
||||
countdownFestivalListData = await Api.getFestivalCountdownList();
|
||||
notifyListeners();
|
||||
_loadRRS();
|
||||
} catch (e) {
|
||||
dPrint(e);
|
||||
}
|
||||
// check Localization update
|
||||
_checkLocalizationUpdate();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
reScanPath();
|
||||
serverUpdateTimer = Timer.periodic(
|
||||
const Duration(minutes: 10),
|
||||
(timer) {
|
||||
updateSCServerStatus();
|
||||
},
|
||||
);
|
||||
|
||||
appUpdateTimer = Timer.periodic(const Duration(minutes: 30), (timer) {
|
||||
_checkLocalizationUpdate();
|
||||
});
|
||||
super.initModel();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
serverUpdateTimer?.cancel();
|
||||
serverUpdateTimer = null;
|
||||
appUpdateTimer?.cancel();
|
||||
appUpdateTimer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
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,
|
||||
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
lastScreenInfo = "扫描完毕,共找到 ${scInstallPaths.length} 个有效安装目录";
|
||||
} catch (e) {
|
||||
lastScreenInfo = "解析 log 文件失败!";
|
||||
AnalyticsApi.touch("error_launchLogs");
|
||||
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 {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
openDir(rsiLauncherInstalledPath) async {
|
||||
await Process.run(SystemHelper.powershellPath,
|
||||
["explorer.exe", "/select,\"$rsiLauncherInstalledPath\""]);
|
||||
}
|
||||
|
||||
onMenuTap(String key) async {
|
||||
const String gameInstallReqInfo =
|
||||
"该功能需要一个有效的安装位置\n\n如果您的游戏未下载完成,请等待下载完毕后使用此功能。\n\n如果您的游戏已下载完毕但未识别,请启动一次游戏后重新打开盒子 或 在设置选项中手动设置安装位置。";
|
||||
switch (key) {
|
||||
case "auto_check":
|
||||
BaseUIContainer(
|
||||
uiCreate: () => GameDoctorUI(),
|
||||
modelCreate: () => GameDoctorUIModel(scInstalledPath))
|
||||
.push(context!);
|
||||
return;
|
||||
case "localization":
|
||||
if (scInstalledPath == "not_install") {
|
||||
showToast(context!, gameInstallReqInfo);
|
||||
return;
|
||||
}
|
||||
await showDialog(
|
||||
context: context!,
|
||||
dismissWithEsc: false,
|
||||
builder: (BuildContext context) {
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => LocalizationUI(),
|
||||
modelCreate: () => LocalizationUIModel(scInstalledPath));
|
||||
});
|
||||
_checkLocalizationUpdate();
|
||||
return;
|
||||
case "performance":
|
||||
if (scInstalledPath == "not_install") {
|
||||
showToast(context!, gameInstallReqInfo);
|
||||
return;
|
||||
}
|
||||
AnalyticsApi.touch("performance_launch");
|
||||
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;
|
||||
}
|
||||
HomeUIModelState build() {
|
||||
state = HomeUIModelState();
|
||||
_init();
|
||||
_loadData();
|
||||
return state;
|
||||
}
|
||||
|
||||
closePlacard() async {
|
||||
final box = await Hive.openBox("app_conf");
|
||||
await box.put("close_placard", appPlacardData?.version);
|
||||
appPlacardData = null;
|
||||
notifyListeners();
|
||||
await box.put("close_placard", state.appPlacardData?.version);
|
||||
state = state.copyWith(appPlacardData: null);
|
||||
}
|
||||
|
||||
goWebView(String title, String url,
|
||||
Future<void> reScanPath() async {
|
||||
state = state.copyWith(
|
||||
scInstalledPath: "not_install", lastScreenInfo: "正在扫描 ...");
|
||||
try {
|
||||
final listData = await SCLoggerHelper.getLauncherLogList();
|
||||
if (listData == null) {
|
||||
state = state.copyWith(scInstalledPath: "not_install");
|
||||
return;
|
||||
}
|
||||
final scInstallPaths = await SCLoggerHelper.getGameInstallPath(listData,
|
||||
withVersion: ["LIVE", "PTU", "EPTU"], checkExists: true);
|
||||
String? scInstalledPath;
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
final lastScreenInfo = "扫描完毕,共找到 ${scInstallPaths.length} 个有效安装目录";
|
||||
state = state.copyWith(
|
||||
scInstalledPath: scInstalledPath,
|
||||
scInstallPaths: scInstallPaths,
|
||||
lastScreenInfo: lastScreenInfo);
|
||||
} catch (e) {
|
||||
state = state.copyWith(
|
||||
scInstalledPath: "not_install", lastScreenInfo: "解析 log 文件失败!");
|
||||
AnalyticsApi.touch("error_launchLogs");
|
||||
// showToast(context!,
|
||||
// "解析 log 文件失败! \n请关闭游戏,退出RSI启动器后重试,若仍有问题,请使用工具箱中的 RSI Launcher log 修复。");
|
||||
}
|
||||
}
|
||||
|
||||
String getRssImage(RssItem item) {
|
||||
final h = html.parse(item.description ?? "");
|
||||
if (h.body == null) return "";
|
||||
for (var node in h.body!.nodes) {
|
||||
if (node is html_dom.Element) {
|
||||
if (node.localName == "img") {
|
||||
return node.attributes["src"]?.trim() ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
String handleTitle(String? title) {
|
||||
if (title == null) return "";
|
||||
title = title.replaceAll("【", "[ ");
|
||||
title = title.replaceAll("】", " ] ");
|
||||
return title;
|
||||
}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
Future<void> goWebView(BuildContext context, String title, String url,
|
||||
{bool useLocalization = false,
|
||||
bool loginMode = false,
|
||||
RsiLoginCallback? rsiLoginCallback}) async {
|
||||
@ -262,8 +132,9 @@ class HomeUIModel extends BaseUIModel {
|
||||
final skip =
|
||||
await box.get("skip_web_localization_tip_version", defaultValue: 0);
|
||||
if (skip != tipVersion) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"星际公民网站汉化",
|
||||
const Text(
|
||||
"本插功能件仅供大致浏览使用,不对任何有关本功能产生的问题负责!在涉及账号操作前请注意确认网站的原本内容!"
|
||||
@ -271,7 +142,7 @@ class HomeUIModel extends BaseUIModel {
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .6));
|
||||
maxWidth: MediaQuery.of(context).size.width * .6));
|
||||
if (!ok) {
|
||||
if (loginMode) {
|
||||
rsiLoginCallback?.call(null, false);
|
||||
@ -282,71 +153,138 @@ class HomeUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
if (!await WebviewWindow.isWebviewAvailable()) {
|
||||
showToast(context!, "需要安装 WebView2 Runtime");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "需要安装 WebView2 Runtime");
|
||||
launchUrlString(
|
||||
"https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
|
||||
return;
|
||||
}
|
||||
final webViewModel = WebViewModel(context!,
|
||||
if (!context.mounted) return;
|
||||
final webViewModel = WebViewModel(context,
|
||||
loginMode: loginMode, loginCallback: rsiLoginCallback);
|
||||
if (useLocalization) {
|
||||
isFixingString = "正在初始化汉化资源...";
|
||||
isFixing = true;
|
||||
notifyListeners();
|
||||
state = state.copyWith(isFixing: true, isFixingString: "正在初始化汉化资源...");
|
||||
try {
|
||||
await webViewModel.initLocalization(appWebLocalizationVersionsData!);
|
||||
await webViewModel.initLocalization(state.webLocalizationVersionsData!);
|
||||
} catch (e) {
|
||||
showToast(context!, "初始化网页汉化资源失败!$e");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "初始化网页汉化资源失败!$e");
|
||||
}
|
||||
isFixingString = "";
|
||||
isFixing = false;
|
||||
state = state.copyWith(isFixingString: "", isFixing: false);
|
||||
}
|
||||
await webViewModel.initWebView(
|
||||
title: title,
|
||||
applicationSupportDir: appGlobalState.applicationSupportDir!,
|
||||
appVersionData: appGlobalState.networkVersionData!,
|
||||
);
|
||||
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;
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await webViewModel.launch(url, appGlobalState.networkVersionData!);
|
||||
}
|
||||
|
||||
bool isRSIServerStatusOK(Map map) {
|
||||
return (map["status"] == "ok" || map["status"] == "operational");
|
||||
}
|
||||
|
||||
Timer? _serverUpdateTimer;
|
||||
Timer? _appUpdateTimer;
|
||||
|
||||
void _init() {
|
||||
reScanPath();
|
||||
_serverUpdateTimer = Timer.periodic(
|
||||
const Duration(minutes: 10),
|
||||
(timer) {
|
||||
_updateSCServerStatus();
|
||||
},
|
||||
);
|
||||
|
||||
_appUpdateTimer = Timer.periodic(const Duration(minutes: 30), (timer) {
|
||||
_checkLocalizationUpdate();
|
||||
});
|
||||
|
||||
ref.onDispose(() {
|
||||
_serverUpdateTimer?.cancel();
|
||||
_serverUpdateTimer = null;
|
||||
_appUpdateTimer?.cancel();
|
||||
_appUpdateTimer = null;
|
||||
});
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await webViewModel.launch(url);
|
||||
notifyListeners();
|
||||
void _loadData() async {
|
||||
if (appGlobalState.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 {
|
||||
state = state.copyWith(appPlacardData: r);
|
||||
}
|
||||
}
|
||||
|
||||
launchRSI() async {
|
||||
if (scInstalledPath == "not_install") {
|
||||
showToast(context!, "该功能需要一个有效的安装位置");
|
||||
final appWebLocalizationVersionsData =
|
||||
AppWebLocalizationVersionsData.fromJson(json.decode(
|
||||
(await RSHttp.getText(
|
||||
"${URLConf.webTranslateHomeUrl}/versions.json"))));
|
||||
final countdownFestivalListData = await Api.getFestivalCountdownList();
|
||||
state = state.copyWith(
|
||||
webLocalizationVersionsData: appWebLocalizationVersionsData,
|
||||
countdownFestivalListData: countdownFestivalListData);
|
||||
_updateSCServerStatus();
|
||||
_loadRRS();
|
||||
} catch (e) {
|
||||
dPrint(e);
|
||||
}
|
||||
// check Localization update
|
||||
_checkLocalizationUpdate();
|
||||
}
|
||||
|
||||
Future<void> _updateSCServerStatus() async {
|
||||
try {
|
||||
final s = await Api.getScServerStatus();
|
||||
dPrint("updateSCServerStatus===$s");
|
||||
state = state.copyWith(scServerStatus: s);
|
||||
} catch (e) {
|
||||
dPrint(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future _loadRRS() async {
|
||||
try {
|
||||
final rssVideoItems = await RSSApi.getRssVideo();
|
||||
state = state.copyWith(rssVideoItems: rssVideoItems);
|
||||
final rssTextItems = await RSSApi.getRssText();
|
||||
state = state.copyWith(rssTextItems: rssTextItems);
|
||||
dPrint("RSS update Success !");
|
||||
} catch (e) {
|
||||
dPrint("_loadRRS Error:$e");
|
||||
}
|
||||
}
|
||||
|
||||
void _checkLocalizationUpdate() {}
|
||||
|
||||
// ignore: avoid_build_context_in_providers
|
||||
launchRSI(BuildContext context) async {
|
||||
if (state.scInstalledPath == "not_install") {
|
||||
showToast(context, "该功能需要一个有效的安装位置");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AppConf.isMSE) {
|
||||
if (isCurGameRunning) {
|
||||
if (ConstConf.isMSE) {
|
||||
if (state.isCurGameRunning) {
|
||||
await Process.run(
|
||||
SystemHelper.powershellPath, ["ps \"StarCitizen\" | kill"]);
|
||||
return;
|
||||
}
|
||||
AnalyticsApi.touch("gameLaunch");
|
||||
showDialog(
|
||||
context: context!,
|
||||
context: context,
|
||||
dismissWithEsc: false,
|
||||
builder: (context) {
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => LoginDialog(),
|
||||
modelCreate: () => LoginDialogModel(scInstalledPath, this));
|
||||
});
|
||||
builder: (context) => const HomeGameLoginDialogUI());
|
||||
} else {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
context,
|
||||
"一键启动功能提示",
|
||||
const Text("为确保账户安全,一键启动功能已在开发版中禁用,我们将在微软商店版本中提供此功能。"
|
||||
"\n\n微软商店版由微软提供可靠的分发下载与数字签名,可有效防止软件被恶意篡改。\n\n提示:您无需使用盒子启动游戏也可使用汉化。"),
|
||||
@ -361,14 +299,21 @@ class HomeUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
|
||||
bool isRSIServerStatusOK(Map map) {
|
||||
return (map["status"] == "ok" || map["status"] == "operational");
|
||||
void onChangeInstallPath(String? value) {
|
||||
if (value == null) return;
|
||||
state = state.copyWith(scInstalledPath: value);
|
||||
}
|
||||
|
||||
doLaunchGame(String launchExe, List<String> args, String installPath,
|
||||
doLaunchGame(
|
||||
// ignore: avoid_build_context_in_providers
|
||||
BuildContext context,
|
||||
String launchExe,
|
||||
List<String> args,
|
||||
String installPath,
|
||||
String? processorAffinity) async {
|
||||
_isGameRunning[installPath] = true;
|
||||
notifyListeners();
|
||||
var runningMap = Map<String, bool>.from(state.isGameRunning);
|
||||
runningMap[installPath] = true;
|
||||
state = state.copyWith(isGameRunning: runningMap);
|
||||
try {
|
||||
late ProcessResult result;
|
||||
if (processorAffinity == null) {
|
||||
@ -391,7 +336,7 @@ class HomeUIModel extends BaseUIModel {
|
||||
dPrint('stderr: ${result.stderr}');
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
final logs = await SCLoggerHelper.getGameRunningLogs(scInstalledPath);
|
||||
final logs = await SCLoggerHelper.getGameRunningLogs(installPath);
|
||||
MapEntry<String, String>? exitInfo;
|
||||
bool hasUrl = false;
|
||||
if (logs != null) {
|
||||
@ -400,7 +345,8 @@ class HomeUIModel extends BaseUIModel {
|
||||
hasUrl = true;
|
||||
}
|
||||
}
|
||||
showToast(context!,
|
||||
if (!context.mounted) return;
|
||||
showToast(context,
|
||||
"游戏非正常退出\nexitCode=${result.exitCode}\nstdout=${result.stdout ?? ""}\nstderr=${result.stderr ?? ""}\n\n诊断信息:${exitInfo == null ? "未知错误,请通过一键诊断加群反馈。" : exitInfo.key} \n${hasUrl ? "请查看弹出的网页链接获得详细信息。" : exitInfo?.value ?? ""}");
|
||||
if (hasUrl) {
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
@ -413,69 +359,8 @@ class HomeUIModel extends BaseUIModel {
|
||||
await launchFile.delete();
|
||||
}
|
||||
} catch (_) {}
|
||||
_isGameRunning[installPath] = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (node is html_dom.Element) {
|
||||
if (node.localName == "img") {
|
||||
return node.attributes["src"]?.trim() ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
runningMap = Map<String, bool>.from(state.isGameRunning);
|
||||
runningMap[installPath] = false;
|
||||
state = state.copyWith(isGameRunning: runningMap);
|
||||
}
|
||||
}
|
||||
|
464
lib/ui/home/home_ui_model.freezed.dart
Normal file
464
lib/ui/home/home_ui_model.freezed.dart
Normal file
@ -0,0 +1,464 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'home_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HomeUIModelState {
|
||||
AppPlacardData? get appPlacardData => throw _privateConstructorUsedError;
|
||||
bool get isFixing => throw _privateConstructorUsedError;
|
||||
String get isFixingString => throw _privateConstructorUsedError;
|
||||
String? get scInstalledPath => throw _privateConstructorUsedError;
|
||||
List<String> get scInstallPaths => throw _privateConstructorUsedError;
|
||||
AppWebLocalizationVersionsData? get webLocalizationVersionsData =>
|
||||
throw _privateConstructorUsedError;
|
||||
String get lastScreenInfo => throw _privateConstructorUsedError;
|
||||
List<RssItem>? get rssVideoItems => throw _privateConstructorUsedError;
|
||||
List<RssItem>? get rssTextItems => throw _privateConstructorUsedError;
|
||||
MapEntry<String, bool>? get localizationUpdateInfo =>
|
||||
throw _privateConstructorUsedError;
|
||||
List<dynamic>? get scServerStatus => throw _privateConstructorUsedError;
|
||||
List<CountdownFestivalItemData>? get countdownFestivalListData =>
|
||||
throw _privateConstructorUsedError;
|
||||
Map<String, bool> get isGameRunning => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$HomeUIModelStateCopyWith<HomeUIModelState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HomeUIModelStateCopyWith<$Res> {
|
||||
factory $HomeUIModelStateCopyWith(
|
||||
HomeUIModelState value, $Res Function(HomeUIModelState) then) =
|
||||
_$HomeUIModelStateCopyWithImpl<$Res, HomeUIModelState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{AppPlacardData? appPlacardData,
|
||||
bool isFixing,
|
||||
String isFixingString,
|
||||
String? scInstalledPath,
|
||||
List<String> scInstallPaths,
|
||||
AppWebLocalizationVersionsData? webLocalizationVersionsData,
|
||||
String lastScreenInfo,
|
||||
List<RssItem>? rssVideoItems,
|
||||
List<RssItem>? rssTextItems,
|
||||
MapEntry<String, bool>? localizationUpdateInfo,
|
||||
List<dynamic>? scServerStatus,
|
||||
List<CountdownFestivalItemData>? countdownFestivalListData,
|
||||
Map<String, bool> isGameRunning});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HomeUIModelStateCopyWithImpl<$Res, $Val extends HomeUIModelState>
|
||||
implements $HomeUIModelStateCopyWith<$Res> {
|
||||
_$HomeUIModelStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? appPlacardData = freezed,
|
||||
Object? isFixing = null,
|
||||
Object? isFixingString = null,
|
||||
Object? scInstalledPath = freezed,
|
||||
Object? scInstallPaths = null,
|
||||
Object? webLocalizationVersionsData = freezed,
|
||||
Object? lastScreenInfo = null,
|
||||
Object? rssVideoItems = freezed,
|
||||
Object? rssTextItems = freezed,
|
||||
Object? localizationUpdateInfo = freezed,
|
||||
Object? scServerStatus = freezed,
|
||||
Object? countdownFestivalListData = freezed,
|
||||
Object? isGameRunning = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
appPlacardData: freezed == appPlacardData
|
||||
? _value.appPlacardData
|
||||
: appPlacardData // ignore: cast_nullable_to_non_nullable
|
||||
as AppPlacardData?,
|
||||
isFixing: null == isFixing
|
||||
? _value.isFixing
|
||||
: isFixing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFixingString: null == isFixingString
|
||||
? _value.isFixingString
|
||||
: isFixingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
scInstalledPath: freezed == scInstalledPath
|
||||
? _value.scInstalledPath
|
||||
: scInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
scInstallPaths: null == scInstallPaths
|
||||
? _value.scInstallPaths
|
||||
: scInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
webLocalizationVersionsData: freezed == webLocalizationVersionsData
|
||||
? _value.webLocalizationVersionsData
|
||||
: webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable
|
||||
as AppWebLocalizationVersionsData?,
|
||||
lastScreenInfo: null == lastScreenInfo
|
||||
? _value.lastScreenInfo
|
||||
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
rssVideoItems: freezed == rssVideoItems
|
||||
? _value.rssVideoItems
|
||||
: rssVideoItems // ignore: cast_nullable_to_non_nullable
|
||||
as List<RssItem>?,
|
||||
rssTextItems: freezed == rssTextItems
|
||||
? _value.rssTextItems
|
||||
: rssTextItems // ignore: cast_nullable_to_non_nullable
|
||||
as List<RssItem>?,
|
||||
localizationUpdateInfo: freezed == localizationUpdateInfo
|
||||
? _value.localizationUpdateInfo
|
||||
: localizationUpdateInfo // ignore: cast_nullable_to_non_nullable
|
||||
as MapEntry<String, bool>?,
|
||||
scServerStatus: freezed == scServerStatus
|
||||
? _value.scServerStatus
|
||||
: scServerStatus // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>?,
|
||||
countdownFestivalListData: freezed == countdownFestivalListData
|
||||
? _value.countdownFestivalListData
|
||||
: countdownFestivalListData // ignore: cast_nullable_to_non_nullable
|
||||
as List<CountdownFestivalItemData>?,
|
||||
isGameRunning: null == isGameRunning
|
||||
? _value.isGameRunning
|
||||
: isGameRunning // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HomeUIModelStateImplCopyWith<$Res>
|
||||
implements $HomeUIModelStateCopyWith<$Res> {
|
||||
factory _$$HomeUIModelStateImplCopyWith(_$HomeUIModelStateImpl value,
|
||||
$Res Function(_$HomeUIModelStateImpl) then) =
|
||||
__$$HomeUIModelStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{AppPlacardData? appPlacardData,
|
||||
bool isFixing,
|
||||
String isFixingString,
|
||||
String? scInstalledPath,
|
||||
List<String> scInstallPaths,
|
||||
AppWebLocalizationVersionsData? webLocalizationVersionsData,
|
||||
String lastScreenInfo,
|
||||
List<RssItem>? rssVideoItems,
|
||||
List<RssItem>? rssTextItems,
|
||||
MapEntry<String, bool>? localizationUpdateInfo,
|
||||
List<dynamic>? scServerStatus,
|
||||
List<CountdownFestivalItemData>? countdownFestivalListData,
|
||||
Map<String, bool> isGameRunning});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HomeUIModelStateImplCopyWithImpl<$Res>
|
||||
extends _$HomeUIModelStateCopyWithImpl<$Res, _$HomeUIModelStateImpl>
|
||||
implements _$$HomeUIModelStateImplCopyWith<$Res> {
|
||||
__$$HomeUIModelStateImplCopyWithImpl(_$HomeUIModelStateImpl _value,
|
||||
$Res Function(_$HomeUIModelStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? appPlacardData = freezed,
|
||||
Object? isFixing = null,
|
||||
Object? isFixingString = null,
|
||||
Object? scInstalledPath = freezed,
|
||||
Object? scInstallPaths = null,
|
||||
Object? webLocalizationVersionsData = freezed,
|
||||
Object? lastScreenInfo = null,
|
||||
Object? rssVideoItems = freezed,
|
||||
Object? rssTextItems = freezed,
|
||||
Object? localizationUpdateInfo = freezed,
|
||||
Object? scServerStatus = freezed,
|
||||
Object? countdownFestivalListData = freezed,
|
||||
Object? isGameRunning = null,
|
||||
}) {
|
||||
return _then(_$HomeUIModelStateImpl(
|
||||
appPlacardData: freezed == appPlacardData
|
||||
? _value.appPlacardData
|
||||
: appPlacardData // ignore: cast_nullable_to_non_nullable
|
||||
as AppPlacardData?,
|
||||
isFixing: null == isFixing
|
||||
? _value.isFixing
|
||||
: isFixing // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isFixingString: null == isFixingString
|
||||
? _value.isFixingString
|
||||
: isFixingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
scInstalledPath: freezed == scInstalledPath
|
||||
? _value.scInstalledPath
|
||||
: scInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
scInstallPaths: null == scInstallPaths
|
||||
? _value._scInstallPaths
|
||||
: scInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
webLocalizationVersionsData: freezed == webLocalizationVersionsData
|
||||
? _value.webLocalizationVersionsData
|
||||
: webLocalizationVersionsData // ignore: cast_nullable_to_non_nullable
|
||||
as AppWebLocalizationVersionsData?,
|
||||
lastScreenInfo: null == lastScreenInfo
|
||||
? _value.lastScreenInfo
|
||||
: lastScreenInfo // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
rssVideoItems: freezed == rssVideoItems
|
||||
? _value._rssVideoItems
|
||||
: rssVideoItems // ignore: cast_nullable_to_non_nullable
|
||||
as List<RssItem>?,
|
||||
rssTextItems: freezed == rssTextItems
|
||||
? _value._rssTextItems
|
||||
: rssTextItems // ignore: cast_nullable_to_non_nullable
|
||||
as List<RssItem>?,
|
||||
localizationUpdateInfo: freezed == localizationUpdateInfo
|
||||
? _value.localizationUpdateInfo
|
||||
: localizationUpdateInfo // ignore: cast_nullable_to_non_nullable
|
||||
as MapEntry<String, bool>?,
|
||||
scServerStatus: freezed == scServerStatus
|
||||
? _value._scServerStatus
|
||||
: scServerStatus // ignore: cast_nullable_to_non_nullable
|
||||
as List<dynamic>?,
|
||||
countdownFestivalListData: freezed == countdownFestivalListData
|
||||
? _value._countdownFestivalListData
|
||||
: countdownFestivalListData // ignore: cast_nullable_to_non_nullable
|
||||
as List<CountdownFestivalItemData>?,
|
||||
isGameRunning: null == isGameRunning
|
||||
? _value._isGameRunning
|
||||
: isGameRunning // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, bool>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$HomeUIModelStateImpl implements _HomeUIModelState {
|
||||
_$HomeUIModelStateImpl(
|
||||
{this.appPlacardData,
|
||||
this.isFixing = false,
|
||||
this.isFixingString = "",
|
||||
this.scInstalledPath,
|
||||
final List<String> scInstallPaths = const [],
|
||||
this.webLocalizationVersionsData,
|
||||
this.lastScreenInfo = "",
|
||||
final List<RssItem>? rssVideoItems,
|
||||
final List<RssItem>? rssTextItems,
|
||||
this.localizationUpdateInfo,
|
||||
final List<dynamic>? scServerStatus,
|
||||
final List<CountdownFestivalItemData>? countdownFestivalListData,
|
||||
final Map<String, bool> isGameRunning = const {}})
|
||||
: _scInstallPaths = scInstallPaths,
|
||||
_rssVideoItems = rssVideoItems,
|
||||
_rssTextItems = rssTextItems,
|
||||
_scServerStatus = scServerStatus,
|
||||
_countdownFestivalListData = countdownFestivalListData,
|
||||
_isGameRunning = isGameRunning;
|
||||
|
||||
@override
|
||||
final AppPlacardData? appPlacardData;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFixing;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String isFixingString;
|
||||
@override
|
||||
final String? scInstalledPath;
|
||||
final List<String> _scInstallPaths;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get scInstallPaths {
|
||||
if (_scInstallPaths is EqualUnmodifiableListView) return _scInstallPaths;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_scInstallPaths);
|
||||
}
|
||||
|
||||
@override
|
||||
final AppWebLocalizationVersionsData? webLocalizationVersionsData;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String lastScreenInfo;
|
||||
final List<RssItem>? _rssVideoItems;
|
||||
@override
|
||||
List<RssItem>? get rssVideoItems {
|
||||
final value = _rssVideoItems;
|
||||
if (value == null) return null;
|
||||
if (_rssVideoItems is EqualUnmodifiableListView) return _rssVideoItems;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
final List<RssItem>? _rssTextItems;
|
||||
@override
|
||||
List<RssItem>? get rssTextItems {
|
||||
final value = _rssTextItems;
|
||||
if (value == null) return null;
|
||||
if (_rssTextItems is EqualUnmodifiableListView) return _rssTextItems;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
final MapEntry<String, bool>? localizationUpdateInfo;
|
||||
final List<dynamic>? _scServerStatus;
|
||||
@override
|
||||
List<dynamic>? get scServerStatus {
|
||||
final value = _scServerStatus;
|
||||
if (value == null) return null;
|
||||
if (_scServerStatus is EqualUnmodifiableListView) return _scServerStatus;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
final List<CountdownFestivalItemData>? _countdownFestivalListData;
|
||||
@override
|
||||
List<CountdownFestivalItemData>? get countdownFestivalListData {
|
||||
final value = _countdownFestivalListData;
|
||||
if (value == null) return null;
|
||||
if (_countdownFestivalListData is EqualUnmodifiableListView)
|
||||
return _countdownFestivalListData;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
final Map<String, bool> _isGameRunning;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<String, bool> get isGameRunning {
|
||||
if (_isGameRunning is EqualUnmodifiableMapView) return _isGameRunning;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_isGameRunning);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomeUIModelState(appPlacardData: $appPlacardData, isFixing: $isFixing, isFixingString: $isFixingString, scInstalledPath: $scInstalledPath, scInstallPaths: $scInstallPaths, webLocalizationVersionsData: $webLocalizationVersionsData, lastScreenInfo: $lastScreenInfo, rssVideoItems: $rssVideoItems, rssTextItems: $rssTextItems, localizationUpdateInfo: $localizationUpdateInfo, scServerStatus: $scServerStatus, countdownFestivalListData: $countdownFestivalListData, isGameRunning: $isGameRunning)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HomeUIModelStateImpl &&
|
||||
(identical(other.appPlacardData, appPlacardData) ||
|
||||
other.appPlacardData == appPlacardData) &&
|
||||
(identical(other.isFixing, isFixing) ||
|
||||
other.isFixing == isFixing) &&
|
||||
(identical(other.isFixingString, isFixingString) ||
|
||||
other.isFixingString == isFixingString) &&
|
||||
(identical(other.scInstalledPath, scInstalledPath) ||
|
||||
other.scInstalledPath == scInstalledPath) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._scInstallPaths, _scInstallPaths) &&
|
||||
(identical(other.webLocalizationVersionsData,
|
||||
webLocalizationVersionsData) ||
|
||||
other.webLocalizationVersionsData ==
|
||||
webLocalizationVersionsData) &&
|
||||
(identical(other.lastScreenInfo, lastScreenInfo) ||
|
||||
other.lastScreenInfo == lastScreenInfo) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._rssVideoItems, _rssVideoItems) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._rssTextItems, _rssTextItems) &&
|
||||
(identical(other.localizationUpdateInfo, localizationUpdateInfo) ||
|
||||
other.localizationUpdateInfo == localizationUpdateInfo) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._scServerStatus, _scServerStatus) &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other._countdownFestivalListData, _countdownFestivalListData) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._isGameRunning, _isGameRunning));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
appPlacardData,
|
||||
isFixing,
|
||||
isFixingString,
|
||||
scInstalledPath,
|
||||
const DeepCollectionEquality().hash(_scInstallPaths),
|
||||
webLocalizationVersionsData,
|
||||
lastScreenInfo,
|
||||
const DeepCollectionEquality().hash(_rssVideoItems),
|
||||
const DeepCollectionEquality().hash(_rssTextItems),
|
||||
localizationUpdateInfo,
|
||||
const DeepCollectionEquality().hash(_scServerStatus),
|
||||
const DeepCollectionEquality().hash(_countdownFestivalListData),
|
||||
const DeepCollectionEquality().hash(_isGameRunning));
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HomeUIModelStateImplCopyWith<_$HomeUIModelStateImpl> get copyWith =>
|
||||
__$$HomeUIModelStateImplCopyWithImpl<_$HomeUIModelStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _HomeUIModelState implements HomeUIModelState {
|
||||
factory _HomeUIModelState(
|
||||
{final AppPlacardData? appPlacardData,
|
||||
final bool isFixing,
|
||||
final String isFixingString,
|
||||
final String? scInstalledPath,
|
||||
final List<String> scInstallPaths,
|
||||
final AppWebLocalizationVersionsData? webLocalizationVersionsData,
|
||||
final String lastScreenInfo,
|
||||
final List<RssItem>? rssVideoItems,
|
||||
final List<RssItem>? rssTextItems,
|
||||
final MapEntry<String, bool>? localizationUpdateInfo,
|
||||
final List<dynamic>? scServerStatus,
|
||||
final List<CountdownFestivalItemData>? countdownFestivalListData,
|
||||
final Map<String, bool> isGameRunning}) = _$HomeUIModelStateImpl;
|
||||
|
||||
@override
|
||||
AppPlacardData? get appPlacardData;
|
||||
@override
|
||||
bool get isFixing;
|
||||
@override
|
||||
String get isFixingString;
|
||||
@override
|
||||
String? get scInstalledPath;
|
||||
@override
|
||||
List<String> get scInstallPaths;
|
||||
@override
|
||||
AppWebLocalizationVersionsData? get webLocalizationVersionsData;
|
||||
@override
|
||||
String get lastScreenInfo;
|
||||
@override
|
||||
List<RssItem>? get rssVideoItems;
|
||||
@override
|
||||
List<RssItem>? get rssTextItems;
|
||||
@override
|
||||
MapEntry<String, bool>? get localizationUpdateInfo;
|
||||
@override
|
||||
List<dynamic>? get scServerStatus;
|
||||
@override
|
||||
List<CountdownFestivalItemData>? get countdownFestivalListData;
|
||||
@override
|
||||
Map<String, bool> get isGameRunning;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HomeUIModelStateImplCopyWith<_$HomeUIModelStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
25
lib/ui/home/home_ui_model.g.dart
Normal file
25
lib/ui/home/home_ui_model.g.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'home_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homeUIModelHash() => r'a911826a7b852408123bf4b8999ac80c3c582fd4';
|
||||
|
||||
/// See also [HomeUIModel].
|
||||
@ProviderFor(HomeUIModel)
|
||||
final homeUIModelProvider =
|
||||
AutoDisposeNotifierProvider<HomeUIModel, HomeUIModelState>.internal(
|
||||
HomeUIModel.new,
|
||||
name: r'homeUIModelProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$homeUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HomeUIModel = AutoDisposeNotifier<HomeUIModelState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
444
lib/ui/home/localization/localization_dialog_ui.dart
Normal file
444
lib/ui/home/localization/localization_dialog_ui.dart
Normal file
@ -0,0 +1,444 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'localization_ui_model.dart';
|
||||
|
||||
class LocalizationDialogUI extends HookConsumerWidget {
|
||||
const LocalizationDialogUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(localizationUIModelProvider);
|
||||
final model = ref.read(localizationUIModelProvider.notifier);
|
||||
final curInstallInfo = state.apiLocalizationData?[state.patchStatus?.value];
|
||||
|
||||
useEffect(() {
|
||||
addPostFrameCallback(() {
|
||||
model.checkUserCfg(context);
|
||||
});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return ContentDialog(
|
||||
title: makeTitle(context, model, state),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .7,
|
||||
minHeight: MediaQuery.of(context).size.height * .9),
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: state.patchStatus?.key == true &&
|
||||
state.patchStatus?.value == "游戏内置"
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: InfoBar(
|
||||
title: const Text("警告"),
|
||||
content: const Text(
|
||||
"您正在使用游戏内置文本,官方文本目前为机器翻译(截至3.21.0),建议您在下方安装社区汉化。"),
|
||||
severity: InfoBarSeverity.info,
|
||||
style: InfoBarThemeData(decoration: (severity) {
|
||||
return const BoxDecoration(
|
||||
color: Color.fromRGBO(155, 7, 7, 1.0));
|
||||
}, iconColor: (severity) {
|
||||
return Colors.white;
|
||||
}),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
makeListContainer(
|
||||
"汉化状态",
|
||||
[
|
||||
if (state.patchStatus == null)
|
||||
makeLoading(context)
|
||||
else ...[
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
"启用(${LocalizationUIModel.languageSupport[state.selectedLanguage]}):"),
|
||||
),
|
||||
const Spacer(),
|
||||
ToggleSwitch(
|
||||
checked: state.patchStatus?.key == true,
|
||||
onChanged: model.updateLangCfg,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text("已安装版本:${state.patchStatus?.value}"),
|
||||
const Spacer(),
|
||||
if (state.patchStatus?.value != "游戏内置")
|
||||
Row(
|
||||
children: [
|
||||
Button(
|
||||
onPressed: model.goFeedback,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.feedback),
|
||||
SizedBox(width: 6),
|
||||
Text("汉化反馈"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
const SizedBox(width: 16),
|
||||
Button(
|
||||
onPressed: model.doDelIniFile(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.delete),
|
||||
SizedBox(width: 6),
|
||||
Text("卸载汉化"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: (curInstallInfo != null &&
|
||||
curInstallInfo.note != null &&
|
||||
curInstallInfo.note!.isNotEmpty)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"备注:",
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"${curInstallInfo.note}",
|
||||
style: TextStyle(
|
||||
color:
|
||||
Colors.white.withOpacity(.8)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
context),
|
||||
makeListContainer(
|
||||
"社区汉化",
|
||||
[
|
||||
if (state.apiLocalizationData == null)
|
||||
makeLoading(context)
|
||||
else if (state.apiLocalizationData!.isEmpty)
|
||||
Center(
|
||||
child: Text(
|
||||
"该语言/版本 暂无可用汉化,敬请期待!",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.8)),
|
||||
),
|
||||
)
|
||||
else
|
||||
for (final item in state.apiLocalizationData!.entries)
|
||||
makeRemoteList(context, model, item, state),
|
||||
],
|
||||
context),
|
||||
const SizedBox(height: 12),
|
||||
IconButton(
|
||||
icon: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(state.enableCustomize
|
||||
? FluentIcons.chevron_up
|
||||
: FluentIcons.chevron_down),
|
||||
const SizedBox(width: 12),
|
||||
const Text("高级功能"),
|
||||
],
|
||||
),
|
||||
onPressed: model.toggleCustomize),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
state.enableCustomize
|
||||
? makeListContainer(
|
||||
"自定义文本",
|
||||
[
|
||||
if (state.customizeList == null)
|
||||
makeLoading(context)
|
||||
else if (state.customizeList!.isEmpty)
|
||||
Center(
|
||||
child: Text(
|
||||
"暂无自定义文本",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.8)),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
for (final file in state.customizeList!)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
model.getCustomizeFileName(file),
|
||||
),
|
||||
const Spacer(),
|
||||
if (state.workingVersion == file)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
else
|
||||
Button(
|
||||
onPressed:
|
||||
model.doLocalInstall(file),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 4,
|
||||
bottom: 4),
|
||||
child: Text("安装"),
|
||||
))
|
||||
],
|
||||
)
|
||||
],
|
||||
],
|
||||
context,
|
||||
actions: [
|
||||
Button(
|
||||
onPressed: () => model.openDir(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.folder_open),
|
||||
SizedBox(width: 6),
|
||||
Text("打开文件夹"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
])
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeRemoteList(BuildContext context, LocalizationUIModel model,
|
||||
MapEntry<String, ScLocalizationData> item, LocalizationUIState state) {
|
||||
final isWorking = state.workingVersion.isNotEmpty;
|
||||
final isMineWorking = state.workingVersion == item.key;
|
||||
final isInstalled = state.patchStatus?.value == item.key;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.value.info}",
|
||||
style: const TextStyle(fontSize: 19),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"版本号:${item.value.versionName}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"通道:${item.value.gameChannel}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"更新时间:${item.value.updateAt}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (isMineWorking)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
else
|
||||
Button(
|
||||
onPressed: ((item.value.enable == true &&
|
||||
!isWorking &&
|
||||
!isInstalled)
|
||||
? model.doRemoteInstall(context, item.value)
|
||||
: null),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8, top: 4, bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Icon(isInstalled
|
||||
? FluentIcons.check_mark
|
||||
: (item.value.enable ?? false)
|
||||
? FluentIcons.download
|
||||
: FluentIcons.disable_updates),
|
||||
),
|
||||
Text(isInstalled
|
||||
? "已安装"
|
||||
: ((item.value.enable ?? false) ? "安装" : "不可用")),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
color: Colors.white.withOpacity(.05),
|
||||
height: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeListContainer(
|
||||
String title, List<Widget> children, BuildContext context,
|
||||
{List<Widget> actions = const []}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 22),
|
||||
),
|
||||
const Spacer(),
|
||||
if (actions.isNotEmpty) ...actions,
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Container(
|
||||
color: Colors.white.withOpacity(.1),
|
||||
height: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...children
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTitle(BuildContext context, LocalizationUIModel model,
|
||||
LocalizationUIState state) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: model.onBack(context)),
|
||||
const SizedBox(width: 12),
|
||||
const Text("汉化管理"),
|
||||
const SizedBox(width: 24),
|
||||
Text(
|
||||
"${model.getScInstallPath()}",
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
"语言: ",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
ComboBox<String>(
|
||||
value: state.selectedLanguage,
|
||||
items: [
|
||||
for (final lang
|
||||
in LocalizationUIModel.languageSupport.entries)
|
||||
ComboBoxItem(
|
||||
value: lang.key,
|
||||
child: Text(lang.value),
|
||||
)
|
||||
],
|
||||
onChanged: state.workingVersion.isNotEmpty
|
||||
? null
|
||||
: (v) {
|
||||
if (v == null) return;
|
||||
model.selectLang(v);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: model.doRefresh(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.refresh),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,419 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
|
||||
|
||||
import 'localization_ui_model.dart';
|
||||
|
||||
class LocalizationUI extends BaseUI<LocalizationUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, LocalizationUIModel model) {
|
||||
final curInstallInfo = model.apiLocalizationData?[model.patchStatus?.value];
|
||||
return ContentDialog(
|
||||
title: makeTitle(context, model),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .7,
|
||||
minHeight: MediaQuery.of(context).size.height * .9),
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: model.patchStatus?.key == true &&
|
||||
model.patchStatus?.value == "游戏内置"
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: InfoBar(
|
||||
title: const Text("警告"),
|
||||
content: const Text(
|
||||
"您正在使用游戏内置文本,官方文本目前为机器翻译(截至3.21.0),建议您在下方安装社区汉化。"),
|
||||
severity: InfoBarSeverity.info,
|
||||
style: InfoBarThemeData(decoration: (severity) {
|
||||
return const BoxDecoration(
|
||||
color: Color.fromRGBO(155, 7, 7, 1.0));
|
||||
}, iconColor: (severity) {
|
||||
return Colors.white;
|
||||
}),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
makeListContainer("汉化状态", [
|
||||
if (model.patchStatus == null)
|
||||
makeLoading(context)
|
||||
else ...[
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
"启用(${LocalizationUIModel.languageSupport[model.selectedLanguage]}):"),
|
||||
),
|
||||
const Spacer(),
|
||||
ToggleSwitch(
|
||||
checked: model.patchStatus?.key == true,
|
||||
onChanged: model.updateLangCfg,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Text("已安装版本:${model.patchStatus?.value}"),
|
||||
const Spacer(),
|
||||
if (model.patchStatus?.value != "游戏内置")
|
||||
Row(
|
||||
children: [
|
||||
Button(
|
||||
onPressed: model.goFeedback,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.feedback),
|
||||
SizedBox(width: 6),
|
||||
Text("汉化反馈"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
const SizedBox(width: 16),
|
||||
Button(
|
||||
onPressed: model.doDelIniFile(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.delete),
|
||||
SizedBox(width: 6),
|
||||
Text("卸载汉化"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: (curInstallInfo != null &&
|
||||
curInstallInfo.note != null &&
|
||||
curInstallInfo.note!.isNotEmpty)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"备注:",
|
||||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"${curInstallInfo.note}",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(.8)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
makeListContainer("社区汉化", [
|
||||
if (model.apiLocalizationData == null)
|
||||
makeLoading(context)
|
||||
else if (model.apiLocalizationData!.isEmpty)
|
||||
Center(
|
||||
child: Text(
|
||||
"该语言/版本 暂无可用汉化,敬请期待!",
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withOpacity(.8)),
|
||||
),
|
||||
)
|
||||
else
|
||||
for (final item in model.apiLocalizationData!.entries)
|
||||
makeRemoteList(context, model, item),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
IconButton(
|
||||
icon: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(model.enableCustomize
|
||||
? FluentIcons.chevron_up
|
||||
: FluentIcons.chevron_down),
|
||||
const SizedBox(width: 12),
|
||||
const Text("高级功能"),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
model.enableCustomize = !model.enableCustomize;
|
||||
model.notifyListeners();
|
||||
}),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
model.enableCustomize
|
||||
? makeListContainer("自定义文本", [
|
||||
if (model.customizeList == null)
|
||||
makeLoading(context)
|
||||
else if (model.customizeList!.isEmpty)
|
||||
Center(
|
||||
child: Text(
|
||||
"暂无自定义文本",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.8)),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
for (final file in model.customizeList!)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
model.getCustomizeFileName(file),
|
||||
),
|
||||
const Spacer(),
|
||||
if (model.workingVersion == file)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
else
|
||||
Button(
|
||||
onPressed: model.doLocalInstall(file),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 4,
|
||||
bottom: 4),
|
||||
child: Text("安装"),
|
||||
))
|
||||
],
|
||||
)
|
||||
],
|
||||
], actions: [
|
||||
Button(
|
||||
onPressed: () => model.openDir(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.folder_open),
|
||||
SizedBox(width: 6),
|
||||
Text("打开文件夹"),
|
||||
],
|
||||
),
|
||||
)),
|
||||
])
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeRemoteList(BuildContext context, LocalizationUIModel model,
|
||||
MapEntry<String, ScLocalizationData> item) {
|
||||
final isWorking = model.workingVersion.isNotEmpty;
|
||||
final isMineWorking = model.workingVersion == item.key;
|
||||
final isInstalled = model.patchStatus?.value == item.key;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"${item.value.info}",
|
||||
style: const TextStyle(fontSize: 19),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"版本号:${item.value.versionName}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"通道:${item.value.gameChannel}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"更新时间:${item.value.updateAt}",
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (isMineWorking)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: ProgressRing(),
|
||||
)
|
||||
else
|
||||
Button(
|
||||
onPressed: ((item.value.enable == true &&
|
||||
!isWorking &&
|
||||
!isInstalled)
|
||||
? model.doRemoteInstall(item.value)
|
||||
: null),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8, top: 4, bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Icon(isInstalled
|
||||
? FluentIcons.check_mark
|
||||
: (item.value.enable ?? false)
|
||||
? FluentIcons.download
|
||||
: FluentIcons.disable_updates),
|
||||
),
|
||||
Text(isInstalled
|
||||
? "已安装"
|
||||
: ((item.value.enable ?? false) ? "安装" : "不可用")),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
color: Colors.white.withOpacity(.05),
|
||||
height: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeListContainer(String title, List<Widget> children,
|
||||
{List<Widget> actions = const []}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 12, bottom: 12, left: 24, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 22),
|
||||
),
|
||||
const Spacer(),
|
||||
if (actions.isNotEmpty) ...actions,
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Container(
|
||||
color: Colors.white.withOpacity(.1),
|
||||
height: 1,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...children
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTitle(BuildContext context, LocalizationUIModel model) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: model.onBack()),
|
||||
const SizedBox(width: 12),
|
||||
Text(getUITitle(context, model)),
|
||||
const SizedBox(width: 24),
|
||||
Text(
|
||||
model.scInstallPath,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: Row(
|
||||
children: [
|
||||
const Text(
|
||||
"语言: ",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
ComboBox<String>(
|
||||
value: model.selectedLanguage,
|
||||
items: [
|
||||
for (final lang
|
||||
in LocalizationUIModel.languageSupport.entries)
|
||||
ComboBoxItem(
|
||||
value: lang.key,
|
||||
child: Text(lang.value),
|
||||
)
|
||||
],
|
||||
onChanged: model.workingVersion.isNotEmpty
|
||||
? null
|
||||
: (v) {
|
||||
if (v == null) return;
|
||||
model.selectLang(v);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: model.doRefresh(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.refresh),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, LocalizationUIModel model) => "汉化管理";
|
||||
}
|
@ -1,233 +1,134 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.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/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart' as log_utils;
|
||||
|
||||
class LocalizationUIModel extends BaseUIModel {
|
||||
final String scInstallPath;
|
||||
part 'localization_ui_model.g.dart';
|
||||
|
||||
part 'localization_ui_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class LocalizationUIState with _$LocalizationUIState {
|
||||
const factory LocalizationUIState({
|
||||
String? selectedLanguage,
|
||||
Map<String, ScLocalizationData>? apiLocalizationData,
|
||||
@Default("") String workingVersion,
|
||||
MapEntry<bool, String>? patchStatus,
|
||||
List<String>? customizeList,
|
||||
@Default(false) bool enableCustomize,
|
||||
}) = _LocalizationUIState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class LocalizationUIModel extends _$LocalizationUIModel {
|
||||
static const languageSupport = {
|
||||
"chinese_(simplified)": "简体中文",
|
||||
"chinese_(traditional)": "繁體中文",
|
||||
};
|
||||
|
||||
late String selectedLanguage;
|
||||
|
||||
Map<String, ScLocalizationData>? apiLocalizationData;
|
||||
|
||||
LocalizationUIModel(this.scInstallPath);
|
||||
|
||||
String workingVersion = "";
|
||||
|
||||
final downloadDir =
|
||||
Directory("${AppConf.applicationSupportDir}\\Localizations");
|
||||
late final _downloadDir =
|
||||
Directory("${appGlobalState.applicationSupportDir}\\Localizations");
|
||||
|
||||
late final customizeDir =
|
||||
Directory("${downloadDir.absolute.path}\\Customize_ini");
|
||||
Directory("${_downloadDir.absolute.path}\\Customize_ini");
|
||||
|
||||
late final scDataDir = Directory("$scInstallPath\\data");
|
||||
late final scDataDir =
|
||||
Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data");
|
||||
|
||||
late final cfgFile = File("${scDataDir.absolute.path}\\system.cfg");
|
||||
|
||||
MapEntry<bool, String>? patchStatus;
|
||||
StreamSubscription? _customizeDirListenSub;
|
||||
|
||||
List<String>? customizeList;
|
||||
|
||||
StreamSubscription? customizeDirListenSub;
|
||||
|
||||
bool enableCustomize = false;
|
||||
String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!;
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
selectedLanguage = languageSupport.entries.first.key;
|
||||
if (!customizeDir.existsSync()) {
|
||||
customizeDir.createSync(recursive: true);
|
||||
LocalizationUIState build() {
|
||||
state = LocalizationUIState(selectedLanguage: languageSupport.keys.first);
|
||||
_init();
|
||||
return state;
|
||||
}
|
||||
customizeDirListenSub = customizeDir.watch().listen((event) {
|
||||
|
||||
_init() async {
|
||||
if (!customizeDir.existsSync()) {
|
||||
await customizeDir.create(recursive: true);
|
||||
}
|
||||
_customizeDirListenSub = customizeDir.watch().listen((event) {
|
||||
_scanCustomizeDir();
|
||||
});
|
||||
super.initModel();
|
||||
ref.onDispose(() {
|
||||
_customizeDirListenSub?.cancel();
|
||||
_customizeDirListenSub = null;
|
||||
});
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
_loadData() async {
|
||||
await _updateStatus();
|
||||
_checkUserCfg();
|
||||
_scanCustomizeDir();
|
||||
final l =
|
||||
await handleError(() => Api.getScLocalizationData(selectedLanguage));
|
||||
final l = await Api.getScLocalizationData(state.selectedLanguage!).unwrap();
|
||||
if (l != null) {
|
||||
apiLocalizationData = {};
|
||||
final apiLocalizationData = <String, ScLocalizationData>{};
|
||||
for (var element in l) {
|
||||
final isPTU = !scInstallPath.contains("LIVE");
|
||||
final isPTU = !_scInstallPath.contains("LIVE");
|
||||
if (isPTU && element.gameChannel == "PTU") {
|
||||
apiLocalizationData![element.versionName ?? ""] = element;
|
||||
apiLocalizationData[element.versionName ?? ""] = element;
|
||||
} else if (!isPTU && element.gameChannel == "PU") {
|
||||
apiLocalizationData![element.versionName ?? ""] = element;
|
||||
apiLocalizationData[element.versionName ?? ""] = element;
|
||||
}
|
||||
}
|
||||
state = state.copyWith(apiLocalizationData: apiLocalizationData);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
customizeDirListenSub?.cancel();
|
||||
super.dispose();
|
||||
void checkUserCfg(BuildContext context) async {
|
||||
final userCfgFile = File("$_scInstallPath\\USER.cfg");
|
||||
if (await userCfgFile.exists()) {
|
||||
final cfgString = await userCfgFile.readAsString();
|
||||
if (cfgString.contains("g_language") &&
|
||||
!cfgString.contains("g_language=${state.selectedLanguage}")) {
|
||||
if (!context.mounted) return;
|
||||
final ok = await showConfirmDialogs(
|
||||
context,
|
||||
"是否移除不兼容的汉化参数",
|
||||
const Text(
|
||||
"USER.cfg 包含不兼容的汉化参数,这可能是以前的汉化文件的残留信息。\n\n这将可能导致汉化无效或乱码,点击确认为您一键移除(不会影响其他配置)。"),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .35));
|
||||
if (ok == true) {
|
||||
var finalString = "";
|
||||
for (var item in cfgString.split("\n")) {
|
||||
if (!item.trim().startsWith("g_language")) {
|
||||
finalString = "$finalString$item\n";
|
||||
}
|
||||
}
|
||||
await userCfgFile.delete();
|
||||
await userCfgFile.create();
|
||||
await userCfgFile.writeAsString(finalString, flush: true);
|
||||
_loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_scanCustomizeDir() {
|
||||
final fileList = customizeDir.listSync();
|
||||
customizeList = [];
|
||||
for (var value in fileList) {
|
||||
if (value is File && value.path.endsWith(".ini")) {
|
||||
customizeList?.add(value.absolute.path);
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String getCustomizeFileName(String path) {
|
||||
return path.split("\\").last;
|
||||
}
|
||||
|
||||
_updateStatus() async {
|
||||
patchStatus = MapEntry(
|
||||
await getLangCfgEnableLang(lang: selectedLanguage),
|
||||
await getInstalledIniVersion(
|
||||
"${scDataDir.absolute.path}\\Localization\\$selectedLanguage\\global.ini"));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
VoidCallback? onBack() {
|
||||
if (workingVersion.isNotEmpty) return null;
|
||||
return () {
|
||||
Navigator.pop(context!);
|
||||
};
|
||||
}
|
||||
|
||||
void selectLang(String v) {
|
||||
selectedLanguage = v;
|
||||
apiLocalizationData = null;
|
||||
notifyListeners();
|
||||
reloadData();
|
||||
}
|
||||
|
||||
VoidCallback? doRefresh() {
|
||||
if (workingVersion.isNotEmpty) return null;
|
||||
return () {
|
||||
apiLocalizationData = null;
|
||||
notifyListeners();
|
||||
reloadData();
|
||||
};
|
||||
}
|
||||
|
||||
VoidCallback? doRemoteInstall(ScLocalizationData value) {
|
||||
return () async {
|
||||
AnalyticsApi.touch("install_localization");
|
||||
final downloadUrl =
|
||||
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
|
||||
final savePath =
|
||||
File("${downloadDir.absolute.path}\\${value.versionName}.sclang");
|
||||
try {
|
||||
workingVersion = value.versionName!;
|
||||
notifyListeners();
|
||||
if (!await savePath.exists()) {
|
||||
// download
|
||||
dPrint("downloading file to $savePath");
|
||||
final r = await RSHttp.get(downloadUrl);
|
||||
if (r.statusCode == 200 && r.data != null) {
|
||||
await savePath.writeAsBytes(r.data!);
|
||||
} else {
|
||||
throw "statusCode Error : ${r.statusCode}";
|
||||
}
|
||||
} else {
|
||||
dPrint("use cache $savePath");
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
// check file
|
||||
final globalIni = await compute(_readArchive, savePath.absolute.path);
|
||||
if (globalIni.isEmpty) {
|
||||
throw "文件受损,请重新下载";
|
||||
}
|
||||
await _installFormString(globalIni, value.versionName ?? "");
|
||||
} catch (e) {
|
||||
await showToast(context!, "安装出错!\n\n $e");
|
||||
if (await savePath.exists()) await savePath.delete();
|
||||
}
|
||||
workingVersion = "";
|
||||
notifyListeners();
|
||||
};
|
||||
}
|
||||
|
||||
Future<bool> getLangCfgEnableLang({String lang = ""}) async {
|
||||
if (!await cfgFile.exists()) return false;
|
||||
final str = (await cfgFile.readAsString()).replaceAll(" ", "");
|
||||
return str.contains("sys_languages=$lang") &&
|
||||
str.contains("g_language=$lang") &&
|
||||
str.contains("g_languageAudio=english");
|
||||
}
|
||||
|
||||
static Future<String> getInstalledIniVersion(String iniPath) async {
|
||||
final iniFile = File(iniPath);
|
||||
if (!await iniFile.exists()) return "游戏内置";
|
||||
final iniStringSplit = (await iniFile.readAsString()).split("\n");
|
||||
for (var i = iniStringSplit.length - 1; i > 0; i--) {
|
||||
if (iniStringSplit[i]
|
||||
.contains("_starcitizen_doctor_localization_version=")) {
|
||||
final v = iniStringSplit[i]
|
||||
.trim()
|
||||
.split("_starcitizen_doctor_localization_version=")[1];
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return "自定义文件";
|
||||
}
|
||||
|
||||
_installFormString(StringBuffer globalIni, String versionName) async {
|
||||
final iniFile = File(
|
||||
"${scDataDir.absolute.path}\\Localization\\$selectedLanguage\\global.ini");
|
||||
if (versionName.isNotEmpty) {
|
||||
if (!globalIni.toString().endsWith("\n")) {
|
||||
globalIni.write("\n");
|
||||
}
|
||||
globalIni.write("_starcitizen_doctor_localization_version=$versionName");
|
||||
}
|
||||
|
||||
/// write cfg
|
||||
if (await cfgFile.exists()) {}
|
||||
|
||||
/// write ini
|
||||
if (await iniFile.exists()) {
|
||||
await iniFile.delete();
|
||||
}
|
||||
await iniFile.create(recursive: true);
|
||||
await iniFile.writeAsString("\uFEFF${globalIni.toString().trim()}",
|
||||
flush: true);
|
||||
await updateLangCfg(true);
|
||||
await _updateStatus();
|
||||
}
|
||||
|
||||
openDir() async {
|
||||
showToast(context!,
|
||||
"即将打开本地化文件夹,请将自定义的 任意名称.ini 文件放入 Customize_ini 文件夹。\n\n添加新文件后未显示请使用右上角刷新按钮。\n\n安装时请确保选择了正确的语言。");
|
||||
await Process.run(SystemHelper.powershellPath,
|
||||
["explorer.exe", "/select,\"${customizeDir.absolute.path}\"\\"]);
|
||||
}
|
||||
|
||||
updateLangCfg(bool enable) async {
|
||||
final status = await getLangCfgEnableLang(lang: selectedLanguage);
|
||||
Future<void> updateLangCfg(bool enable) async {
|
||||
final selectedLanguage = state.selectedLanguage!;
|
||||
final status = await _getLangCfgEnableLang(lang: selectedLanguage);
|
||||
final exists = await cfgFile.exists();
|
||||
if (status == enable) {
|
||||
await _updateStatus();
|
||||
@ -276,20 +177,112 @@ class LocalizationUIModel extends BaseUIModel {
|
||||
await cfgFile.create(recursive: true);
|
||||
await cfgFile.writeAsString(newStr.toString());
|
||||
await _updateStatus();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void goFeedback() {
|
||||
launchUrlString(URLConf.feedbackUrl);
|
||||
}
|
||||
|
||||
VoidCallback? doDelIniFile() {
|
||||
return () async {
|
||||
final iniFile = File(
|
||||
"${scDataDir.absolute.path}\\Localization\\$selectedLanguage\\global.ini");
|
||||
"${scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
|
||||
if (await iniFile.exists()) await iniFile.delete();
|
||||
await updateLangCfg(false);
|
||||
await _updateStatus();
|
||||
};
|
||||
}
|
||||
|
||||
/// read locale active
|
||||
void toggleCustomize() {
|
||||
state = state.copyWith(enableCustomize: !state.enableCustomize);
|
||||
}
|
||||
|
||||
String getCustomizeFileName(String path) {
|
||||
return path.split("\\").last;
|
||||
}
|
||||
|
||||
VoidCallback? doLocalInstall(String filePath) {
|
||||
if (state.workingVersion.isNotEmpty) return null;
|
||||
return () async {
|
||||
final f = File(filePath);
|
||||
if (!await f.exists()) return;
|
||||
state = state.copyWith(workingVersion: filePath);
|
||||
final str = await f.readAsString();
|
||||
await _installFormString(
|
||||
StringBuffer(str), "自定义_${getCustomizeFileName(filePath)}");
|
||||
state = state.copyWith(workingVersion: "");
|
||||
};
|
||||
}
|
||||
|
||||
_installFormString(StringBuffer globalIni, String versionName) async {
|
||||
final iniFile = File(
|
||||
"${scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini");
|
||||
if (versionName.isNotEmpty) {
|
||||
if (!globalIni.toString().endsWith("\n")) {
|
||||
globalIni.write("\n");
|
||||
}
|
||||
globalIni.write("_starcitizen_doctor_localization_version=$versionName");
|
||||
}
|
||||
|
||||
/// write cfg
|
||||
if (await cfgFile.exists()) {}
|
||||
|
||||
/// write ini
|
||||
if (await iniFile.exists()) {
|
||||
await iniFile.delete();
|
||||
}
|
||||
await iniFile.create(recursive: true);
|
||||
await iniFile.writeAsString("\uFEFF${globalIni.toString().trim()}",
|
||||
flush: true);
|
||||
await updateLangCfg(true);
|
||||
await _updateStatus();
|
||||
}
|
||||
|
||||
openDir(BuildContext context) async {
|
||||
showToast(context,
|
||||
"即将打开本地化文件夹,请将自定义的 任意名称.ini 文件放入 Customize_ini 文件夹。\n\n添加新文件后未显示请使用右上角刷新按钮。\n\n安装时请确保选择了正确的语言。");
|
||||
await Process.run(SystemHelper.powershellPath,
|
||||
["explorer.exe", "/select,\"${customizeDir.absolute.path}\"\\"]);
|
||||
}
|
||||
|
||||
VoidCallback? doRemoteInstall(
|
||||
BuildContext context, ScLocalizationData value) {
|
||||
return () async {
|
||||
AnalyticsApi.touch("install_localization");
|
||||
final downloadUrl =
|
||||
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
|
||||
final savePath =
|
||||
File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
|
||||
try {
|
||||
state = state.copyWith(workingVersion: value.versionName!);
|
||||
if (!await savePath.exists()) {
|
||||
// download
|
||||
dPrint("downloading file to $savePath");
|
||||
final r = await RSHttp.get(downloadUrl);
|
||||
if (r.statusCode == 200 && r.data != null) {
|
||||
await savePath.writeAsBytes(r.data!);
|
||||
} else {
|
||||
throw "statusCode Error : ${r.statusCode}";
|
||||
}
|
||||
} else {
|
||||
dPrint("use cache $savePath");
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
// check file
|
||||
final globalIni = await compute(_readArchive, savePath.absolute.path);
|
||||
if (globalIni.isEmpty) {
|
||||
throw "文件受损,请重新下载";
|
||||
}
|
||||
await _installFormString(globalIni, value.versionName ?? "");
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
await showToast(context, "安装出错!\n\n $e");
|
||||
if (await savePath.exists()) await savePath.delete();
|
||||
}
|
||||
state = state.copyWith(workingVersion: "");
|
||||
};
|
||||
}
|
||||
|
||||
static StringBuffer _readArchive(String savePath) {
|
||||
final inputStream = InputFileStream(savePath);
|
||||
final archive =
|
||||
@ -308,87 +301,74 @@ class LocalizationUIModel extends BaseUIModel {
|
||||
return globalIni;
|
||||
}
|
||||
|
||||
VoidCallback? doLocalInstall(String filePath) {
|
||||
if (workingVersion.isNotEmpty) return null;
|
||||
return () async {
|
||||
final f = File(filePath);
|
||||
if (!await f.exists()) return;
|
||||
workingVersion = filePath;
|
||||
notifyListeners();
|
||||
final str = await f.readAsString();
|
||||
await _installFormString(
|
||||
StringBuffer(str), "自定义_${getCustomizeFileName(filePath)}");
|
||||
workingVersion = "";
|
||||
notifyListeners();
|
||||
String? getScInstallPath() {
|
||||
return ref.read(homeUIModelProvider).scInstalledPath;
|
||||
}
|
||||
|
||||
void selectLang(String v) {
|
||||
state = state.copyWith(selectedLanguage: v);
|
||||
_loadData();
|
||||
}
|
||||
|
||||
VoidCallback? onBack(BuildContext context) {
|
||||
if (state.workingVersion.isNotEmpty) return null;
|
||||
return () {
|
||||
Navigator.pop(context);
|
||||
};
|
||||
}
|
||||
|
||||
void _checkUserCfg() async {
|
||||
final userCfgFile = File("$scInstallPath\\USER.cfg");
|
||||
if (await userCfgFile.exists()) {
|
||||
final cfgString = await userCfgFile.readAsString();
|
||||
if (cfgString.contains("g_language") &&
|
||||
!cfgString.contains("g_language=$selectedLanguage")) {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!,
|
||||
"是否移除不兼容的汉化参数",
|
||||
const Text(
|
||||
"USER.cfg 包含不兼容的汉化参数,这可能是以前的汉化文件的残留信息。\n\n这将可能导致汉化无效或乱码,点击确认为您一键移除(不会影响其他配置)。"),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .35));
|
||||
if (ok == true) {
|
||||
var finalString = "";
|
||||
for (var item in cfgString.split("\n")) {
|
||||
if (!item.trim().startsWith("g_language")) {
|
||||
finalString = "$finalString$item\n";
|
||||
}
|
||||
}
|
||||
await userCfgFile.delete();
|
||||
await userCfgFile.create();
|
||||
await userCfgFile.writeAsString(finalString, flush: true);
|
||||
reloadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
VoidCallback? doRefresh() {
|
||||
if (state.workingVersion.isNotEmpty) return null;
|
||||
return () {
|
||||
state = state.copyWith(apiLocalizationData: null);
|
||||
_loadData();
|
||||
};
|
||||
}
|
||||
|
||||
static Future<MapEntry<String, bool>?> checkLocalizationUpdates(
|
||||
List<String> gameInstallPaths) async {
|
||||
final updateInfo = <String, bool>{};
|
||||
for (var kv in languageSupport.entries) {
|
||||
final l = await Api.getScLocalizationData(kv.key);
|
||||
for (var value in gameInstallPaths) {
|
||||
final iniPath = "$value\\data\\Localization\\${kv.key}\\global.ini";
|
||||
if (!await File(iniPath).exists()) {
|
||||
continue;
|
||||
}
|
||||
final installed = await getInstalledIniVersion(iniPath);
|
||||
if (installed == "游戏内置" || installed == "自定义文件") {
|
||||
continue;
|
||||
}
|
||||
final hasUpdate = l
|
||||
.where((element) => element.versionName == installed)
|
||||
.firstOrNull ==
|
||||
null;
|
||||
updateInfo[value] = hasUpdate;
|
||||
void _scanCustomizeDir() {
|
||||
final fileList = customizeDir.listSync();
|
||||
final customizeList = <String>[];
|
||||
for (var value in fileList) {
|
||||
if (value is File && value.path.endsWith(".ini")) {
|
||||
customizeList.add(value.absolute.path);
|
||||
}
|
||||
}
|
||||
log_utils.dPrint("checkLocalizationUpdates ==== $updateInfo");
|
||||
for (var v in updateInfo.entries) {
|
||||
if (v.value) {
|
||||
for (var element in AppConf.gameChannels) {
|
||||
if (v.key.contains("StarCitizen\\$element")) {
|
||||
return MapEntry(element, true);
|
||||
} else {
|
||||
return const MapEntry("", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
state = state.copyWith(customizeList: customizeList);
|
||||
}
|
||||
|
||||
void goFeedback() {
|
||||
launchUrlString(URLConf.feedbackUrl);
|
||||
_updateStatus() async {
|
||||
final patchStatus = MapEntry(
|
||||
await _getLangCfgEnableLang(lang: state.selectedLanguage!),
|
||||
await _getInstalledIniVersion(
|
||||
"${scDataDir.absolute.path}\\Localization\\${state.selectedLanguage}\\global.ini"));
|
||||
state = state.copyWith(patchStatus: patchStatus);
|
||||
}
|
||||
|
||||
Future<bool> _getLangCfgEnableLang({String lang = ""}) async {
|
||||
if (!await cfgFile.exists()) return false;
|
||||
final str = (await cfgFile.readAsString()).replaceAll(" ", "");
|
||||
return str.contains("sys_languages=$lang") &&
|
||||
str.contains("g_language=$lang") &&
|
||||
str.contains("g_languageAudio=english");
|
||||
}
|
||||
|
||||
static Future<String> _getInstalledIniVersion(String iniPath) async {
|
||||
final iniFile = File(iniPath);
|
||||
if (!await iniFile.exists()) return "游戏内置";
|
||||
final iniStringSplit = (await iniFile.readAsString()).split("\n");
|
||||
for (var i = iniStringSplit.length - 1; i > 0; i--) {
|
||||
if (iniStringSplit[i]
|
||||
.contains("_starcitizen_doctor_localization_version=")) {
|
||||
final v = iniStringSplit[i]
|
||||
.trim()
|
||||
.split("_starcitizen_doctor_localization_version=")[1];
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return "自定义文件";
|
||||
}
|
||||
|
||||
Future checkLangUpdate() async {
|
||||
// TODO 检查更新
|
||||
}
|
||||
}
|
||||
|
272
lib/ui/home/localization/localization_ui_model.freezed.dart
Normal file
272
lib/ui/home/localization/localization_ui_model.freezed.dart
Normal file
@ -0,0 +1,272 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'localization_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LocalizationUIState {
|
||||
String? get selectedLanguage => throw _privateConstructorUsedError;
|
||||
Map<String, ScLocalizationData>? get apiLocalizationData =>
|
||||
throw _privateConstructorUsedError;
|
||||
String get workingVersion => throw _privateConstructorUsedError;
|
||||
MapEntry<bool, String>? get patchStatus => throw _privateConstructorUsedError;
|
||||
List<String>? get customizeList => throw _privateConstructorUsedError;
|
||||
bool get enableCustomize => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$LocalizationUIStateCopyWith<LocalizationUIState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LocalizationUIStateCopyWith<$Res> {
|
||||
factory $LocalizationUIStateCopyWith(
|
||||
LocalizationUIState value, $Res Function(LocalizationUIState) then) =
|
||||
_$LocalizationUIStateCopyWithImpl<$Res, LocalizationUIState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? selectedLanguage,
|
||||
Map<String, ScLocalizationData>? apiLocalizationData,
|
||||
String workingVersion,
|
||||
MapEntry<bool, String>? patchStatus,
|
||||
List<String>? customizeList,
|
||||
bool enableCustomize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LocalizationUIStateCopyWithImpl<$Res, $Val extends LocalizationUIState>
|
||||
implements $LocalizationUIStateCopyWith<$Res> {
|
||||
_$LocalizationUIStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? selectedLanguage = freezed,
|
||||
Object? apiLocalizationData = freezed,
|
||||
Object? workingVersion = null,
|
||||
Object? patchStatus = freezed,
|
||||
Object? customizeList = freezed,
|
||||
Object? enableCustomize = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
selectedLanguage: freezed == selectedLanguage
|
||||
? _value.selectedLanguage
|
||||
: selectedLanguage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
apiLocalizationData: freezed == apiLocalizationData
|
||||
? _value.apiLocalizationData
|
||||
: apiLocalizationData // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, ScLocalizationData>?,
|
||||
workingVersion: null == workingVersion
|
||||
? _value.workingVersion
|
||||
: workingVersion // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
patchStatus: freezed == patchStatus
|
||||
? _value.patchStatus
|
||||
: patchStatus // ignore: cast_nullable_to_non_nullable
|
||||
as MapEntry<bool, String>?,
|
||||
customizeList: freezed == customizeList
|
||||
? _value.customizeList
|
||||
: customizeList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
enableCustomize: null == enableCustomize
|
||||
? _value.enableCustomize
|
||||
: enableCustomize // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LocalizationUIStateImplCopyWith<$Res>
|
||||
implements $LocalizationUIStateCopyWith<$Res> {
|
||||
factory _$$LocalizationUIStateImplCopyWith(_$LocalizationUIStateImpl value,
|
||||
$Res Function(_$LocalizationUIStateImpl) then) =
|
||||
__$$LocalizationUIStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String? selectedLanguage,
|
||||
Map<String, ScLocalizationData>? apiLocalizationData,
|
||||
String workingVersion,
|
||||
MapEntry<bool, String>? patchStatus,
|
||||
List<String>? customizeList,
|
||||
bool enableCustomize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LocalizationUIStateImplCopyWithImpl<$Res>
|
||||
extends _$LocalizationUIStateCopyWithImpl<$Res, _$LocalizationUIStateImpl>
|
||||
implements _$$LocalizationUIStateImplCopyWith<$Res> {
|
||||
__$$LocalizationUIStateImplCopyWithImpl(_$LocalizationUIStateImpl _value,
|
||||
$Res Function(_$LocalizationUIStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? selectedLanguage = freezed,
|
||||
Object? apiLocalizationData = freezed,
|
||||
Object? workingVersion = null,
|
||||
Object? patchStatus = freezed,
|
||||
Object? customizeList = freezed,
|
||||
Object? enableCustomize = null,
|
||||
}) {
|
||||
return _then(_$LocalizationUIStateImpl(
|
||||
selectedLanguage: freezed == selectedLanguage
|
||||
? _value.selectedLanguage
|
||||
: selectedLanguage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
apiLocalizationData: freezed == apiLocalizationData
|
||||
? _value._apiLocalizationData
|
||||
: apiLocalizationData // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, ScLocalizationData>?,
|
||||
workingVersion: null == workingVersion
|
||||
? _value.workingVersion
|
||||
: workingVersion // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
patchStatus: freezed == patchStatus
|
||||
? _value.patchStatus
|
||||
: patchStatus // ignore: cast_nullable_to_non_nullable
|
||||
as MapEntry<bool, String>?,
|
||||
customizeList: freezed == customizeList
|
||||
? _value._customizeList
|
||||
: customizeList // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
enableCustomize: null == enableCustomize
|
||||
? _value.enableCustomize
|
||||
: enableCustomize // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$LocalizationUIStateImpl implements _LocalizationUIState {
|
||||
const _$LocalizationUIStateImpl(
|
||||
{this.selectedLanguage,
|
||||
final Map<String, ScLocalizationData>? apiLocalizationData,
|
||||
this.workingVersion = "",
|
||||
this.patchStatus,
|
||||
final List<String>? customizeList,
|
||||
this.enableCustomize = false})
|
||||
: _apiLocalizationData = apiLocalizationData,
|
||||
_customizeList = customizeList;
|
||||
|
||||
@override
|
||||
final String? selectedLanguage;
|
||||
final Map<String, ScLocalizationData>? _apiLocalizationData;
|
||||
@override
|
||||
Map<String, ScLocalizationData>? get apiLocalizationData {
|
||||
final value = _apiLocalizationData;
|
||||
if (value == null) return null;
|
||||
if (_apiLocalizationData is EqualUnmodifiableMapView)
|
||||
return _apiLocalizationData;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final String workingVersion;
|
||||
@override
|
||||
final MapEntry<bool, String>? patchStatus;
|
||||
final List<String>? _customizeList;
|
||||
@override
|
||||
List<String>? get customizeList {
|
||||
final value = _customizeList;
|
||||
if (value == null) return null;
|
||||
if (_customizeList is EqualUnmodifiableListView) return _customizeList;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool enableCustomize;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LocalizationUIState(selectedLanguage: $selectedLanguage, apiLocalizationData: $apiLocalizationData, workingVersion: $workingVersion, patchStatus: $patchStatus, customizeList: $customizeList, enableCustomize: $enableCustomize)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LocalizationUIStateImpl &&
|
||||
(identical(other.selectedLanguage, selectedLanguage) ||
|
||||
other.selectedLanguage == selectedLanguage) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._apiLocalizationData, _apiLocalizationData) &&
|
||||
(identical(other.workingVersion, workingVersion) ||
|
||||
other.workingVersion == workingVersion) &&
|
||||
(identical(other.patchStatus, patchStatus) ||
|
||||
other.patchStatus == patchStatus) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._customizeList, _customizeList) &&
|
||||
(identical(other.enableCustomize, enableCustomize) ||
|
||||
other.enableCustomize == enableCustomize));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
selectedLanguage,
|
||||
const DeepCollectionEquality().hash(_apiLocalizationData),
|
||||
workingVersion,
|
||||
patchStatus,
|
||||
const DeepCollectionEquality().hash(_customizeList),
|
||||
enableCustomize);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LocalizationUIStateImplCopyWith<_$LocalizationUIStateImpl> get copyWith =>
|
||||
__$$LocalizationUIStateImplCopyWithImpl<_$LocalizationUIStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _LocalizationUIState implements LocalizationUIState {
|
||||
const factory _LocalizationUIState(
|
||||
{final String? selectedLanguage,
|
||||
final Map<String, ScLocalizationData>? apiLocalizationData,
|
||||
final String workingVersion,
|
||||
final MapEntry<bool, String>? patchStatus,
|
||||
final List<String>? customizeList,
|
||||
final bool enableCustomize}) = _$LocalizationUIStateImpl;
|
||||
|
||||
@override
|
||||
String? get selectedLanguage;
|
||||
@override
|
||||
Map<String, ScLocalizationData>? get apiLocalizationData;
|
||||
@override
|
||||
String get workingVersion;
|
||||
@override
|
||||
MapEntry<bool, String>? get patchStatus;
|
||||
@override
|
||||
List<String>? get customizeList;
|
||||
@override
|
||||
bool get enableCustomize;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$LocalizationUIStateImplCopyWith<_$LocalizationUIStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/localization/localization_ui_model.g.dart
Normal file
27
lib/ui/home/localization/localization_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'localization_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$localizationUIModelHash() =>
|
||||
r'654fd38b5f38bee5fd2cab69ab003846a311a4ff';
|
||||
|
||||
/// See also [LocalizationUIModel].
|
||||
@ProviderFor(LocalizationUIModel)
|
||||
final localizationUIModelProvider = AutoDisposeNotifierProvider<
|
||||
LocalizationUIModel, LocalizationUIState>.internal(
|
||||
LocalizationUIModel.new,
|
||||
name: r'localizationUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$localizationUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$LocalizationUIModel = AutoDisposeNotifier<LocalizationUIState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,115 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/widgets/cache_image.dart';
|
||||
|
||||
import 'login_dialog_ui_model.dart';
|
||||
|
||||
class LoginDialog extends BaseUI<LoginDialogModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, LoginDialogModel model) {
|
||||
return ContentDialog(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .56,
|
||||
),
|
||||
title: (model.loginStatus == 2) ? null : const Text("一键启动"),
|
||||
content: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 230),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Row(),
|
||||
if (model.loginStatus == 0) ...[
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("登录中..."),
|
||||
const SizedBox(height: 12),
|
||||
const ProgressRing(),
|
||||
if (model.isDeviceSupportWinHello)
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
"* 若开启了自动填充,请留意弹出的 Windows Hello 窗口",
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Colors.white.withOpacity(.6)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
] else if (model.loginStatus == 1) ...[
|
||||
Text("请输入RSI账户 [${model.nickname}] 的邮箱,以保存登录状态(输入错误会导致无法进入游戏!)"),
|
||||
const SizedBox(height: 12),
|
||||
TextFormBox(
|
||||
// controller: model.emailCtrl,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
"*该操作同一账号只需执行一次,输入错误请在盒子设置中清理,切换账号请在汉化浏览器中操作。",
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.white.withOpacity(.6),
|
||||
),
|
||||
)
|
||||
] else if (model.loginStatus == 2 || model.loginStatus == 3) ...[
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"欢迎回来!",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (model.avatarUrl != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: model.avatarUrl!,
|
||||
width: 128,
|
||||
height: 128,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
model.nickname,
|
||||
style: const TextStyle(
|
||||
fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(model.loginStatus == 2
|
||||
? "正在为您启动游戏..."
|
||||
: "正在等待优化CPU参数..."),
|
||||
const SizedBox(height: 12),
|
||||
const ProgressRing(),
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: const [
|
||||
// if (model.loginStatus == 1) ...[
|
||||
// Button(
|
||||
// child: const Padding(
|
||||
// padding: EdgeInsets.all(4),
|
||||
// child: Text("取消"),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.pop(context);
|
||||
// }),
|
||||
// const SizedBox(width: 80),
|
||||
// FilledButton(
|
||||
// child: const Padding(
|
||||
// padding: EdgeInsets.all(4),
|
||||
// child: Text("保存"),
|
||||
// ),
|
||||
// onPressed: () => model.onSaveEmail()),
|
||||
// ],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, LoginDialogModel model) => "";
|
||||
}
|
@ -1,15 +1,23 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/data/game_performance_data.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'performance_ui_model.dart';
|
||||
|
||||
class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
class HomePerformanceUI extends HookConsumerWidget {
|
||||
const HomePerformanceUI({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, PerformanceUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(homePerformanceUIModelProvider);
|
||||
final model = ref.read(homePerformanceUIModelProvider.notifier);
|
||||
|
||||
var content = makeLoading(context);
|
||||
|
||||
if (model.performanceMap != null) {
|
||||
if (state.performanceMap != null) {
|
||||
content = Stack(
|
||||
children: [
|
||||
Padding(
|
||||
@ -20,7 +28,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
if (model.showGraphicsPerformanceTip)
|
||||
if (state.showGraphicsPerformanceTip)
|
||||
InfoBar(
|
||||
title: const Text("图形优化提示"),
|
||||
content: const Text(
|
||||
@ -32,7 +40,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"当前状态:${model.enabled ? "已应用" : "未应用"}",
|
||||
"当前状态:${state.enabled ? "已应用" : "未应用"}",
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(width: 32),
|
||||
@ -72,7 +80,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
" 恢复默认 ",
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
onPressed: () => model.clean()),
|
||||
onPressed: () => model.clean(context)),
|
||||
const SizedBox(width: 24),
|
||||
Button(
|
||||
child: const Text(
|
||||
@ -98,16 +106,16 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: 1,
|
||||
crossAxisSpacing: 1,
|
||||
itemCount: model.performanceMap!.length,
|
||||
itemCount: state.performanceMap!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return makeItemGroup(
|
||||
model.performanceMap!.entries.elementAt(index));
|
||||
return makeItemGroup(context,
|
||||
state.performanceMap!.entries.elementAt(index), model);
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (model.workingString.isNotEmpty)
|
||||
if (state.workingString.isNotEmpty)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(150),
|
||||
@ -118,7 +126,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
children: [
|
||||
const ProgressRing(),
|
||||
const SizedBox(height: 12),
|
||||
Text(model.workingString),
|
||||
Text(state.workingString),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -127,10 +135,16 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
return makeDefaultPage(context, model, content: content);
|
||||
return makeDefaultPage(context,
|
||||
title: "性能优化 -> ${model.scPath}",
|
||||
useBodyContainer: true,
|
||||
content: content);
|
||||
}
|
||||
|
||||
Widget makeItemGroup(MapEntry<String?, List<GamePerformanceData>> group) {
|
||||
Widget makeItemGroup(
|
||||
BuildContext context,
|
||||
MapEntry<String?, List<GamePerformanceData>> group,
|
||||
HomePerformanceUIModel model) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Container(
|
||||
@ -152,7 +166,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
color: FluentTheme.of(context).cardColor.withOpacity(.2),
|
||||
height: 1),
|
||||
const SizedBox(height: 6),
|
||||
for (final item in group.value) makeItem(item)
|
||||
for (final item in group.value) makeItem(context, item, model)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -160,8 +174,8 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeItem(GamePerformanceData item) {
|
||||
final model = ref.watch(provider);
|
||||
Widget makeItem(BuildContext context, GamePerformanceData item,
|
||||
HomePerformanceUIModel model) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
||||
child: Column(
|
||||
@ -191,10 +205,10 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
v >= (item.min ?? 0)) {
|
||||
item.value = v;
|
||||
}
|
||||
setState(() {});
|
||||
model.updateState();
|
||||
},
|
||||
onTapOutside: (e) {
|
||||
setState(() {});
|
||||
model.updateState();
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -207,7 +221,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
max: item.max?.toDouble() ?? 0,
|
||||
onChanged: (double value) {
|
||||
item.value = value.toInt();
|
||||
setState(() {});
|
||||
model.updateState();
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -222,7 +236,7 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
checked: item.value == 1,
|
||||
onChanged: (bool value) {
|
||||
item.value = value ? 1 : 0;
|
||||
setState(() {});
|
||||
model.updateState();
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -261,8 +275,4 @@ class PerformanceUI extends BaseUI<PerformanceUIModel> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, PerformanceUIModel model) =>
|
||||
"性能优化 ${model.scPath}";
|
||||
}
|
||||
|
@ -1,70 +1,117 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/data/game_performance_data.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
|
||||
class PerformanceUIModel extends BaseUIModel {
|
||||
String scPath;
|
||||
part 'performance_ui_model.freezed.dart';
|
||||
|
||||
PerformanceUIModel(this.scPath);
|
||||
part 'performance_ui_model.g.dart';
|
||||
|
||||
TextEditingController customizeCtrl = TextEditingController();
|
||||
@freezed
|
||||
class HomePerformanceUIState with _$HomePerformanceUIState {
|
||||
const factory HomePerformanceUIState({
|
||||
@Default(true) bool showGraphicsPerformanceTip,
|
||||
@Default(false) bool enabled,
|
||||
Map<String, List<GamePerformanceData>>? performanceMap,
|
||||
@Default("") String workingString,
|
||||
}) = _HomePerformanceUIState;
|
||||
}
|
||||
|
||||
Map<String?, List<GamePerformanceData>>? performanceMap;
|
||||
@riverpod
|
||||
class HomePerformanceUIModel extends _$HomePerformanceUIModel {
|
||||
String get scPath => ref.read(homeUIModelProvider).scInstalledPath!;
|
||||
|
||||
List<String> inAppKeys = [];
|
||||
final customizeCtrl = TextEditingController(text: "");
|
||||
|
||||
String workingString = "";
|
||||
final List<String> _inAppKeys = [];
|
||||
|
||||
late final confFile = File("$scPath\\USER.cfg");
|
||||
|
||||
bool enabled = false;
|
||||
|
||||
bool showGraphicsPerformanceTip = false;
|
||||
static const _graphicsPerformanceTipVersion = 1;
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
HomePerformanceUIState build() {
|
||||
state = const HomePerformanceUIState();
|
||||
_init();
|
||||
return state;
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
customizeCtrl.clear();
|
||||
inAppKeys.clear();
|
||||
_inAppKeys.clear();
|
||||
final String jsonString =
|
||||
await rootBundle.loadString('assets/performance.json');
|
||||
final list = json.decode(jsonString);
|
||||
|
||||
if (list is List) {
|
||||
performanceMap = {};
|
||||
final performanceMap = <String, List<GamePerformanceData>>{};
|
||||
for (var element in list) {
|
||||
final item = GamePerformanceData.fromJson(element);
|
||||
if (item.key != "customize") {
|
||||
inAppKeys.add(item.key ?? "");
|
||||
_inAppKeys.add(item.key ?? "");
|
||||
}
|
||||
performanceMap?[item.group] ??= [];
|
||||
performanceMap?[item.group]?.add(item);
|
||||
performanceMap[item.group!] ??= [];
|
||||
performanceMap[item.group]?.add(item);
|
||||
}
|
||||
state = state.copyWith(performanceMap: performanceMap);
|
||||
}
|
||||
|
||||
if (await confFile.exists()) {
|
||||
await _readConf();
|
||||
} else {
|
||||
enabled = false;
|
||||
state = state.copyWith(enabled: false);
|
||||
}
|
||||
|
||||
final box = await Hive.openBox("app_conf");
|
||||
final v = box.get("close_graphics_performance_tip", defaultValue: -1);
|
||||
showGraphicsPerformanceTip = v != _graphicsPerformanceTipVersion;
|
||||
notifyListeners();
|
||||
state = state.copyWith(
|
||||
showGraphicsPerformanceTip: v != _graphicsPerformanceTipVersion);
|
||||
}
|
||||
|
||||
_readConf() async {
|
||||
if (state.performanceMap == null) return;
|
||||
state = state.copyWith(enabled: true);
|
||||
|
||||
final confString = await confFile.readAsString();
|
||||
for (var value in confString.split("\n")) {
|
||||
final kv = value.split("=");
|
||||
for (var m in state.performanceMap!.entries) {
|
||||
for (var value in m.value) {
|
||||
if (value.key == kv[0].trim()) {
|
||||
final v = int.tryParse(kv[1].trim());
|
||||
if (v != null) value.value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kv.length == 2 && !_inAppKeys.contains(kv[0].trim())) {
|
||||
customizeCtrl.text =
|
||||
"${customizeCtrl.text}${kv[0].trim()}=${kv[1].trim()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeTip() async {
|
||||
final box = await Hive.openBox("app_conf");
|
||||
await box.put(
|
||||
"close_graphics_performance_tip", _graphicsPerformanceTipVersion);
|
||||
_init();
|
||||
}
|
||||
|
||||
onChangePreProfile(String key) {
|
||||
switch (key) {
|
||||
case "low":
|
||||
performanceMap?.forEach((key, v) {
|
||||
if (key?.contains("图形") ?? false) {
|
||||
state.performanceMap?.forEach((key, v) {
|
||||
if (key.contains("图形")) {
|
||||
for (var element in v) {
|
||||
element.value = element.min;
|
||||
}
|
||||
@ -72,8 +119,8 @@ class PerformanceUIModel extends BaseUIModel {
|
||||
});
|
||||
break;
|
||||
case "medium":
|
||||
performanceMap?.forEach((key, v) {
|
||||
if (key?.contains("图形") ?? false) {
|
||||
state.performanceMap?.forEach((key, v) {
|
||||
if (key.contains("图形")) {
|
||||
for (var element in v) {
|
||||
element.value = ((element.max ?? 0) ~/ 2);
|
||||
}
|
||||
@ -81,8 +128,8 @@ class PerformanceUIModel extends BaseUIModel {
|
||||
});
|
||||
break;
|
||||
case "high":
|
||||
performanceMap?.forEach((key, v) {
|
||||
if (key?.contains("图形") ?? false) {
|
||||
state.performanceMap?.forEach((key, v) {
|
||||
if (key.contains("图形")) {
|
||||
for (var element in v) {
|
||||
element.value = ((element.max ?? 0) / 1.5).ceil();
|
||||
}
|
||||
@ -90,8 +137,8 @@ class PerformanceUIModel extends BaseUIModel {
|
||||
});
|
||||
break;
|
||||
case "ultra":
|
||||
performanceMap?.forEach((key, v) {
|
||||
if (key?.contains("图形") ?? false) {
|
||||
state.performanceMap?.forEach((key, v) {
|
||||
if (key.contains("图形")) {
|
||||
for (var element in v) {
|
||||
element.value = element.max;
|
||||
}
|
||||
@ -99,16 +146,50 @@ class PerformanceUIModel extends BaseUIModel {
|
||||
});
|
||||
break;
|
||||
}
|
||||
notifyListeners();
|
||||
state = state.copyWith();
|
||||
}
|
||||
|
||||
refresh() async {
|
||||
_init();
|
||||
}
|
||||
|
||||
clean(BuildContext context) async {
|
||||
state = state.copyWith(workingString: "删除配置文件...");
|
||||
if (await confFile.exists()) {
|
||||
await confFile.delete(recursive: true);
|
||||
}
|
||||
state = state.copyWith(workingString: "清理着色器");
|
||||
if (!context.mounted) return;
|
||||
await cleanShaderCache(context);
|
||||
state = state.copyWith(workingString: "完成...");
|
||||
await await Future.delayed(const Duration(milliseconds: 300));
|
||||
await _init();
|
||||
state = state.copyWith(workingString: "");
|
||||
}
|
||||
|
||||
cleanShaderCache(BuildContext? context) async {
|
||||
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
|
||||
final l =
|
||||
await Directory(gameShaderCachePath!).list(recursive: false).toList();
|
||||
for (var value in l) {
|
||||
if (value is Directory) {
|
||||
if (!value.absolute.path.contains("Crashes")) {
|
||||
await value.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
if (context != null && context.mounted) {
|
||||
showToast(context, "清理着色器后首次进入游戏可能会出现卡顿,请耐心等待游戏初始化完毕。");
|
||||
}
|
||||
}
|
||||
|
||||
applyProfile(bool cleanShader) async {
|
||||
if (performanceMap == null) return;
|
||||
if (state.performanceMap == null) return;
|
||||
AnalyticsApi.touch("performance_apply");
|
||||
workingString = "生成配置文件";
|
||||
notifyListeners();
|
||||
state = state.copyWith(workingString: "生成配置文件");
|
||||
String conf = "";
|
||||
for (var v in performanceMap!.entries) {
|
||||
for (var v in state.performanceMap!.entries) {
|
||||
for (var c in v.value) {
|
||||
if (c.key != "customize") {
|
||||
conf = "$conf${c.key}=${c.value}\n";
|
||||
@ -125,87 +206,23 @@ class PerformanceUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
workingString = "写出配置文件";
|
||||
notifyListeners();
|
||||
state = state.copyWith(workingString: "写出配置文件");
|
||||
if (await confFile.exists()) {
|
||||
await confFile.delete();
|
||||
}
|
||||
await confFile.create();
|
||||
await confFile.writeAsString(conf);
|
||||
if (cleanShader) {
|
||||
workingString = "清理着色器";
|
||||
notifyListeners();
|
||||
await _cleanShaderCache();
|
||||
state = state.copyWith(workingString: "清理着色器");
|
||||
await cleanShaderCache(null);
|
||||
}
|
||||
workingString = "完成...";
|
||||
notifyListeners();
|
||||
state = state.copyWith(workingString: "完成...");
|
||||
await await Future.delayed(const Duration(milliseconds: 300));
|
||||
await reloadData();
|
||||
workingString = "";
|
||||
notifyListeners();
|
||||
await _init();
|
||||
state = state.copyWith(workingString: "清理着色器");
|
||||
}
|
||||
|
||||
Future<void> _cleanShaderCache() async {
|
||||
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
|
||||
final l =
|
||||
await Directory(gameShaderCachePath!).list(recursive: false).toList();
|
||||
for (var value in l) {
|
||||
if (value is Directory) {
|
||||
if (!value.absolute.path.contains("Crashes")) {
|
||||
await value.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
showToast(context!, "清理着色器后首次进入游戏可能会出现卡顿,请耐心等待游戏初始化完毕。");
|
||||
}
|
||||
|
||||
_readConf() async {
|
||||
if (performanceMap == null) return;
|
||||
enabled = true;
|
||||
final confString = await confFile.readAsString();
|
||||
for (var value in confString.split("\n")) {
|
||||
final kv = value.split("=");
|
||||
for (var m in performanceMap!.entries) {
|
||||
for (var value in m.value) {
|
||||
if (value.key == kv[0].trim()) {
|
||||
final v = int.tryParse(kv[1].trim());
|
||||
if (v != null) value.value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kv.length == 2 && !inAppKeys.contains(kv[0].trim())) {
|
||||
customizeCtrl.text =
|
||||
"${customizeCtrl.text}${kv[0].trim()}=${kv[1].trim()}\n";
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
clean() async {
|
||||
workingString = "删除配置文件...";
|
||||
notifyListeners();
|
||||
if (await confFile.exists()) {
|
||||
await confFile.delete(recursive: true);
|
||||
}
|
||||
workingString = "清理着色器";
|
||||
notifyListeners();
|
||||
await _cleanShaderCache();
|
||||
workingString = "完成...";
|
||||
await await Future.delayed(const Duration(milliseconds: 300));
|
||||
await reloadData();
|
||||
workingString = "";
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
refresh() async {
|
||||
await reloadData();
|
||||
}
|
||||
|
||||
closeTip() async {
|
||||
final box = await Hive.openBox("app_conf");
|
||||
await box.put(
|
||||
"close_graphics_performance_tip", _graphicsPerformanceTipVersion);
|
||||
loadData();
|
||||
updateState() {
|
||||
state = state.copyWith();
|
||||
}
|
||||
}
|
||||
|
224
lib/ui/home/performance/performance_ui_model.freezed.dart
Normal file
224
lib/ui/home/performance/performance_ui_model.freezed.dart
Normal file
@ -0,0 +1,224 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'performance_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$HomePerformanceUIState {
|
||||
bool get showGraphicsPerformanceTip => throw _privateConstructorUsedError;
|
||||
bool get enabled => throw _privateConstructorUsedError;
|
||||
Map<String, List<GamePerformanceData>>? get performanceMap =>
|
||||
throw _privateConstructorUsedError;
|
||||
String get workingString => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$HomePerformanceUIStateCopyWith<HomePerformanceUIState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $HomePerformanceUIStateCopyWith<$Res> {
|
||||
factory $HomePerformanceUIStateCopyWith(HomePerformanceUIState value,
|
||||
$Res Function(HomePerformanceUIState) then) =
|
||||
_$HomePerformanceUIStateCopyWithImpl<$Res, HomePerformanceUIState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool showGraphicsPerformanceTip,
|
||||
bool enabled,
|
||||
Map<String, List<GamePerformanceData>>? performanceMap,
|
||||
String workingString});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$HomePerformanceUIStateCopyWithImpl<$Res,
|
||||
$Val extends HomePerformanceUIState>
|
||||
implements $HomePerformanceUIStateCopyWith<$Res> {
|
||||
_$HomePerformanceUIStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? showGraphicsPerformanceTip = null,
|
||||
Object? enabled = null,
|
||||
Object? performanceMap = freezed,
|
||||
Object? workingString = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
showGraphicsPerformanceTip: null == showGraphicsPerformanceTip
|
||||
? _value.showGraphicsPerformanceTip
|
||||
: showGraphicsPerformanceTip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
enabled: null == enabled
|
||||
? _value.enabled
|
||||
: enabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
performanceMap: freezed == performanceMap
|
||||
? _value.performanceMap
|
||||
: performanceMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, List<GamePerformanceData>>?,
|
||||
workingString: null == workingString
|
||||
? _value.workingString
|
||||
: workingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$HomePerformanceUIStateImplCopyWith<$Res>
|
||||
implements $HomePerformanceUIStateCopyWith<$Res> {
|
||||
factory _$$HomePerformanceUIStateImplCopyWith(
|
||||
_$HomePerformanceUIStateImpl value,
|
||||
$Res Function(_$HomePerformanceUIStateImpl) then) =
|
||||
__$$HomePerformanceUIStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool showGraphicsPerformanceTip,
|
||||
bool enabled,
|
||||
Map<String, List<GamePerformanceData>>? performanceMap,
|
||||
String workingString});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$HomePerformanceUIStateImplCopyWithImpl<$Res>
|
||||
extends _$HomePerformanceUIStateCopyWithImpl<$Res,
|
||||
_$HomePerformanceUIStateImpl>
|
||||
implements _$$HomePerformanceUIStateImplCopyWith<$Res> {
|
||||
__$$HomePerformanceUIStateImplCopyWithImpl(
|
||||
_$HomePerformanceUIStateImpl _value,
|
||||
$Res Function(_$HomePerformanceUIStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? showGraphicsPerformanceTip = null,
|
||||
Object? enabled = null,
|
||||
Object? performanceMap = freezed,
|
||||
Object? workingString = null,
|
||||
}) {
|
||||
return _then(_$HomePerformanceUIStateImpl(
|
||||
showGraphicsPerformanceTip: null == showGraphicsPerformanceTip
|
||||
? _value.showGraphicsPerformanceTip
|
||||
: showGraphicsPerformanceTip // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
enabled: null == enabled
|
||||
? _value.enabled
|
||||
: enabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
performanceMap: freezed == performanceMap
|
||||
? _value._performanceMap
|
||||
: performanceMap // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, List<GamePerformanceData>>?,
|
||||
workingString: null == workingString
|
||||
? _value.workingString
|
||||
: workingString // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$HomePerformanceUIStateImpl implements _HomePerformanceUIState {
|
||||
const _$HomePerformanceUIStateImpl(
|
||||
{this.showGraphicsPerformanceTip = true,
|
||||
this.enabled = false,
|
||||
final Map<String, List<GamePerformanceData>>? performanceMap,
|
||||
this.workingString = ""})
|
||||
: _performanceMap = performanceMap;
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool showGraphicsPerformanceTip;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool enabled;
|
||||
final Map<String, List<GamePerformanceData>>? _performanceMap;
|
||||
@override
|
||||
Map<String, List<GamePerformanceData>>? get performanceMap {
|
||||
final value = _performanceMap;
|
||||
if (value == null) return null;
|
||||
if (_performanceMap is EqualUnmodifiableMapView) return _performanceMap;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final String workingString;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HomePerformanceUIState(showGraphicsPerformanceTip: $showGraphicsPerformanceTip, enabled: $enabled, performanceMap: $performanceMap, workingString: $workingString)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$HomePerformanceUIStateImpl &&
|
||||
(identical(other.showGraphicsPerformanceTip,
|
||||
showGraphicsPerformanceTip) ||
|
||||
other.showGraphicsPerformanceTip ==
|
||||
showGraphicsPerformanceTip) &&
|
||||
(identical(other.enabled, enabled) || other.enabled == enabled) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._performanceMap, _performanceMap) &&
|
||||
(identical(other.workingString, workingString) ||
|
||||
other.workingString == workingString));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
showGraphicsPerformanceTip,
|
||||
enabled,
|
||||
const DeepCollectionEquality().hash(_performanceMap),
|
||||
workingString);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$HomePerformanceUIStateImplCopyWith<_$HomePerformanceUIStateImpl>
|
||||
get copyWith => __$$HomePerformanceUIStateImplCopyWithImpl<
|
||||
_$HomePerformanceUIStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _HomePerformanceUIState implements HomePerformanceUIState {
|
||||
const factory _HomePerformanceUIState(
|
||||
{final bool showGraphicsPerformanceTip,
|
||||
final bool enabled,
|
||||
final Map<String, List<GamePerformanceData>>? performanceMap,
|
||||
final String workingString}) = _$HomePerformanceUIStateImpl;
|
||||
|
||||
@override
|
||||
bool get showGraphicsPerformanceTip;
|
||||
@override
|
||||
bool get enabled;
|
||||
@override
|
||||
Map<String, List<GamePerformanceData>>? get performanceMap;
|
||||
@override
|
||||
String get workingString;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$HomePerformanceUIStateImplCopyWith<_$HomePerformanceUIStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
27
lib/ui/home/performance/performance_ui_model.g.dart
Normal file
27
lib/ui/home/performance/performance_ui_model.g.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'performance_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$homePerformanceUIModelHash() =>
|
||||
r'85e3390e954b35ffeb7cbacf85619b5a61f866bb';
|
||||
|
||||
/// See also [HomePerformanceUIModel].
|
||||
@ProviderFor(HomePerformanceUIModel)
|
||||
final homePerformanceUIModelProvider = AutoDisposeNotifierProvider<
|
||||
HomePerformanceUIModel, HomePerformanceUIState>.internal(
|
||||
HomePerformanceUIModel.new,
|
||||
name: r'homePerformanceUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$homePerformanceUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$HomePerformanceUIModel = AutoDisposeNotifier<HomePerformanceUIState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,43 +0,0 @@
|
||||
import 'package:markdown_widget/config/all.dart';
|
||||
import 'package:markdown_widget/widget/blocks/leaf/code_block.dart';
|
||||
import 'package:markdown_widget/widget/markdown.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
|
||||
import 'webview_localization_capture_ui_model.dart';
|
||||
|
||||
class WebviewLocalizationCaptureUI
|
||||
extends BaseUI<WebviewLocalizationCaptureUIModel> {
|
||||
@override
|
||||
Widget? buildBody(
|
||||
BuildContext context, WebviewLocalizationCaptureUIModel model) {
|
||||
return makeDefaultPage(context, model,
|
||||
content: model.data.isEmpty
|
||||
? const Center(
|
||||
child: Text("等待数据"),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MarkdownWidget(
|
||||
data: model.renderString,
|
||||
config: MarkdownConfig(configs: [
|
||||
const PreConfig(
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromRGBO(0, 0, 0, .4),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||
)),
|
||||
]),
|
||||
))
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(FluentIcons.refresh), onPressed: model.doClean)
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(
|
||||
BuildContext context, WebviewLocalizationCaptureUIModel model) =>
|
||||
"Webview 翻译捕获工具";
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/webview/webview.dart';
|
||||
|
||||
class WebviewLocalizationCaptureUIModel extends BaseUIModel {
|
||||
final WebViewModel webViewModel;
|
||||
|
||||
WebviewLocalizationCaptureUIModel(this.webViewModel);
|
||||
|
||||
Map<String, dynamic> data = {};
|
||||
|
||||
String renderString = "";
|
||||
|
||||
final jsonEncoder = const JsonEncoder.withIndent(' ');
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
webViewModel.addOnWebMessageReceivedCallback(_onMessage);
|
||||
super.initModel();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
webViewModel.removeOnWebMessageReceivedCallback(_onMessage);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onMessage(String message) {
|
||||
final map = json.decode(message);
|
||||
if (map["action"] == "webview_localization_capture") {
|
||||
dPrint(
|
||||
"<WebviewLocalizationCaptureUIModel> webview_localization_capture message == $map");
|
||||
final key = map["key"];
|
||||
if (key != null && key.toString().trim() != "") {
|
||||
if (!(webViewModel.curReplaceWords?.containsKey(key) ?? false)) {
|
||||
data[key] = map["value"];
|
||||
}
|
||||
_updateRenderString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateRenderString() {
|
||||
renderString = "```json\n${jsonEncoder.convert(data)}\n```";
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
doClean() {
|
||||
data.clear();
|
||||
_updateRenderString();
|
||||
}
|
||||
}
|
@ -1,23 +1,30 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/main.dart';
|
||||
import 'package:starcitizen_doctor/ui/about/about_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/about/about_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/party_room/party_room_home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/settings/settings_ui.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/provider/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/party_room/party_room_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'home/home_ui_model.dart';
|
||||
import 'index_ui_model.dart';
|
||||
import 'party_room/party_room_home_ui.dart';
|
||||
import 'about/about_ui.dart';
|
||||
import 'home/home_ui.dart';
|
||||
import 'settings/settings_ui.dart';
|
||||
import 'tools/tools_ui.dart';
|
||||
|
||||
class IndexUI extends HookConsumerWidget {
|
||||
const IndexUI({super.key});
|
||||
|
||||
class IndexUI extends BaseUI<IndexUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, IndexUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// pre init child
|
||||
ref.watch(homeUIModelProvider.select((value) => null));
|
||||
ref.watch(settingsUIModelProvider.select((value) => null));
|
||||
|
||||
final curIndex = useState(0);
|
||||
return NavigationView(
|
||||
appBar: NavigationAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
@ -35,7 +42,7 @@ class IndexUI extends BaseUI<IndexUIModel> {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
"SC汉化盒子 V${AppConf.appVersion} ${AppConf.isMSE ? "" : " Dev"}")
|
||||
"SC汉化盒子 V${ConstConf.appVersion} ${ConstConf.isMSE ? "" : " Dev"}")
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -55,89 +62,42 @@ class IndexUI extends BaseUI<IndexUIModel> {
|
||||
color: Colors.white.withOpacity(.6),
|
||||
),
|
||||
),
|
||||
if (model.aria2TotalTaskNum != 0)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 6, right: 6, bottom: 1.5, top: 1.5),
|
||||
child: Text(
|
||||
"${model.aria2TotalTaskNum}",
|
||||
style: const TextStyle(
|
||||
fontSize: 8,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
))
|
||||
_makeAria2TaskNumWidget()
|
||||
],
|
||||
),
|
||||
onPressed: model.goDownloader),
|
||||
onPressed: () => _goDownloader(context),
|
||||
// onPressed: model.goDownloader
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
const WindowButtons()
|
||||
],
|
||||
)),
|
||||
pane: NavigationPane(
|
||||
selected: model.curIndex,
|
||||
items: getNavigationPaneItems(model),
|
||||
selected: curIndex.value,
|
||||
items: getNavigationPaneItems(curIndex),
|
||||
size: const NavigationPaneSize(openWidth: 64),
|
||||
),
|
||||
paneBodyBuilder: (item, child) {
|
||||
// final name =
|
||||
// item?.key is ValueKey ? (item!.key as ValueKey).value : null;
|
||||
return FocusTraversalGroup(
|
||||
key: ValueKey('body_${model.curIndex}'),
|
||||
child: getPage(model),
|
||||
key: ValueKey('body_$curIndex'),
|
||||
child: getPage(curIndex.value),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget getPage(IndexUIModel model) {
|
||||
switch (model.curIndex) {
|
||||
case 0:
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => HomeUI(),
|
||||
modelCreate: () =>
|
||||
model.getChildUIModelProviders<HomeUIModel>("home"));
|
||||
case 1:
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => PartyRoomHomeUI(),
|
||||
modelCreate: () =>
|
||||
model.getChildUIModelProviders<PartyRoomHomeUIModel>("party"));
|
||||
case 2:
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => ToolsUI(),
|
||||
modelCreate: () =>
|
||||
model.getChildUIModelProviders<ToolsUIModel>("tools"));
|
||||
case 3:
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => SettingUI(),
|
||||
modelCreate: () =>
|
||||
model.getChildUIModelProviders<SettingUIModel>("settings"));
|
||||
case 4:
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => AboutUI(),
|
||||
modelCreate: () =>
|
||||
model.getChildUIModelProviders<AboutUIModel>("about"));
|
||||
}
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
List<NavigationPaneItem> getNavigationPaneItems(IndexUIModel model) {
|
||||
final menus = {
|
||||
Map<IconData, String> get pageMenus => {
|
||||
FluentIcons.home: "首页",
|
||||
FluentIcons.game: "大厅",
|
||||
FluentIcons.toolbox: "工具",
|
||||
FluentIcons.settings: "设置",
|
||||
FluentIcons.info: "关于",
|
||||
};
|
||||
|
||||
List<NavigationPaneItem> getNavigationPaneItems(
|
||||
ValueNotifier<int> curIndexState) {
|
||||
return [
|
||||
for (final kv in menus.entries)
|
||||
for (final kv in pageMenus.entries)
|
||||
PaneItem(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 6, left: 4),
|
||||
@ -154,13 +114,66 @@ class IndexUI extends BaseUI<IndexUIModel> {
|
||||
),
|
||||
// title: Text(kv.value),
|
||||
body: const SizedBox.shrink(),
|
||||
onTap: () {
|
||||
model.onIndexMenuTap(kv.value);
|
||||
},
|
||||
onTap: () => _onTapIndexMenu(kv.value, curIndexState),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, IndexUIModel model) => "";
|
||||
Widget getPage(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return const HomeUI();
|
||||
case 1:
|
||||
return const PartyRoomUI();
|
||||
case 2:
|
||||
return const ToolsUI();
|
||||
case 3:
|
||||
return const SettingsUI();
|
||||
case 4:
|
||||
return const AboutUI();
|
||||
default:
|
||||
return Center(
|
||||
child: Text("UnimplPage $value"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapIndexMenu(String value, ValueNotifier<int> curIndexState) {
|
||||
final pageIndex =
|
||||
pageMenus.values.toList().indexWhere((element) => element == value);
|
||||
curIndexState.value = pageIndex;
|
||||
}
|
||||
|
||||
Widget _makeAria2TaskNumWidget() {
|
||||
return Consumer(
|
||||
builder: (BuildContext context, WidgetRef ref, Widget? child) {
|
||||
final aria2cState = ref.watch(aria2cModelProvider);
|
||||
if (!aria2cState.hasDownloadTask) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 6, right: 6, bottom: 1.5, top: 1.5),
|
||||
child: Text(
|
||||
"${aria2cState.aria2TotalTaskNum}",
|
||||
style: const TextStyle(
|
||||
fontSize: 8,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_goDownloader(BuildContext context) {
|
||||
context.push('/index/downloader');
|
||||
}
|
||||
}
|
||||
|
@ -1,124 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:aria2/models/aria2GlobalStat.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/io/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/global_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/about/about_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/downloader/downloader_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/downloader/downloader_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'party_room/party_room_home_ui_model.dart';
|
||||
|
||||
class IndexUIModel extends BaseUIModel {
|
||||
int curIndex = 0;
|
||||
|
||||
Aria2GlobalStat? aria2globalStat;
|
||||
|
||||
int get aria2TotalTaskNum => aria2globalStat == null
|
||||
? 0
|
||||
: ((aria2globalStat!.numActive ?? 0) +
|
||||
(aria2globalStat!.numWaiting ?? 0));
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
_checkRuntime();
|
||||
_listenAria2c();
|
||||
Future.delayed(const Duration(milliseconds: 300))
|
||||
.then((value) => globalUIModel.doCheckUpdate(context!));
|
||||
super.initModel();
|
||||
}
|
||||
|
||||
@override
|
||||
BaseUIModel? onCreateChildUIModel(modelKey) {
|
||||
switch (modelKey) {
|
||||
case "home":
|
||||
return HomeUIModel();
|
||||
case "tools":
|
||||
return ToolsUIModel();
|
||||
case "settings":
|
||||
return SettingUIModel();
|
||||
case "about":
|
||||
return AboutUIModel();
|
||||
case "party":
|
||||
return PartyRoomHomeUIModel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void onIndexMenuTap(String value) {
|
||||
final index = {
|
||||
"首页": 0,
|
||||
"大厅": 1,
|
||||
"工具": 2,
|
||||
"设置": 3,
|
||||
"关于": 4,
|
||||
};
|
||||
curIndex = index[value] ?? 0;
|
||||
switch (curIndex) {
|
||||
case 0:
|
||||
getCreatedChildUIModel("home")?.reloadData();
|
||||
break;
|
||||
case 1:
|
||||
getCreatedChildUIModel("party")?.reloadData();
|
||||
break;
|
||||
case 2:
|
||||
getCreatedChildUIModel("tools")?.reloadData();
|
||||
break;
|
||||
case 3:
|
||||
getCreatedChildUIModel("settings")?.reloadData();
|
||||
break;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _checkRuntime() async {
|
||||
Future<void> onError() async {
|
||||
await showToast(context!, "运行环境出错,请检查系统环境变量 (PATH)!");
|
||||
await launchUrlString(
|
||||
"https://answers.microsoft.com/zh-hans/windows/forum/all/%E7%B3%BB%E7%BB%9F%E7%8E%AF%E5%A2%83%E5%8F%98/b88369e6-2620-4a77-b07a-d0af50894a07");
|
||||
await AnalyticsApi.touch("error_powershell");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
var result =
|
||||
await Process.run(SystemHelper.powershellPath, ["echo", "ping"]);
|
||||
if (result.stdout.toString().startsWith("ping")) {
|
||||
dPrint("powershell check pass");
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
} catch (e) {
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> goDownloader() async {
|
||||
await BaseUIContainer(
|
||||
uiCreate: () => DownloaderUI(),
|
||||
modelCreate: () => DownloaderUIModel()).push(context!);
|
||||
}
|
||||
|
||||
void _listenAria2c() async {
|
||||
while (true) {
|
||||
if (!mounted) return;
|
||||
try {
|
||||
if (Aria2cManager.isAvailable) {
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
aria2globalStat = await aria2c.getGlobalStat();
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("aria2globalStat update error:$e");
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
|
||||
import 'party_room_create_dialog_ui_model.dart';
|
||||
|
||||
class PartyRoomCreateDialogUI extends BaseUI<PartyRoomCreateDialogUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, PartyRoomCreateDialogUIModel model) {
|
||||
return ContentDialog(
|
||||
title: makeTitle(context, model),
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .6),
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 12),
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 130),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (model.userName == null) ...[
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: makeLoading(context),
|
||||
)
|
||||
] else ...[
|
||||
Row(
|
||||
children: [
|
||||
const Text("请选择一种玩法"),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<RoomType>(
|
||||
value: model.selectedRoomType,
|
||||
items: [
|
||||
for (final t in model.roomTypes.entries)
|
||||
ComboBoxItem(
|
||||
value: t.value,
|
||||
child: Text(t.value.name),
|
||||
)
|
||||
],
|
||||
onChanged: model.onChangeRoomType)),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (model.selectedRoomType != null &&
|
||||
(model.selectedRoomType?.subTypes.isNotEmpty ?? false))
|
||||
...makeSubTypeSelectWidgets(context, model),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
const Text("游戏用户名(自动获取)"),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextFormBox(
|
||||
initialValue: model.userName,
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
const Text("最大玩家数(2 ~ 32)"),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextFormBox(
|
||||
controller: model.playerMaxCtrl,
|
||||
onChanged: (_) => model.notifyListeners(),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
],
|
||||
keyboardType: TextInputType.number,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const Text("公告(可选)"),
|
||||
const SizedBox(height: 12),
|
||||
TextFormBox(
|
||||
controller: model.announcementCtrl,
|
||||
maxLines: 5,
|
||||
placeholder: "可编写 任务简报,集合地点,船只要求,活动规则等,公告将会自动发送给进入房间的玩家。",
|
||||
placeholderStyle:
|
||||
TextStyle(color: Colors.white.withOpacity(.4)),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
for (var v in [
|
||||
"创建房间后,其他玩家可以在大厅首页看到您的房间和您选择的玩法,当一个玩家选择加入房间时,你们将可以互相看到对方的用户名。当房间人数达到最大玩家数时,将不再接受新的玩家加入。",
|
||||
"这是《SC汉化盒子》提供的公益服务,请勿滥用,我们保留拒绝服务的权力。"
|
||||
]) ...[
|
||||
Text(
|
||||
v,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
if (model.isWorking)
|
||||
const ProgressRing()
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: model.onSubmit(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(3),
|
||||
child: Text("创建房间"),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color generateColorFromSeed(String seed) {
|
||||
int hash = utf8
|
||||
.encode(seed)
|
||||
.fold(0, (previousValue, element) => 31 * previousValue + element);
|
||||
Random random = Random(hash);
|
||||
return Color.fromARGB(
|
||||
255, random.nextInt(256), random.nextInt(256), random.nextInt(256));
|
||||
}
|
||||
|
||||
List<Widget> makeSubTypeSelectWidgets(
|
||||
BuildContext context, PartyRoomCreateDialogUIModel model) {
|
||||
bool isItemSelected(RoomSubtype subtype) {
|
||||
return model.selectedSubType.contains(subtype);
|
||||
}
|
||||
|
||||
return [
|
||||
const SizedBox(height: 24),
|
||||
const Text("标签(可选)"),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
for (var item in model.selectedRoomType!.subTypes)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isItemSelected(item)
|
||||
? generateColorFromSeed(item.name).withOpacity(.4)
|
||||
: FluentTheme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(1000)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 12),
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
child: IconButton(
|
||||
icon: Row(
|
||||
children: [
|
||||
Icon(isItemSelected(item)
|
||||
? FluentIcons.check_mark
|
||||
: FluentIcons.add),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
item.name,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isItemSelected(item)
|
||||
? null
|
||||
: Colors.white.withOpacity(.4)),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => model.onTapSubType(item)),
|
||||
)
|
||||
],
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
Widget makeTitle(BuildContext context, PartyRoomCreateDialogUIModel model) {
|
||||
return Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.back,
|
||||
size: 22,
|
||||
),
|
||||
onPressed: model.onBack()),
|
||||
const SizedBox(width: 12),
|
||||
Text(getUITitle(context, model)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, PartyRoomCreateDialogUIModel model) =>
|
||||
"创建房间";
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/grpc/party_room_server.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
import 'package:starcitizen_doctor/global_ui_model.dart';
|
||||
|
||||
class PartyRoomCreateDialogUIModel extends BaseUIModel {
|
||||
Map<String?, RoomType> roomTypes;
|
||||
|
||||
RoomType? selectedRoomType;
|
||||
|
||||
List<RoomSubtype> selectedSubType = [];
|
||||
|
||||
PartyRoomCreateDialogUIModel(this.roomTypes);
|
||||
|
||||
String? userName;
|
||||
|
||||
bool isWorking = false;
|
||||
|
||||
final playerMaxCtrl = TextEditingController(text: "8");
|
||||
final announcementCtrl = TextEditingController();
|
||||
|
||||
@override
|
||||
initModel() {
|
||||
super.initModel();
|
||||
roomTypes.removeWhere((key, value) => key == "");
|
||||
}
|
||||
|
||||
@override
|
||||
loadData() async {
|
||||
userName = await globalUIModel.getRunningGameUser();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onBack() {
|
||||
if (isWorking) return null;
|
||||
return () {
|
||||
Navigator.pop(context!);
|
||||
};
|
||||
}
|
||||
|
||||
void onChangeRoomType(RoomType? value) {
|
||||
selectedSubType = [];
|
||||
selectedRoomType = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onTapSubType(RoomSubtype item) {
|
||||
if (!selectedSubType.contains(item)) {
|
||||
selectedSubType.add(item);
|
||||
} else {
|
||||
selectedSubType.remove(item);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
final maxPlayer = int.tryParse(playerMaxCtrl.text) ?? 0;
|
||||
if (selectedRoomType == null) return null;
|
||||
if (maxPlayer < 2 || maxPlayer > 32) return null;
|
||||
return () async {
|
||||
isWorking = true;
|
||||
notifyListeners();
|
||||
final room = await handleError(() => PartyRoomGrpcServer.createRoom(
|
||||
RoomData(
|
||||
roomTypeID: selectedRoomType?.id,
|
||||
roomSubTypeIds: [for (var value in selectedSubType) value.id],
|
||||
owner: userName,
|
||||
deviceUUID: AppConf.deviceUUID,
|
||||
maxPlayer: maxPlayer,
|
||||
announcement: announcementCtrl.text.trim())));
|
||||
isWorking = false;
|
||||
notifyListeners();
|
||||
if (room != null) {
|
||||
Navigator.pop(context!, room);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
import 'package:starcitizen_doctor/widgets/cache_image.dart';
|
||||
|
||||
import 'party_room_chat_ui_model.dart';
|
||||
|
||||
class PartyRoomChatUI extends BaseUI<PartyRoomChatUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, PartyRoomChatUIModel model) {
|
||||
final roomData = model.serverResultRoomData;
|
||||
if (roomData == null) return makeLoading(context);
|
||||
final typesMap = model.partyRoomHomeUIModel.roomTypes;
|
||||
final title =
|
||||
"${roomData.owner} 的 ${typesMap?[roomData.roomTypeID]?.name ?? roomData.roomTypeID}房间";
|
||||
// final createTime =
|
||||
// DateTime.fromMillisecondsSinceEpoch(roomData.createTime.toInt());
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(color: Colors.black.withOpacity(.25)),
|
||||
child: makeTitleBar(model, title, roomData),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 220,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
decoration: BoxDecoration(color: Colors.black.withOpacity(.07)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
makeItemRow("玩家数量:",
|
||||
"${model.playersMap?.length ?? 0} / ${roomData.maxPlayer}"),
|
||||
const SizedBox(height: 12),
|
||||
if (model.playersMap == null)
|
||||
Expanded(child: makeLoading(context))
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.playersMap!.entries
|
||||
.elementAt(index)
|
||||
.value;
|
||||
return Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: item.avatar,
|
||||
width: 28,
|
||||
height: 28,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(child: Text(item.playerName)),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 3, bottom: 3, left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius:
|
||||
BorderRadius.circular(1000)),
|
||||
child: Text(
|
||||
"${model.playerStatusMap[item.status] ?? item.status}",
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: model.playersMap!.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeItemRow(String title, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 1, bottom: 1),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTitleBar(
|
||||
PartyRoomChatUIModel model, String title, RoomData roomData) {
|
||||
return Row(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: roomData.avatar,
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 3, bottom: 3, left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green, borderRadius: BorderRadius.circular(1000)),
|
||||
child: Text(
|
||||
"${model.partyRoomHomeUIModel.roomStatus[roomData.status]}")),
|
||||
const SizedBox(width: 12),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
FluentIcons.leave,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => model.onClose())
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, PartyRoomChatUIModel model) => "Chat";
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/grpc/party_room_server.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
import 'package:starcitizen_doctor/global_ui_model.dart';
|
||||
|
||||
import 'party_room_home_ui_model.dart';
|
||||
|
||||
class PartyRoomChatUIModel extends BaseUIModel {
|
||||
PartyRoomHomeUIModel partyRoomHomeUIModel;
|
||||
|
||||
PartyRoomChatUIModel(this.partyRoomHomeUIModel);
|
||||
|
||||
RoomData? selectRoom;
|
||||
|
||||
RoomData? serverResultRoomData;
|
||||
|
||||
ResponseStream<RoomUpdateMessage>? roomStream;
|
||||
|
||||
Map<String, RoomUserData>? playersMap;
|
||||
|
||||
setRoom(RoomData? selectRoom) {
|
||||
if (this.selectRoom == null) {
|
||||
this.selectRoom = selectRoom;
|
||||
notifyListeners();
|
||||
loadRoom();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
final playerStatusMap = {
|
||||
RoomUserStatus.RoomUserStatusJoin: "在线",
|
||||
RoomUserStatus.RoomUserStatusLostOffline: "离线",
|
||||
RoomUserStatus.RoomUserStatusLeave: "已离开",
|
||||
RoomUserStatus.RoomUserStatusWaitingConnect: "正在连接...",
|
||||
};
|
||||
|
||||
onClose() async {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!, "确认离开房间?", const Text("离开房间后,您的位置将被释放。"));
|
||||
if (ok == true) {
|
||||
partyRoomHomeUIModel.pageCtrl.animateToPage(0,
|
||||
duration: const Duration(milliseconds: 130),
|
||||
curve: Curves.easeInOutSine);
|
||||
disposeRoom();
|
||||
}
|
||||
}
|
||||
|
||||
loadRoom() async {
|
||||
if (selectRoom == null) return;
|
||||
final userName = await globalUIModel.getRunningGameUser();
|
||||
if (userName == null) return;
|
||||
roomStream = PartyRoomGrpcServer.joinRoom(
|
||||
selectRoom!.id, userName, AppConf.deviceUUID);
|
||||
roomStream!.listen((value) {
|
||||
dPrint("PartyRoomChatUIModel.roomStream.listen === $value");
|
||||
if (value.roomUpdateType == RoomUpdateType.RoomClose) {
|
||||
} else if (value.roomUpdateType == RoomUpdateType.RoomUpdateData) {
|
||||
if (value.hasRoomData()) {
|
||||
serverResultRoomData = value.roomData;
|
||||
}
|
||||
if (value.usersData.isNotEmpty) {
|
||||
_updatePlayerList(value.usersData);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
})
|
||||
..onError((err) {
|
||||
// showToast(context!, "连接到服务器出现错误:$err");
|
||||
dPrint("PartyRoomChatUIModel.roomStream onError $err");
|
||||
})
|
||||
..onDone(() {
|
||||
// showToast(context!, "房间已关闭");
|
||||
dPrint("PartyRoomChatUIModel.roomStream onDone");
|
||||
});
|
||||
}
|
||||
|
||||
disposeRoom() {
|
||||
selectRoom = null;
|
||||
roomStream?.cancel();
|
||||
roomStream = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _updatePlayerList(List<RoomUserData> usersData) {
|
||||
playersMap ??= {};
|
||||
for (var element in usersData) {
|
||||
playersMap![element.playerName] = element;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:flutter_tilt/flutter_tilt.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
import 'package:starcitizen_doctor/widgets/cache_image.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'party_room_home_ui_model.dart';
|
||||
|
||||
class PartyRoomHomeUI extends BaseUI<PartyRoomHomeUIModel> {
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(const Duration(milliseconds: 16)).then((_) {
|
||||
ref.watch(provider).checkUIInit();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final model = ref.watch(provider);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"联机大厅,敬请期待 !",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
launchUrlString("https://wj.qq.com/s2/14112124/f4c8/");
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("诚邀您参与 "),
|
||||
Text(
|
||||
"问卷调查。",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
// return PageView(
|
||||
// controller: model.pageCtrl,
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
// children: [
|
||||
// super.build(context),
|
||||
// BaseUIContainer(
|
||||
// uiCreate: () => PartyRoomChatUI(),
|
||||
// modelCreate: () =>
|
||||
// model.getChildUIModelProviders<PartyRoomChatUIModel>("chat"))
|
||||
// ],
|
||||
// );
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, PartyRoomHomeUIModel model) {
|
||||
if (model.pingServerMessage == null) return makeLoading(context);
|
||||
if (model.pingServerMessage!.isNotEmpty) {
|
||||
return Center(
|
||||
child: Text("${model.pingServerMessage}"),
|
||||
);
|
||||
}
|
||||
if (model.roomTypes == null) return makeLoading(context);
|
||||
return Column(
|
||||
children: [
|
||||
makeHeader(context, model),
|
||||
if (model.rooms == null)
|
||||
Expanded(child: makeLoading(context))
|
||||
else if (model.rooms!.isEmpty)
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: Text("没有符合条件的房间!"),
|
||||
))
|
||||
else
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: AlignedGridView.count(
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 24,
|
||||
crossAxisSpacing: 24,
|
||||
itemCount: model.rooms!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return makeRoomItemWidget(context, model, index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeRoomItemWidget(
|
||||
BuildContext context,
|
||||
PartyRoomHomeUIModel model,
|
||||
int index,
|
||||
) {
|
||||
final item = model.rooms![index];
|
||||
final itemType = model.roomTypes?[item.roomTypeID];
|
||||
final itemSubTypes = {
|
||||
for (var t in itemType?.subTypes ?? <RoomSubtype>[]) t.id: t
|
||||
};
|
||||
final createTime =
|
||||
DateTime.fromMillisecondsSinceEpoch(item.createTime.toInt());
|
||||
return Tilt(
|
||||
borderRadius: BorderRadius.circular(13),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(13),
|
||||
image: DecorationImage(
|
||||
image: ExtendedNetworkImageProvider(item.avatar, cache: true),
|
||||
fit: BoxFit.cover)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(.4),
|
||||
borderRadius: BorderRadius.circular(13),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(13),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 16, top: 12, bottom: 12),
|
||||
child: GestureDetector(
|
||||
onTap: () => model.onTapRoom(item),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${itemType?.name ?? item.roomTypeID}房间",
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 3, bottom: 3, left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(1000)),
|
||||
child: Text("${model.roomStatus[item.status]}")),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
makeItemRow("房主:", item.owner),
|
||||
makeItemRow("玩家数量:",
|
||||
"${item.curPlayer} / ${item.maxPlayer}"),
|
||||
makeItemRow("创建时间:", "${createTime.toLocal()}"),
|
||||
],
|
||||
),
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
child: CacheNetImage(
|
||||
url: item.avatar,
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
for (var value in item.roomSubTypeIds)
|
||||
makeSubTypeTag(value, model, itemSubTypes),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeSubTypeTag(String id, PartyRoomHomeUIModel model,
|
||||
Map<String, RoomSubtype> itemSubTypes) {
|
||||
final name = itemSubTypes[id]?.name ?? id;
|
||||
final color = generateColorFromSeed(name).withOpacity(.6);
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 5, bottom: 5),
|
||||
margin: const EdgeInsets.only(right: 6),
|
||||
decoration:
|
||||
BoxDecoration(color: color, borderRadius: BorderRadius.circular(12)),
|
||||
child: Text(
|
||||
name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color generateColorFromSeed(String seed) {
|
||||
int hash = utf8
|
||||
.encode(seed)
|
||||
.fold(0, (previousValue, element) => 31 * previousValue + element);
|
||||
Random random = Random(hash);
|
||||
return Color.fromARGB(
|
||||
255, random.nextInt(256), random.nextInt(256), random.nextInt(256));
|
||||
}
|
||||
|
||||
Widget makeItemRow(String title, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 1, bottom: 1),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: Colors.white.withOpacity(.6)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeHeader(BuildContext context, PartyRoomHomeUIModel model) {
|
||||
final subTypes = model.getCurRoomSubTypes();
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24, top: 16, bottom: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Text("房间类型:"),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<RoomType>(
|
||||
value: model.selectedRoomType,
|
||||
items: [
|
||||
for (final t in model.roomTypes!.entries)
|
||||
ComboBoxItem(
|
||||
value: t.value,
|
||||
child: Text(t.value.name),
|
||||
)
|
||||
],
|
||||
onChanged: model.onChangeRoomType)),
|
||||
if (subTypes != null) ...[
|
||||
const SizedBox(width: 24),
|
||||
const Text("子类型:"),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<RoomSubtype>(
|
||||
value: model.selectedRoomSubType,
|
||||
items: [
|
||||
for (final t in subTypes.entries)
|
||||
ComboBoxItem(
|
||||
value: t.value,
|
||||
child: Text(t.value.name),
|
||||
)
|
||||
],
|
||||
onChanged: model.onChangeRoomSubType)),
|
||||
],
|
||||
const SizedBox(width: 24),
|
||||
const Text("房间状态:"),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<RoomStatus>(
|
||||
value: model.selectedStatus,
|
||||
items: [
|
||||
for (final t in model.roomStatus.entries)
|
||||
ComboBoxItem(
|
||||
value: t.key,
|
||||
child: Text(t.value),
|
||||
)
|
||||
],
|
||||
onChanged: model.onChangeRoomStatus)),
|
||||
const SizedBox(width: 24),
|
||||
const Text("排序:"),
|
||||
SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<RoomSortType>(
|
||||
value: model.selectedSortType,
|
||||
items: [
|
||||
for (final t in model.roomSorts.entries)
|
||||
ComboBoxItem(
|
||||
value: t.key,
|
||||
child: Text(t.value),
|
||||
)
|
||||
],
|
||||
onChanged: model.onChangeRoomSort)),
|
||||
const Spacer(),
|
||||
Button(
|
||||
onPressed: model.onRefreshRoom,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.refresh),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: () => model.onCreateRoom(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(3),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(FluentIcons.add),
|
||||
SizedBox(width: 6),
|
||||
Text("创建房间")
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
model.selectedRoomType?.desc ?? "",
|
||||
style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(.4)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, PartyRoomHomeUIModel model) =>
|
||||
"PartyRoom";
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/generated/grpc/party_room_server/index.pb.dart';
|
||||
import 'package:starcitizen_doctor/ui/party_room/dialogs/party_room_create_dialog_ui_model.dart';
|
||||
|
||||
import 'dialogs/party_room_create_dialog_ui.dart';
|
||||
import 'party_room_chat_ui_model.dart';
|
||||
|
||||
class PartyRoomHomeUIModel extends BaseUIModel {
|
||||
String? pingServerMessage;
|
||||
|
||||
Map<String?, RoomType>? roomTypes;
|
||||
|
||||
RoomType? selectedRoomType;
|
||||
|
||||
RoomSubtype? selectedRoomSubType;
|
||||
|
||||
final roomStatus = <RoomStatus, String>{
|
||||
RoomStatus.All: "全部",
|
||||
RoomStatus.Open: "开启中",
|
||||
RoomStatus.Full: "已满员",
|
||||
RoomStatus.Closed: "已封闭",
|
||||
RoomStatus.WillOffline: "房主离线",
|
||||
RoomStatus.Offline: "已离线",
|
||||
};
|
||||
|
||||
RoomStatus selectedStatus = RoomStatus.All;
|
||||
|
||||
final roomSorts = <RoomSortType, String>{
|
||||
RoomSortType.Default: "默认",
|
||||
RoomSortType.MostPlayerNumber: "最多玩家",
|
||||
RoomSortType.MinimumPlayerNumber: "最少玩家",
|
||||
RoomSortType.RecentlyCreated: "最近创建",
|
||||
RoomSortType.OldestCreated: "最久创建",
|
||||
};
|
||||
|
||||
RoomSortType selectedSortType = RoomSortType.Default;
|
||||
|
||||
int pageNum = 0;
|
||||
|
||||
List<RoomData>? rooms;
|
||||
|
||||
final pageCtrl = PageController();
|
||||
|
||||
@override
|
||||
BaseUIModel? onCreateChildUIModel(modelKey) {
|
||||
switch (modelKey) {
|
||||
case "chat":
|
||||
return PartyRoomChatUIModel(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
// if (pingServerMessage != "") {
|
||||
// pingServerMessage = null;
|
||||
// notifyListeners();
|
||||
// await _pingServer();
|
||||
// }
|
||||
// await _loadPage();
|
||||
}
|
||||
|
||||
// @override
|
||||
// reloadData() async {
|
||||
// pageNum = 0;
|
||||
// rooms = null;
|
||||
// notifyListeners();
|
||||
// _touchUser();
|
||||
// return super.reloadData();
|
||||
// }
|
||||
|
||||
// _loadPage() async {
|
||||
// final r = await handleError(() => PartyRoomGrpcServer.getRoomList(
|
||||
// RoomListPageReqData(
|
||||
// pageNum: Int64.tryParseInt("$pageNum"),
|
||||
// typeID: selectedRoomType?.id ?? "",
|
||||
// subTypeID: selectedRoomSubType?.id ?? "",
|
||||
// status: selectedStatus)));
|
||||
// if (r == null) return;
|
||||
// if (r.pageData.hasNext) {
|
||||
// pageNum++;
|
||||
// } else {
|
||||
// pageNum = -1;
|
||||
// }
|
||||
// rooms = r.rooms;
|
||||
// notifyListeners();
|
||||
// }
|
||||
//
|
||||
// _pingServer() async {
|
||||
// try {
|
||||
// final r = await PartyRoomGrpcServer.pingServer();
|
||||
// dPrint(
|
||||
// "[PartyRoomHomeUIModel] Connected! serverVersion ==> ${r.serverVersion}");
|
||||
// pingServerMessage = "";
|
||||
// notifyListeners();
|
||||
// } catch (e) {
|
||||
// pingServerMessage = "服务器连接失败,请稍后重试。\n$e";
|
||||
// notifyListeners();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Future<void> _loadTypes() async {
|
||||
// final r = await handleError(() => PartyRoomGrpcServer.getRoomTypes());
|
||||
// if (r == null) return;
|
||||
// selectedRoomType =
|
||||
// RoomType(id: "", name: "全部", desc: "查看所有类型的房间,寻找一起玩的伙伴。");
|
||||
// selectedRoomSubType = RoomSubtype(id: "", name: "全部");
|
||||
// roomTypes = {"": selectedRoomType!};
|
||||
// for (var element in r.roomTypes) {
|
||||
// roomTypes![element.id] = element;
|
||||
// }
|
||||
// notifyListeners();
|
||||
// }
|
||||
|
||||
Map<String, RoomSubtype>? getCurRoomSubTypes() {
|
||||
if (selectedRoomType?.subTypes == null) return null;
|
||||
Map<String, RoomSubtype> types = {};
|
||||
for (var element in selectedRoomType!.subTypes) {
|
||||
types[element.id] = element;
|
||||
}
|
||||
if (types.isEmpty) return null;
|
||||
final allSubType = RoomSubtype(id: "", name: "全部");
|
||||
selectedRoomSubType ??= allSubType;
|
||||
return {"all": allSubType}..addAll(types);
|
||||
}
|
||||
|
||||
void onChangeRoomType(RoomType? value) {
|
||||
selectedRoomType = value;
|
||||
selectedRoomSubType = null;
|
||||
reloadData();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onChangeRoomStatus(RoomStatus? value) {
|
||||
if (value == null) return;
|
||||
selectedStatus = value;
|
||||
reloadData();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onChangeRoomSort(RoomSortType? value) {
|
||||
if (value == null) return;
|
||||
selectedSortType = value;
|
||||
reloadData();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onChangeRoomSubType(RoomSubtype? value) {
|
||||
if (value == null) return;
|
||||
selectedRoomSubType = value;
|
||||
reloadData();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onCreateRoom() async {
|
||||
final room = await showDialog(
|
||||
context: context!,
|
||||
dismissWithEsc: false,
|
||||
builder: (BuildContext context) {
|
||||
return BaseUIContainer(
|
||||
uiCreate: () => PartyRoomCreateDialogUI(),
|
||||
modelCreate: () =>
|
||||
PartyRoomCreateDialogUIModel(Map.from(roomTypes!)));
|
||||
},
|
||||
);
|
||||
if (room == null) return;
|
||||
dPrint(room);
|
||||
reloadData();
|
||||
}
|
||||
|
||||
onRefreshRoom() {
|
||||
reloadData();
|
||||
}
|
||||
|
||||
// Future<void> _touchUser() async {
|
||||
// if (getCreatedChildUIModel<PartyRoomChatUIModel>("chat")?.selectRoom ==
|
||||
// null) {
|
||||
// final userName = await globalUIModel.getRunningGameUser();
|
||||
// if (userName == null) return;
|
||||
// // 检测用户已加入的房间
|
||||
// final room = await handleError(() =>
|
||||
// PartyRoomGrpcServer.touchUserRoom(userName, AppConf.deviceUUID));
|
||||
// dPrint("touch room == ${room?.toProto3Json()}");
|
||||
// if (room == null || room.id == "") return;
|
||||
// onTapRoom(room);
|
||||
// }
|
||||
// }
|
||||
|
||||
onTapRoom(RoomData item) {
|
||||
getCreatedChildUIModel<PartyRoomChatUIModel>("chat", create: true)
|
||||
?.setRoom(item);
|
||||
notifyListeners();
|
||||
pageCtrl.animateToPage(1,
|
||||
duration: const Duration(milliseconds: 100), curve: Curves.easeInExpo);
|
||||
}
|
||||
|
||||
void checkUIInit() {
|
||||
if (getCreatedChildUIModel<PartyRoomChatUIModel>("chat")?.selectRoom !=
|
||||
null) {
|
||||
pageCtrl.jumpToPage(1);
|
||||
}
|
||||
}
|
||||
}
|
40
lib/ui/party_room/party_room_ui.dart
Normal file
40
lib/ui/party_room/party_room_ui.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class PartyRoomUI extends HookConsumerWidget {
|
||||
const PartyRoomUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"联机大厅,敬请期待 !",
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
launchUrlString("https://wj.qq.com/s2/14112124/f4c8/");
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("诚邀您参与 "),
|
||||
Text(
|
||||
"问卷调查。",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,11 +1,16 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart';
|
||||
|
||||
class SettingUI extends BaseUI<SettingUIModel> {
|
||||
class SettingsUI extends HookConsumerWidget {
|
||||
const SettingsUI({super.key});
|
||||
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, SettingUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sate = ref.watch(settingsUIModelProvider);
|
||||
final model = ref.read(settingsUIModelProvider.notifier);
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
@ -13,53 +18,53 @@ class SettingUI extends BaseUI<SettingUIModel> {
|
||||
child: Column(
|
||||
children: [
|
||||
makeSettingsItem(const Icon(FluentIcons.link, size: 20), "创建设置快捷方式",
|
||||
subTitle: "在桌面创建《SC汉化盒子》快捷方式", onTap: model.addShortCut),
|
||||
if (AppConf.isMSE) ...[
|
||||
subTitle: "在桌面创建《SC汉化盒子》快捷方式", onTap: ()=> model.addShortCut(context)),
|
||||
if (ConstConf.isMSE) ...[
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(
|
||||
const Icon(FluentIcons.reset_device, size: 20), "重置自动密码填充",
|
||||
subTitle:
|
||||
"启用:${model.isEnableAutoLogin ? "已启用" : "已禁用"} 设备支持:${model.isDeviceSupportWinHello ? "支持" : "不支持"} 邮箱:${model.autoLoginEmail} 密码:${model.isEnableAutoLoginPwd ? "已加密保存" : "未保存"}",
|
||||
onTap: model.onResetAutoLogin),
|
||||
"启用:${sate.isEnableAutoLogin ? "已启用" : "已禁用"} 设备支持:${sate.isDeviceSupportWinHello ? "支持" : "不支持"} 邮箱:${sate.autoLoginEmail} 密码:${sate.isEnableAutoLoginPwd ? "已加密保存" : "未保存"}",
|
||||
onTap: ()=> model.onResetAutoLogin(context)),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(const Icon(FontAwesomeIcons.microchip, size: 20),
|
||||
"启动游戏时忽略能效核心( 适用于Intel 12th+ 处理器 ) [实验性功能,请随时反馈]",
|
||||
subTitle:
|
||||
"已设置的核心数量:${model.inputGameLaunchECore} (此功能适用于首页的盒子一键启动 或 工具中的RSI启动器管理员模式,当为 0 时不启用此功能 )",
|
||||
onTap: model.setGameLaunchECore),
|
||||
"已设置的核心数量:${sate.inputGameLaunchECore} (此功能适用于首页的盒子一键启动 或 工具中的RSI启动器管理员模式,当为 0 时不启用此功能 )",
|
||||
onTap:()=> model.setGameLaunchECore(context)),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(const Icon(FluentIcons.folder_open, size: 20),
|
||||
"设置启动器文件(RSI Launcher.exe)",
|
||||
subTitle: model.customLauncherPath != null
|
||||
? "${model.customLauncherPath}"
|
||||
subTitle: sate.customLauncherPath != null
|
||||
? "${sate.customLauncherPath}"
|
||||
: "手动设置启动器位置,建议仅在无法自动扫描安装位置时使用",
|
||||
onTap: model.setLauncherPath, onDel: () {
|
||||
onTap: ()=> model.setLauncherPath(context), onDel: () {
|
||||
model.delName("custom_launcher_path");
|
||||
}),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(const Icon(FluentIcons.game, size: 20),
|
||||
"设置游戏文件 (StarCitizen.exe)",
|
||||
subTitle: model.customGamePath != null
|
||||
? "${model.customGamePath}"
|
||||
subTitle: sate.customGamePath != null
|
||||
? "${sate.customGamePath}"
|
||||
: "手动设置游戏安装位置,建议仅在无法自动扫描安装位置时使用",
|
||||
onTap: model.setGamePath, onDel: () {
|
||||
onTap: ()=> model.setGamePath(context), onDel: () {
|
||||
model.delName("custom_game_path");
|
||||
}),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(const Icon(FluentIcons.delete, size: 20), "清理汉化文件缓存",
|
||||
subTitle:
|
||||
"缓存大小 ${(model.locationCacheSize / 1024 / 1024).toStringAsFixed(2)}MB,清理盒子下载的汉化文件缓存,不会影响已安装的汉化",
|
||||
onTap: model.cleanLocationCache),
|
||||
"缓存大小 ${(sate.locationCacheSize / 1024 / 1024).toStringAsFixed(2)}MB,清理盒子下载的汉化文件缓存,不会影响已安装的汉化",
|
||||
onTap: ()=> model.cleanLocationCache(context)),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(
|
||||
const Icon(FluentIcons.speed_high, size: 20), "工具站访问加速",
|
||||
onTap: () =>
|
||||
model.onChangeToolSiteMirror(!model.isEnableToolSiteMirrors),
|
||||
model.onChangeToolSiteMirror(!sate.isEnableToolSiteMirrors),
|
||||
subTitle:
|
||||
"使用镜像服务器加速访问 Dps Uex 等工具网站,若访问异常请关闭该功能。 为保护账户安全,任何情况下都不会加速RSI官网。",
|
||||
onSwitch: model.onChangeToolSiteMirror,
|
||||
switchStatus: model.isEnableToolSiteMirrors),
|
||||
switchStatus: sate.isEnableToolSiteMirrors),
|
||||
const SizedBox(height: 12),
|
||||
makeSettingsItem(
|
||||
const Icon(FluentIcons.document_set, size: 20), "查看log",
|
||||
@ -123,7 +128,4 @@ class SettingUI extends BaseUI<SettingUIModel> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, SettingUIModel model) => "SettingUI";
|
||||
}
|
||||
|
@ -1,36 +1,54 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/common/win32/credentials.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
class SettingUIModel extends BaseUIModel {
|
||||
var isDeviceSupportWinHello = false;
|
||||
part 'settings_ui_model.g.dart';
|
||||
|
||||
String autoLoginEmail = "-";
|
||||
bool isEnableAutoLogin = false;
|
||||
bool isEnableAutoLoginPwd = false;
|
||||
bool isEnableToolSiteMirrors = false;
|
||||
String inputGameLaunchECore = "0";
|
||||
part 'settings_ui_model.freezed.dart';
|
||||
|
||||
String? customLauncherPath;
|
||||
String? customGamePath;
|
||||
|
||||
int locationCacheSize = 0;
|
||||
@freezed
|
||||
class SettingsUIState with _$SettingsUIState {
|
||||
const factory SettingsUIState({
|
||||
@Default(false) isDeviceSupportWinHello,
|
||||
@Default("-") String autoLoginEmail,
|
||||
@Default(false) bool isEnableAutoLogin,
|
||||
@Default(false) bool isEnableAutoLoginPwd,
|
||||
@Default(false) bool isEnableToolSiteMirrors,
|
||||
@Default("0") String inputGameLaunchECore,
|
||||
String? customLauncherPath,
|
||||
String? customGamePath,
|
||||
@Default(0) int locationCacheSize,
|
||||
}) = _SettingsUIState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class SettingsUIModel extends _$SettingsUIModel {
|
||||
@override
|
||||
loadData() async {
|
||||
dPrint("SettingUIModel.loadData");
|
||||
SettingsUIState build() {
|
||||
state = const SettingsUIState();
|
||||
_initState();
|
||||
return state;
|
||||
}
|
||||
|
||||
void _initState() async {
|
||||
final LocalAuthentication localAuth = LocalAuthentication();
|
||||
isDeviceSupportWinHello = await localAuth.isDeviceSupported();
|
||||
notifyListeners();
|
||||
final isDeviceSupportWinHello = await localAuth.isDeviceSupported();
|
||||
state = state.copyWith(isDeviceSupportWinHello: isDeviceSupportWinHello);
|
||||
_updateGameLaunchECore();
|
||||
if (AppConf.isMSE) {
|
||||
if (ConstConf.isMSE) {
|
||||
_updateAutoLoginAccount();
|
||||
}
|
||||
_loadCustomPath();
|
||||
@ -38,32 +56,39 @@ class SettingUIModel extends BaseUIModel {
|
||||
_loadToolSiteMirrorState();
|
||||
}
|
||||
|
||||
Future<void> onResetAutoLogin() async {
|
||||
final ok = await showConfirmDialogs(context!, "确认重置自动填充?",
|
||||
Future<void> onResetAutoLogin(BuildContext context) async {
|
||||
final ok = await showConfirmDialogs(context, "确认重置自动填充?",
|
||||
const Text("这将会删除本地的账号记录,或在下次启动游戏时将自动填充选择 ‘否’ 以禁用自动填充。"));
|
||||
if (ok) {
|
||||
final userBox = await Hive.openBox("rsi_account_data");
|
||||
await userBox.deleteFromDisk();
|
||||
Win32Credentials.delete("SCToolbox_RSI_Account_secret");
|
||||
showToast(context!, "已清理自动填充数据");
|
||||
reloadData();
|
||||
if (!context.mounted) return;
|
||||
|
||||
showToast(context, "已清理自动填充数据");
|
||||
_initState();
|
||||
}
|
||||
}
|
||||
|
||||
Future _updateAutoLoginAccount() async {
|
||||
final userBox = await Hive.openBox("rsi_account_data");
|
||||
autoLoginEmail = userBox.get("account_email", defaultValue: "-");
|
||||
isEnableAutoLogin = userBox.get("enable", defaultValue: true);
|
||||
isEnableAutoLoginPwd =
|
||||
final autoLoginEmail = userBox.get("account_email", defaultValue: "-");
|
||||
final isEnableAutoLogin = userBox.get("enable", defaultValue: true);
|
||||
final isEnableAutoLoginPwd =
|
||||
userBox.get("account_pwd_encrypted", defaultValue: "") != "";
|
||||
notifyListeners();
|
||||
|
||||
state = state.copyWith(
|
||||
autoLoginEmail: autoLoginEmail,
|
||||
isEnableAutoLogin: isEnableAutoLogin,
|
||||
isEnableAutoLoginPwd: isEnableAutoLoginPwd);
|
||||
}
|
||||
|
||||
Future<void> setGameLaunchECore() async {
|
||||
Future<void> setGameLaunchECore(BuildContext context) async {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
final defaultInput =
|
||||
userBox.get("gameLaunch_eCore_count", defaultValue: "0");
|
||||
final input = await showInputDialogs(context!,
|
||||
if (!context.mounted) return;
|
||||
final input = await showInputDialogs(context,
|
||||
title: "请输入要忽略的 CPU 核心数",
|
||||
content:
|
||||
"Tip:您的设备拥有几个能效核心就输入几,非大小核设备请保持 0\n\n此功能适用于首页的盒子一键启动 或 工具中的 RSI启动器管理员模式,当为 0 时不启用此功能。",
|
||||
@ -71,17 +96,17 @@ class SettingUIModel extends BaseUIModel {
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly]);
|
||||
if (input == null) return;
|
||||
userBox.put("gameLaunch_eCore_count", input);
|
||||
reloadData();
|
||||
_initState();
|
||||
}
|
||||
|
||||
Future _updateGameLaunchECore() async {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
inputGameLaunchECore =
|
||||
final inputGameLaunchECore =
|
||||
userBox.get("gameLaunch_eCore_count", defaultValue: "0");
|
||||
notifyListeners();
|
||||
state = state.copyWith(inputGameLaunchECore: inputGameLaunchECore);
|
||||
}
|
||||
|
||||
Future<void> setLauncherPath() async {
|
||||
Future<void> setLauncherPath(BuildContext context) async {
|
||||
final r = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: "请选择RSI启动器位置(RSI Launcher.exe)",
|
||||
type: FileType.custom,
|
||||
@ -90,14 +115,16 @@ class SettingUIModel extends BaseUIModel {
|
||||
final fileName = r.files.first.path!;
|
||||
if (fileName.endsWith("\\RSI Launcher.exe")) {
|
||||
await _saveCustomPath("custom_launcher_path", fileName);
|
||||
showToast(context!, "设置成功,在对应页面点击刷新即可扫描出新路径");
|
||||
reloadData();
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "设置成功,在对应页面点击刷新即可扫描出新路径");
|
||||
_initState();
|
||||
} else {
|
||||
showToast(context!, "文件有误!");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "文件有误!");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setGamePath() async {
|
||||
Future<void> setGamePath(BuildContext context) async {
|
||||
final r = await FilePicker.platform.pickFiles(
|
||||
dialogTitle: "请选择游戏安装位置(StarCitizen.exe)",
|
||||
type: FileType.custom,
|
||||
@ -111,10 +138,12 @@ class SettingUIModel extends BaseUIModel {
|
||||
RegExp pathRegex = RegExp(r"\\[^\\]+\\Bin64\\StarCitizen\.exe$");
|
||||
String extractedPath = fileName.replaceFirst(pathRegex, '');
|
||||
await _saveCustomPath("custom_game_path", extractedPath);
|
||||
showToast(context!, "设置成功,在对应页面点击刷新即可扫描出新路径");
|
||||
reloadData();
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "设置成功,在对应页面点击刷新即可扫描出新路径");
|
||||
_initState();
|
||||
} else {
|
||||
showToast(context!, "文件有误!");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "文件有误!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,36 +154,40 @@ class SettingUIModel extends BaseUIModel {
|
||||
|
||||
_loadCustomPath() async {
|
||||
final confBox = await Hive.openBox("app_conf");
|
||||
customLauncherPath = confBox.get("custom_launcher_path");
|
||||
customGamePath = confBox.get("custom_game_path");
|
||||
final customLauncherPath = confBox.get("custom_launcher_path");
|
||||
final customGamePath = confBox.get("custom_game_path");
|
||||
state = state.copyWith(
|
||||
customLauncherPath: customLauncherPath, customGamePath: customGamePath);
|
||||
}
|
||||
|
||||
Future<void> delName(String key) async {
|
||||
final confBox = await Hive.openBox("app_conf");
|
||||
await confBox.delete(key);
|
||||
reloadData();
|
||||
_initState();
|
||||
}
|
||||
|
||||
_loadLocationCacheSize() async {
|
||||
final len = await SystemHelper.getDirLen(
|
||||
"${AppConf.applicationSupportDir}/Localizations");
|
||||
locationCacheSize = len;
|
||||
notifyListeners();
|
||||
"${appGlobalState.applicationSupportDir}/Localizations");
|
||||
final locationCacheSize = len;
|
||||
state = state.copyWith(locationCacheSize: locationCacheSize);
|
||||
}
|
||||
|
||||
Future<void> cleanLocationCache() async {
|
||||
Future<void> cleanLocationCache(BuildContext context) async {
|
||||
final ok = await showConfirmDialogs(
|
||||
context!, "确认清理汉化缓存?", const Text("这不会影响已安装的汉化。"));
|
||||
context, "确认清理汉化缓存?", const Text("这不会影响已安装的汉化。"));
|
||||
if (ok == true) {
|
||||
final dir = Directory("${AppConf.applicationSupportDir}/Localizations");
|
||||
await handleError(() => dir.delete(recursive: true));
|
||||
reloadData();
|
||||
final dir =
|
||||
Directory("${appGlobalState.applicationSupportDir}/Localizations");
|
||||
if (!context.mounted) return;
|
||||
await dir.delete(recursive: true).unwrap(context: context);
|
||||
_initState();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addShortCut() async {
|
||||
if (AppConf.isMSE) {
|
||||
showToast(context!, "因微软版功能限制,请在接下来打开的窗口中 手动将《SC汉化盒子》拖动到桌面,即可创建快捷方式。");
|
||||
Future<void> addShortCut(BuildContext context) async {
|
||||
if (ConstConf.isMSE) {
|
||||
showToast(context, "因微软版功能限制,请在接下来打开的窗口中 手动将《SC汉化盒子》拖动到桌面,即可创建快捷方式。");
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
Process.run("explorer.exe", ["shell:AppsFolder"]);
|
||||
return;
|
||||
@ -174,24 +207,25 @@ class SettingUIModel extends BaseUIModel {
|
||||
}
|
||||
""";
|
||||
await Process.run(SystemHelper.powershellPath, [script]);
|
||||
showToast(context!, "创建完毕,请返回桌面查看");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "创建完毕,请返回桌面查看");
|
||||
}
|
||||
|
||||
_loadToolSiteMirrorState() async {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
isEnableToolSiteMirrors =
|
||||
final isEnableToolSiteMirrors =
|
||||
userBox.get("isEnableToolSiteMirrors", defaultValue: false);
|
||||
notifyListeners();
|
||||
state = state.copyWith(isEnableToolSiteMirrors: isEnableToolSiteMirrors);
|
||||
}
|
||||
|
||||
void onChangeToolSiteMirror(bool? b) async {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
isEnableToolSiteMirrors = b == true;
|
||||
final isEnableToolSiteMirrors = b == true;
|
||||
await userBox.put("isEnableToolSiteMirrors", isEnableToolSiteMirrors);
|
||||
notifyListeners();
|
||||
_initState();
|
||||
}
|
||||
|
||||
showLogs() async {
|
||||
SystemHelper.openDir(AppConf.appLogFile?.absolute.path);
|
||||
SystemHelper.openDir(getDPrintFile()?.absolute.path.replaceAll("/", "\\"));
|
||||
}
|
||||
}
|
||||
|
323
lib/ui/settings/settings_ui_model.freezed.dart
Normal file
323
lib/ui/settings/settings_ui_model.freezed.dart
Normal file
@ -0,0 +1,323 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'settings_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$SettingsUIState {
|
||||
dynamic get isDeviceSupportWinHello => throw _privateConstructorUsedError;
|
||||
String get autoLoginEmail => throw _privateConstructorUsedError;
|
||||
bool get isEnableAutoLogin => throw _privateConstructorUsedError;
|
||||
bool get isEnableAutoLoginPwd => throw _privateConstructorUsedError;
|
||||
bool get isEnableToolSiteMirrors => throw _privateConstructorUsedError;
|
||||
String get inputGameLaunchECore => throw _privateConstructorUsedError;
|
||||
String? get customLauncherPath => throw _privateConstructorUsedError;
|
||||
String? get customGamePath => throw _privateConstructorUsedError;
|
||||
int get locationCacheSize => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$SettingsUIStateCopyWith<SettingsUIState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SettingsUIStateCopyWith<$Res> {
|
||||
factory $SettingsUIStateCopyWith(
|
||||
SettingsUIState value, $Res Function(SettingsUIState) then) =
|
||||
_$SettingsUIStateCopyWithImpl<$Res, SettingsUIState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{dynamic isDeviceSupportWinHello,
|
||||
String autoLoginEmail,
|
||||
bool isEnableAutoLogin,
|
||||
bool isEnableAutoLoginPwd,
|
||||
bool isEnableToolSiteMirrors,
|
||||
String inputGameLaunchECore,
|
||||
String? customLauncherPath,
|
||||
String? customGamePath,
|
||||
int locationCacheSize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$SettingsUIStateCopyWithImpl<$Res, $Val extends SettingsUIState>
|
||||
implements $SettingsUIStateCopyWith<$Res> {
|
||||
_$SettingsUIStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isDeviceSupportWinHello = freezed,
|
||||
Object? autoLoginEmail = null,
|
||||
Object? isEnableAutoLogin = null,
|
||||
Object? isEnableAutoLoginPwd = null,
|
||||
Object? isEnableToolSiteMirrors = null,
|
||||
Object? inputGameLaunchECore = null,
|
||||
Object? customLauncherPath = freezed,
|
||||
Object? customGamePath = freezed,
|
||||
Object? locationCacheSize = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
|
||||
? _value.isDeviceSupportWinHello
|
||||
: isDeviceSupportWinHello // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
autoLoginEmail: null == autoLoginEmail
|
||||
? _value.autoLoginEmail
|
||||
: autoLoginEmail // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isEnableAutoLogin: null == isEnableAutoLogin
|
||||
? _value.isEnableAutoLogin
|
||||
: isEnableAutoLogin // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEnableAutoLoginPwd: null == isEnableAutoLoginPwd
|
||||
? _value.isEnableAutoLoginPwd
|
||||
: isEnableAutoLoginPwd // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEnableToolSiteMirrors: null == isEnableToolSiteMirrors
|
||||
? _value.isEnableToolSiteMirrors
|
||||
: isEnableToolSiteMirrors // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
inputGameLaunchECore: null == inputGameLaunchECore
|
||||
? _value.inputGameLaunchECore
|
||||
: inputGameLaunchECore // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
customLauncherPath: freezed == customLauncherPath
|
||||
? _value.customLauncherPath
|
||||
: customLauncherPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
customGamePath: freezed == customGamePath
|
||||
? _value.customGamePath
|
||||
: customGamePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
locationCacheSize: null == locationCacheSize
|
||||
? _value.locationCacheSize
|
||||
: locationCacheSize // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$SettingsUIStateImplCopyWith<$Res>
|
||||
implements $SettingsUIStateCopyWith<$Res> {
|
||||
factory _$$SettingsUIStateImplCopyWith(_$SettingsUIStateImpl value,
|
||||
$Res Function(_$SettingsUIStateImpl) then) =
|
||||
__$$SettingsUIStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{dynamic isDeviceSupportWinHello,
|
||||
String autoLoginEmail,
|
||||
bool isEnableAutoLogin,
|
||||
bool isEnableAutoLoginPwd,
|
||||
bool isEnableToolSiteMirrors,
|
||||
String inputGameLaunchECore,
|
||||
String? customLauncherPath,
|
||||
String? customGamePath,
|
||||
int locationCacheSize});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$SettingsUIStateImplCopyWithImpl<$Res>
|
||||
extends _$SettingsUIStateCopyWithImpl<$Res, _$SettingsUIStateImpl>
|
||||
implements _$$SettingsUIStateImplCopyWith<$Res> {
|
||||
__$$SettingsUIStateImplCopyWithImpl(
|
||||
_$SettingsUIStateImpl _value, $Res Function(_$SettingsUIStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isDeviceSupportWinHello = freezed,
|
||||
Object? autoLoginEmail = null,
|
||||
Object? isEnableAutoLogin = null,
|
||||
Object? isEnableAutoLoginPwd = null,
|
||||
Object? isEnableToolSiteMirrors = null,
|
||||
Object? inputGameLaunchECore = null,
|
||||
Object? customLauncherPath = freezed,
|
||||
Object? customGamePath = freezed,
|
||||
Object? locationCacheSize = null,
|
||||
}) {
|
||||
return _then(_$SettingsUIStateImpl(
|
||||
isDeviceSupportWinHello: freezed == isDeviceSupportWinHello
|
||||
? _value.isDeviceSupportWinHello!
|
||||
: isDeviceSupportWinHello,
|
||||
autoLoginEmail: null == autoLoginEmail
|
||||
? _value.autoLoginEmail
|
||||
: autoLoginEmail // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
isEnableAutoLogin: null == isEnableAutoLogin
|
||||
? _value.isEnableAutoLogin
|
||||
: isEnableAutoLogin // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEnableAutoLoginPwd: null == isEnableAutoLoginPwd
|
||||
? _value.isEnableAutoLoginPwd
|
||||
: isEnableAutoLoginPwd // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isEnableToolSiteMirrors: null == isEnableToolSiteMirrors
|
||||
? _value.isEnableToolSiteMirrors
|
||||
: isEnableToolSiteMirrors // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
inputGameLaunchECore: null == inputGameLaunchECore
|
||||
? _value.inputGameLaunchECore
|
||||
: inputGameLaunchECore // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
customLauncherPath: freezed == customLauncherPath
|
||||
? _value.customLauncherPath
|
||||
: customLauncherPath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
customGamePath: freezed == customGamePath
|
||||
? _value.customGamePath
|
||||
: customGamePath // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
locationCacheSize: null == locationCacheSize
|
||||
? _value.locationCacheSize
|
||||
: locationCacheSize // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$SettingsUIStateImpl implements _SettingsUIState {
|
||||
const _$SettingsUIStateImpl(
|
||||
{this.isDeviceSupportWinHello = false,
|
||||
this.autoLoginEmail = "-",
|
||||
this.isEnableAutoLogin = false,
|
||||
this.isEnableAutoLoginPwd = false,
|
||||
this.isEnableToolSiteMirrors = false,
|
||||
this.inputGameLaunchECore = "0",
|
||||
this.customLauncherPath,
|
||||
this.customGamePath,
|
||||
this.locationCacheSize = 0});
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final dynamic isDeviceSupportWinHello;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String autoLoginEmail;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isEnableAutoLogin;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isEnableAutoLoginPwd;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isEnableToolSiteMirrors;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String inputGameLaunchECore;
|
||||
@override
|
||||
final String? customLauncherPath;
|
||||
@override
|
||||
final String? customGamePath;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int locationCacheSize;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SettingsUIState(isDeviceSupportWinHello: $isDeviceSupportWinHello, autoLoginEmail: $autoLoginEmail, isEnableAutoLogin: $isEnableAutoLogin, isEnableAutoLoginPwd: $isEnableAutoLoginPwd, isEnableToolSiteMirrors: $isEnableToolSiteMirrors, inputGameLaunchECore: $inputGameLaunchECore, customLauncherPath: $customLauncherPath, customGamePath: $customGamePath, locationCacheSize: $locationCacheSize)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$SettingsUIStateImpl &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other.isDeviceSupportWinHello, isDeviceSupportWinHello) &&
|
||||
(identical(other.autoLoginEmail, autoLoginEmail) ||
|
||||
other.autoLoginEmail == autoLoginEmail) &&
|
||||
(identical(other.isEnableAutoLogin, isEnableAutoLogin) ||
|
||||
other.isEnableAutoLogin == isEnableAutoLogin) &&
|
||||
(identical(other.isEnableAutoLoginPwd, isEnableAutoLoginPwd) ||
|
||||
other.isEnableAutoLoginPwd == isEnableAutoLoginPwd) &&
|
||||
(identical(
|
||||
other.isEnableToolSiteMirrors, isEnableToolSiteMirrors) ||
|
||||
other.isEnableToolSiteMirrors == isEnableToolSiteMirrors) &&
|
||||
(identical(other.inputGameLaunchECore, inputGameLaunchECore) ||
|
||||
other.inputGameLaunchECore == inputGameLaunchECore) &&
|
||||
(identical(other.customLauncherPath, customLauncherPath) ||
|
||||
other.customLauncherPath == customLauncherPath) &&
|
||||
(identical(other.customGamePath, customGamePath) ||
|
||||
other.customGamePath == customGamePath) &&
|
||||
(identical(other.locationCacheSize, locationCacheSize) ||
|
||||
other.locationCacheSize == locationCacheSize));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(isDeviceSupportWinHello),
|
||||
autoLoginEmail,
|
||||
isEnableAutoLogin,
|
||||
isEnableAutoLoginPwd,
|
||||
isEnableToolSiteMirrors,
|
||||
inputGameLaunchECore,
|
||||
customLauncherPath,
|
||||
customGamePath,
|
||||
locationCacheSize);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$SettingsUIStateImplCopyWith<_$SettingsUIStateImpl> get copyWith =>
|
||||
__$$SettingsUIStateImplCopyWithImpl<_$SettingsUIStateImpl>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _SettingsUIState implements SettingsUIState {
|
||||
const factory _SettingsUIState(
|
||||
{final dynamic isDeviceSupportWinHello,
|
||||
final String autoLoginEmail,
|
||||
final bool isEnableAutoLogin,
|
||||
final bool isEnableAutoLoginPwd,
|
||||
final bool isEnableToolSiteMirrors,
|
||||
final String inputGameLaunchECore,
|
||||
final String? customLauncherPath,
|
||||
final String? customGamePath,
|
||||
final int locationCacheSize}) = _$SettingsUIStateImpl;
|
||||
|
||||
@override
|
||||
dynamic get isDeviceSupportWinHello;
|
||||
@override
|
||||
String get autoLoginEmail;
|
||||
@override
|
||||
bool get isEnableAutoLogin;
|
||||
@override
|
||||
bool get isEnableAutoLoginPwd;
|
||||
@override
|
||||
bool get isEnableToolSiteMirrors;
|
||||
@override
|
||||
String get inputGameLaunchECore;
|
||||
@override
|
||||
String? get customLauncherPath;
|
||||
@override
|
||||
String? get customGamePath;
|
||||
@override
|
||||
int get locationCacheSize;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$SettingsUIStateImplCopyWith<_$SettingsUIStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
26
lib/ui/settings/settings_ui_model.g.dart
Normal file
26
lib/ui/settings/settings_ui_model.g.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'settings_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$settingsUIModelHash() => r'34ac24f658a081350be7d2b3bda810d101b888a1';
|
||||
|
||||
/// See also [SettingsUIModel].
|
||||
@ProviderFor(SettingsUIModel)
|
||||
final settingsUIModelProvider =
|
||||
AutoDisposeNotifierProvider<SettingsUIModel, SettingsUIState>.internal(
|
||||
SettingsUIModel.new,
|
||||
name: r'settingsUIModelProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$settingsUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SettingsUIModel = AutoDisposeNotifier<SettingsUIState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
263
lib/ui/settings/upgrade_dialog.dart
Normal file
263
lib/ui/settings/upgrade_dialog.dart
Normal file
@ -0,0 +1,263 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Material;
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:markdown/markdown.dart' as markdown;
|
||||
import 'package:starcitizen_doctor/api/api.dart';
|
||||
import 'package:starcitizen_doctor/app.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/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class UpgradeDialogUI extends HookConsumerWidget {
|
||||
const UpgradeDialogUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appState = ref.watch(appGlobalModelProvider);
|
||||
final appModel = ref.read(appGlobalModelProvider.notifier);
|
||||
final description = useState<String?>(null);
|
||||
final isUsingDiversion = useState(false);
|
||||
final isUpgrading = useState(false);
|
||||
final progress = useState(0.0);
|
||||
final downloadUrl = useState("");
|
||||
|
||||
final targetVersion = ConstConf.isMSE
|
||||
? appState.networkVersionData!.mSELastVersion!
|
||||
: appState.networkVersionData!.lastVersion!;
|
||||
|
||||
final minVersionCode = ConstConf.isMSE
|
||||
? appState.networkVersionData?.mSEMinVersionCode
|
||||
: appState.networkVersionData?.minVersionCode;
|
||||
|
||||
useEffect(() {
|
||||
_getUpdateInfo(context, targetVersion, description, downloadUrl);
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return Material(
|
||||
child: ContentDialog(
|
||||
title: Text("发现新版本 -> $targetVersion"),
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (description.value == null) ...[
|
||||
const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
ProgressRing(),
|
||||
SizedBox(height: 16),
|
||||
Text("正在获取新版本详情...")
|
||||
],
|
||||
),
|
||||
)
|
||||
] else
|
||||
...makeMarkdownView(description.value!,
|
||||
attachmentsUrl: URLConf.giteaAttachmentsUrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (isUsingDiversion.value) ...[
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onTap: _launchReleaseUrl,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(.1),
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Text(
|
||||
"提示:当前正在使用分流服务器进行更新,可能会出现下载速度下降,但有助于我们进行成本控制,若下载异常请点击这里跳转手动安装。",
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withOpacity(.7)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (isUpgrading.value) ...[
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Text(progress.value == 100
|
||||
? "正在安装: "
|
||||
: "正在下载: ${progress.value.toStringAsFixed(2)}% "),
|
||||
Expanded(
|
||||
child: ProgressBar(
|
||||
value: progress.value == 100 ? null : progress.value,
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: isUpgrading.value
|
||||
? null
|
||||
: [
|
||||
if (downloadUrl.value.isNotEmpty)
|
||||
FilledButton(
|
||||
onPressed: () => _doUpgrade(
|
||||
context,
|
||||
appState,
|
||||
isUpgrading,
|
||||
appModel,
|
||||
downloadUrl,
|
||||
description,
|
||||
isUsingDiversion,
|
||||
progress),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 4, bottom: 4, left: 8, right: 8),
|
||||
child: Text("立即更新"),
|
||||
)),
|
||||
if (ConstConf.appVersionCode >= (minVersionCode ?? 0))
|
||||
Button(
|
||||
onPressed: () => _doCancel(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 4, bottom: 4, left: 8, right: 8),
|
||||
child: Text("下次吧"),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _getUpdateInfo(
|
||||
BuildContext context,
|
||||
String targetVersion,
|
||||
ValueNotifier<String?> description,
|
||||
ValueNotifier<String> downloadUrl) async {
|
||||
try {
|
||||
final r = await Api.getAppReleaseDataByVersionName(targetVersion);
|
||||
description.value = r["body"];
|
||||
final assets = List.of(r["assets"] ?? []);
|
||||
for (var asset in assets) {
|
||||
if (asset["name"].toString().endsWith("SETUP.exe")) {
|
||||
downloadUrl.value = asset["browser_download_url"];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("UpgradeDialogUIModel.loadData Error : $e");
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context, false);
|
||||
}
|
||||
}
|
||||
|
||||
void _launchReleaseUrl() {
|
||||
launchUrlString(URLConf.devReleaseUrl);
|
||||
}
|
||||
|
||||
void _doCancel(BuildContext context) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
|
||||
String _getDiversionUrl(String description) {
|
||||
try {
|
||||
final htmlStr = markdown.markdownToHtml(description);
|
||||
final html = html_parser.parse(htmlStr);
|
||||
for (var element in html.querySelectorAll('a')) {
|
||||
String linkText = element.text;
|
||||
String linkUrl = element.attributes['href'] ?? '';
|
||||
if (linkText.trim().endsWith("_SETUP.exe")) {
|
||||
final diversionDownloadUrl = linkUrl.trim();
|
||||
dPrint("diversionDownloadUrl === $diversionDownloadUrl");
|
||||
return diversionDownloadUrl;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dPrint("_checkDiversionUrl Error:$e");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Future<void> _doUpgrade(
|
||||
BuildContext context,
|
||||
AppGlobalState appState,
|
||||
ValueNotifier<bool> isUpgrading,
|
||||
AppGlobalModel appModel,
|
||||
ValueNotifier<String> downloadUrl,
|
||||
ValueNotifier<String?> description,
|
||||
ValueNotifier<bool> isUsingDiversion,
|
||||
ValueNotifier<double> progress) async {
|
||||
if (ConstConf.isMSE) {
|
||||
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
if (ConstConf.appVersionCode <
|
||||
(appState.networkVersionData?.minVersionCode ?? 0)) {
|
||||
exit(0);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
_doCancel(context);
|
||||
return;
|
||||
}
|
||||
isUpgrading.value = true;
|
||||
final fileName = "${appModel.getUpgradePath()}/next_SETUP.exe";
|
||||
try {
|
||||
// check diversionDownloadUrl
|
||||
var url = downloadUrl.value;
|
||||
final diversionDownloadUrl = _getDiversionUrl(description.value!);
|
||||
final dio = Dio();
|
||||
if (diversionDownloadUrl.isNotEmpty) {
|
||||
try {
|
||||
final resp = await dio.head(diversionDownloadUrl,
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 10)));
|
||||
if (resp.statusCode == 200) {
|
||||
isUsingDiversion.value = true;
|
||||
url = diversionDownloadUrl;
|
||||
} else {
|
||||
isUsingDiversion.value = false;
|
||||
}
|
||||
dPrint("diversionDownloadUrl head resp == ${resp.headers}");
|
||||
} catch (e) {
|
||||
dPrint("diversionDownloadUrl err:$e");
|
||||
}
|
||||
}
|
||||
await dio.download(url, fileName,
|
||||
onReceiveProgress: (int count, int total) {
|
||||
progress.value = (count / total) * 100;
|
||||
});
|
||||
} catch (_) {
|
||||
isUpgrading.value = false;
|
||||
progress.value = 0;
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "下载失败,请尝试手动安装!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final r = await (Process.run(
|
||||
SystemHelper.powershellPath, ["start", fileName, "/SILENT"]));
|
||||
if (r.stderr.toString().isNotEmpty) {
|
||||
throw r.stderr;
|
||||
}
|
||||
exit(0);
|
||||
} catch (_) {
|
||||
isUpgrading.value = false;
|
||||
progress.value = 0;
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "运行失败,请尝试手动安装!");
|
||||
Process.run(SystemHelper.powershellPath,
|
||||
["explorer.exe", "/select,\"$fileName\""]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
import 'package:flutter/material.dart' show Material;
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
|
||||
import 'upgrade_dialog_ui_model.dart';
|
||||
|
||||
class UpgradeDialogUI extends BaseUI<UpgradeDialogUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, UpgradeDialogUIModel model) {
|
||||
return Material(
|
||||
child: ContentDialog(
|
||||
title: Text("发现新版本 -> ${model.targetVersion}"),
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .55),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (model.description == null) ...[
|
||||
const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
ProgressRing(),
|
||||
SizedBox(height: 16),
|
||||
Text("正在获取新版本详情...")
|
||||
],
|
||||
),
|
||||
)
|
||||
] else
|
||||
...makeMarkdownView(model.description!,
|
||||
attachmentsUrl: URLConf.giteaAttachmentsUrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (model.isUsingDiversion) ...[
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onTap: model.launchReleaseUrl,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(.1),
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: Text(
|
||||
"提示:当前正在使用分流服务器进行更新,可能会出现下载速度下降,但有助于我们进行成本控制,若下载异常请点击这里跳转手动安装。",
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: Colors.white.withOpacity(.7)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (model.isUpgrading) ...[
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Text(model.progress == 100
|
||||
? "正在安装: "
|
||||
: "正在下载: ${model.progress?.toStringAsFixed(2) ?? 0}% "),
|
||||
Expanded(
|
||||
child: ProgressBar(
|
||||
value: model.progress == 100 ? null : model.progress,
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: model.isUpgrading
|
||||
? null
|
||||
: [
|
||||
if (model.downloadUrl.isNotEmpty)
|
||||
FilledButton(
|
||||
onPressed: model.doUpgrade,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 4, bottom: 4, left: 8, right: 8),
|
||||
child: Text("立即更新"),
|
||||
)),
|
||||
if (AppConf.appVersionCode >=
|
||||
(AppConf.networkVersionData?.minVersionCode ?? 0))
|
||||
Button(
|
||||
onPressed: model.doCancel,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 4, bottom: 4, left: 8, right: 8),
|
||||
child: Text("下次吧"),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, UpgradeDialogUIModel model) => "";
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:markdown/markdown.dart';
|
||||
import 'package:starcitizen_doctor/api/api.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:html/parser.dart';
|
||||
|
||||
class UpgradeDialogUIModel extends BaseUIModel {
|
||||
String? description;
|
||||
String targetVersion = "";
|
||||
String downloadUrl = "";
|
||||
String? diversionDownloadUrl;
|
||||
bool isUsingDiversion = false;
|
||||
|
||||
bool isUpgrading = false;
|
||||
double? progress;
|
||||
|
||||
@override
|
||||
Future loadData() async {
|
||||
// get download url for gitlab release
|
||||
try {
|
||||
targetVersion = AppConf.isMSE
|
||||
? AppConf.networkVersionData!.mSELastVersion!
|
||||
: AppConf.networkVersionData!.lastVersion!;
|
||||
final r = await Api.getAppReleaseDataByVersionName(targetVersion);
|
||||
description = r["body"];
|
||||
_checkDiversionUrl();
|
||||
final assets = List.of(r["assets"] ?? []);
|
||||
for (var asset in assets) {
|
||||
if (asset["name"].toString().endsWith("SETUP.exe")) {
|
||||
downloadUrl = asset["browser_download_url"];
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
dPrint("UpgradeDialogUIModel.loadData Error : $e");
|
||||
Navigator.pop(context!, false);
|
||||
}
|
||||
}
|
||||
|
||||
doUpgrade() async {
|
||||
if (AppConf.isMSE) {
|
||||
launchUrlString("ms-windows-store://pdp/?productid=9NF3SWFWNKL1");
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
if (AppConf.appVersionCode <
|
||||
(AppConf.networkVersionData?.minVersionCode ?? 0)) {
|
||||
exit(0);
|
||||
}
|
||||
Navigator.pop(context!);
|
||||
}
|
||||
isUpgrading = true;
|
||||
notifyListeners();
|
||||
final fileName = "${AppConf.getUpgradePath()}/next_SETUP.exe";
|
||||
try {
|
||||
// check diversionDownloadUrl
|
||||
var url = downloadUrl;
|
||||
final dio = Dio();
|
||||
if (diversionDownloadUrl != null) {
|
||||
try {
|
||||
final resp = await dio.head(diversionDownloadUrl!,
|
||||
options: Options(
|
||||
sendTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 10)));
|
||||
if (resp.statusCode == 200) {
|
||||
isUsingDiversion = true;
|
||||
url = diversionDownloadUrl!;
|
||||
notifyListeners();
|
||||
} else {
|
||||
isUsingDiversion = false;
|
||||
notifyListeners();
|
||||
}
|
||||
dPrint("diversionDownloadUrl head resp == ${resp.headers}");
|
||||
} catch (e) {
|
||||
dPrint("diversionDownloadUrl err:$e");
|
||||
}
|
||||
}
|
||||
await dio.download(url, fileName,
|
||||
onReceiveProgress: (int count, int total) {
|
||||
progress = (count / total) * 100;
|
||||
notifyListeners();
|
||||
});
|
||||
} catch (_) {
|
||||
isUpgrading = false;
|
||||
progress = null;
|
||||
showToast(context!, "下载失败,请尝试手动安装!");
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final r = await (Process.run(
|
||||
SystemHelper.powershellPath, ["start", fileName, "/SILENT"]));
|
||||
if (r.stderr.toString().isNotEmpty) {
|
||||
throw r.stderr;
|
||||
}
|
||||
exit(0);
|
||||
} catch (_) {
|
||||
isUpgrading = false;
|
||||
progress = null;
|
||||
showToast(context!, "运行失败,请尝试手动安装!");
|
||||
Process.run(SystemHelper.powershellPath,
|
||||
["explorer.exe", "/select,\"$fileName\""]);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void doCancel() {
|
||||
Navigator.pop(context!, true);
|
||||
}
|
||||
|
||||
void _checkDiversionUrl() {
|
||||
try {
|
||||
final htmlStr = markdownToHtml(description!);
|
||||
final html = parse(htmlStr);
|
||||
html.querySelectorAll('a').forEach((element) {
|
||||
String linkText = element.text;
|
||||
String linkUrl = element.attributes['href'] ?? '';
|
||||
if (linkText.trim().endsWith("_SETUP.exe")) {
|
||||
diversionDownloadUrl = linkUrl.trim();
|
||||
dPrint("diversionDownloadUrl === $diversionDownloadUrl");
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
dPrint("_checkDiversionUrl Error:$e");
|
||||
}
|
||||
}
|
||||
|
||||
void launchReleaseUrl() {
|
||||
launchUrlString(URLConf.devReleaseUrl);
|
||||
}
|
||||
}
|
@ -1,12 +1,30 @@
|
||||
import 'package:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/app.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/const_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/provider/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'splash_ui_model.dart';
|
||||
class SplashUI extends HookConsumerWidget {
|
||||
const SplashUI({super.key});
|
||||
|
||||
class SplashUI extends BaseUI<SplashUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, SplashUIModel model) {
|
||||
return makeDefaultPage(context, model,
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final stepState = useState(0);
|
||||
final step = stepState.value;
|
||||
|
||||
useEffect(() {
|
||||
final appModel = ref.read(appGlobalModelProvider.notifier);
|
||||
_initApp(context, appModel, stepState, ref);
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
return makeDefaultPage(context,
|
||||
content: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -15,9 +33,9 @@ class SplashUI extends BaseUI<SplashUIModel> {
|
||||
const SizedBox(height: 32),
|
||||
const ProgressRing(),
|
||||
const SizedBox(height: 32),
|
||||
if (model.step == 0) const Text("正在检测可用性,这可能需要一点时间..."),
|
||||
if (model.step == 1) const Text("正在检查更新..."),
|
||||
if (model.step == 2) const Text("即将完成..."),
|
||||
if (step == 0) const Text("正在检测可用性,这可能需要一点时间..."),
|
||||
if (step == 1) const Text("正在检查更新..."),
|
||||
if (step == 2) const Text("即将完成..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -34,12 +52,27 @@ class SplashUI extends BaseUI<SplashUIModel> {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
"SC汉化盒子 V${AppConf.appVersion} ${AppConf.isMSE ? "" : " Dev"}")
|
||||
"SC汉化盒子 V${ConstConf.appVersion} ${ConstConf.isMSE ? "" : " Dev"}")
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, SplashUIModel model) => "";
|
||||
void _initApp(BuildContext context, AppGlobalModel appModel,
|
||||
ValueNotifier<int> stepState, WidgetRef ref) async {
|
||||
await appModel.initApp();
|
||||
AnalyticsApi.touch("launch");
|
||||
try {
|
||||
await URLConf.checkHost();
|
||||
} catch (e) {
|
||||
dPrint("checkHost Error:$e");
|
||||
}
|
||||
stepState.value = 1;
|
||||
if (!context.mounted) return;
|
||||
await appModel.checkUpdate(context);
|
||||
stepState.value = 2;
|
||||
ref.read(aria2cModelProvider);
|
||||
if (!context.mounted) return;
|
||||
context.go("/index");
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
import 'package:starcitizen_doctor/api/analytics.dart';
|
||||
import 'package:starcitizen_doctor/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/io/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/ui/index_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/index_ui_model.dart';
|
||||
|
||||
import '../common/conf/app_conf.dart';
|
||||
|
||||
class SplashUIModel extends BaseUIModel {
|
||||
int step = 0;
|
||||
|
||||
@override
|
||||
void initModel() {
|
||||
_initApp();
|
||||
super.initModel();
|
||||
}
|
||||
|
||||
Future<void> _initApp() async {
|
||||
AnalyticsApi.touch("launch");
|
||||
try {
|
||||
await URLConf.checkHost();
|
||||
} catch (e) {
|
||||
dPrint("checkHost Error:$e");
|
||||
}
|
||||
step = 1;
|
||||
notifyListeners();
|
||||
await AppConf.checkUpdate();
|
||||
step = 2;
|
||||
notifyListeners();
|
||||
await Aria2cManager.checkLazyLoad();
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context!,
|
||||
BaseUIContainer(
|
||||
uiCreate: () => IndexUI(),
|
||||
modelCreate: () => IndexUIModel()).makeRoute(context!),
|
||||
(route) => false);
|
||||
}
|
||||
}
|
@ -1,11 +1,25 @@
|
||||
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:starcitizen_doctor/base/ui.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
|
||||
import 'tools_ui_model.dart';
|
||||
class ToolsUI extends HookConsumerWidget {
|
||||
const ToolsUI({super.key});
|
||||
|
||||
class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
@override
|
||||
Widget? buildBody(BuildContext context, ToolsUIModel model) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(toolsUIModelProvider);
|
||||
final model = ref.read(toolsUIModelProvider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
addPostFrameCallback(() {
|
||||
model.loadToolsCard(context, skipPathScan: false);
|
||||
});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
@ -18,15 +32,18 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
makeGameLauncherPathSelect(context, model),
|
||||
makeGameLauncherPathSelect(context, model, state),
|
||||
const SizedBox(height: 12),
|
||||
makeGamePathSelect(context, model),
|
||||
makeGamePathSelect(context, model, state),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Button(
|
||||
onPressed: model.working ? null : model.loadData,
|
||||
onPressed: state.working
|
||||
? null
|
||||
: () =>
|
||||
model.loadToolsCard(context, skipPathScan: false),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 30, bottom: 30, left: 12, right: 12),
|
||||
@ -37,7 +54,7 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (model.items.isEmpty)
|
||||
if (state.items.isEmpty)
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
@ -58,12 +75,12 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
crossAxisCount: 3,
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
itemCount: (model.isItemLoading)
|
||||
? model.items.length + 1
|
||||
: model.items.length,
|
||||
itemCount: (state.isItemLoading)
|
||||
? state.items.length + 1
|
||||
: state.items.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == model.items.length) {
|
||||
if (index == state.items.length) {
|
||||
return Container(
|
||||
width: 300,
|
||||
height: 200,
|
||||
@ -73,7 +90,7 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
),
|
||||
child: makeLoading(context));
|
||||
}
|
||||
final item = model.items[index];
|
||||
final item = state.items[index];
|
||||
return Container(
|
||||
width: 300,
|
||||
height: 200,
|
||||
@ -119,7 +136,7 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
children: [
|
||||
const Spacer(),
|
||||
Button(
|
||||
onPressed: model.working
|
||||
onPressed: state.working
|
||||
? null
|
||||
: item.onTap == null
|
||||
? null
|
||||
@ -148,7 +165,7 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
)
|
||||
],
|
||||
),
|
||||
if (model.working)
|
||||
if (state.working)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(150),
|
||||
@ -168,7 +185,8 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGamePathSelect(BuildContext context, ToolsUIModel model) {
|
||||
Widget makeGamePathSelect(
|
||||
BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -178,18 +196,17 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<String>(
|
||||
value: model.scInstalledPath,
|
||||
value: state.scInstalledPath,
|
||||
items: [
|
||||
for (final path in model.scInstallPaths)
|
||||
for (final path in state.scInstallPaths)
|
||||
ComboBoxItem(
|
||||
value: path,
|
||||
child: Text(path),
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.loadData(skipPathScan: true);
|
||||
model.scInstalledPath = v!;
|
||||
model.notifyListeners();
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
model.onChangeGamePath(v!);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -200,12 +217,13 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.folder_open),
|
||||
),
|
||||
onPressed: () => model.openDir(model.scInstalledPath))
|
||||
onPressed: () => model.openDir(state.scInstalledPath))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeGameLauncherPathSelect(BuildContext context, ToolsUIModel model) {
|
||||
Widget makeGameLauncherPathSelect(
|
||||
BuildContext context, ToolsUIModel model, ToolsUIState state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -215,18 +233,17 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: ComboBox<String>(
|
||||
value: model.rsiLauncherInstalledPath,
|
||||
value: state.rsiLauncherInstalledPath,
|
||||
items: [
|
||||
for (final path in model.rsiLauncherInstallPaths)
|
||||
for (final path in state.rsiLauncherInstallPaths)
|
||||
ComboBoxItem(
|
||||
value: path,
|
||||
child: Text(path),
|
||||
)
|
||||
],
|
||||
onChanged: (v) {
|
||||
model.loadData(skipPathScan: true);
|
||||
model.rsiLauncherInstalledPath = v!;
|
||||
model.notifyListeners();
|
||||
model.loadToolsCard(context, skipPathScan: true);
|
||||
model.onChangeLauncherPath(v!);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -237,11 +254,8 @@ class ToolsUI extends BaseUI<ToolsUIModel> {
|
||||
padding: EdgeInsets.all(6),
|
||||
child: Icon(FluentIcons.folder_open),
|
||||
),
|
||||
onPressed: () => model.openDir(model.rsiLauncherInstalledPath))
|
||||
onPressed: () => model.openDir(state.rsiLauncherInstalledPath))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getUITitle(BuildContext context, ToolsUIModel model) => "ToolsUI";
|
||||
}
|
||||
|
@ -1,100 +1,122 @@
|
||||
// ignore_for_file: avoid_build_context_in_providers
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
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/base/ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/log_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/helper/system_helper.dart';
|
||||
import 'package:starcitizen_doctor/common/io/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/downloader/downloader_ui.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/downloader/downloader_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/provider.dart';
|
||||
import 'package:starcitizen_doctor/provider/aria2c.dart';
|
||||
import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart';
|
||||
import 'package:starcitizen_doctor/widgets/widgets.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
part 'tools_ui_model.g.dart';
|
||||
|
||||
class ToolsUIModel extends BaseUIModel {
|
||||
bool _working = false;
|
||||
part 'tools_ui_model.freezed.dart';
|
||||
|
||||
String scInstalledPath = "";
|
||||
String rsiLauncherInstalledPath = "";
|
||||
class ToolsItemData {
|
||||
String key;
|
||||
|
||||
List<String> scInstallPaths = [];
|
||||
List<String> rsiLauncherInstallPaths = [];
|
||||
ToolsItemData(this.key, this.name, this.infoString, this.icon, {this.onTap});
|
||||
|
||||
set working(bool b) {
|
||||
_working = b;
|
||||
notifyListeners();
|
||||
String name;
|
||||
String infoString;
|
||||
Widget icon;
|
||||
AsyncCallback? onTap;
|
||||
}
|
||||
|
||||
bool get working => _working;
|
||||
|
||||
var items = <_ToolsItemData>[];
|
||||
|
||||
bool isItemLoading = false;
|
||||
@freezed
|
||||
class ToolsUIState with _$ToolsUIState {
|
||||
const factory ToolsUIState({
|
||||
@Default(false) bool working,
|
||||
@Default("") String scInstalledPath,
|
||||
@Default("") String rsiLauncherInstalledPath,
|
||||
@Default([]) List<String> scInstallPaths,
|
||||
@Default([]) List<String> rsiLauncherInstallPaths,
|
||||
@Default([]) List<ToolsItemData> items,
|
||||
@Default(false) bool isItemLoading,
|
||||
}) = _ToolsUIState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class ToolsUIModel extends _$ToolsUIModel {
|
||||
@override
|
||||
Future loadData({bool skipPathScan = false}) async {
|
||||
if (isItemLoading) return;
|
||||
items.clear();
|
||||
notifyListeners();
|
||||
ToolsUIState build() {
|
||||
state = const ToolsUIState();
|
||||
return state;
|
||||
}
|
||||
|
||||
loadToolsCard(BuildContext context, {bool skipPathScan = false}) async {
|
||||
if (state.isItemLoading) return;
|
||||
var items = <ToolsItemData>[];
|
||||
state = state.copyWith(items: items, isItemLoading: true);
|
||||
if (!skipPathScan) {
|
||||
await reScanPath();
|
||||
await reScanPath(context);
|
||||
}
|
||||
try {
|
||||
items = [
|
||||
_ToolsItemData(
|
||||
"systeminfo",
|
||||
ToolsItemData(
|
||||
"systemnfo",
|
||||
"查看系统信息",
|
||||
"查看系统关键信息,用于快速问诊 \n\n耗时操作,请耐心等待。",
|
||||
const Icon(FluentIcons.system, size: 28),
|
||||
onTap: _showSystemInfo,
|
||||
onTap: () => _showSystemInfo(context),
|
||||
),
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"p4k_downloader",
|
||||
"P4K 分流下载 / 修复",
|
||||
"使用星际公民中文百科提供的分流下载服务,可用于下载或修复 p4k。 \n资源有限,请勿滥用。",
|
||||
const Icon(FontAwesomeIcons.download, size: 28),
|
||||
onTap: _downloadP4k,
|
||||
onTap: () => _downloadP4k(context),
|
||||
),
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"reinstall_eac",
|
||||
"重装 EasyAntiCheat 反作弊",
|
||||
"若您遇到 EAC 错误,且自动修复无效,请尝试使用此功能重装 EAC。",
|
||||
const Icon(FluentIcons.game, size: 28),
|
||||
onTap: _reinstallEAC,
|
||||
onTap: () => _reinstallEAC(context),
|
||||
),
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"rsilauncher_admin_mode",
|
||||
"RSI Launcher 管理员模式",
|
||||
"以管理员身份运行RSI启动器,可能会解决一些问题。\n\n若设置了能效核心屏蔽参数,也会在此应用。",
|
||||
const Icon(FluentIcons.admin, size: 28),
|
||||
onTap: _adminRSILauncher,
|
||||
onTap: () => _adminRSILauncher(context),
|
||||
),
|
||||
];
|
||||
isItemLoading = true;
|
||||
items.add(await _addShaderCard());
|
||||
notifyListeners();
|
||||
items.add(await _addPhotographyCard());
|
||||
notifyListeners();
|
||||
items.addAll(await _addLogCard());
|
||||
notifyListeners();
|
||||
items.addAll(await _addNvmePatchCard());
|
||||
notifyListeners();
|
||||
// close loading
|
||||
isItemLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
state = state.copyWith(items: items);
|
||||
if (!context.mounted) return;
|
||||
items.add(await _addShaderCard(context));
|
||||
state = state.copyWith(items: items);
|
||||
if (!context.mounted) return;
|
||||
items.add(await _addPhotographyCard(context));
|
||||
state = state.copyWith(items: items);
|
||||
if (!context.mounted) return;
|
||||
items.addAll(await _addLogCard(context));
|
||||
state = state.copyWith(items: items);
|
||||
if (!context.mounted) return;
|
||||
items.addAll(await _addNvmePatchCard(context));
|
||||
state = state.copyWith(items: items, isItemLoading: false);
|
||||
} catch (e) {
|
||||
showToast(context!, "初始化失败,请截图报告给开发者。$e");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "初始化失败,请截图报告给开发者。$e");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<List<_ToolsItemData>> _addLogCard() async {
|
||||
Future<List<ToolsItemData>> _addLogCard(BuildContext context) async {
|
||||
double logPathLen = 0;
|
||||
try {
|
||||
logPathLen =
|
||||
@ -103,83 +125,85 @@ class ToolsUIModel extends BaseUIModel {
|
||||
1024;
|
||||
} catch (_) {}
|
||||
return [
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"rsilauncher_log_fix",
|
||||
"RSI Launcher Log 修复",
|
||||
"在某些情况下 RSI启动器 的 log 文件会损坏,导致无法完成问题扫描,使用此工具清理损坏的 log 文件。\n\n当前日志文件大小:${(logPathLen.toStringAsFixed(4))} MB",
|
||||
const Icon(FontAwesomeIcons.bookBible, size: 28),
|
||||
onTap: _rsiLogFix,
|
||||
onTap: () => _rsiLogFix(context),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<_ToolsItemData>> _addNvmePatchCard() async {
|
||||
Future<List<ToolsItemData>> _addNvmePatchCard(BuildContext context) async {
|
||||
final nvmePatchStatus = await SystemHelper.checkNvmePatchStatus();
|
||||
return [
|
||||
if (nvmePatchStatus)
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"remove_nvme_settings",
|
||||
"移除 nvme 注册表补丁",
|
||||
"若您使用 nvme 补丁出现问题,请运行此工具。(可能导致游戏 安装/更新 不可用。)\n\n当前补丁状态:${(nvmePatchStatus) ? "已安装" : "未安装"}",
|
||||
const Icon(FluentIcons.hard_drive, size: 28),
|
||||
onTap: nvmePatchStatus
|
||||
? () async {
|
||||
working = true;
|
||||
state = state.copyWith(working: true);
|
||||
await SystemHelper.doRemoveNvmePath();
|
||||
working = false;
|
||||
showToast(context!, "已移除,重启生效!");
|
||||
loadData(skipPathScan: true);
|
||||
state = state.copyWith(working: false);
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "已移除,重启电脑生效!");
|
||||
loadToolsCard(context, skipPathScan: true);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
if (!nvmePatchStatus)
|
||||
_ToolsItemData(
|
||||
ToolsItemData(
|
||||
"add_nvme_settings",
|
||||
"写入 nvme 注册表补丁",
|
||||
"手动写入NVM补丁,该功能仅在您知道自己在作什么的情况下使用",
|
||||
const Icon(FontAwesomeIcons.cashRegister, size: 28),
|
||||
onTap: () async {
|
||||
working = true;
|
||||
state = state.copyWith(working: true);
|
||||
final r = await SystemHelper.addNvmePatch();
|
||||
if (r == "") {
|
||||
showToast(context!,
|
||||
"修复成功,请尝试重启后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
|
||||
notifyListeners();
|
||||
if (!context.mounted) return;
|
||||
showToast(context,
|
||||
"修复成功,请尝试重启电脑后继续安装游戏! 若注册表修改操作导致其他软件出现兼容问题,请使用 工具 中的 NVME 注册表清理。");
|
||||
} else {
|
||||
showToast(context!, "修复失败,$r");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "修复失败,$r");
|
||||
}
|
||||
working = false;
|
||||
loadData(skipPathScan: true);
|
||||
state = state.copyWith(working: false);
|
||||
loadToolsCard(context, skipPathScan: true);
|
||||
},
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
Future<_ToolsItemData> _addShaderCard() async {
|
||||
Future<ToolsItemData> _addShaderCard(BuildContext context) async {
|
||||
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
|
||||
return _ToolsItemData(
|
||||
return ToolsItemData(
|
||||
"clean_shaders",
|
||||
"清理着色器缓存",
|
||||
"若游戏画面出现异常或版本更新后可使用本工具清理过期的着色器(当大于500M时,建议清理) \n\n缓存大小:${((await SystemHelper.getDirLen(gameShaderCachePath ?? "", skipPath: [
|
||||
"$gameShaderCachePath\\Crashes"
|
||||
])) / 1024 / 1024).toStringAsFixed(4)} MB",
|
||||
const Icon(FontAwesomeIcons.shapes, size: 28),
|
||||
onTap: _cleanShaderCache,
|
||||
onTap: () => _cleanShaderCache(context),
|
||||
);
|
||||
}
|
||||
|
||||
Future<_ToolsItemData> _addPhotographyCard() async {
|
||||
Future<ToolsItemData> _addPhotographyCard(BuildContext context) async {
|
||||
// 获取配置文件状态
|
||||
final isEnable = await _checkPhotographyStatus();
|
||||
final isEnable = await _checkPhotographyStatus(context);
|
||||
|
||||
return _ToolsItemData(
|
||||
return ToolsItemData(
|
||||
"photography_mode",
|
||||
isEnable ? "关闭摄影模式" : "开启摄影模式",
|
||||
isEnable
|
||||
? "还原镜头摇晃效果。\n\n@拉邦那 Lapernum 提供参数信息。"
|
||||
: "一键关闭游戏内镜头晃动以便于摄影操作。\n\n @拉邦那 Lapernum 提供参数信息。",
|
||||
const Icon(FontAwesomeIcons.camera, size: 28),
|
||||
onTap: () => _onChangePhotographyMode(isEnable),
|
||||
onTap: () => _onChangePhotographyMode(context, isEnable),
|
||||
);
|
||||
}
|
||||
|
||||
@ -188,11 +212,19 @@ class ToolsUIModel extends BaseUIModel {
|
||||
/// -----------------------------------------------------------------------------------------
|
||||
/// -----------------------------------------------------------------------------------------
|
||||
|
||||
Future<void> reScanPath() async {
|
||||
scInstallPaths.clear();
|
||||
rsiLauncherInstallPaths.clear();
|
||||
scInstalledPath = "";
|
||||
rsiLauncherInstalledPath = "";
|
||||
Future<void> reScanPath(BuildContext context) async {
|
||||
var scInstallPaths = <String>[];
|
||||
var rsiLauncherInstallPaths = <String>[];
|
||||
var scInstalledPath = "";
|
||||
var rsiLauncherInstalledPath = "";
|
||||
|
||||
state = state.copyWith(
|
||||
scInstalledPath: scInstalledPath,
|
||||
rsiLauncherInstalledPath: rsiLauncherInstalledPath,
|
||||
scInstallPaths: scInstallPaths,
|
||||
rsiLauncherInstallPaths: rsiLauncherInstallPaths,
|
||||
);
|
||||
|
||||
try {
|
||||
rsiLauncherInstalledPath = await SystemHelper.getRSILauncherPath();
|
||||
rsiLauncherInstallPaths.add(rsiLauncherInstalledPath);
|
||||
@ -205,29 +237,37 @@ class ToolsUIModel extends BaseUIModel {
|
||||
if (scInstallPaths.isNotEmpty) {
|
||||
scInstalledPath = scInstallPaths.first;
|
||||
}
|
||||
state = state.copyWith(
|
||||
scInstalledPath: scInstalledPath,
|
||||
rsiLauncherInstalledPath: rsiLauncherInstalledPath,
|
||||
scInstallPaths: scInstallPaths,
|
||||
rsiLauncherInstallPaths: rsiLauncherInstallPaths,
|
||||
);
|
||||
} catch (e) {
|
||||
dPrint(e);
|
||||
showToast(context!, "解析 log 文件失败!\n请尝试使用 RSI Launcher log 修复 工具!");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "解析 log 文件失败!\n请尝试使用 RSI Launcher log 修复 工具!");
|
||||
}
|
||||
notifyListeners();
|
||||
|
||||
if (rsiLauncherInstalledPath == "") {
|
||||
showToast(context!, "未找到 RSI 启动器,请尝试重新安装,或在设置中手动添加。");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "未找到 RSI 启动器,请尝试重新安装,或在设置中手动添加。");
|
||||
}
|
||||
if (scInstalledPath == "") {
|
||||
showToast(context!, "未找到星际公民游戏安装位置,请至少完成一次游戏启动操作 或在设置中手动添加。");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "未找到星际公民游戏安装位置,请至少完成一次游戏启动操作 或在设置中手动添加。");
|
||||
}
|
||||
}
|
||||
|
||||
/// 重装EAC
|
||||
Future<void> _reinstallEAC() async {
|
||||
if (scInstalledPath.isEmpty) {
|
||||
showToast(context!, "该功能需要一个有效的游戏安装目录");
|
||||
Future<void> _reinstallEAC(BuildContext context) async {
|
||||
if (state.scInstalledPath.isEmpty) {
|
||||
showToast(context, "该功能需要一个有效的游戏安装目录");
|
||||
return;
|
||||
}
|
||||
working = true;
|
||||
state = state.copyWith(working: true);
|
||||
try {
|
||||
final eacPath = "$scInstalledPath\\EasyAntiCheat";
|
||||
final eacPath = "${state.scInstalledPath}\\EasyAntiCheat";
|
||||
final eacJsonPath = "$eacPath\\Settings.json";
|
||||
if (await File(eacJsonPath).exists()) {
|
||||
Map<String, String> envVars = Platform.environment;
|
||||
@ -246,18 +286,20 @@ class ToolsUIModel extends BaseUIModel {
|
||||
if (await dir.exists()) {
|
||||
await dir.delete(recursive: true);
|
||||
}
|
||||
final eacLauncher = File("$scInstalledPath\\StarCitizen_Launcher.exe");
|
||||
final eacLauncher =
|
||||
File("${state.scInstalledPath}\\StarCitizen_Launcher.exe");
|
||||
if (await eacLauncher.exists()) {
|
||||
await eacLauncher.delete(recursive: true);
|
||||
}
|
||||
showToast(context!,
|
||||
if (!context.mounted) return;
|
||||
showToast(context,
|
||||
"已为您移除 EAC 文件,接下来将为您打开 RSI 启动器,请您前往 SETTINGS -> VERIFY 重装 EAC。");
|
||||
_adminRSILauncher();
|
||||
_adminRSILauncher(context);
|
||||
} catch (e) {
|
||||
showToast(context!, "出现错误:$e");
|
||||
showToast(context, "出现错误:$e");
|
||||
}
|
||||
working = false;
|
||||
loadData(skipPathScan: true);
|
||||
state = state.copyWith(working: false);
|
||||
loadToolsCard(context, skipPathScan: true);
|
||||
}
|
||||
|
||||
Future<String> getSystemInfo() async {
|
||||
@ -269,31 +311,34 @@ class ToolsUIModel extends BaseUIModel {
|
||||
}
|
||||
|
||||
/// 管理员模式运行 RSI 启动器
|
||||
Future _adminRSILauncher() async {
|
||||
if (rsiLauncherInstalledPath == "") {
|
||||
showToast(context!, "未找到 RSI 启动器目录,请您尝试手动操作。");
|
||||
Future _adminRSILauncher(BuildContext context) async {
|
||||
if (state.rsiLauncherInstalledPath == "") {
|
||||
showToast(context, "未找到 RSI 启动器目录,请您尝试手动操作。");
|
||||
}
|
||||
handleError(
|
||||
() => SystemHelper.checkAndLaunchRSILauncher(rsiLauncherInstalledPath));
|
||||
SystemHelper.checkAndLaunchRSILauncher(state.rsiLauncherInstalledPath);
|
||||
}
|
||||
|
||||
Future<void> _rsiLogFix() async {
|
||||
working = true;
|
||||
Future<void> _rsiLogFix(BuildContext context) async {
|
||||
state = state.copyWith(working: true);
|
||||
final path = await SCLoggerHelper.getLogFilePath();
|
||||
if (!await File(path!).exists()) {
|
||||
if (!context.mounted) return;
|
||||
showToast(
|
||||
context!, "日志文件不存在,请尝试进行一次游戏启动或游戏安装,并退出启动器,若无法解决问题,请尝试将启动器更新至最新版本!");
|
||||
context, "日志文件不存在,请尝试进行一次游戏启动或游戏安装,并退出启动器,若无法解决问题,请尝试将启动器更新至最新版本!");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SystemHelper.killRSILauncher();
|
||||
await File(path).delete(recursive: true);
|
||||
showToast(context!, "清理完毕,请完成一次安装 / 游戏启动 操作。");
|
||||
SystemHelper.checkAndLaunchRSILauncher(rsiLauncherInstalledPath);
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "清理完毕,请完成一次安装 / 游戏启动 操作。");
|
||||
SystemHelper.checkAndLaunchRSILauncher(state.rsiLauncherInstalledPath);
|
||||
} catch (_) {
|
||||
showToast(context!, "清理失败,请手动移除,文件位置:$path");
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "清理失败,请手动移除,文件位置:$path");
|
||||
}
|
||||
working = false;
|
||||
|
||||
state = state.copyWith(working: false);
|
||||
}
|
||||
|
||||
openDir(path) async {
|
||||
@ -301,11 +346,12 @@ class ToolsUIModel extends BaseUIModel {
|
||||
SystemHelper.powershellPath, ["explorer.exe", "/select,\"$path\""]);
|
||||
}
|
||||
|
||||
Future _showSystemInfo() async {
|
||||
working = true;
|
||||
Future _showSystemInfo(BuildContext context) async {
|
||||
state = state.copyWith(working: true);
|
||||
final systemInfo = await getSystemInfo();
|
||||
if (!context.mounted) return;
|
||||
showDialog<String>(
|
||||
context: context!,
|
||||
context: context,
|
||||
builder: (context) => ContentDialog(
|
||||
title: const Text('系统信息'),
|
||||
content: Text(systemInfo),
|
||||
@ -323,11 +369,11 @@ class ToolsUIModel extends BaseUIModel {
|
||||
],
|
||||
),
|
||||
);
|
||||
working = false;
|
||||
state = state.copyWith(working: false);
|
||||
}
|
||||
|
||||
Future<void> _cleanShaderCache() async {
|
||||
working = true;
|
||||
Future<void> _cleanShaderCache(BuildContext context) async {
|
||||
state = state.copyWith(working: true);
|
||||
final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath();
|
||||
final l =
|
||||
await Directory(gameShaderCachePath!).list(recursive: false).toList();
|
||||
@ -338,42 +384,46 @@ class ToolsUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
loadData(skipPathScan: true);
|
||||
working = false;
|
||||
if (!context.mounted) return;
|
||||
loadToolsCard(context, skipPathScan: true);
|
||||
state = state.copyWith(working: false);
|
||||
}
|
||||
|
||||
Future<void> _downloadP4k() async {
|
||||
String savePath = scInstalledPath;
|
||||
Future<void> _downloadP4k(BuildContext context) async {
|
||||
String savePath = state.scInstalledPath;
|
||||
String fileName = "Data.p4k";
|
||||
|
||||
if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) {
|
||||
showToast(context!, "RSI启动器正在运行!请先关闭启动器再使用此功能!",
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "RSI启动器正在运行!请先关闭启动器再使用此功能!",
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context!).size.width * .35));
|
||||
maxWidth: MediaQuery.of(context).size.width * .35));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
await showToast(
|
||||
context!,
|
||||
context,
|
||||
"P4k 是星际公民的核心游戏文件,高达 100GB+,盒子提供的离线下载是为了帮助一些p4k文件下载超级慢的用户 或用于修复官方启动器无法修复的 p4k 文件。"
|
||||
"\n\n接下来会弹窗询问您保存位置(可以选择星际公民文件夹也可以选择别处),下载完成后请确保 P4K 文件夹位于 LIVE 文件夹内,之后使用星际公民启动器校验更新即可。");
|
||||
|
||||
try {
|
||||
working = true;
|
||||
notifyListeners();
|
||||
|
||||
await Aria2cManager.launchDaemon();
|
||||
final aria2c = Aria2cManager.getClient();
|
||||
state = state.copyWith(working: true);
|
||||
final aria2cManager = ref.read(aria2cModelProvider.notifier);
|
||||
await aria2cManager
|
||||
.launchDaemon(appGlobalState.applicationBinaryModuleDir!);
|
||||
final aria2c = ref.read(aria2cModelProvider).aria2c!;
|
||||
|
||||
// check download task list
|
||||
for (var value in [
|
||||
...await aria2c.tellActive(),
|
||||
...await aria2c.tellWaiting(0, 100000)
|
||||
]) {
|
||||
final t = DownloaderUIModel.getTaskTypeAndName(value);
|
||||
final t = HomeDownloaderUIModel.getTaskTypeAndName(value);
|
||||
if (t.key == "torrent" && t.value.contains("Data.p4k")) {
|
||||
showToast(context!, "已经有一个p4k下载任务正在进行中,请前往下载管理器查看!");
|
||||
working = false;
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "已经有一个p4k下载任务正在进行中,请前往下载管理器查看!");
|
||||
state = state.copyWith(working: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -386,8 +436,9 @@ class ToolsUIModel extends BaseUIModel {
|
||||
}
|
||||
}
|
||||
if (torrentUrl == "") {
|
||||
working = false;
|
||||
showToast(context!, "功能维护中,请稍后重试!");
|
||||
state = state.copyWith(working: false);
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "功能维护中,请稍后重试!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -396,44 +447,46 @@ class ToolsUIModel extends BaseUIModel {
|
||||
fileName: fileName,
|
||||
lockParentWindow: true);
|
||||
if (userSelect == null) {
|
||||
working = false;
|
||||
state = state.copyWith(working: false);
|
||||
return;
|
||||
}
|
||||
|
||||
savePath = userSelect;
|
||||
dPrint(savePath);
|
||||
notifyListeners();
|
||||
|
||||
if (savePath.endsWith("\\$fileName")) {
|
||||
savePath = savePath.substring(0, savePath.length - fileName.length - 1);
|
||||
}
|
||||
|
||||
final btData = await handleError(() => RSHttp.get(torrentUrl));
|
||||
if (!context.mounted) return;
|
||||
final btData = await RSHttp.get(torrentUrl).unwrap(context: context);
|
||||
if (btData == null || btData.data == null) {
|
||||
working = false;
|
||||
state = state.copyWith(working: false);
|
||||
return;
|
||||
}
|
||||
final b64Str = base64Encode(btData.data!);
|
||||
|
||||
final gid =
|
||||
await aria2c.addTorrent(b64Str, extraParams: {"dir": savePath});
|
||||
working = false;
|
||||
state = state.copyWith(working: false);
|
||||
dPrint("Aria2cManager.aria2c.addUri resp === $gid");
|
||||
await aria2c.saveSession();
|
||||
AnalyticsApi.touch("p4k_download");
|
||||
|
||||
BaseUIContainer(
|
||||
uiCreate: () => DownloaderUI(),
|
||||
modelCreate: () => DownloaderUIModel()).push(context!);
|
||||
if (!context.mounted) return;
|
||||
context.push("/index/downloader");
|
||||
} catch (e) {
|
||||
working = false;
|
||||
showToast(context!, "初始化失败!: $e");
|
||||
state = state.copyWith(working: false);
|
||||
if (!context.mounted) return;
|
||||
showToast(context, "初始化失败!: $e");
|
||||
}
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
launchUrlString(
|
||||
"https://citizenwiki.cn/SC%E6%B1%89%E5%8C%96%E7%9B%92%E5%AD%90#%E5%88%86%E6%B5%81%E4%B8%8B%E8%BD%BD%E6%95%99%E7%A8%8B");
|
||||
}
|
||||
|
||||
Future<bool> _checkPhotographyStatus({bool? setMode}) async {
|
||||
Future<bool> _checkPhotographyStatus(BuildContext context,
|
||||
{bool? setMode}) async {
|
||||
final scInstalledPath = state.scInstalledPath;
|
||||
const keys = ["AudioShakeStrength", "CameraSpringMovement", "ShakeScale"];
|
||||
final attributesFile = File(
|
||||
"$scInstalledPath\\USER\\Client\\0\\Profiles\\default\\attributes.xml");
|
||||
@ -459,7 +512,8 @@ class ToolsUIModel extends BaseUIModel {
|
||||
return isEnable;
|
||||
} else {
|
||||
if (!await attributesFile.exists()) {
|
||||
showToast(context!, "配置文件不存在,请尝试运行一次游戏");
|
||||
if (!context.mounted) return false;
|
||||
showToast(context, "配置文件不存在,请尝试运行一次游戏");
|
||||
return false;
|
||||
}
|
||||
final xmlFile = XmlDocument.parse(await attributesFile.readAsString());
|
||||
@ -482,19 +536,17 @@ class ToolsUIModel extends BaseUIModel {
|
||||
return true;
|
||||
}
|
||||
|
||||
_onChangePhotographyMode(bool isEnable) async {
|
||||
await handleError(() => _checkPhotographyStatus(setMode: !isEnable));
|
||||
reloadData();
|
||||
}
|
||||
_onChangePhotographyMode(BuildContext context, bool isEnable) async {
|
||||
_checkPhotographyStatus(context, setMode: !isEnable)
|
||||
.unwrap(context: context);
|
||||
loadToolsCard(context, skipPathScan: true);
|
||||
}
|
||||
|
||||
class _ToolsItemData {
|
||||
String key;
|
||||
|
||||
_ToolsItemData(this.key, this.name, this.infoString, this.icon, {this.onTap});
|
||||
|
||||
String name;
|
||||
String infoString;
|
||||
Widget icon;
|
||||
AsyncCallback? onTap;
|
||||
void onChangeGamePath(String v) {
|
||||
state = state.copyWith(scInstalledPath: v);
|
||||
}
|
||||
|
||||
void onChangeLauncherPath(String s) {
|
||||
state = state.copyWith(rsiLauncherInstalledPath: s);
|
||||
}
|
||||
}
|
||||
|
300
lib/ui/tools/tools_ui_model.freezed.dart
Normal file
300
lib/ui/tools/tools_ui_model.freezed.dart
Normal file
@ -0,0 +1,300 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'tools_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ToolsUIState {
|
||||
bool get working => throw _privateConstructorUsedError;
|
||||
String get scInstalledPath => throw _privateConstructorUsedError;
|
||||
String get rsiLauncherInstalledPath => throw _privateConstructorUsedError;
|
||||
List<String> get scInstallPaths => throw _privateConstructorUsedError;
|
||||
List<String> get rsiLauncherInstallPaths =>
|
||||
throw _privateConstructorUsedError;
|
||||
List<ToolsItemData> get items => throw _privateConstructorUsedError;
|
||||
bool get isItemLoading => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$ToolsUIStateCopyWith<ToolsUIState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ToolsUIStateCopyWith<$Res> {
|
||||
factory $ToolsUIStateCopyWith(
|
||||
ToolsUIState value, $Res Function(ToolsUIState) then) =
|
||||
_$ToolsUIStateCopyWithImpl<$Res, ToolsUIState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool working,
|
||||
String scInstalledPath,
|
||||
String rsiLauncherInstalledPath,
|
||||
List<String> scInstallPaths,
|
||||
List<String> rsiLauncherInstallPaths,
|
||||
List<ToolsItemData> items,
|
||||
bool isItemLoading});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ToolsUIStateCopyWithImpl<$Res, $Val extends ToolsUIState>
|
||||
implements $ToolsUIStateCopyWith<$Res> {
|
||||
_$ToolsUIStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? working = null,
|
||||
Object? scInstalledPath = null,
|
||||
Object? rsiLauncherInstalledPath = null,
|
||||
Object? scInstallPaths = null,
|
||||
Object? rsiLauncherInstallPaths = null,
|
||||
Object? items = null,
|
||||
Object? isItemLoading = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
working: null == working
|
||||
? _value.working
|
||||
: working // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
scInstalledPath: null == scInstalledPath
|
||||
? _value.scInstalledPath
|
||||
: scInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
rsiLauncherInstalledPath: null == rsiLauncherInstalledPath
|
||||
? _value.rsiLauncherInstalledPath
|
||||
: rsiLauncherInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
scInstallPaths: null == scInstallPaths
|
||||
? _value.scInstallPaths
|
||||
: scInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
rsiLauncherInstallPaths: null == rsiLauncherInstallPaths
|
||||
? _value.rsiLauncherInstallPaths
|
||||
: rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
items: null == items
|
||||
? _value.items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<ToolsItemData>,
|
||||
isItemLoading: null == isItemLoading
|
||||
? _value.isItemLoading
|
||||
: isItemLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ToolsUIStateImplCopyWith<$Res>
|
||||
implements $ToolsUIStateCopyWith<$Res> {
|
||||
factory _$$ToolsUIStateImplCopyWith(
|
||||
_$ToolsUIStateImpl value, $Res Function(_$ToolsUIStateImpl) then) =
|
||||
__$$ToolsUIStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{bool working,
|
||||
String scInstalledPath,
|
||||
String rsiLauncherInstalledPath,
|
||||
List<String> scInstallPaths,
|
||||
List<String> rsiLauncherInstallPaths,
|
||||
List<ToolsItemData> items,
|
||||
bool isItemLoading});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ToolsUIStateImplCopyWithImpl<$Res>
|
||||
extends _$ToolsUIStateCopyWithImpl<$Res, _$ToolsUIStateImpl>
|
||||
implements _$$ToolsUIStateImplCopyWith<$Res> {
|
||||
__$$ToolsUIStateImplCopyWithImpl(
|
||||
_$ToolsUIStateImpl _value, $Res Function(_$ToolsUIStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? working = null,
|
||||
Object? scInstalledPath = null,
|
||||
Object? rsiLauncherInstalledPath = null,
|
||||
Object? scInstallPaths = null,
|
||||
Object? rsiLauncherInstallPaths = null,
|
||||
Object? items = null,
|
||||
Object? isItemLoading = null,
|
||||
}) {
|
||||
return _then(_$ToolsUIStateImpl(
|
||||
working: null == working
|
||||
? _value.working
|
||||
: working // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
scInstalledPath: null == scInstalledPath
|
||||
? _value.scInstalledPath
|
||||
: scInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
rsiLauncherInstalledPath: null == rsiLauncherInstalledPath
|
||||
? _value.rsiLauncherInstalledPath
|
||||
: rsiLauncherInstalledPath // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
scInstallPaths: null == scInstallPaths
|
||||
? _value._scInstallPaths
|
||||
: scInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
rsiLauncherInstallPaths: null == rsiLauncherInstallPaths
|
||||
? _value._rsiLauncherInstallPaths
|
||||
: rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
items: null == items
|
||||
? _value._items
|
||||
: items // ignore: cast_nullable_to_non_nullable
|
||||
as List<ToolsItemData>,
|
||||
isItemLoading: null == isItemLoading
|
||||
? _value.isItemLoading
|
||||
: isItemLoading // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ToolsUIStateImpl implements _ToolsUIState {
|
||||
const _$ToolsUIStateImpl(
|
||||
{this.working = false,
|
||||
this.scInstalledPath = "",
|
||||
this.rsiLauncherInstalledPath = "",
|
||||
final List<String> scInstallPaths = const [],
|
||||
final List<String> rsiLauncherInstallPaths = const [],
|
||||
final List<ToolsItemData> items = const [],
|
||||
this.isItemLoading = false})
|
||||
: _scInstallPaths = scInstallPaths,
|
||||
_rsiLauncherInstallPaths = rsiLauncherInstallPaths,
|
||||
_items = items;
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool working;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String scInstalledPath;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String rsiLauncherInstalledPath;
|
||||
final List<String> _scInstallPaths;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get scInstallPaths {
|
||||
if (_scInstallPaths is EqualUnmodifiableListView) return _scInstallPaths;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_scInstallPaths);
|
||||
}
|
||||
|
||||
final List<String> _rsiLauncherInstallPaths;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<String> get rsiLauncherInstallPaths {
|
||||
if (_rsiLauncherInstallPaths is EqualUnmodifiableListView)
|
||||
return _rsiLauncherInstallPaths;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_rsiLauncherInstallPaths);
|
||||
}
|
||||
|
||||
final List<ToolsItemData> _items;
|
||||
@override
|
||||
@JsonKey()
|
||||
List<ToolsItemData> get items {
|
||||
if (_items is EqualUnmodifiableListView) return _items;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_items);
|
||||
}
|
||||
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isItemLoading;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ToolsUIState(working: $working, scInstalledPath: $scInstalledPath, rsiLauncherInstalledPath: $rsiLauncherInstalledPath, scInstallPaths: $scInstallPaths, rsiLauncherInstallPaths: $rsiLauncherInstallPaths, items: $items, isItemLoading: $isItemLoading)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ToolsUIStateImpl &&
|
||||
(identical(other.working, working) || other.working == working) &&
|
||||
(identical(other.scInstalledPath, scInstalledPath) ||
|
||||
other.scInstalledPath == scInstalledPath) &&
|
||||
(identical(
|
||||
other.rsiLauncherInstalledPath, rsiLauncherInstalledPath) ||
|
||||
other.rsiLauncherInstalledPath == rsiLauncherInstalledPath) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other._scInstallPaths, _scInstallPaths) &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other._rsiLauncherInstallPaths, _rsiLauncherInstallPaths) &&
|
||||
const DeepCollectionEquality().equals(other._items, _items) &&
|
||||
(identical(other.isItemLoading, isItemLoading) ||
|
||||
other.isItemLoading == isItemLoading));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
working,
|
||||
scInstalledPath,
|
||||
rsiLauncherInstalledPath,
|
||||
const DeepCollectionEquality().hash(_scInstallPaths),
|
||||
const DeepCollectionEquality().hash(_rsiLauncherInstallPaths),
|
||||
const DeepCollectionEquality().hash(_items),
|
||||
isItemLoading);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ToolsUIStateImplCopyWith<_$ToolsUIStateImpl> get copyWith =>
|
||||
__$$ToolsUIStateImplCopyWithImpl<_$ToolsUIStateImpl>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ToolsUIState implements ToolsUIState {
|
||||
const factory _ToolsUIState(
|
||||
{final bool working,
|
||||
final String scInstalledPath,
|
||||
final String rsiLauncherInstalledPath,
|
||||
final List<String> scInstallPaths,
|
||||
final List<String> rsiLauncherInstallPaths,
|
||||
final List<ToolsItemData> items,
|
||||
final bool isItemLoading}) = _$ToolsUIStateImpl;
|
||||
|
||||
@override
|
||||
bool get working;
|
||||
@override
|
||||
String get scInstalledPath;
|
||||
@override
|
||||
String get rsiLauncherInstalledPath;
|
||||
@override
|
||||
List<String> get scInstallPaths;
|
||||
@override
|
||||
List<String> get rsiLauncherInstallPaths;
|
||||
@override
|
||||
List<ToolsItemData> get items;
|
||||
@override
|
||||
bool get isItemLoading;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ToolsUIStateImplCopyWith<_$ToolsUIStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
25
lib/ui/tools/tools_ui_model.g.dart
Normal file
25
lib/ui/tools/tools_ui_model.g.dart
Normal file
@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'tools_ui_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$toolsUIModelHash() => r'4fb78bfe350d792cfdadd3314f4763097ea1b279';
|
||||
|
||||
/// See also [ToolsUIModel].
|
||||
@ProviderFor(ToolsUIModel)
|
||||
final toolsUIModelProvider =
|
||||
AutoDisposeNotifierProvider<ToolsUIModel, ToolsUIState>.internal(
|
||||
ToolsUIModel.new,
|
||||
name: r'toolsUIModelProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$toolsUIModelHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ToolsUIModel = AutoDisposeNotifier<ToolsUIState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -6,17 +6,17 @@ import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:desktop_webview_window/desktop_webview_window.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/app_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/conf/url_conf.dart';
|
||||
import 'package:starcitizen_doctor/common/io/rs_http.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||
import 'package:starcitizen_doctor/common/win32/credentials.dart';
|
||||
import 'package:starcitizen_doctor/data/app_version_data.dart';
|
||||
import 'package:starcitizen_doctor/data/app_web_localization_versions_data.dart';
|
||||
|
||||
import '../../../base/ui.dart';
|
||||
|
||||
typedef RsiLoginCallback = void Function(Map? data, bool success);
|
||||
|
||||
class WebViewModel {
|
||||
@ -52,7 +52,10 @@ class WebViewModel {
|
||||
|
||||
final RsiLoginCallback? loginCallback;
|
||||
|
||||
initWebView({String title = ""}) async {
|
||||
initWebView(
|
||||
{String title = "",
|
||||
required String applicationSupportDir,
|
||||
required AppVersionData appVersionData}) async {
|
||||
try {
|
||||
final userBox = await Hive.openBox("app_conf");
|
||||
isEnableToolSiteMirrors =
|
||||
@ -61,8 +64,7 @@ class WebViewModel {
|
||||
configuration: CreateConfiguration(
|
||||
windowWidth: loginMode ? 960 : 1920,
|
||||
windowHeight: loginMode ? 720 : 1080,
|
||||
userDataFolderWindows:
|
||||
"${AppConf.applicationSupportDir}/webview_data",
|
||||
userDataFolderWindows: "$applicationSupportDir/webview_data",
|
||||
title: title));
|
||||
// webview.openDevToolsWindow();
|
||||
webview.isNavigating.addListener(() async {
|
||||
@ -140,8 +142,8 @@ class WebViewModel {
|
||||
webview.evaluateJavaScript(
|
||||
"getRSILauncherToken(\"$loginChannel\");");
|
||||
}
|
||||
} else if (url
|
||||
.startsWith(await _handleMirrorsUrl("https://www.erkul.games"))) {
|
||||
} else if (url.startsWith(await _handleMirrorsUrl(
|
||||
"https://www.erkul.games", appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(localizationScript);
|
||||
@ -149,8 +151,8 @@ class WebViewModel {
|
||||
final replaceWords = _getLocalizationResource("DPS");
|
||||
await webview.evaluateJavaScript(
|
||||
"WebLocalizationUpdateReplaceWords(${json.encode(replaceWords)},$enableCapture)");
|
||||
} else if (url
|
||||
.startsWith(await _handleMirrorsUrl("https://uexcorp.space"))) {
|
||||
} else if (url.startsWith(await _handleMirrorsUrl(
|
||||
"https://uexcorp.space", appVersionData))) {
|
||||
dPrint("load script");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await webview.evaluateJavaScript(localizationScript);
|
||||
@ -186,10 +188,11 @@ class WebViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _handleMirrorsUrl(String url) async {
|
||||
Future<String> _handleMirrorsUrl(
|
||||
String url, AppVersionData appVersionData) async {
|
||||
var finalUrl = url;
|
||||
if (isEnableToolSiteMirrors) {
|
||||
for (var kv in AppConf.networkVersionData!.webMirrors!.entries) {
|
||||
for (var kv in appVersionData.webMirrors!.entries) {
|
||||
if (url.startsWith(kv.key)) {
|
||||
finalUrl = url.replaceFirst(kv.key, kv.value);
|
||||
}
|
||||
@ -198,8 +201,8 @@ class WebViewModel {
|
||||
return finalUrl;
|
||||
}
|
||||
|
||||
launch(String url) async {
|
||||
webview.launch(await _handleMirrorsUrl(url));
|
||||
launch(String url, AppVersionData appVersionData) async {
|
||||
webview.launch(await _handleMirrorsUrl(url, appVersionData));
|
||||
}
|
||||
|
||||
initLocalization(AppWebLocalizationVersionsData v) async {
|
@ -1,25 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
class MyPageRoute extends FluentPageRoute {
|
||||
late final WidgetBuilder _builder;
|
||||
|
||||
MyPageRoute({required super.builder}) : _builder = builder;
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
assert(debugCheckHasFluentTheme(context));
|
||||
final result = _builder(context);
|
||||
return Semantics(
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
child: EntrancePageTransition(
|
||||
animation: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: FluentTheme.of(context).animationCurve,
|
||||
),
|
||||
child: result,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,11 +1,18 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:markdown_widget/config/all.dart';
|
||||
import 'package:markdown_widget/widget/all.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:markdown_widget/config/all.dart';
|
||||
import 'package:markdown_widget/widget/all.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../base/ui.dart';
|
||||
export 'src/cache_image.dart';
|
||||
export 'src/countdown_time_text.dart';
|
||||
export '../common/utils/async.dart';
|
||||
export '../common/utils/base_utils.dart';
|
||||
|
||||
Widget makeLoading(
|
||||
BuildContext context, {
|
||||
@ -16,46 +23,66 @@ Widget makeLoading(
|
||||
child: SizedBox(
|
||||
width: width,
|
||||
height: width,
|
||||
// child: Lottie.asset("images/lottie/loading.zip", width: width),
|
||||
child: const ProgressRing(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeSafeAre(BuildContext context, {bool withKeyboard = true}) {
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
Widget makeDefaultPage(BuildContext context,
|
||||
{Widget? titleRow,
|
||||
List<Widget>? actions,
|
||||
Widget? content,
|
||||
bool automaticallyImplyLeading = true,
|
||||
String title = "",
|
||||
bool useBodyContainer = false}) {
|
||||
return NavigationView(
|
||||
appBar: NavigationAppBar(
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
title: DragToMoveArea(
|
||||
child: titleRow ??
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
if (withKeyboard)
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(title),
|
||||
],
|
||||
));
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [...?actions, const WindowButtons()],
|
||||
)),
|
||||
content: useBodyContainer
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
color: FluentTheme.of(context).scaffoldBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
child: content,
|
||||
)
|
||||
: content,
|
||||
);
|
||||
}
|
||||
|
||||
makeSvgColor(Color color) {
|
||||
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
|
||||
}
|
||||
class WindowButtons extends StatelessWidget {
|
||||
const WindowButtons({super.key});
|
||||
|
||||
bool isPadUI(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return size.width >= size.height;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final FluentThemeData theme = FluentTheme.of(context);
|
||||
return SizedBox(
|
||||
width: 138,
|
||||
height: 50,
|
||||
child: WindowCaption(
|
||||
brightness: theme.brightness,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fastPadding(
|
||||
{required double? all,
|
||||
required Widget child,
|
||||
double left = 0.0,
|
||||
double top = 0.0,
|
||||
double right = 0.0,
|
||||
double bottom = 0.0}) {
|
||||
return Padding(
|
||||
padding: all != null
|
||||
? EdgeInsets.all(all)
|
||||
: EdgeInsets.only(left: left, top: top, right: right, bottom: bottom),
|
||||
child: child);
|
||||
}
|
||||
|
||||
List<Widget> makeMarkdownView(String description, {String? attachmentsUrl}) {
|
||||
@ -103,10 +130,79 @@ List<Widget> makeMarkdownView(String description, {String? attachmentsUrl}) {
|
||||
]));
|
||||
}
|
||||
|
||||
class NoScrollBehavior extends ScrollBehavior {
|
||||
ColorFilter makeSvgColor(Color color) {
|
||||
return ui.ColorFilter.mode(color, ui.BlendMode.srcIn);
|
||||
}
|
||||
|
||||
CustomTransitionPage<T> myPageBuilder<T>(
|
||||
BuildContext context, GoRouterState state, Widget child) {
|
||||
return CustomTransitionPage(
|
||||
child: child,
|
||||
transitionDuration: const Duration(milliseconds: 150),
|
||||
reverseTransitionDuration: const Duration(milliseconds: 150),
|
||||
transitionsBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
)),
|
||||
child: child,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class LoadingWidget<T> extends HookConsumerWidget {
|
||||
final T? data;
|
||||
final Future<T?> Function()? onLoadData;
|
||||
final Widget Function(BuildContext context, T data) childBuilder;
|
||||
|
||||
const LoadingWidget(
|
||||
{super.key, this.data, required this.childBuilder, this.onLoadData});
|
||||
|
||||
@override
|
||||
Widget buildOverscrollIndicator(
|
||||
BuildContext context, Widget child, ScrollableDetails details) {
|
||||
return child;
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final dataState = useState<T?>(null);
|
||||
final errorMsg = useState("");
|
||||
useEffect(() {
|
||||
if (data == null && onLoadData != null) {
|
||||
_loadData(dataState, errorMsg);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
if (errorMsg.value.isNotEmpty) {
|
||||
return Button(
|
||||
onPressed: () {
|
||||
_loadData(dataState, errorMsg);
|
||||
},
|
||||
child: Center(
|
||||
child: Text(errorMsg.value),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dataState.value == null && data == null) return makeLoading(context);
|
||||
return childBuilder(context, (data ?? dataState.value) as T);
|
||||
}
|
||||
|
||||
void _loadData(
|
||||
ValueNotifier<T?> dataState, ValueNotifier<String> errorMsg) async {
|
||||
errorMsg.value = "";
|
||||
try {
|
||||
final r = await onLoadData!();
|
||||
dataState.value = r;
|
||||
} catch (e) {
|
||||
errorMsg.value = e.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addPostFrameCallback(Function() callback) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
callback();
|
||||
});
|
||||
}
|
17
pubspec.yaml
17
pubspec.yaml
@ -30,7 +30,12 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_riverpod: ^2.3.6
|
||||
flutter_riverpod: ^2.4.10
|
||||
riverpod_annotation: ^2.3.4
|
||||
flutter_hooks: ^0.20.5
|
||||
hooks_riverpod: ^2.4.10
|
||||
json_annotation: ^4.8.1
|
||||
go_router: ^13.2.0
|
||||
window_manager: ^0.3.2
|
||||
fluent_ui: 4.8.5
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
@ -73,8 +78,8 @@ dependencies:
|
||||
rust_builder:
|
||||
path: rust_builder
|
||||
aria2:
|
||||
#git: https://github.com/xkeyC/dart_aria2_rpc.git
|
||||
path: ../../xkeyC/dart_aria2_rpc
|
||||
git: https://github.com/xkeyC/dart_aria2_rpc.git
|
||||
# path: ../../xkeyC/dart_aria2_rpc
|
||||
intl: ^0.18.0
|
||||
synchronized: ^3.1.0+1
|
||||
dependency_overrides:
|
||||
@ -91,8 +96,12 @@ dev_dependencies:
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^3.0.0
|
||||
msix: ^3.16.4
|
||||
build_runner: ^2.4.6
|
||||
build_runner: ^2.4.8
|
||||
freezed: ^2.4.5
|
||||
json_serializable: ^6.7.1
|
||||
riverpod_generator: ^2.3.11
|
||||
custom_lint: ^0.6.2
|
||||
riverpod_lint: ^2.3.9
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
@ -94,7 +94,7 @@ fn _reade_resp_header(r_header: &HeaderMap) -> HashMap<String, String> {
|
||||
for ele in r_header {
|
||||
resp_headers.insert(
|
||||
ele.0.as_str().to_string(),
|
||||
ele.1.to_str().unwrap().to_string(),
|
||||
ele.1.to_str().unwrap_or("").to_string(),
|
||||
);
|
||||
}
|
||||
resp_headers
|
||||
|
Loading…
Reference in New Issue
Block a user