diff --git a/lib/app.dart b/lib/app.dart index 2764b9d..9a299e9 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -24,6 +24,7 @@ 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'; @@ -46,6 +47,10 @@ GoRouter router(RouterRef ref) { 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) => diff --git a/lib/app.g.dart b/lib/app.g.dart index 6abdbbe..cdc57ee 100644 --- a/lib/app.g.dart +++ b/lib/app.g.dart @@ -6,7 +6,7 @@ part of 'app.dart'; // RiverpodGenerator // ************************************************************************** -String _$routerHash() => r'1277f4e4a70e811181503413f6dda3fb7e461f9d'; +String _$routerHash() => r'e7b1e3a9fd74b4f00e3d71017615d7fb82bd649d'; /// See also [router]. @ProviderFor(router) diff --git a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart index edfe390..c49fdac 100644 --- a/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart +++ b/lib/ui/home/dialogs/home_game_login_dialog_ui_model.dart @@ -144,6 +144,7 @@ class HomeGameLoginUIModel extends _$HomeGameLoginUIModel { } } + if (!context.mounted) return; _readyForLaunch(homeState, context); }, useLocalization: true, homeState: homeState); } 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 index 0529769..048de58 100644 --- 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 @@ -7,7 +7,7 @@ part of 'home_game_login_dialog_ui_model.dart'; // ************************************************************************** String _$homeGameLoginUIModelHash() => - r'3747a303c86553319c515ab29933abda935edb16'; + r'c26dfd89985ff9246104135c288b673b7f15acf0'; /// See also [HomeGameLoginUIModel]. @ProviderFor(HomeGameLoginUIModel) diff --git a/lib/ui/home/downloader/home_downloader_ui.dart b/lib/ui/home/downloader/home_downloader_ui.dart new file mode 100644 index 0000000..54c8740 --- /dev/null +++ b/lib/ui/home/downloader/home_downloader_ui.dart @@ -0,0 +1,246 @@ +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 'home_downloader_ui_model.dart'; + +class HomeDownloaderUI extends HookConsumerWidget { + const HomeDownloaderUI({super.key}); + + @override + 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: [ + const SizedBox(height: 12), + Row( + children: [ + const Spacer(), + const SizedBox(width: 24), + const SizedBox(width: 12), + for (final item in , String>{ + const MapEntry("settings", FluentIcons.settings): "限速设置", + if (state.tasks.isNotEmpty) + const MapEntry("pause_all", FluentIcons.pause): "全部暂停", + if (state.waitingTasks.isNotEmpty) + const MapEntry("resume_all", FluentIcons.download): "恢复全部", + if (state.tasks.isNotEmpty || state.waitingTasks.isNotEmpty) + const MapEntry("cancel_all", FluentIcons.cancel): "全部取消", + }.entries) + Padding( + padding: const EdgeInsets.only(left: 6, right: 6), + child: Button( + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + children: [ + Icon(item.key.value), + const SizedBox(width: 6), + Text(item.value), + ], + ), + ), + onPressed: () => + model.onTapButton(context, item.key.key)), + ), + const SizedBox(width: 12), + ], + ), + if (model.getTasksLen() == 0) + const Expanded( + child: Center( + child: Text("无下载任务"), + )) + else + Expanded( + child: ListView.builder( + itemBuilder: (BuildContext context, int index) { + final (task, type, isFirstType) = model.getTaskAndType(index); + final nt = HomeDownloaderUIModel.getTaskTypeAndName(task); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isFirstType) + Column( + children: [ + Container( + padding: EdgeInsets.only( + left: 24, + right: 24, + top: index == 0 ? 0 : 12, + bottom: 12), + margin: const EdgeInsets.only(top: 6, bottom: 6), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Text( + "${model.listHeaderStatusMap[type]}", + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold), + ), + ], + )), + ], + ), + ), + ], + ), + Container( + padding: const EdgeInsets.only( + left: 12, right: 12, top: 12, bottom: 12), + margin: const EdgeInsets.only( + left: 12, right: 12, top: 6, bottom: 6), + decoration: BoxDecoration( + color: FluentTheme.of(context) + .cardColor + .withOpacity(.06), + borderRadius: BorderRadius.circular(7), + ), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nt.value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 6), + Row( + children: [ + Text( + "总大小:${FileSize.getSize(task.totalLength ?? 0)}", + style: const TextStyle(fontSize: 14), + ), + const SizedBox(width: 12), + if (nt.key == "torrent" && + task.verifiedLength != null && + task.verifiedLength != 0) + Text( + "校验中...(${FileSize.getSize(task.verifiedLength)})", + style: const TextStyle(fontSize: 14), + ) + else if (task.status == "active") + Text( + "下载中... (${((task.completedLength ?? 0) * 100 / (task.totalLength ?? 1)).toStringAsFixed(4)}%)") + else + Text( + "状态:${model.statusMap[task.status]}", + style: const TextStyle(fontSize: 14), + ), + const SizedBox(width: 24), + if (task.status == "active" && + task.verifiedLength == null) + Text( + "ETA: ${model.formatter.format(DateTime.now().add(Duration(seconds: model.getETA(task))))}"), + ], + ), + ], + ), + const Spacer(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "已上传:${FileSize.getSize(task.uploadLength)}"), + Text( + "已下载:${FileSize.getSize(task.completedLength)}"), + ], + ), + const SizedBox(width: 18), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "↑:${FileSize.getSize(task.uploadSpeed)}/s"), + Text( + "↓:${FileSize.getSize(task.downloadSpeed)}/s"), + ], + ), + const SizedBox(width: 32), + if (type != "stopped") + DropDownButton( + closeAfterClick: false, + title: const Padding( + padding: EdgeInsets.all(3), + child: Text('选项'), + ), + items: [ + if (task.status == "paused") + MenuFlyoutItem( + leading: + const Icon(FluentIcons.download), + text: const Text('继续下载'), + onPressed: () => + model.resumeTask(task.gid)) + else if (task.status == "active") + MenuFlyoutItem( + leading: const Icon(FluentIcons.pause), + text: const Text('暂停下载'), + onPressed: () => + model.pauseTask(task.gid)), + const MenuFlyoutSeparator(), + MenuFlyoutItem( + leading: const Icon( + FluentIcons.chrome_close, + size: 14, + ), + text: const Text('取消下载'), + onPressed: () => + model.cancelTask(context, task.gid)), + MenuFlyoutItem( + leading: const Icon( + FluentIcons.folder_open, + size: 14, + ), + text: const Text('打开文件夹'), + onPressed: () => model.openFolder(task)), + ], + ), + const SizedBox(width: 12), + ], + ), + ), + ], + ); + }, + itemCount: model.getTasksLen(), + )), + Container( + color: FluentTheme.of(context).cardColor.withOpacity(.06), + child: Padding( + padding: const EdgeInsets.only(left: 12, bottom: 3, top: 3), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: state.isAvailable ? Colors.green : Colors.white, + borderRadius: BorderRadius.circular(1000), + ), + ), + const SizedBox(width: 12), + Text( + "下载: ${FileSize.getSize(state.globalStat?.downloadSpeed ?? 0)}/s 上传:${FileSize.getSize(state.globalStat?.uploadSpeed ?? 0)}/s", + style: const TextStyle(fontSize: 12), + ) + ], + ), + ), + ), + ], + ), + useBodyContainer: true); + } +} diff --git a/lib/ui/home/downloader/home_downloader_ui_model.dart b/lib/ui/home/downloader/home_downloader_ui_model.dart new file mode 100644 index 0000000..a4aa3d6 --- /dev/null +++ b/lib/ui/home/downloader/home_downloader_ui_model.dart @@ -0,0 +1,306 @@ +// 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:intl/intl.dart'; +import 'package:riverpod_annotation/riverpod_annotation.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/provider/aria2c.dart'; + +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": "下载中...", + "waiting": "等待中", + "paused": "已暂停", + "error": "下载失败", + "complete": "下载完成", + "removed": "已删除", + }; + + final listHeaderStatusMap = { + "active": "下载中", + "waiting": "等待中", + "stopped": "已结束", + }; + + @override + HomeDownloaderUIState build() { + state = const HomeDownloaderUIState(); + _listenDownloader(); + ref.onDispose(() { + _disposed = true; + }); + return state; + } + + onTapButton(BuildContext context, String key) async { + final aria2cState = ref.read(aria2cModelProvider); + switch (key) { + case "pause_all": + if (!aria2cState.isRunning) return; + await aria2cState.aria2c?.pauseAll(); + await aria2cState.aria2c?.saveSession(); + return; + case "resume_all": + if (!aria2cState.isRunning) return; + await aria2cState.aria2c?.unpauseAll(); + await aria2cState.aria2c?.saveSession(); + return; + case "cancel_all": + final userOK = await showConfirmDialogs( + context, "确认取消全部任务?", const Text("如果文件不再需要,你可能需要手动删除下载文件。")); + if (userOK == true) { + if (!aria2cState.isRunning) return; + try { + for (var value in [...state.tasks, ...state.waitingTasks]) { + await aria2cState.aria2c?.remove(value.gid!); + } + await aria2cState.aria2c?.saveSession(); + } catch (e) { + dPrint("DownloadsUIModel cancel_all Error: $e"); + } + } + return; + case "settings": + _showDownloadSpeedSettings(context); + return; + } + } + + int getTasksLen() { + return state.tasks.length + + state.waitingTasks.length + + state.stoppedTasks.length; + } + + (Aria2Task, String, bool) getTaskAndType(int index) { + final tempList = [ + ...state.tasks, + ...state.waitingTasks, + ...state.stoppedTasks + ]; + if (index >= 0 && index < state.tasks.length) { + return (tempList[index], "active", index == 0); + } + if (index >= state.tasks.length && + index < state.tasks.length + state.waitingTasks.length) { + return (tempList[index], "waiting", index == state.tasks.length); + } + if (index >= state.tasks.length + state.waitingTasks.length && + index < tempList.length) { + return ( + tempList[index], + "stopped", + index == state.tasks.length + state.waitingTasks.length + ); + } + throw Exception("Index out of range or element is null"); + } + + static MapEntry getTaskTypeAndName(Aria2Task task) { + if (task.bittorrent == null) { + String uri = task.files?[0]['uris'][0]['uri'] as String; + return MapEntry("url", uri.split('/').last); + } else if (task.bittorrent != null) { + if (task.bittorrent!.containsKey('info')) { + var btName = task.bittorrent?["info"]["name"]; + return MapEntry("torrent", btName ?? 'torrent'); + } else { + return MapEntry("magnet", '[METADATA]${task.infoHash}'); + } + } else { + return const MapEntry("metaLink", '==========metaLink============'); + } + } + + 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) { + for (var element in task.files!) { + final f = Aria2File.fromJson(element); + l.add(f); + } + } + return l; + } + + openFolder(Aria2Task task) { + final f = getFilesFormTask(task).firstOrNull; + if (f != null) { + SystemHelper.openDir(File(f.path!).absolute.path.replaceAll("/", "\\")); + } + } + + _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( + text: box.get("downloader_up_limit", defaultValue: "")); + final downCtrl = TextEditingController( + text: box.get("downloader_down_limit", defaultValue: "")); + + final ifr = FilteringTextInputFormatter.allow(RegExp(r'^\d*[km]?$')); + + if (!context.mounted) return; + final ok = await showConfirmDialogs( + context, + "限速设置", + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "SC 汉化盒子使用 p2p 网络来加速文件下载,如果您流量有限,可在此处将上传带宽设置为 1(byte)。", + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(.6), + ), + ), + const SizedBox(height: 24), + const Text("请输入下载单位,如:1、100k、10m, 0或留空为不限速。"), + const SizedBox(height: 12), + const Text("上传限速:"), + const SizedBox(height: 6), + TextFormBox( + placeholder: "1、100k、10m、0", + controller: upCtrl, + placeholderStyle: TextStyle(color: Colors.white.withOpacity(.6)), + inputFormatters: [ifr], + ), + const SizedBox(height: 12), + const Text("下载限速:"), + const SizedBox(height: 6), + TextFormBox( + placeholder: "1、100k、10m、0", + controller: downCtrl, + placeholderStyle: TextStyle(color: Colors.white.withOpacity(.6)), + inputFormatters: [ifr], + ), + const SizedBox(height: 24), + Text( + "* P2P 上传仅在下载文件时进行,下载完成后会关闭 p2p 连接。如您想参与做种,请通过关于页面联系我们。", + style: TextStyle( + fontSize: 13, + color: Colors.white.withOpacity(.6), + ), + ) + ], + )); + if (ok == true) { + 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()); + } + } + } +} 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_model.dart b/lib/ui/home/game_doctor/game_doctor_ui_model.dart index 3f5fe21..8f186f7 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.dart @@ -200,7 +200,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { } } - final cnExp = RegExp(r"[^\x00-\xff]"); + final _cnExp = RegExp(r"[^\x00-\xff]"); // ignore: avoid_build_context_in_providers Future _checkPreInstall(BuildContext context, String scInstalledPath, @@ -217,7 +217,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { await showToast(context, lastScreenInfo); } - if (cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) { + if (_cnExp.hasMatch(await SCLoggerHelper.getLogFilePath() ?? "")) { checkResult.add(const MapEntry("cn_user_name", "")); } @@ -235,7 +235,7 @@ class HomeGameDoctorUIModel extends _$HomeGameDoctorUIModel { final checkedPath = []; for (var installPath in listData) { if (!checkedPath.contains(installPath)) { - if (cnExp.hasMatch(installPath)) { + if (_cnExp.hasMatch(installPath)) { checkResult.add(MapEntry("cn_install_path", installPath)); } if (scInstalledPath == "not_install") { 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 index 7bec1d3..61883ab 100644 --- a/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart +++ b/lib/ui/home/game_doctor/game_doctor_ui_model.g.dart @@ -7,7 +7,7 @@ part of 'game_doctor_ui_model.dart'; // ************************************************************************** String _$homeGameDoctorUIModelHash() => - r'59cbd6f866bacbc24eb0c0eb0ad88fe7f2dac4a7'; + r'1e32d75095de065cf2cdedf444f74ffc753ce66f'; /// See also [HomeGameDoctorUIModel]. @ProviderFor(HomeGameDoctorUIModel) diff --git a/lib/ui/index_ui.dart b/lib/ui/index_ui.dart index 803bff7..81d9df3 100644 --- a/lib/ui/index_ui.dart +++ b/lib/ui/index_ui.dart @@ -1,5 +1,6 @@ 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'; @@ -57,7 +58,7 @@ class IndexUI extends HookConsumerWidget { _makeAria2TaskNumWidget() ], ), - onPressed: () {}, + onPressed: () => _goDownloader(context), // onPressed: model.goDownloader ), const SizedBox(width: 24), @@ -156,4 +157,8 @@ class IndexUI extends HookConsumerWidget { }, ); } + + _goDownloader(BuildContext context) { + context.push('/index/downloader'); + } }