diff --git a/analysis_options.yaml b/analysis_options.yaml index 3a2332d..7f789a0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -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 diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..9a299e9 --- /dev/null +++ b/lib/app.dart @@ -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 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 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; +} diff --git a/lib/app.freezed.dart b/lib/app.freezed.dart new file mode 100644 index 0000000..351b336 --- /dev/null +++ b/lib/app.freezed.dart @@ -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 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 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 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; +} diff --git a/lib/app.g.dart b/lib/app.g.dart new file mode 100644 index 0000000..cdc57ee --- /dev/null +++ b/lib/app.g.dart @@ -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.internal( + router, + name: r'routerProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$routerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RouterRef = AutoDisposeProviderRef; +String _$appGlobalModelHash() => r'9c114910aed546bfd469c8bbfa50cdd4a5be5028'; + +/// See also [AppGlobalModel]. +@ProviderFor(AppGlobalModel) +final appGlobalModelProvider = + AutoDisposeNotifierProvider.internal( + AppGlobalModel.new, + name: r'appGlobalModelProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$appGlobalModelHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AppGlobalModel = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/base/ui.dart b/lib/base/ui.dart deleted file mode 100644 index 683aaf8..0000000 --- a/lib/base/ui.dart +++ /dev/null @@ -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 Function() uiCreate; - final dynamic Function() modelCreate; - - const BaseUIContainer( - {super.key, required this.uiCreate, required this.modelCreate}); - - @override - // ignore: no_logic_in_create_state - ConsumerState 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 - extends ConsumerState { - BaseUIModel? _needDisposeModel; - late final ChangeNotifierProvider provider = bindUIModel(); - - // final GlobalKey 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? 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 bindUIModel() { - final createdModel = widget.modelCreate(); - if (createdModel is T) { - _needDisposeModel = createdModel; - return ChangeNotifierProvider((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"); - } -} diff --git a/lib/base/ui_model.dart b/lib/base/ui_model.dart deleted file mode 100644 index c755e65..0000000 --- a/lib/base/ui_model.dart +++ /dev/null @@ -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 handleError(Future 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? _childUIModels; - Map? _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 getChildUIModelProviders( - modelKey) { - _childUIProviders ??= {}; - if (_childUIProviders![modelKey] == null) { - _childUIProviders![modelKey] = ChangeNotifierProvider((ref) { - final c = (_getChildUIModel(modelKey) as M); - return c..context = context; - }); - } - return _childUIProviders![modelKey]!; - } - - T? getCreatedChildUIModel(String modelKey, - {bool create = false}) { - if (create && _childUIModels?[modelKey] == null) { - _getChildUIModel(modelKey); - } - return _childUIModels?[modelKey] as T?; - } - - Future reloadAllChildModels() async { - if (_childUIModels == null) return; - final futureList = []; - 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"); - } -} diff --git a/lib/common/conf/app_conf.dart b/lib/common/conf/app_conf.dart deleted file mode 100644 index 1012928..0000000 --- a/lib/common/conf/app_conf.dart +++ /dev/null @@ -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 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 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"); - } - } -} diff --git a/lib/common/conf/binary_conf.dart b/lib/common/conf/binary_conf.dart index 5b5fd80..9d34558 100644 --- a/lib/common/conf/binary_conf.dart +++ b/lib/common/conf/binary_conf.dart @@ -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 modules) async { - final workingDir = AppConf.applicationBinaryModuleDir; + static Future extractModule(List modules, String workingDir) async { for (var m in _modules.entries) { if (!modules.contains(m.key)) continue; final name = m.key; diff --git a/lib/common/conf/const_conf.dart b/lib/common/conf/const_conf.dart new file mode 100644 index 0000000..5daf280 --- /dev/null +++ b/lib/common/conf/const_conf.dart @@ -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"; +} diff --git a/lib/common/grpc/party_room_server.dart b/lib/common/grpc/party_room_server.dart deleted file mode 100644 index 9189cdf..0000000 --- a/lib/common/grpc/party_room_server.dart +++ /dev/null @@ -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 pingServer() async { - final r = await _indexService.pingServer(PingData( - data: "PING", clientVersion: Int64.parseInt(clientVersion.toString()))); - return r; - } - - static Future getRoomTypes() async { - final r = await _indexService.getRoomTypes(Empty()); - return r; - } - - static Future getRoomList(RoomListPageReqData req) async { - return await _indexService.getRoomList(req); - } - - static Future createRoom(RoomData roomData) async { - return await _indexService.createRoom(roomData); - } - - static Future touchUserRoom(String userName, String deviceUUID) { - return _indexService - .touchUser(PreUser(userName: userName, deviceUUID: deviceUUID)); - } - - static ResponseStream joinRoom( - String roomID, String userName, String deviceUUID) { - return _indexService.joinRoom( - PreUser(roomID: roomID, userName: userName, deviceUUID: deviceUUID)); - } -} diff --git a/lib/common/io/rs_http.dart b/lib/common/io/rs_http.dart index 9d20fd1..08a9b64 100644 --- a/lib/common/io/rs_http.dart +++ b/lib/common/io/rs_http.dart @@ -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" }); } diff --git a/lib/common/utils/async.dart b/lib/common/utils/async.dart new file mode 100644 index 0000000..b6a4bd3 --- /dev/null +++ b/lib/common/utils/async.dart @@ -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 unwrap({BuildContext? context}) async { + try { + return await this; + } catch (e) { + dPrint("unwrap error:$e"); + if (context != null) { + showToast(context, "出现错误: $e"); + } + return null; + } + } +} diff --git a/lib/common/utils/log.dart b/lib/common/utils/log.dart index 2485901..b08e260 100644 --- a/lib/common/utils/log.dart +++ b/lib/common/utils/log.dart @@ -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; +} diff --git a/lib/common/utils/provider.dart b/lib/common/utils/provider.dart new file mode 100644 index 0000000..e1c5c2c --- /dev/null +++ b/lib/common/utils/provider.dart @@ -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); +} diff --git a/lib/generated/grpc/party_room_server/chat.pb.dart b/lib/generated/grpc/party_room_server/chat.pb.dart deleted file mode 100644 index 47dffaf..0000000 --- a/lib/generated/grpc/party_room_server/chat.pb.dart +++ /dev/null @@ -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( - 3, _omitFieldNames ? '' : 'receiverType', $pb.PbFieldType.OE, - protoName: 'receiverType', - defaultOrMaker: ReceiverType.RoomMsg, - valueOf: ReceiverType.valueOf, - enumValues: ReceiverType.values) - ..e( - 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 createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static ChatMessage getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(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'); diff --git a/lib/generated/grpc/party_room_server/chat.pbenum.dart b/lib/generated/grpc/party_room_server/chat.pbenum.dart deleted file mode 100644 index 405cda8..0000000 --- a/lib/generated/grpc/party_room_server/chat.pbenum.dart +++ /dev/null @@ -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 values = [ - 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 values = [ - 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'); diff --git a/lib/generated/grpc/party_room_server/chat.pbgrpc.dart b/lib/generated/grpc/party_room_server/chat.pbgrpc.dart deleted file mode 100644 index a5c0122..0000000 --- a/lib/generated/grpc/party_room_server/chat.pbgrpc.dart +++ /dev/null @@ -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); -} diff --git a/lib/generated/grpc/party_room_server/chat.pbjson.dart b/lib/generated/grpc/party_room_server/chat.pbjson.dart deleted file mode 100644 index f05b0ac..0000000 --- a/lib/generated/grpc/party_room_server/chat.pbjson.dart +++ /dev/null @@ -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'); diff --git a/lib/generated/grpc/party_room_server/index.pb.dart b/lib/generated/grpc/party_room_server/index.pb.dart deleted file mode 100644 index 38d4ad6..0000000 --- a/lib/generated/grpc/party_room_server/index.pb.dart +++ /dev/null @@ -1,1359 +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:fixnum/fixnum.dart' as $fixnum; -import 'package:protobuf/protobuf.dart' as $pb; - -import 'index.pbenum.dart'; - -export 'index.pbenum.dart'; - -class Empty extends $pb.GeneratedMessage { - factory Empty() => create(); - Empty._() : super(); - factory Empty.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory Empty.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'Empty', - createEmptyInstance: create) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - Empty clone() => Empty()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - Empty copyWith(void Function(Empty) updates) => - super.copyWith((message) => updates(message as Empty)) as Empty; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static Empty create() => Empty._(); - Empty createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static Empty getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static Empty? _defaultInstance; -} - -class BaseRespData extends $pb.GeneratedMessage { - factory BaseRespData({ - $core.int? code, - $core.String? message, - }) { - final $result = create(); - if (code != null) { - $result.code = code; - } - if (message != null) { - $result.message = message; - } - return $result; - } - BaseRespData._() : super(); - factory BaseRespData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory BaseRespData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'BaseRespData', - createEmptyInstance: create) - ..a<$core.int>(1, _omitFieldNames ? '' : 'code', $pb.PbFieldType.O3) - ..aOS(2, _omitFieldNames ? '' : 'message') - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - BaseRespData clone() => BaseRespData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - BaseRespData copyWith(void Function(BaseRespData) updates) => - super.copyWith((message) => updates(message as BaseRespData)) - as BaseRespData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static BaseRespData create() => BaseRespData._(); - BaseRespData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static BaseRespData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static BaseRespData? _defaultInstance; - - @$pb.TagNumber(1) - $core.int get code => $_getIZ(0); - @$pb.TagNumber(1) - set code($core.int v) { - $_setSignedInt32(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasCode() => $_has(0); - @$pb.TagNumber(1) - void clearCode() => clearField(1); - - @$pb.TagNumber(2) - $core.String get message => $_getSZ(1); - @$pb.TagNumber(2) - set message($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasMessage() => $_has(1); - @$pb.TagNumber(2) - void clearMessage() => clearField(2); -} - -class BasePageRespData extends $pb.GeneratedMessage { - factory BasePageRespData({ - $core.int? code, - $core.String? message, - $core.bool? hasNext, - $fixnum.Int64? curPageNum, - $fixnum.Int64? pageSize, - }) { - final $result = create(); - if (code != null) { - $result.code = code; - } - if (message != null) { - $result.message = message; - } - if (hasNext != null) { - $result.hasNext = hasNext; - } - if (curPageNum != null) { - $result.curPageNum = curPageNum; - } - if (pageSize != null) { - $result.pageSize = pageSize; - } - return $result; - } - BasePageRespData._() : super(); - factory BasePageRespData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory BasePageRespData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'BasePageRespData', - createEmptyInstance: create) - ..a<$core.int>(1, _omitFieldNames ? '' : 'code', $pb.PbFieldType.O3) - ..aOS(2, _omitFieldNames ? '' : 'message') - ..aOB(3, _omitFieldNames ? '' : 'hasNext', protoName: 'hasNext') - ..a<$fixnum.Int64>( - 4, _omitFieldNames ? '' : 'curPageNum', $pb.PbFieldType.OU6, - protoName: 'curPageNum', defaultOrMaker: $fixnum.Int64.ZERO) - ..aInt64(5, _omitFieldNames ? '' : 'pageSize', protoName: 'pageSize') - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - BasePageRespData clone() => BasePageRespData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - BasePageRespData copyWith(void Function(BasePageRespData) updates) => - super.copyWith((message) => updates(message as BasePageRespData)) - as BasePageRespData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static BasePageRespData create() => BasePageRespData._(); - BasePageRespData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static BasePageRespData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static BasePageRespData? _defaultInstance; - - @$pb.TagNumber(1) - $core.int get code => $_getIZ(0); - @$pb.TagNumber(1) - set code($core.int v) { - $_setSignedInt32(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasCode() => $_has(0); - @$pb.TagNumber(1) - void clearCode() => clearField(1); - - @$pb.TagNumber(2) - $core.String get message => $_getSZ(1); - @$pb.TagNumber(2) - set message($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasMessage() => $_has(1); - @$pb.TagNumber(2) - void clearMessage() => clearField(2); - - @$pb.TagNumber(3) - $core.bool get hasNext => $_getBF(2); - @$pb.TagNumber(3) - set hasNext($core.bool v) { - $_setBool(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasHasNext() => $_has(2); - @$pb.TagNumber(3) - void clearHasNext() => clearField(3); - - @$pb.TagNumber(4) - $fixnum.Int64 get curPageNum => $_getI64(3); - @$pb.TagNumber(4) - set curPageNum($fixnum.Int64 v) { - $_setInt64(3, v); - } - - @$pb.TagNumber(4) - $core.bool hasCurPageNum() => $_has(3); - @$pb.TagNumber(4) - void clearCurPageNum() => clearField(4); - - @$pb.TagNumber(5) - $fixnum.Int64 get pageSize => $_getI64(4); - @$pb.TagNumber(5) - set pageSize($fixnum.Int64 v) { - $_setInt64(4, v); - } - - @$pb.TagNumber(5) - $core.bool hasPageSize() => $_has(4); - @$pb.TagNumber(5) - void clearPageSize() => clearField(5); -} - -class PingData extends $pb.GeneratedMessage { - factory PingData({ - $core.String? data, - $fixnum.Int64? clientVersion, - $fixnum.Int64? serverVersion, - }) { - final $result = create(); - if (data != null) { - $result.data = data; - } - if (clientVersion != null) { - $result.clientVersion = clientVersion; - } - if (serverVersion != null) { - $result.serverVersion = serverVersion; - } - return $result; - } - PingData._() : super(); - factory PingData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory PingData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'PingData', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'data') - ..a<$fixnum.Int64>( - 2, _omitFieldNames ? '' : 'clientVersion', $pb.PbFieldType.OS6, - protoName: 'clientVersion', defaultOrMaker: $fixnum.Int64.ZERO) - ..a<$fixnum.Int64>( - 3, _omitFieldNames ? '' : 'serverVersion', $pb.PbFieldType.OS6, - protoName: 'serverVersion', defaultOrMaker: $fixnum.Int64.ZERO) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - PingData clone() => PingData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - PingData copyWith(void Function(PingData) updates) => - super.copyWith((message) => updates(message as PingData)) as PingData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static PingData create() => PingData._(); - PingData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static PingData getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static PingData? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get data => $_getSZ(0); - @$pb.TagNumber(1) - set data($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasData() => $_has(0); - @$pb.TagNumber(1) - void clearData() => clearField(1); - - @$pb.TagNumber(2) - $fixnum.Int64 get clientVersion => $_getI64(1); - @$pb.TagNumber(2) - set clientVersion($fixnum.Int64 v) { - $_setInt64(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasClientVersion() => $_has(1); - @$pb.TagNumber(2) - void clearClientVersion() => clearField(2); - - @$pb.TagNumber(3) - $fixnum.Int64 get serverVersion => $_getI64(2); - @$pb.TagNumber(3) - set serverVersion($fixnum.Int64 v) { - $_setInt64(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasServerVersion() => $_has(2); - @$pb.TagNumber(3) - void clearServerVersion() => clearField(3); -} - -class RoomTypesData extends $pb.GeneratedMessage { - factory RoomTypesData({ - $core.Iterable? roomTypes, - }) { - final $result = create(); - if (roomTypes != null) { - $result.roomTypes.addAll(roomTypes); - } - return $result; - } - RoomTypesData._() : super(); - factory RoomTypesData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomTypesData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomTypesData', - createEmptyInstance: create) - ..pc(1, _omitFieldNames ? '' : 'roomTypes', $pb.PbFieldType.PM, - protoName: 'roomTypes', subBuilder: RoomType.create) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomTypesData clone() => RoomTypesData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomTypesData copyWith(void Function(RoomTypesData) updates) => - super.copyWith((message) => updates(message as RoomTypesData)) - as RoomTypesData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomTypesData create() => RoomTypesData._(); - RoomTypesData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomTypesData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomTypesData? _defaultInstance; - - @$pb.TagNumber(1) - $core.List get roomTypes => $_getList(0); -} - -class RoomType extends $pb.GeneratedMessage { - factory RoomType({ - $core.String? id, - $core.String? name, - $core.String? icon, - $core.String? desc, - $core.Iterable? subTypes, - }) { - final $result = create(); - if (id != null) { - $result.id = id; - } - if (name != null) { - $result.name = name; - } - if (icon != null) { - $result.icon = icon; - } - if (desc != null) { - $result.desc = desc; - } - if (subTypes != null) { - $result.subTypes.addAll(subTypes); - } - return $result; - } - RoomType._() : super(); - factory RoomType.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomType.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomType', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'id') - ..aOS(2, _omitFieldNames ? '' : 'name') - ..aOS(3, _omitFieldNames ? '' : 'icon') - ..aOS(4, _omitFieldNames ? '' : 'desc') - ..pc(5, _omitFieldNames ? '' : 'subTypes', $pb.PbFieldType.PM, - protoName: 'subTypes', subBuilder: RoomSubtype.create) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomType clone() => RoomType()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomType copyWith(void Function(RoomType) updates) => - super.copyWith((message) => updates(message as RoomType)) as RoomType; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomType create() => RoomType._(); - RoomType createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomType getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static RoomType? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => clearField(1); - - @$pb.TagNumber(2) - $core.String get name => $_getSZ(1); - @$pb.TagNumber(2) - set name($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasName() => $_has(1); - @$pb.TagNumber(2) - void clearName() => clearField(2); - - @$pb.TagNumber(3) - $core.String get icon => $_getSZ(2); - @$pb.TagNumber(3) - set icon($core.String v) { - $_setString(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasIcon() => $_has(2); - @$pb.TagNumber(3) - void clearIcon() => clearField(3); - - @$pb.TagNumber(4) - $core.String get desc => $_getSZ(3); - @$pb.TagNumber(4) - set desc($core.String v) { - $_setString(3, v); - } - - @$pb.TagNumber(4) - $core.bool hasDesc() => $_has(3); - @$pb.TagNumber(4) - void clearDesc() => clearField(4); - - @$pb.TagNumber(5) - $core.List get subTypes => $_getList(4); -} - -class RoomSubtype extends $pb.GeneratedMessage { - factory RoomSubtype({ - $core.String? id, - $core.String? name, - }) { - final $result = create(); - if (id != null) { - $result.id = id; - } - if (name != null) { - $result.name = name; - } - return $result; - } - RoomSubtype._() : super(); - factory RoomSubtype.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomSubtype.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomSubtype', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'id') - ..aOS(2, _omitFieldNames ? '' : 'name') - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomSubtype clone() => RoomSubtype()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomSubtype copyWith(void Function(RoomSubtype) updates) => - super.copyWith((message) => updates(message as RoomSubtype)) - as RoomSubtype; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomSubtype create() => RoomSubtype._(); - RoomSubtype createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomSubtype getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomSubtype? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => clearField(1); - - @$pb.TagNumber(2) - $core.String get name => $_getSZ(1); - @$pb.TagNumber(2) - set name($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasName() => $_has(1); - @$pb.TagNumber(2) - void clearName() => clearField(2); -} - -class RoomData extends $pb.GeneratedMessage { - factory RoomData({ - $core.String? id, - $core.String? roomTypeID, - $core.Iterable<$core.String>? roomSubTypeIds, - $core.String? owner, - $core.int? maxPlayer, - $fixnum.Int64? createTime, - $core.int? curPlayer, - RoomStatus? status, - $core.String? deviceUUID, - $core.String? announcement, - $core.String? avatar, - $fixnum.Int64? updateTime, - }) { - final $result = create(); - if (id != null) { - $result.id = id; - } - if (roomTypeID != null) { - $result.roomTypeID = roomTypeID; - } - if (roomSubTypeIds != null) { - $result.roomSubTypeIds.addAll(roomSubTypeIds); - } - if (owner != null) { - $result.owner = owner; - } - if (maxPlayer != null) { - $result.maxPlayer = maxPlayer; - } - if (createTime != null) { - $result.createTime = createTime; - } - if (curPlayer != null) { - $result.curPlayer = curPlayer; - } - if (status != null) { - $result.status = status; - } - if (deviceUUID != null) { - $result.deviceUUID = deviceUUID; - } - if (announcement != null) { - $result.announcement = announcement; - } - if (avatar != null) { - $result.avatar = avatar; - } - if (updateTime != null) { - $result.updateTime = updateTime; - } - return $result; - } - RoomData._() : super(); - factory RoomData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomData', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'id') - ..aOS(2, _omitFieldNames ? '' : 'roomTypeID', protoName: 'roomTypeID') - ..pPS(3, _omitFieldNames ? '' : 'roomSubTypeIds', - protoName: 'roomSubTypeIds') - ..aOS(4, _omitFieldNames ? '' : 'owner') - ..a<$core.int>(5, _omitFieldNames ? '' : 'maxPlayer', $pb.PbFieldType.O3, - protoName: 'maxPlayer') - ..aInt64(6, _omitFieldNames ? '' : 'createTime', protoName: 'createTime') - ..a<$core.int>(7, _omitFieldNames ? '' : 'curPlayer', $pb.PbFieldType.O3, - protoName: 'curPlayer') - ..e(8, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, - defaultOrMaker: RoomStatus.All, - valueOf: RoomStatus.valueOf, - enumValues: RoomStatus.values) - ..aOS(9, _omitFieldNames ? '' : 'deviceUUID', protoName: 'deviceUUID') - ..aOS(10, _omitFieldNames ? '' : 'announcement') - ..aOS(11, _omitFieldNames ? '' : 'avatar') - ..aInt64(12, _omitFieldNames ? '' : 'updateTime', protoName: 'updateTime') - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomData clone() => RoomData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomData copyWith(void Function(RoomData) updates) => - super.copyWith((message) => updates(message as RoomData)) as RoomData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomData create() => RoomData._(); - RoomData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomData getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static RoomData? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => clearField(1); - - @$pb.TagNumber(2) - $core.String get roomTypeID => $_getSZ(1); - @$pb.TagNumber(2) - set roomTypeID($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasRoomTypeID() => $_has(1); - @$pb.TagNumber(2) - void clearRoomTypeID() => clearField(2); - - @$pb.TagNumber(3) - $core.List<$core.String> get roomSubTypeIds => $_getList(2); - - @$pb.TagNumber(4) - $core.String get owner => $_getSZ(3); - @$pb.TagNumber(4) - set owner($core.String v) { - $_setString(3, v); - } - - @$pb.TagNumber(4) - $core.bool hasOwner() => $_has(3); - @$pb.TagNumber(4) - void clearOwner() => clearField(4); - - @$pb.TagNumber(5) - $core.int get maxPlayer => $_getIZ(4); - @$pb.TagNumber(5) - set maxPlayer($core.int v) { - $_setSignedInt32(4, v); - } - - @$pb.TagNumber(5) - $core.bool hasMaxPlayer() => $_has(4); - @$pb.TagNumber(5) - void clearMaxPlayer() => clearField(5); - - @$pb.TagNumber(6) - $fixnum.Int64 get createTime => $_getI64(5); - @$pb.TagNumber(6) - set createTime($fixnum.Int64 v) { - $_setInt64(5, v); - } - - @$pb.TagNumber(6) - $core.bool hasCreateTime() => $_has(5); - @$pb.TagNumber(6) - void clearCreateTime() => clearField(6); - - @$pb.TagNumber(7) - $core.int get curPlayer => $_getIZ(6); - @$pb.TagNumber(7) - set curPlayer($core.int v) { - $_setSignedInt32(6, v); - } - - @$pb.TagNumber(7) - $core.bool hasCurPlayer() => $_has(6); - @$pb.TagNumber(7) - void clearCurPlayer() => clearField(7); - - @$pb.TagNumber(8) - RoomStatus get status => $_getN(7); - @$pb.TagNumber(8) - set status(RoomStatus v) { - setField(8, v); - } - - @$pb.TagNumber(8) - $core.bool hasStatus() => $_has(7); - @$pb.TagNumber(8) - void clearStatus() => clearField(8); - - @$pb.TagNumber(9) - $core.String get deviceUUID => $_getSZ(8); - @$pb.TagNumber(9) - set deviceUUID($core.String v) { - $_setString(8, v); - } - - @$pb.TagNumber(9) - $core.bool hasDeviceUUID() => $_has(8); - @$pb.TagNumber(9) - void clearDeviceUUID() => clearField(9); - - @$pb.TagNumber(10) - $core.String get announcement => $_getSZ(9); - @$pb.TagNumber(10) - set announcement($core.String v) { - $_setString(9, v); - } - - @$pb.TagNumber(10) - $core.bool hasAnnouncement() => $_has(9); - @$pb.TagNumber(10) - void clearAnnouncement() => clearField(10); - - @$pb.TagNumber(11) - $core.String get avatar => $_getSZ(10); - @$pb.TagNumber(11) - set avatar($core.String v) { - $_setString(10, v); - } - - @$pb.TagNumber(11) - $core.bool hasAvatar() => $_has(10); - @$pb.TagNumber(11) - void clearAvatar() => clearField(11); - - @$pb.TagNumber(12) - $fixnum.Int64 get updateTime => $_getI64(11); - @$pb.TagNumber(12) - set updateTime($fixnum.Int64 v) { - $_setInt64(11, v); - } - - @$pb.TagNumber(12) - $core.bool hasUpdateTime() => $_has(11); - @$pb.TagNumber(12) - void clearUpdateTime() => clearField(12); -} - -class RoomListPageReqData extends $pb.GeneratedMessage { - factory RoomListPageReqData({ - $core.String? typeID, - $core.String? subTypeID, - RoomStatus? status, - RoomSortType? sort, - $fixnum.Int64? pageNum, - }) { - final $result = create(); - if (typeID != null) { - $result.typeID = typeID; - } - if (subTypeID != null) { - $result.subTypeID = subTypeID; - } - if (status != null) { - $result.status = status; - } - if (sort != null) { - $result.sort = sort; - } - if (pageNum != null) { - $result.pageNum = pageNum; - } - return $result; - } - RoomListPageReqData._() : super(); - factory RoomListPageReqData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomListPageReqData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomListPageReqData', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'typeID', protoName: 'typeID') - ..aOS(2, _omitFieldNames ? '' : 'subTypeID', protoName: 'subTypeID') - ..e(3, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, - defaultOrMaker: RoomStatus.All, - valueOf: RoomStatus.valueOf, - enumValues: RoomStatus.values) - ..e(4, _omitFieldNames ? '' : 'sort', $pb.PbFieldType.OE, - defaultOrMaker: RoomSortType.Default, - valueOf: RoomSortType.valueOf, - enumValues: RoomSortType.values) - ..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'pageNum', $pb.PbFieldType.OU6, - protoName: 'pageNum', defaultOrMaker: $fixnum.Int64.ZERO) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomListPageReqData clone() => RoomListPageReqData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomListPageReqData copyWith(void Function(RoomListPageReqData) updates) => - super.copyWith((message) => updates(message as RoomListPageReqData)) - as RoomListPageReqData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomListPageReqData create() => RoomListPageReqData._(); - RoomListPageReqData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomListPageReqData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomListPageReqData? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get typeID => $_getSZ(0); - @$pb.TagNumber(1) - set typeID($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasTypeID() => $_has(0); - @$pb.TagNumber(1) - void clearTypeID() => clearField(1); - - @$pb.TagNumber(2) - $core.String get subTypeID => $_getSZ(1); - @$pb.TagNumber(2) - set subTypeID($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasSubTypeID() => $_has(1); - @$pb.TagNumber(2) - void clearSubTypeID() => clearField(2); - - @$pb.TagNumber(3) - RoomStatus get status => $_getN(2); - @$pb.TagNumber(3) - set status(RoomStatus v) { - setField(3, v); - } - - @$pb.TagNumber(3) - $core.bool hasStatus() => $_has(2); - @$pb.TagNumber(3) - void clearStatus() => clearField(3); - - @$pb.TagNumber(4) - RoomSortType get sort => $_getN(3); - @$pb.TagNumber(4) - set sort(RoomSortType v) { - setField(4, v); - } - - @$pb.TagNumber(4) - $core.bool hasSort() => $_has(3); - @$pb.TagNumber(4) - void clearSort() => clearField(4); - - @$pb.TagNumber(5) - $fixnum.Int64 get pageNum => $_getI64(4); - @$pb.TagNumber(5) - set pageNum($fixnum.Int64 v) { - $_setInt64(4, v); - } - - @$pb.TagNumber(5) - $core.bool hasPageNum() => $_has(4); - @$pb.TagNumber(5) - void clearPageNum() => clearField(5); -} - -class RoomListData extends $pb.GeneratedMessage { - factory RoomListData({ - BasePageRespData? pageData, - $core.Iterable? rooms, - }) { - final $result = create(); - if (pageData != null) { - $result.pageData = pageData; - } - if (rooms != null) { - $result.rooms.addAll(rooms); - } - return $result; - } - RoomListData._() : super(); - factory RoomListData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomListData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomListData', - createEmptyInstance: create) - ..aOM(1, _omitFieldNames ? '' : 'pageData', - protoName: 'pageData', subBuilder: BasePageRespData.create) - ..pc(2, _omitFieldNames ? '' : 'rooms', $pb.PbFieldType.PM, - subBuilder: RoomData.create) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomListData clone() => RoomListData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomListData copyWith(void Function(RoomListData) updates) => - super.copyWith((message) => updates(message as RoomListData)) - as RoomListData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomListData create() => RoomListData._(); - RoomListData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomListData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomListData? _defaultInstance; - - @$pb.TagNumber(1) - BasePageRespData get pageData => $_getN(0); - @$pb.TagNumber(1) - set pageData(BasePageRespData v) { - setField(1, v); - } - - @$pb.TagNumber(1) - $core.bool hasPageData() => $_has(0); - @$pb.TagNumber(1) - void clearPageData() => clearField(1); - @$pb.TagNumber(1) - BasePageRespData ensurePageData() => $_ensure(0); - - @$pb.TagNumber(2) - $core.List get rooms => $_getList(1); -} - -class PreUser extends $pb.GeneratedMessage { - factory PreUser({ - $core.String? userName, - $core.String? deviceUUID, - $core.String? roomID, - }) { - final $result = create(); - if (userName != null) { - $result.userName = userName; - } - if (deviceUUID != null) { - $result.deviceUUID = deviceUUID; - } - if (roomID != null) { - $result.roomID = roomID; - } - return $result; - } - PreUser._() : super(); - factory PreUser.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory PreUser.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'PreUser', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'userName', protoName: 'userName') - ..aOS(2, _omitFieldNames ? '' : 'deviceUUID', protoName: 'deviceUUID') - ..aOS(3, _omitFieldNames ? '' : 'roomID', protoName: 'roomID') - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - PreUser clone() => PreUser()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - PreUser copyWith(void Function(PreUser) updates) => - super.copyWith((message) => updates(message as PreUser)) as PreUser; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static PreUser create() => PreUser._(); - PreUser createEmptyInstance() => create(); - static $pb.PbList createRepeated() => $pb.PbList(); - @$core.pragma('dart2js:noInline') - static PreUser getDefault() => - _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); - static PreUser? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get userName => $_getSZ(0); - @$pb.TagNumber(1) - set userName($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasUserName() => $_has(0); - @$pb.TagNumber(1) - void clearUserName() => clearField(1); - - @$pb.TagNumber(2) - $core.String get deviceUUID => $_getSZ(1); - @$pb.TagNumber(2) - set deviceUUID($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasDeviceUUID() => $_has(1); - @$pb.TagNumber(2) - void clearDeviceUUID() => clearField(2); - - @$pb.TagNumber(3) - $core.String get roomID => $_getSZ(2); - @$pb.TagNumber(3) - set roomID($core.String v) { - $_setString(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasRoomID() => $_has(2); - @$pb.TagNumber(3) - void clearRoomID() => clearField(3); -} - -class RoomUserData extends $pb.GeneratedMessage { - factory RoomUserData({ - $core.String? id, - $core.String? playerName, - $core.String? avatar, - RoomUserStatus? status, - }) { - final $result = create(); - if (id != null) { - $result.id = id; - } - if (playerName != null) { - $result.playerName = playerName; - } - if (avatar != null) { - $result.avatar = avatar; - } - if (status != null) { - $result.status = status; - } - return $result; - } - RoomUserData._() : super(); - factory RoomUserData.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomUserData.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomUserData', - createEmptyInstance: create) - ..aOS(1, _omitFieldNames ? '' : 'id') - ..aOS(2, _omitFieldNames ? '' : 'playerName', protoName: 'playerName') - ..aOS(3, _omitFieldNames ? '' : 'Avatar', protoName: 'Avatar') - ..e(4, _omitFieldNames ? '' : 'status', $pb.PbFieldType.OE, - defaultOrMaker: RoomUserStatus.RoomUserStatusJoin, - valueOf: RoomUserStatus.valueOf, - enumValues: RoomUserStatus.values) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomUserData clone() => RoomUserData()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomUserData copyWith(void Function(RoomUserData) updates) => - super.copyWith((message) => updates(message as RoomUserData)) - as RoomUserData; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomUserData create() => RoomUserData._(); - RoomUserData createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomUserData getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomUserData? _defaultInstance; - - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String v) { - $_setString(0, v); - } - - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => clearField(1); - - @$pb.TagNumber(2) - $core.String get playerName => $_getSZ(1); - @$pb.TagNumber(2) - set playerName($core.String v) { - $_setString(1, v); - } - - @$pb.TagNumber(2) - $core.bool hasPlayerName() => $_has(1); - @$pb.TagNumber(2) - void clearPlayerName() => clearField(2); - - @$pb.TagNumber(3) - $core.String get avatar => $_getSZ(2); - @$pb.TagNumber(3) - set avatar($core.String v) { - $_setString(2, v); - } - - @$pb.TagNumber(3) - $core.bool hasAvatar() => $_has(2); - @$pb.TagNumber(3) - void clearAvatar() => clearField(3); - - @$pb.TagNumber(4) - RoomUserStatus get status => $_getN(3); - @$pb.TagNumber(4) - set status(RoomUserStatus v) { - setField(4, v); - } - - @$pb.TagNumber(4) - $core.bool hasStatus() => $_has(3); - @$pb.TagNumber(4) - void clearStatus() => clearField(4); -} - -class RoomUpdateMessage extends $pb.GeneratedMessage { - factory RoomUpdateMessage({ - RoomData? roomData, - $core.Iterable? usersData, - RoomUpdateType? roomUpdateType, - }) { - final $result = create(); - if (roomData != null) { - $result.roomData = roomData; - } - if (usersData != null) { - $result.usersData.addAll(usersData); - } - if (roomUpdateType != null) { - $result.roomUpdateType = roomUpdateType; - } - return $result; - } - RoomUpdateMessage._() : super(); - factory RoomUpdateMessage.fromBuffer($core.List<$core.int> i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromBuffer(i, r); - factory RoomUpdateMessage.fromJson($core.String i, - [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => - create()..mergeFromJson(i, r); - - static final $pb.BuilderInfo _i = $pb.BuilderInfo( - _omitMessageNames ? '' : 'RoomUpdateMessage', - createEmptyInstance: create) - ..aOM(1, _omitFieldNames ? '' : 'roomData', - protoName: 'roomData', subBuilder: RoomData.create) - ..pc( - 2, _omitFieldNames ? '' : 'usersData', $pb.PbFieldType.PM, - protoName: 'usersData', subBuilder: RoomUserData.create) - ..e( - 3, _omitFieldNames ? '' : 'roomUpdateType', $pb.PbFieldType.OE, - protoName: 'roomUpdateType', - defaultOrMaker: RoomUpdateType.RoomUpdateData, - valueOf: RoomUpdateType.valueOf, - enumValues: RoomUpdateType.values) - ..hasRequiredFields = false; - - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' - 'Will be removed in next major version') - RoomUpdateMessage clone() => RoomUpdateMessage()..mergeFromMessage(this); - @$core.Deprecated('Using this can add significant overhead to your binary. ' - 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' - 'Will be removed in next major version') - RoomUpdateMessage copyWith(void Function(RoomUpdateMessage) updates) => - super.copyWith((message) => updates(message as RoomUpdateMessage)) - as RoomUpdateMessage; - - $pb.BuilderInfo get info_ => _i; - - @$core.pragma('dart2js:noInline') - static RoomUpdateMessage create() => RoomUpdateMessage._(); - RoomUpdateMessage createEmptyInstance() => create(); - static $pb.PbList createRepeated() => - $pb.PbList(); - @$core.pragma('dart2js:noInline') - static RoomUpdateMessage getDefault() => _defaultInstance ??= - $pb.GeneratedMessage.$_defaultFor(create); - static RoomUpdateMessage? _defaultInstance; - - @$pb.TagNumber(1) - RoomData get roomData => $_getN(0); - @$pb.TagNumber(1) - set roomData(RoomData v) { - setField(1, v); - } - - @$pb.TagNumber(1) - $core.bool hasRoomData() => $_has(0); - @$pb.TagNumber(1) - void clearRoomData() => clearField(1); - @$pb.TagNumber(1) - RoomData ensureRoomData() => $_ensure(0); - - @$pb.TagNumber(2) - $core.List get usersData => $_getList(1); - - @$pb.TagNumber(3) - RoomUpdateType get roomUpdateType => $_getN(2); - @$pb.TagNumber(3) - set roomUpdateType(RoomUpdateType v) { - setField(3, v); - } - - @$pb.TagNumber(3) - $core.bool hasRoomUpdateType() => $_has(2); - @$pb.TagNumber(3) - void clearRoomUpdateType() => clearField(3); -} - -const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); -const _omitMessageNames = - $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/grpc/party_room_server/index.pbenum.dart b/lib/generated/grpc/party_room_server/index.pbenum.dart deleted file mode 100644 index 34fe2f8..0000000 --- a/lib/generated/grpc/party_room_server/index.pbenum.dart +++ /dev/null @@ -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 values = [ - 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 values = [ - 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 values = [ - 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 values = [ - 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'); diff --git a/lib/generated/grpc/party_room_server/index.pbgrpc.dart b/lib/generated/grpc/party_room_server/index.pbgrpc.dart deleted file mode 100644 index 8501d93..0000000 --- a/lib/generated/grpc/party_room_server/index.pbgrpc.dart +++ /dev/null @@ -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); -} diff --git a/lib/generated/grpc/party_room_server/index.pbjson.dart b/lib/generated/grpc/party_room_server/index.pbjson.dart deleted file mode 100644 index d599efc..0000000 --- a/lib/generated/grpc/party_room_server/index.pbjson.dart +++ /dev/null @@ -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=='); diff --git a/lib/global_ui_model.dart b/lib/global_ui_model.dart deleted file mode 100644 index 6e11d99..0000000 --- a/lib/global_ui_model.dart +++ /dev/null @@ -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 getRunningGameUser() async { - await Future.delayed(const Duration(milliseconds: 300)); - - ///TODO 实现获取运行中用户名 - return "xkeyC"; - } - - Future 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(); - } -} diff --git a/lib/main.dart b/lib/main.dart index 1406c37..cab0b00 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 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, ); } } diff --git a/lib/common/io/aria2c.dart b/lib/provider/aria2c.dart similarity index 51% rename from lib/common/io/aria2c.dart rename to lib/provider/aria2c.dart index 83ee7a5..b1263df 100644 --- a/lib/common/io/aria2c.dart +++ b/lib/provider/aria2c.dart @@ -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; +@freezed +class Aria2cModelState with _$Aria2cModelState { + const factory Aria2cModelState({ + required String aria2cDir, + Aria2c? aria2c, + Aria2GlobalStat? aria2globalStat, + }) = _Aria2cModelState; +} - static Aria2c getClient() { - if (_aria2c != null) return _aria2c!; - throw "not connect!"; - } +extension Aria2cModelExt on Aria2cModelState { + bool get isRunning => aria2c != null; - static bool get isAvailable => _isDaemonRunning && _aria2c != null; + bool get hasDownloadTask => aria2globalStat != null && aria2TotalTaskNum > 0; - static Future checkLazyLoad() async { - try { - final sessionFile = File("$_aria2cDir\\aria2.session"); - // 有下载任务则第一时间初始化 - if (await sessionFile.exists() && - (await sessionFile.readAsString()).trim().isNotEmpty) { - await launchDaemon(); - } - } catch (e) { - dPrint("Aria2cManager.checkLazyLoad Error:$e"); + 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"); + // 有下载任务则第一时间初始化 + if (await sessionFile.exists() && + (await sessionFile.readAsString()).trim().isNotEmpty) { + 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 getFreePort() async { + Future 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 _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 _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 _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)); + } + } } diff --git a/lib/provider/aria2c.freezed.dart b/lib/provider/aria2c.freezed.dart new file mode 100644 index 0000000..eeb7609 --- /dev/null +++ b/lib/provider/aria2c.freezed.dart @@ -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 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 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; +} diff --git a/lib/provider/aria2c.g.dart b/lib/provider/aria2c.g.dart new file mode 100644 index 0000000..f9db6d6 --- /dev/null +++ b/lib/provider/aria2c.g.dart @@ -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.internal( + Aria2cModel.new, + name: r'aria2cModelProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$aria2cModelHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Aria2cModel = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/about/about_ui.dart b/lib/ui/about/about_ui.dart index f0c41cf..4a1ef96 100644 --- a/lib/ui/about/about_ui.dart +++ b/lib/ui/about/about_ui.dart @@ -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 { - 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 { 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 { 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 { 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, "已经是最新版本!"); + } + } + } } diff --git a/lib/ui/about/about_ui_model.dart b/lib/ui/about/about_ui_model.dart deleted file mode 100644 index edbf32c..0000000 --- a/lib/ui/about/about_ui_model.dart +++ /dev/null @@ -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 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!, "已是最新版本"); - } - } -} diff --git a/lib/ui/home/countdown/countdown_dialog_ui.dart b/lib/ui/home/countdown/countdown_dialog_ui.dart deleted file mode 100644 index 0f09bf3..0000000 --- a/lib/ui/home/countdown/countdown_dialog_ui.dart +++ /dev/null @@ -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 { - @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) => ""; -} diff --git a/lib/ui/home/countdown/countdown_dialog_ui_model.dart b/lib/ui/home/countdown/countdown_dialog_ui_model.dart deleted file mode 100644 index 1c70ca8..0000000 --- a/lib/ui/home/countdown/countdown_dialog_ui_model.dart +++ /dev/null @@ -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 countdownFestivalListData; - - CountdownDialogUIModel(this.countdownFestivalListData); - - onBack() { - Navigator.pop(context!); - } -} diff --git a/lib/ui/home/dialogs/home_countdown_dialog_ui.dart b/lib/ui/home/dialogs/home_countdown_dialog_ui.dart new file mode 100644 index 0000000..9e1e035 --- /dev/null +++ b/lib/ui/home/dialogs/home_countdown_dialog_ui.dart @@ -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)), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui.dart new file mode 100644 index 0000000..c2dc880 --- /dev/null +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui.dart @@ -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(), + ], + ), + ) + ] + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/home/login/login_dialog_ui_model.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart similarity index 59% rename from lib/ui/home/login/login_dialog_ui_model.dart rename to lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart index 809667d..c49fdac 100644 --- a/lib/ui/home/login/login_dialog_ui_model.dart +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart @@ -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; +@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; +} +@riverpod +class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { @override - void initModel() { - _launchWebLogin(); - super.initModel(); + HomeGameLoginState build() { + return const HomeGameLoginState(loginStatus: 0); } - Future _launchWebLogin() async { - isDeviceSupportWinHello = await localAuth.isDeviceSupported(); - notifyListeners(); - goWebView("登录 RSI 账户", "https://robertsspaceindustries.com/connect", + final LocalAuthentication _localAuth = LocalAuthentication(); + + // ignore: avoid_build_context_in_providers + Future 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 payload = Jwt.parseJwt(authToken!); - nickname = payload["nickname"] ?? ""; + final Map 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 _readyForLaunch() async { + Future _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")) { diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.freezed.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.freezed.dart new file mode 100644 index 0000000..5bd18ae --- /dev/null +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.freezed.dart @@ -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 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? get releaseInfo => throw _privateConstructorUsedError; + String? get installPath => throw _privateConstructorUsedError; + bool? get isDeviceSupportWinHello => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $HomeGameLoginStateCopyWith 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? 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?, + 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? 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?, + 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? 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? _releaseInfo; + @override + Map? 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? 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? get releaseInfo; + @override + String? get installPath; + @override + bool? get isDeviceSupportWinHello; + @override + @JsonKey(ignore: true) + _$$LoginStatusImplCopyWith<_$LoginStatusImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart new file mode 100644 index 0000000..048de58 --- /dev/null +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.g.dart @@ -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; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/dialogs/home_md_content_dialog_ui.dart b/lib/ui/home/dialogs/home_md_content_dialog_ui.dart new file mode 100644 index 0000000..9e28b6b --- /dev/null +++ b/lib/ui/home/dialogs/home_md_content_dialog_ui.dart @@ -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 _getContent() async { + final r = await RSHttp.getText(url); + return r; + } +} diff --git a/lib/ui/home/dialogs/md_content_dialog_ui.dart b/lib/ui/home/dialogs/md_content_dialog_ui.dart deleted file mode 100644 index 367da4f..0000000 --- a/lib/ui/home/dialogs/md_content_dialog_ui.dart +++ /dev/null @@ -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 { - @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; -} diff --git a/lib/ui/home/dialogs/md_content_dialog_ui_model.dart b/lib/ui/home/dialogs/md_content_dialog_ui_model.dart deleted file mode 100644 index 11e03da..0000000 --- a/lib/ui/home/dialogs/md_content_dialog_ui_model.dart +++ /dev/null @@ -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(); - } -} diff --git a/lib/ui/home/downloader/downloader_ui.dart b/lib/ui/home/downloader/home_downloader_ui.dart similarity index 88% rename from lib/ui/home/downloader/downloader_ui.dart rename to lib/ui/home/downloader/home_downloader_ui.dart index 759dfc3..54c8740 100644 --- a/lib/ui/home/downloader/downloader_ui.dart +++ b/lib/ui/home/downloader/home_downloader_ui.dart @@ -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 { - 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 { const SizedBox(width: 12), for (final item in , 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 { ], ), ), - 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 { 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 { 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 { ), 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 { 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 { ), ), ], - )); + ), + useBodyContainer: true); } - - @override - String getUITitle(BuildContext context, DownloaderUIModel model) => "下载管理"; } diff --git a/lib/ui/home/downloader/downloader_ui_model.dart b/lib/ui/home/downloader/home_downloader_ui_model.dart similarity index 53% rename from lib/ui/home/downloader/downloader_ui_model.dart rename to lib/ui/home/downloader/home_downloader_ui_model.dart index 9e7fa07..a4aa3d6 100644 --- a/lib/ui/home/downloader/downloader_ui_model.dart +++ b/lib/ui/home/downloader/home_downloader_ui_model.dart @@ -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 tasks = []; - List waitingTasks = []; - List 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 tasks, + @Default([]) List waitingTasks, + @Default([]) List 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 = [...tasks, ...waitingTasks, ...stoppedTasks]; - if (index >= 0 && index < tasks.length) { + final tempList = [ + ...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 resumeTask(String? gid) async { + final aria2c = ref.read(aria2cModelProvider).aria2c; + if (gid != null) { + await aria2c?.unpause(gid); + } + } + + Future pauseTask(String? gid) async { + final aria2c = ref.read(aria2cModelProvider).aria2c; + if (gid != null) { + await aria2c?.pause(gid); + } + } + + Future 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 getFilesFormTask(Aria2Task task) { List 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 resumeTask(String? gid) async { - final aria2c = Aria2cManager.getClient(); - if (gid != null) { - await aria2c.unpause(gid); - } - } - - Future pauseTask(String? gid) async { - final aria2c = Aria2cManager.getClient(); - - if (gid != null) { - await aria2c.pause(gid); - } - } - - Future 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 _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 _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() - ..maxOverallUploadLimit = upByte - ..maxOverallDownloadLimit = downByte)); + 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) + .unwrap(); if (r != null) { await box.put('downloader_up_limit', upCtrl.text.trim()); await box.put('downloader_down_limit', downCtrl.text.trim()); - notifyListeners(); } } } diff --git a/lib/ui/home/downloader/home_downloader_ui_model.freezed.dart b/lib/ui/home/downloader/home_downloader_ui_model.freezed.dart new file mode 100644 index 0000000..efec400 --- /dev/null +++ b/lib/ui/home/downloader/home_downloader_ui_model.freezed.dart @@ -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 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 get tasks => throw _privateConstructorUsedError; + List get waitingTasks => throw _privateConstructorUsedError; + List get stoppedTasks => throw _privateConstructorUsedError; + Aria2GlobalStat? get globalStat => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $HomeDownloaderUIStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HomeDownloaderUIStateCopyWith<$Res> { + factory $HomeDownloaderUIStateCopyWith(HomeDownloaderUIState value, + $Res Function(HomeDownloaderUIState) then) = + _$HomeDownloaderUIStateCopyWithImpl<$Res, HomeDownloaderUIState>; + @useResult + $Res call( + {List tasks, + List waitingTasks, + List 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, + waitingTasks: null == waitingTasks + ? _value.waitingTasks + : waitingTasks // ignore: cast_nullable_to_non_nullable + as List, + stoppedTasks: null == stoppedTasks + ? _value.stoppedTasks + : stoppedTasks // ignore: cast_nullable_to_non_nullable + as List, + 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 tasks, + List waitingTasks, + List 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, + waitingTasks: null == waitingTasks + ? _value._waitingTasks + : waitingTasks // ignore: cast_nullable_to_non_nullable + as List, + stoppedTasks: null == stoppedTasks + ? _value._stoppedTasks + : stoppedTasks // ignore: cast_nullable_to_non_nullable + as List, + globalStat: freezed == globalStat + ? _value.globalStat + : globalStat // ignore: cast_nullable_to_non_nullable + as Aria2GlobalStat?, + )); + } +} + +/// @nodoc + +class _$HomeDownloaderUIStateImpl implements _HomeDownloaderUIState { + const _$HomeDownloaderUIStateImpl( + {final List tasks = const [], + final List waitingTasks = const [], + final List stoppedTasks = const [], + this.globalStat}) + : _tasks = tasks, + _waitingTasks = waitingTasks, + _stoppedTasks = stoppedTasks; + + final List _tasks; + @override + @JsonKey() + List get tasks { + if (_tasks is EqualUnmodifiableListView) return _tasks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tasks); + } + + final List _waitingTasks; + @override + @JsonKey() + List get waitingTasks { + if (_waitingTasks is EqualUnmodifiableListView) return _waitingTasks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_waitingTasks); + } + + final List _stoppedTasks; + @override + @JsonKey() + List 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 tasks, + final List waitingTasks, + final List stoppedTasks, + final Aria2GlobalStat? globalStat}) = _$HomeDownloaderUIStateImpl; + + @override + List get tasks; + @override + List get waitingTasks; + @override + List get stoppedTasks; + @override + Aria2GlobalStat? get globalStat; + @override + @JsonKey(ignore: true) + _$$HomeDownloaderUIStateImplCopyWith<_$HomeDownloaderUIStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/downloader/home_downloader_ui_model.g.dart b/lib/ui/home/downloader/home_downloader_ui_model.g.dart new file mode 100644 index 0000000..c211874 --- /dev/null +++ b/lib/ui/home/downloader/home_downloader_ui_model.g.dart @@ -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; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/game_doctor/game_doctor_ui.dart b/lib/ui/home/game_doctor/game_doctor_ui.dart index a938397..11ff98d 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui.dart @@ -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 { +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 { ], ), ), - 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 { 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 { ), )) ] 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 { 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 { )); } - List 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 { ); } - Widget makeResultItem( - MapEntry item, GameDoctorUIModel model) { + List 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 item, + HomeGameDoctorState state, HomeGameDoctorUIModel model) { final errorNames = { "unSupport_system": MapEntry("不支持的操作系统,游戏可能无法运行", "请升级您的系统 (${item.value})"), @@ -193,12 +219,10 @@ class GameDoctorUI extends BaseUI { ), ), 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 { ); } - @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; + } + } } diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.dart index 2ba6a9e..8f186f7 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.dart @@ -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>? checkResult; - - set lastScreenInfo(String info) { - _lastScreenInfo = info; - notifyListeners(); - } - - bool isChecking = false; - - bool isFixing = false; - String isFixingString = ""; - - final cnExp = RegExp(r"[^\x00-\xff]"); +@freezed +class HomeGameDoctorState with _$HomeGameDoctorState { + const factory HomeGameDoctorState({ + @Default(false) bool isChecking, + @Default(false) bool isFixing, + @Default("") String lastScreenInfo, + @Default("") String isFixingString, + List>? checkResult, + }) = _HomeGameDoctorState; +} +@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 doFix( + // ignore: avoid_build_context_in_providers + BuildContext context, + MapEntry item) async { + final checkResult = + List>.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 = >[]; // 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> 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> 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> 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 doFix(MapEntry 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; - } - } } diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart new file mode 100644 index 0000000..8289532 --- /dev/null +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.freezed.dart @@ -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 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>? get checkResult => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $HomeGameDoctorStateCopyWith 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>? 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>?, + ) 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>? 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>?, + )); + } +} + +/// @nodoc + +class _$HomeGameDoctorStateImpl implements _HomeGameDoctorState { + const _$HomeGameDoctorStateImpl( + {this.isChecking = false, + this.isFixing = false, + this.lastScreenInfo = "", + this.isFixingString = "", + final List>? 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>? _checkResult; + @override + List>? 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>? checkResult}) = + _$HomeGameDoctorStateImpl; + + @override + bool get isChecking; + @override + bool get isFixing; + @override + String get lastScreenInfo; + @override + String get isFixingString; + @override + List>? get checkResult; + @override + @JsonKey(ignore: true) + _$$HomeGameDoctorStateImplCopyWith<_$HomeGameDoctorStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart new file mode 100644 index 0000000..61883ab --- /dev/null +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart @@ -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; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/home_ui.dart b/lib/ui/home/home_ui.dart index ad72858..f55b4b1 100644 --- a/lib/ui/home/home_ui.dart +++ b/lib/ui/home/home_ui.dart @@ -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 { @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 { 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 { 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 { ); } - List makeIndex(BuildContext context, HomeUIModel model) { + List makeIndex( + BuildContext context, HomeUIModel model, HomeUIModelState homeState) { const double width = 280; return [ Stack( @@ -86,7 +96,7 @@ class HomeUI extends BaseUI { height: 260, ), ), - makeGameStatusCard(context, model, 340) + makeGameStatusCard(context, model, 340, homeState) ], ), ), @@ -95,12 +105,12 @@ class HomeUI extends BaseUI { 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 { const SizedBox(width: 6), Expanded( child: ComboBox( - 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 - ? null - : () => model.launchRSI(), - child: Padding( - padding: const EdgeInsets.all(6), - child: Icon( - model.isCurGameRunning - ? FluentIcons.stop_solid - : FluentIcons.play, - color: model.isCurGameRunning - ? Colors.red.withOpacity(.8) - : null, - ), - )), - ), + Button( + onPressed: homeState.webLocalizationVersionsData == null + ? null + : () => model.launchRSI(context), + child: Padding( + padding: const EdgeInsets.all(6), + child: Icon( + homeState.isCurGameRunning + ? FluentIcons.stop_solid + : FluentIcons.play, + 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)), + child: const Padding( + padding: EdgeInsets.all(6), + child: Icon(FluentIcons.folder_open), + ), + onPressed: () => SystemHelper.openDir(homeState.scInstalledPath), + ), const SizedBox(width: 12), Button( onPressed: model.reScanPath, @@ -172,12 +175,13 @@ class HomeUI extends BaseUI { ), ), 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 { 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 { 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 { 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 { ), ), 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 { ); } - 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 { 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 { ), )), 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 { 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 { ), )); }, - itemCount: model.rssTextItems?.length, + itemCount: homeState.rssTextItems?.length, ), const SizedBox(height: 12), ], @@ -421,9 +426,10 @@ class HomeUI extends BaseUI { 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 { 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 states) { return Container( width: 300, @@ -483,15 +489,15 @@ class HomeUI extends BaseUI { ], )), 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 { ); } - @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 { 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 { ); } - 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 { 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 { ), 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 { ); } - 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 { 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 { ), ); } + + _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 { diff --git a/lib/ui/home/home_ui_model.dart b/lib/ui/home/home_ui_model.dart index 060fda4..a2a1d14 100644 --- a/lib/ui/home/home_ui_model.dart +++ b/lib/ui/home/home_ui_model.dart @@ -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 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 scInstallPaths, + AppWebLocalizationVersionsData? webLocalizationVersionsData, + @Default("") String lastScreenInfo, + List? rssVideoItems, + List? rssTextItems, + MapEntry? localizationUpdateInfo, + List? scServerStatus, + List? countdownFestivalListData, + @Default({}) Map isGameRunning, + }) = _HomeUIModelState; +} - bool isFixing = false; - String isFixingString = ""; - - final Map _isGameRunning = {}; - - bool get isCurGameRunning => _isGameRunning[scInstalledPath] ?? false; - - List? rssVideoItems; - List? rssTextItems; - - AppWebLocalizationVersionsData? appWebLocalizationVersionsData; - - List? countdownFestivalListData; - - MapEntry? 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 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 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 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); - notifyListeners(); + await webViewModel.launch(url, appGlobalState.networkVersionData!); } - launchRSI() async { - if (scInstalledPath == "not_install") { - showToast(context!, "该功能需要一个有效的安装位置"); + 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; + }); + } + + 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); + } + } + + 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 _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 args, String installPath, + doLaunchGame( + // ignore: avoid_build_context_in_providers + BuildContext context, + String launchExe, + List args, + String installPath, String? processorAffinity) async { - _isGameRunning[installPath] = true; - notifyListeners(); + var runningMap = Map.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? 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 _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.from(state.isGameRunning); + runningMap[installPath] = false; + state = state.copyWith(isGameRunning: runningMap); } } diff --git a/lib/ui/home/home_ui_model.freezed.dart b/lib/ui/home/home_ui_model.freezed.dart new file mode 100644 index 0000000..ebf6120 --- /dev/null +++ b/lib/ui/home/home_ui_model.freezed.dart @@ -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 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 get scInstallPaths => throw _privateConstructorUsedError; + AppWebLocalizationVersionsData? get webLocalizationVersionsData => + throw _privateConstructorUsedError; + String get lastScreenInfo => throw _privateConstructorUsedError; + List? get rssVideoItems => throw _privateConstructorUsedError; + List? get rssTextItems => throw _privateConstructorUsedError; + MapEntry? get localizationUpdateInfo => + throw _privateConstructorUsedError; + List? get scServerStatus => throw _privateConstructorUsedError; + List? get countdownFestivalListData => + throw _privateConstructorUsedError; + Map get isGameRunning => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $HomeUIModelStateCopyWith 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 scInstallPaths, + AppWebLocalizationVersionsData? webLocalizationVersionsData, + String lastScreenInfo, + List? rssVideoItems, + List? rssTextItems, + MapEntry? localizationUpdateInfo, + List? scServerStatus, + List? countdownFestivalListData, + Map 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, + 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?, + rssTextItems: freezed == rssTextItems + ? _value.rssTextItems + : rssTextItems // ignore: cast_nullable_to_non_nullable + as List?, + localizationUpdateInfo: freezed == localizationUpdateInfo + ? _value.localizationUpdateInfo + : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable + as MapEntry?, + scServerStatus: freezed == scServerStatus + ? _value.scServerStatus + : scServerStatus // ignore: cast_nullable_to_non_nullable + as List?, + countdownFestivalListData: freezed == countdownFestivalListData + ? _value.countdownFestivalListData + : countdownFestivalListData // ignore: cast_nullable_to_non_nullable + as List?, + isGameRunning: null == isGameRunning + ? _value.isGameRunning + : isGameRunning // ignore: cast_nullable_to_non_nullable + as Map, + ) 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 scInstallPaths, + AppWebLocalizationVersionsData? webLocalizationVersionsData, + String lastScreenInfo, + List? rssVideoItems, + List? rssTextItems, + MapEntry? localizationUpdateInfo, + List? scServerStatus, + List? countdownFestivalListData, + Map 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, + 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?, + rssTextItems: freezed == rssTextItems + ? _value._rssTextItems + : rssTextItems // ignore: cast_nullable_to_non_nullable + as List?, + localizationUpdateInfo: freezed == localizationUpdateInfo + ? _value.localizationUpdateInfo + : localizationUpdateInfo // ignore: cast_nullable_to_non_nullable + as MapEntry?, + scServerStatus: freezed == scServerStatus + ? _value._scServerStatus + : scServerStatus // ignore: cast_nullable_to_non_nullable + as List?, + countdownFestivalListData: freezed == countdownFestivalListData + ? _value._countdownFestivalListData + : countdownFestivalListData // ignore: cast_nullable_to_non_nullable + as List?, + isGameRunning: null == isGameRunning + ? _value._isGameRunning + : isGameRunning // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$HomeUIModelStateImpl implements _HomeUIModelState { + _$HomeUIModelStateImpl( + {this.appPlacardData, + this.isFixing = false, + this.isFixingString = "", + this.scInstalledPath, + final List scInstallPaths = const [], + this.webLocalizationVersionsData, + this.lastScreenInfo = "", + final List? rssVideoItems, + final List? rssTextItems, + this.localizationUpdateInfo, + final List? scServerStatus, + final List? countdownFestivalListData, + final Map 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 _scInstallPaths; + @override + @JsonKey() + List 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? _rssVideoItems; + @override + List? 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? _rssTextItems; + @override + List? 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? localizationUpdateInfo; + final List? _scServerStatus; + @override + List? 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? _countdownFestivalListData; + @override + List? 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 _isGameRunning; + @override + @JsonKey() + Map 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 scInstallPaths, + final AppWebLocalizationVersionsData? webLocalizationVersionsData, + final String lastScreenInfo, + final List? rssVideoItems, + final List? rssTextItems, + final MapEntry? localizationUpdateInfo, + final List? scServerStatus, + final List? countdownFestivalListData, + final Map isGameRunning}) = _$HomeUIModelStateImpl; + + @override + AppPlacardData? get appPlacardData; + @override + bool get isFixing; + @override + String get isFixingString; + @override + String? get scInstalledPath; + @override + List get scInstallPaths; + @override + AppWebLocalizationVersionsData? get webLocalizationVersionsData; + @override + String get lastScreenInfo; + @override + List? get rssVideoItems; + @override + List? get rssTextItems; + @override + MapEntry? get localizationUpdateInfo; + @override + List? get scServerStatus; + @override + List? get countdownFestivalListData; + @override + Map get isGameRunning; + @override + @JsonKey(ignore: true) + _$$HomeUIModelStateImplCopyWith<_$HomeUIModelStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/home_ui_model.g.dart b/lib/ui/home/home_ui_model.g.dart new file mode 100644 index 0000000..9a7d2c9 --- /dev/null +++ b/lib/ui/home/home_ui_model.g.dart @@ -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.internal( + HomeUIModel.new, + name: r'homeUIModelProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$homeUIModelHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$HomeUIModel = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/localization/localization_dialog_ui.dart b/lib/ui/home/localization/localization_dialog_ui.dart new file mode 100644 index 0000000..aba0d12 --- /dev/null +++ b/lib/ui/home/localization/localization_dialog_ui.dart @@ -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 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 children, BuildContext context, + {List 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( + 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), + )), + ], + ); + } +} diff --git a/lib/ui/home/localization/localization_ui.dart b/lib/ui/home/localization/localization_ui.dart deleted file mode 100644 index 83ef1c2..0000000 --- a/lib/ui/home/localization/localization_ui.dart +++ /dev/null @@ -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 { - @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 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 children, - {List 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( - 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) => "汉化管理"; -} diff --git a/lib/ui/home/localization/localization_ui_model.dart b/lib/ui/home/localization/localization_ui_model.dart index 36ea5a2..be9689b 100644 --- a/lib/ui/home/localization/localization_ui_model.dart +++ b/lib/ui/home/localization/localization_ui_model.dart @@ -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? apiLocalizationData, + @Default("") String workingVersion, + MapEntry? patchStatus, + List? customizeList, + @Default(false) bool enableCustomize, + }) = _LocalizationUIState; +} + +@riverpod +class LocalizationUIModel extends _$LocalizationUIModel { static const languageSupport = { "chinese_(simplified)": "简体中文", "chinese_(traditional)": "繁體中文", }; - late String selectedLanguage; - - Map? 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? patchStatus; + StreamSubscription? _customizeDirListenSub; - List? customizeList; - - StreamSubscription? customizeDirListenSub; - - bool enableCustomize = false; + String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!; @override - void initModel() { - selectedLanguage = languageSupport.entries.first.key; + LocalizationUIState build() { + state = LocalizationUIState(selectedLanguage: languageSupport.keys.first); + _init(); + return state; + } + + _init() async { if (!customizeDir.existsSync()) { - customizeDir.createSync(recursive: true); + await customizeDir.create(recursive: true); } - customizeDirListenSub = customizeDir.watch().listen((event) { + _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 = {}; 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(); - } - - _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}"; + 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"; + } } - } else { - dPrint("use cache $savePath"); + await userCfgFile.delete(); + await userCfgFile.create(); + await userCfgFile.writeAsString(finalString, flush: true); + _loadData(); } - 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 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 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 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?> checkLocalizationUpdates( - List gameInstallPaths) async { - final updateInfo = {}; - 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 = []; + 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 _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 _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 检查更新 } } diff --git a/lib/ui/home/localization/localization_ui_model.freezed.dart b/lib/ui/home/localization/localization_ui_model.freezed.dart new file mode 100644 index 0000000..9f1bf50 --- /dev/null +++ b/lib/ui/home/localization/localization_ui_model.freezed.dart @@ -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 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? get apiLocalizationData => + throw _privateConstructorUsedError; + String get workingVersion => throw _privateConstructorUsedError; + MapEntry? get patchStatus => throw _privateConstructorUsedError; + List? get customizeList => throw _privateConstructorUsedError; + bool get enableCustomize => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $LocalizationUIStateCopyWith 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? apiLocalizationData, + String workingVersion, + MapEntry? patchStatus, + List? 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?, + 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?, + customizeList: freezed == customizeList + ? _value.customizeList + : customizeList // ignore: cast_nullable_to_non_nullable + as List?, + 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? apiLocalizationData, + String workingVersion, + MapEntry? patchStatus, + List? 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?, + 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?, + customizeList: freezed == customizeList + ? _value._customizeList + : customizeList // ignore: cast_nullable_to_non_nullable + as List?, + 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? apiLocalizationData, + this.workingVersion = "", + this.patchStatus, + final List? customizeList, + this.enableCustomize = false}) + : _apiLocalizationData = apiLocalizationData, + _customizeList = customizeList; + + @override + final String? selectedLanguage; + final Map? _apiLocalizationData; + @override + Map? 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? patchStatus; + final List? _customizeList; + @override + List? 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? apiLocalizationData, + final String workingVersion, + final MapEntry? patchStatus, + final List? customizeList, + final bool enableCustomize}) = _$LocalizationUIStateImpl; + + @override + String? get selectedLanguage; + @override + Map? get apiLocalizationData; + @override + String get workingVersion; + @override + MapEntry? get patchStatus; + @override + List? get customizeList; + @override + bool get enableCustomize; + @override + @JsonKey(ignore: true) + _$$LocalizationUIStateImplCopyWith<_$LocalizationUIStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/localization/localization_ui_model.g.dart b/lib/ui/home/localization/localization_ui_model.g.dart new file mode 100644 index 0000000..cfdaf16 --- /dev/null +++ b/lib/ui/home/localization/localization_ui_model.g.dart @@ -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; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/login/login_dialog_ui.dart b/lib/ui/home/login/login_dialog_ui.dart deleted file mode 100644 index a563bb3..0000000 --- a/lib/ui/home/login/login_dialog_ui.dart +++ /dev/null @@ -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 { - @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) => ""; -} diff --git a/lib/ui/home/performance/performance_ui.dart b/lib/ui/home/performance/performance_ui.dart index cf6f45b..8087db5 100644 --- a/lib/ui/home/performance/performance_ui.dart +++ b/lib/ui/home/performance/performance_ui.dart @@ -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 { +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 { 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 { Row( children: [ Text( - "当前状态:${model.enabled ? "已应用" : "未应用"}", + "当前状态:${state.enabled ? "已应用" : "未应用"}", style: const TextStyle(fontSize: 18), ), const SizedBox(width: 32), @@ -72,7 +80,7 @@ class PerformanceUI extends BaseUI { " 恢复默认 ", 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 { 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 { children: [ const ProgressRing(), const SizedBox(height: 12), - Text(model.workingString), + Text(state.workingString), ], ), ), @@ -127,10 +135,16 @@ class PerformanceUI extends BaseUI { ); } - return makeDefaultPage(context, model, content: content); + return makeDefaultPage(context, + title: "性能优化 -> ${model.scPath}", + useBodyContainer: true, + content: content); } - Widget makeItemGroup(MapEntry> group) { + Widget makeItemGroup( + BuildContext context, + MapEntry> group, + HomePerformanceUIModel model) { return Padding( padding: const EdgeInsets.all(12), child: Container( @@ -152,7 +166,7 @@ class PerformanceUI extends BaseUI { 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 { ); } - 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 { v >= (item.min ?? 0)) { item.value = v; } - setState(() {}); + model.updateState(); }, onTapOutside: (e) { - setState(() {}); + model.updateState(); }, ), ), @@ -207,7 +221,7 @@ class PerformanceUI extends BaseUI { max: item.max?.toDouble() ?? 0, onChanged: (double value) { item.value = value.toInt(); - setState(() {}); + model.updateState(); }, ), ) @@ -222,7 +236,7 @@ class PerformanceUI extends BaseUI { checked: item.value == 1, onChanged: (bool value) { item.value = value ? 1 : 0; - setState(() {}); + model.updateState(); }, ) ], @@ -261,8 +275,4 @@ class PerformanceUI extends BaseUI { ), ); } - - @override - String getUITitle(BuildContext context, PerformanceUIModel model) => - "性能优化 ${model.scPath}"; } diff --git a/lib/ui/home/performance/performance_ui_model.dart b/lib/ui/home/performance/performance_ui_model.dart index d532dc4..6a41c84 100644 --- a/lib/ui/home/performance/performance_ui_model.dart +++ b/lib/ui/home/performance/performance_ui_model.dart @@ -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>? performanceMap, + @Default("") String workingString, + }) = _HomePerformanceUIState; +} - Map>? performanceMap; +@riverpod +class HomePerformanceUIModel extends _$HomePerformanceUIModel { + String get scPath => ref.read(homeUIModelProvider).scInstalledPath!; - List inAppKeys = []; + final customizeCtrl = TextEditingController(text: ""); - String workingString = ""; + final List _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 _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 = >{}; 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 _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(); } } diff --git a/lib/ui/home/performance/performance_ui_model.freezed.dart b/lib/ui/home/performance/performance_ui_model.freezed.dart new file mode 100644 index 0000000..16b4f23 --- /dev/null +++ b/lib/ui/home/performance/performance_ui_model.freezed.dart @@ -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 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>? get performanceMap => + throw _privateConstructorUsedError; + String get workingString => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $HomePerformanceUIStateCopyWith 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>? 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>?, + 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>? 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>?, + 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>? performanceMap, + this.workingString = ""}) + : _performanceMap = performanceMap; + + @override + @JsonKey() + final bool showGraphicsPerformanceTip; + @override + @JsonKey() + final bool enabled; + final Map>? _performanceMap; + @override + Map>? 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>? performanceMap, + final String workingString}) = _$HomePerformanceUIStateImpl; + + @override + bool get showGraphicsPerformanceTip; + @override + bool get enabled; + @override + Map>? get performanceMap; + @override + String get workingString; + @override + @JsonKey(ignore: true) + _$$HomePerformanceUIStateImplCopyWith<_$HomePerformanceUIStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/home/performance/performance_ui_model.g.dart b/lib/ui/home/performance/performance_ui_model.g.dart new file mode 100644 index 0000000..aeb365c --- /dev/null +++ b/lib/ui/home/performance/performance_ui_model.g.dart @@ -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; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/webview/webview_localization_capture_ui.dart b/lib/ui/home/webview/webview_localization_capture_ui.dart deleted file mode 100644 index c51df92..0000000 --- a/lib/ui/home/webview/webview_localization_capture_ui.dart +++ /dev/null @@ -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 { - @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 翻译捕获工具"; -} diff --git a/lib/ui/home/webview/webview_localization_capture_ui_model.dart b/lib/ui/home/webview/webview_localization_capture_ui_model.dart deleted file mode 100644 index 2beefbb..0000000 --- a/lib/ui/home/webview/webview_localization_capture_ui_model.dart +++ /dev/null @@ -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 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( - " 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(); - } -} diff --git a/lib/ui/index_ui.dart b/lib/ui/index_ui.dart index 13af9ca..d39b207 100644 --- a/lib/ui/index_ui.dart +++ b/lib/ui/index_ui.dart @@ -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 { @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 { ), const SizedBox(width: 12), const Text( - "SC汉化盒子 V${AppConf.appVersion} ${AppConf.isMSE ? "" : " Dev"}") + "SC汉化盒子 V${ConstConf.appVersion} ${ConstConf.isMSE ? "" : " Dev"}") ], ), ), @@ -45,99 +52,52 @@ class IndexUI extends BaseUI { mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( - icon: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(6), - child: Icon( - FluentIcons.installation, - size: 22, - color: Colors.white.withOpacity(.6), - ), + icon: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(6), + child: Icon( + FluentIcons.installation, + size: 22, + 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, - ), - ), - )) - ], - ), - onPressed: model.goDownloader), + ), + _makeAria2TaskNumWidget() + ], + ), + 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("home")); - case 1: - return BaseUIContainer( - uiCreate: () => PartyRoomHomeUI(), - modelCreate: () => - model.getChildUIModelProviders("party")); - case 2: - return BaseUIContainer( - uiCreate: () => ToolsUI(), - modelCreate: () => - model.getChildUIModelProviders("tools")); - case 3: - return BaseUIContainer( - uiCreate: () => SettingUI(), - modelCreate: () => - model.getChildUIModelProviders("settings")); - case 4: - return BaseUIContainer( - uiCreate: () => AboutUI(), - modelCreate: () => - model.getChildUIModelProviders("about")); - } - return const SizedBox(); - } + Map get pageMenus => { + FluentIcons.home: "首页", + FluentIcons.game: "大厅", + FluentIcons.toolbox: "工具", + FluentIcons.settings: "设置", + FluentIcons.info: "关于", + }; - List getNavigationPaneItems(IndexUIModel model) { - final menus = { - FluentIcons.home: "首页", - FluentIcons.game: "大厅", - FluentIcons.toolbox: "工具", - FluentIcons.settings: "设置", - FluentIcons.info: "关于", - }; + List getNavigationPaneItems( + ValueNotifier 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 { ), // 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 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'); + } } diff --git a/lib/ui/index_ui_model.dart b/lib/ui/index_ui_model.dart deleted file mode 100644 index c12326d..0000000 --- a/lib/ui/index_ui_model.dart +++ /dev/null @@ -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 _checkRuntime() async { - Future 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 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)); - } - } -} diff --git a/lib/ui/party_room/dialogs/party_room_create_dialog_ui.dart b/lib/ui/party_room/dialogs/party_room_create_dialog_ui.dart deleted file mode 100644 index dcba1c8..0000000 --- a/lib/ui/party_room/dialogs/party_room_create_dialog_ui.dart +++ /dev/null @@ -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 { - @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( - 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 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) => - "创建房间"; -} diff --git a/lib/ui/party_room/dialogs/party_room_create_dialog_ui_model.dart b/lib/ui/party_room/dialogs/party_room_create_dialog_ui_model.dart deleted file mode 100644 index 3d66013..0000000 --- a/lib/ui/party_room/dialogs/party_room_create_dialog_ui_model.dart +++ /dev/null @@ -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 roomTypes; - - RoomType? selectedRoomType; - - List 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); - } - }; - } -} diff --git a/lib/ui/party_room/party_room_chat_ui.dart b/lib/ui/party_room/party_room_chat_ui.dart deleted file mode 100644 index 91b7fe9..0000000 --- a/lib/ui/party_room/party_room_chat_ui.dart +++ /dev/null @@ -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 { - @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"; -} diff --git a/lib/ui/party_room/party_room_chat_ui_model.dart b/lib/ui/party_room/party_room_chat_ui_model.dart deleted file mode 100644 index 8d1b9e5..0000000 --- a/lib/ui/party_room/party_room_chat_ui_model.dart +++ /dev/null @@ -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? roomStream; - - Map? 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 usersData) { - playersMap ??= {}; - for (var element in usersData) { - playersMap![element.playerName] = element; - } - notifyListeners(); - } -} diff --git a/lib/ui/party_room/party_room_home_ui.dart b/lib/ui/party_room/party_room_home_ui.dart deleted file mode 100644 index 852fdc7..0000000 --- a/lib/ui/party_room/party_room_home_ui.dart +++ /dev/null @@ -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 { - @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("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 ?? []) 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 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( - 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( - 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( - 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( - 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"; -} diff --git a/lib/ui/party_room/party_room_home_ui_model.dart b/lib/ui/party_room/party_room_home_ui_model.dart deleted file mode 100644 index 9ddc2d4..0000000 --- a/lib/ui/party_room/party_room_home_ui_model.dart +++ /dev/null @@ -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? roomTypes; - - RoomType? selectedRoomType; - - RoomSubtype? selectedRoomSubType; - - final roomStatus = { - RoomStatus.All: "全部", - RoomStatus.Open: "开启中", - RoomStatus.Full: "已满员", - RoomStatus.Closed: "已封闭", - RoomStatus.WillOffline: "房主离线", - RoomStatus.Offline: "已离线", - }; - - RoomStatus selectedStatus = RoomStatus.All; - - final roomSorts = { - RoomSortType.Default: "默认", - RoomSortType.MostPlayerNumber: "最多玩家", - RoomSortType.MinimumPlayerNumber: "最少玩家", - RoomSortType.RecentlyCreated: "最近创建", - RoomSortType.OldestCreated: "最久创建", - }; - - RoomSortType selectedSortType = RoomSortType.Default; - - int pageNum = 0; - - List? 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 _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? getCurRoomSubTypes() { - if (selectedRoomType?.subTypes == null) return null; - Map 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 _touchUser() async { - // if (getCreatedChildUIModel("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("chat", create: true) - ?.setRoom(item); - notifyListeners(); - pageCtrl.animateToPage(1, - duration: const Duration(milliseconds: 100), curve: Curves.easeInExpo); - } - - void checkUIInit() { - if (getCreatedChildUIModel("chat")?.selectRoom != - null) { - pageCtrl.jumpToPage(1); - } - } -} diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart new file mode 100644 index 0000000..2e7cdfd --- /dev/null +++ b/lib/ui/party_room/party_room_ui.dart @@ -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, + ), + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/settings/settings_ui.dart b/lib/ui/settings/settings_ui.dart index 551de95..f101b52 100644 --- a/lib/ui/settings/settings_ui.dart +++ b/lib/ui/settings/settings_ui.dart @@ -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 { +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 { 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 { ), ); } - - @override - String getUITitle(BuildContext context, SettingUIModel model) => "SettingUI"; } diff --git a/lib/ui/settings/settings_ui_model.dart b/lib/ui/settings/settings_ui_model.dart index 66e1935..fe7eb02 100644 --- a/lib/ui/settings/settings_ui_model.dart +++ b/lib/ui/settings/settings_ui_model.dart @@ -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 onResetAutoLogin() async { - final ok = await showConfirmDialogs(context!, "确认重置自动填充?", + Future 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 setGameLaunchECore() async { + Future 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 setLauncherPath() async { + Future 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 setGamePath() async { + Future 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 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 cleanLocationCache() async { + Future 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 addShortCut() async { - if (AppConf.isMSE) { - showToast(context!, "因微软版功能限制,请在接下来打开的窗口中 手动将《SC汉化盒子》拖动到桌面,即可创建快捷方式。"); + Future 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("/", "\\")); } } diff --git a/lib/ui/settings/settings_ui_model.freezed.dart b/lib/ui/settings/settings_ui_model.freezed.dart new file mode 100644 index 0000000..df28db8 --- /dev/null +++ b/lib/ui/settings/settings_ui_model.freezed.dart @@ -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 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 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; +} diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart new file mode 100644 index 0000000..c79a21a --- /dev/null +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -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.internal( + SettingsUIModel.new, + name: r'settingsUIModelProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$settingsUIModelHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SettingsUIModel = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/settings/upgrade_dialog.dart b/lib/ui/settings/upgrade_dialog.dart new file mode 100644 index 0000000..1e4db2d --- /dev/null +++ b/lib/ui/settings/upgrade_dialog.dart @@ -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(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 _getUpdateInfo( + BuildContext context, + String targetVersion, + ValueNotifier description, + ValueNotifier 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 _doUpgrade( + BuildContext context, + AppGlobalState appState, + ValueNotifier isUpgrading, + AppGlobalModel appModel, + ValueNotifier downloadUrl, + ValueNotifier description, + ValueNotifier isUsingDiversion, + ValueNotifier 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\""]); + } + } +} diff --git a/lib/ui/settings/upgrade_dialog_ui.dart b/lib/ui/settings/upgrade_dialog_ui.dart deleted file mode 100644 index 67240ec..0000000 --- a/lib/ui/settings/upgrade_dialog_ui.dart +++ /dev/null @@ -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 { - @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) => ""; -} diff --git a/lib/ui/settings/upgrade_dialog_ui_model.dart b/lib/ui/settings/upgrade_dialog_ui_model.dart deleted file mode 100644 index 2c6b055..0000000 --- a/lib/ui/settings/upgrade_dialog_ui_model.dart +++ /dev/null @@ -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); - } -} diff --git a/lib/ui/splash_ui.dart b/lib/ui/splash_ui.dart index 20dd7b6..b964d53 100644 --- a/lib/ui/splash_ui.dart +++ b/lib/ui/splash_ui.dart @@ -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 { @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 { 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 { ), 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 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"); + } } diff --git a/lib/ui/splash_ui_model.dart b/lib/ui/splash_ui_model.dart deleted file mode 100644 index 5bc6e6f..0000000 --- a/lib/ui/splash_ui_model.dart +++ /dev/null @@ -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 _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); - } -} diff --git a/lib/ui/tools/tools_ui.dart b/lib/ui/tools/tools_ui.dart index c79eab3..397bae6 100644 --- a/lib/ui/tools/tools_ui.dart +++ b/lib/ui/tools/tools_ui.dart @@ -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 { @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 { 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 { ), ), 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 { 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 { ), 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 { children: [ const Spacer(), Button( - onPressed: model.working + onPressed: state.working ? null : item.onTap == null ? null @@ -148,7 +165,7 @@ class ToolsUI extends BaseUI { ) ], ), - if (model.working) + if (state.working) Container( decoration: BoxDecoration( color: Colors.black.withAlpha(150), @@ -168,7 +185,8 @@ class ToolsUI extends BaseUI { ); } - 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 { child: SizedBox( height: 36, child: ComboBox( - 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 { 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 { child: SizedBox( height: 36, child: ComboBox( - 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 { 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"; } diff --git a/lib/ui/tools/tools_ui_model.dart b/lib/ui/tools/tools_ui_model.dart index b13b0e1..780cbd4 100644 --- a/lib/ui/tools/tools_ui_model.dart +++ b/lib/ui/tools/tools_ui_model.dart @@ -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 scInstallPaths = []; - List 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; +} + +@freezed +class ToolsUIState with _$ToolsUIState { + const factory ToolsUIState({ + @Default(false) bool working, + @Default("") String scInstalledPath, + @Default("") String rsiLauncherInstalledPath, + @Default([]) List scInstallPaths, + @Default([]) List rsiLauncherInstallPaths, + @Default([]) List items, + @Default(false) bool isItemLoading, + }) = _ToolsUIState; +} + +@riverpod +class ToolsUIModel extends _$ToolsUIModel { + @override + ToolsUIState build() { + state = const ToolsUIState(); + return state; } - bool get working => _working; - - var items = <_ToolsItemData>[]; - - bool isItemLoading = false; - - @override - Future loadData({bool skipPathScan = false}) async { - if (isItemLoading) return; - items.clear(); - notifyListeners(); + loadToolsCard(BuildContext context, {bool skipPathScan = false}) async { + if (state.isItemLoading) return; + var items = []; + 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> _addLogCard() async { + Future> _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> _addNvmePatchCard() async { + Future> _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 _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 _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 reScanPath() async { - scInstallPaths.clear(); - rsiLauncherInstallPaths.clear(); - scInstalledPath = ""; - rsiLauncherInstalledPath = ""; + Future reScanPath(BuildContext context) async { + var scInstallPaths = []; + var rsiLauncherInstallPaths = []; + 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 _reinstallEAC() async { - if (scInstalledPath.isEmpty) { - showToast(context!, "该功能需要一个有效的游戏安装目录"); + Future _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 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 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 _rsiLogFix() async { - working = true; + Future _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( - 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 _cleanShaderCache() async { - working = true; + Future _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 _downloadP4k() async { - String savePath = scInstalledPath; + Future _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 _checkPhotographyStatus({bool? setMode}) async { + Future _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); + } + + void onChangeGamePath(String v) { + state = state.copyWith(scInstalledPath: v); + } + + void onChangeLauncherPath(String s) { + state = state.copyWith(rsiLauncherInstalledPath: s); } } - -class _ToolsItemData { - String key; - - _ToolsItemData(this.key, this.name, this.infoString, this.icon, {this.onTap}); - - String name; - String infoString; - Widget icon; - AsyncCallback? onTap; -} diff --git a/lib/ui/tools/tools_ui_model.freezed.dart b/lib/ui/tools/tools_ui_model.freezed.dart new file mode 100644 index 0000000..9cff683 --- /dev/null +++ b/lib/ui/tools/tools_ui_model.freezed.dart @@ -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 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 get scInstallPaths => throw _privateConstructorUsedError; + List get rsiLauncherInstallPaths => + throw _privateConstructorUsedError; + List get items => throw _privateConstructorUsedError; + bool get isItemLoading => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ToolsUIStateCopyWith 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 scInstallPaths, + List rsiLauncherInstallPaths, + List 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, + rsiLauncherInstallPaths: null == rsiLauncherInstallPaths + ? _value.rsiLauncherInstallPaths + : rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable + as List, + items: null == items + ? _value.items + : items // ignore: cast_nullable_to_non_nullable + as List, + 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 scInstallPaths, + List rsiLauncherInstallPaths, + List 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, + rsiLauncherInstallPaths: null == rsiLauncherInstallPaths + ? _value._rsiLauncherInstallPaths + : rsiLauncherInstallPaths // ignore: cast_nullable_to_non_nullable + as List, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + 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 scInstallPaths = const [], + final List rsiLauncherInstallPaths = const [], + final List 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 _scInstallPaths; + @override + @JsonKey() + List get scInstallPaths { + if (_scInstallPaths is EqualUnmodifiableListView) return _scInstallPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_scInstallPaths); + } + + final List _rsiLauncherInstallPaths; + @override + @JsonKey() + List get rsiLauncherInstallPaths { + if (_rsiLauncherInstallPaths is EqualUnmodifiableListView) + return _rsiLauncherInstallPaths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_rsiLauncherInstallPaths); + } + + final List _items; + @override + @JsonKey() + List 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 scInstallPaths, + final List rsiLauncherInstallPaths, + final List items, + final bool isItemLoading}) = _$ToolsUIStateImpl; + + @override + bool get working; + @override + String get scInstalledPath; + @override + String get rsiLauncherInstalledPath; + @override + List get scInstallPaths; + @override + List get rsiLauncherInstallPaths; + @override + List get items; + @override + bool get isItemLoading; + @override + @JsonKey(ignore: true) + _$$ToolsUIStateImplCopyWith<_$ToolsUIStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/ui/tools/tools_ui_model.g.dart b/lib/ui/tools/tools_ui_model.g.dart new file mode 100644 index 0000000..04f1e67 --- /dev/null +++ b/lib/ui/tools/tools_ui_model.g.dart @@ -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.internal( + ToolsUIModel.new, + name: r'toolsUIModelProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$toolsUIModelHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ToolsUIModel = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/ui/home/webview/webview.dart b/lib/ui/webview/webview.dart similarity index 92% rename from lib/ui/home/webview/webview.dart rename to lib/ui/webview/webview.dart index 2f2b47d..8406608 100644 --- a/lib/ui/home/webview/webview.dart +++ b/lib/ui/webview/webview.dart @@ -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 _handleMirrorsUrl(String url) async { + Future _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 { diff --git a/lib/widgets/my_page_route.dart b/lib/widgets/my_page_route.dart deleted file mode 100644 index 8966b5c..0000000 --- a/lib/widgets/my_page_route.dart +++ /dev/null @@ -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 animation, - Animation 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, - ), - ); - } -} diff --git a/lib/widgets/cache_image.dart b/lib/widgets/src/cache_image.dart similarity index 100% rename from lib/widgets/cache_image.dart rename to lib/widgets/src/cache_image.dart diff --git a/lib/widgets/countdown_time_text.dart b/lib/widgets/src/countdown_time_text.dart similarity index 100% rename from lib/widgets/countdown_time_text.dart rename to lib/widgets/src/countdown_time_text.dart diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 2376d44..624e5ef 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -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( - children: [ - const SizedBox(height: 4), - if (withKeyboard) - SizedBox( - height: MediaQuery.of(context).viewInsets.bottom, +Widget makeDefaultPage(BuildContext context, + {Widget? titleRow, + List? actions, + Widget? content, + bool automaticallyImplyLeading = true, + String title = "", + bool useBodyContainer = false}) { + return NavigationView( + appBar: NavigationAppBar( + automaticallyImplyLeading: automaticallyImplyLeading, + title: DragToMoveArea( + child: titleRow ?? + Column( + children: [ + 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; -} - -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); + @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, + ), + ); + } } List makeMarkdownView(String description, {String? attachmentsUrl}) { @@ -103,10 +130,79 @@ List makeMarkdownView(String description, {String? attachmentsUrl}) { ])); } -class NoScrollBehavior extends ScrollBehavior { +ColorFilter makeSvgColor(Color color) { + return ui.ColorFilter.mode(color, ui.BlendMode.srcIn); +} + +CustomTransitionPage myPageBuilder( + BuildContext context, GoRouterState state, Widget child) { + return CustomTransitionPage( + child: child, + transitionDuration: const Duration(milliseconds: 150), + reverseTransitionDuration: const Duration(milliseconds: 150), + transitionsBuilder: (BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + 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 extends HookConsumerWidget { + final T? data; + final Future 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(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 dataState, ValueNotifier 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(); + }); +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 5959dbc..8d494f2 100644 --- a/pubspec.yaml +++ b/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 diff --git a/rust/src/http_package/mod.rs b/rust/src/http_package/mod.rs index dd985e6..3cf961f 100644 --- a/rust/src/http_package/mod.rs +++ b/rust/src/http_package/mod.rs @@ -94,7 +94,7 @@ fn _reade_resp_header(r_header: &HeaderMap) -> HashMap { 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