diff --git a/common/lib/src/backend/auth_backend_helper.dart b/common/lib/src/backend/auth_backend_helper.dart index f6e5015..b3d8ed6 100644 --- a/common/lib/src/backend/auth_backend_helper.dart +++ b/common/lib/src/backend/auth_backend_helper.dart @@ -17,7 +17,7 @@ String? _lastPort; typedef BackendErrorHandler = void Function(String); -Stream startAuthBackend({ +Stream startAuthBackend({ required AuthBackendType type, required String host, required String port, @@ -30,80 +30,80 @@ Stream startAuthBackend({ host = host.trim(); port = port.trim(); if(type != AuthBackendType.local || port != kDefaultBackendPort.toString()) { - yield AuthBackendResult(AuthBackendResultType.starting); + yield AuthBackendEvent(AuthBackendEventType.starting); } if (host.isEmpty) { - yield AuthBackendResult(AuthBackendResultType.startMissingHostError); + yield AuthBackendEvent(AuthBackendEventType.startMissingHostError); return; } if (port.isEmpty) { - yield AuthBackendResult(AuthBackendResultType.startMissingPortError); + yield AuthBackendEvent(AuthBackendEventType.startMissingPortError); return; } final portNumber = int.tryParse(port); if (portNumber == null) { - yield AuthBackendResult(AuthBackendResultType.startIllegalPortError); + yield AuthBackendEvent(AuthBackendEventType.startIllegalPortError); return; } if ((type != AuthBackendType.local || port != kDefaultBackendPort.toString()) && !(await isAuthBackendPortFree())) { - yield AuthBackendResult(AuthBackendResultType.startFreeingPort); + yield AuthBackendEvent(AuthBackendEventType.startFreeingPort); final result = await freeAuthBackendPort(); if(!result) { - yield AuthBackendResult(AuthBackendResultType.startFreePortError); + yield AuthBackendEvent(AuthBackendEventType.startFreePortError); return; } - yield AuthBackendResult(AuthBackendResultType.startFreePortSuccess); + yield AuthBackendEvent(AuthBackendEventType.startFreePortSuccess); } switch(type){ case AuthBackendType.embedded: process = await _startEmbedded(detached, onError: onError); - yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(process: process)); + yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(process: process)); break; case AuthBackendType.remote: - yield AuthBackendResult(AuthBackendResultType.startPingingRemote); + yield AuthBackendEvent(AuthBackendEventType.startPingingRemote); final uriResult = await _ping(host, portNumber); if(uriResult == null) { - yield AuthBackendResult(AuthBackendResultType.startPingError); + yield AuthBackendEvent(AuthBackendEventType.startPingError); return; } server = await _startRemote(uriResult); - yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server)); + yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(server: server)); break; case AuthBackendType.local: if(portNumber != kDefaultBackendPort) { - yield AuthBackendResult(AuthBackendResultType.startPingingLocal); + yield AuthBackendEvent(AuthBackendEventType.startPingingLocal); final uriResult = await _ping(kDefaultBackendHost, portNumber); if(uriResult == null) { - yield AuthBackendResult(AuthBackendResultType.startPingError); + yield AuthBackendEvent(AuthBackendEventType.startPingError); return; } server = await _startRemote(Uri.parse("http://$kDefaultBackendHost:$port")); - yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server)); + yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(server: server)); } break; } - yield AuthBackendResult(AuthBackendResultType.startPingingLocal); + yield AuthBackendEvent(AuthBackendEventType.startPingingLocal); final uriResult = await _ping(kDefaultBackendHost, kDefaultBackendPort); if(uriResult == null) { - yield AuthBackendResult(AuthBackendResultType.startPingError); + yield AuthBackendEvent(AuthBackendEventType.startPingError); process?.kill(ProcessSignal.sigterm); server?.close(force: true); return; } - yield AuthBackendResult(AuthBackendResultType.startSuccess); + yield AuthBackendEvent(AuthBackendEventType.startSuccess); }catch(error, stackTrace) { - yield AuthBackendResult( - AuthBackendResultType.startError, + yield AuthBackendEvent( + AuthBackendEventType.startError, error: error, stackTrace: stackTrace ); @@ -141,8 +141,8 @@ Future _startEmbedded(bool detached, {BackendErrorHandler? onError}) as Future _startRemote(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort); -Stream stopAuthBackend({required AuthBackendType type, required AuthBackendImplementation? implementation}) async* { - yield AuthBackendResult(AuthBackendResultType.stopping); +Stream stopAuthBackend({required AuthBackendType type, required AuthBackendImplementation? implementation}) async* { + yield AuthBackendEvent(AuthBackendEventType.stopping); try{ switch(type){ case AuthBackendType.embedded: @@ -158,10 +158,10 @@ Stream stopAuthBackend({required AuthBackendType type, requir await implementation?.server?.close(force: true); break; } - yield AuthBackendResult(AuthBackendResultType.stopSuccess); + yield AuthBackendEvent(AuthBackendEventType.stopSuccess); }catch(error, stackTrace){ - yield AuthBackendResult( - AuthBackendResultType.stopError, + yield AuthBackendEvent( + AuthBackendEventType.stopError, error: error, stackTrace: stackTrace ); diff --git a/common/lib/src/backend/auth_backend_result.dart b/common/lib/src/backend/auth_backend_result.dart index 57ae0be..ecb6943 100644 --- a/common/lib/src/backend/auth_backend_result.dart +++ b/common/lib/src/backend/auth_backend_result.dart @@ -1,12 +1,12 @@ import 'dart:io'; -class AuthBackendResult { - final AuthBackendResultType type; +class AuthBackendEvent { + final AuthBackendEventType type; final AuthBackendImplementation? implementation; final Object? error; final StackTrace? stackTrace; - AuthBackendResult(this.type, {this.implementation, this.error, this.stackTrace}); + AuthBackendEvent(this.type, {this.implementation, this.error, this.stackTrace}); @override String toString() { @@ -14,14 +14,7 @@ class AuthBackendResult { } } -class AuthBackendImplementation { - final Process? process; - final HttpServer? server; - - AuthBackendImplementation({this.process, this.server}); -} - -enum AuthBackendResultType { +enum AuthBackendEventType { starting, startMissingHostError, startMissingPortError, @@ -43,5 +36,12 @@ enum AuthBackendResultType { bool get isError => name.endsWith("Error"); - bool get isSuccess => this == AuthBackendResultType.startSuccess || this == AuthBackendResultType.stopSuccess; + bool get isSuccess => this == AuthBackendEventType.startSuccess || this == AuthBackendEventType.stopSuccess; +} + +class AuthBackendImplementation { + final Process? process; + final HttpServer? server; + + AuthBackendImplementation({this.process, this.server}); } \ No newline at end of file diff --git a/gui/lib/main.dart b/gui/lib/main.dart index 526ee01..42f73ad 100644 --- a/gui/lib/main.dart +++ b/gui/lib/main.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart'; @@ -16,7 +15,7 @@ import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/server_browser_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; import 'package:reboot_launcher/src/message/error.dart'; -import 'package:reboot_launcher/src/pager/pager.dart'; +import 'package:reboot_launcher/src/widget/page/pager.dart'; import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/url_protocol.dart'; import 'package:system_theme/system_theme.dart'; diff --git a/gui/lib/src/controller/backend_controller.dart b/gui/lib/src/controller/backend_controller.dart index a2614bd..3433905 100644 --- a/gui/lib/src/controller/backend_controller.dart +++ b/gui/lib/src/controller/backend_controller.dart @@ -8,9 +8,9 @@ import 'package:get_storage/get_storage.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/src/util/keyboard.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; -typedef BackendInteractiveEventHandler = InfoBarEntry? Function(AuthBackendType, AuthBackendResult); +typedef BackendInteractiveEventHandler = SnackBar? Function(AuthBackendType, AuthBackendEvent); class BackendController extends GetxController { static const String storageName = "v3_backend_storage"; @@ -27,7 +27,7 @@ class BackendController extends GetxController { late final RxBool detached; AuthBackendImplementation? implementation; StreamSubscription? _worker; - InfoBarEntry? _interactiveEntry; + SnackBar? _interactiveEntry; BackendController() { _storage = appWithNoStorage ? null : GetStorage(storageName); diff --git a/gui/lib/src/controller/dll_controller.dart b/gui/lib/src/controller/dll_controller.dart index 5b71309..39b061a 100644 --- a/gui/lib/src/controller/dll_controller.dart +++ b/gui/lib/src/controller/dll_controller.dart @@ -7,8 +7,8 @@ import 'package:get_storage/get_storage.dart'; import 'package:path/path.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/main.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/page/settings_page.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/page/settings/page.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:version/version.dart'; import 'package:path/path.dart' as path; @@ -75,7 +75,7 @@ class DllController extends GetxController { } Future updateGameServerDll({bool force = false, bool silent = false}) async { - InfoBarEntry? infoBarEntry; + SnackBar? SnackBar; try { if(customGameServer.value) { status.value = UpdateStatus.success; @@ -94,7 +94,7 @@ class DllController extends GetxController { } if(!silent) { - infoBarEntry = showRebootInfoBar( + SnackBar = SnackBar.open( translations.downloadingDll("reboot"), loading: true, duration: null @@ -111,19 +111,19 @@ class DllController extends GetxController { ).then((values) => values.reduce((first, second) => first && second)); if(!result) { status.value = UpdateStatus.error; - showRebootInfoBar( + SnackBar.open( translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, "reboot"), duration: infoBarLongDuration, severity: InfoBarSeverity.error ); - infoBarEntry?.close(); + SnackBar?.close(); return false; } timestamp.value = DateTime.now().millisecondsSinceEpoch; status.value = UpdateStatus.success; - infoBarEntry?.close(); + SnackBar?.close(); if(!silent) { - infoBarEntry = showRebootInfoBar( + SnackBar = SnackBar.open( translations.downloadDllSuccess("reboot"), severity: InfoBarSeverity.success, duration: infoBarShortDuration @@ -132,20 +132,20 @@ class DllController extends GetxController { _listenToFileEvents(GameDll.gameServer); return true; }catch(message) { - infoBarEntry?.close(); + SnackBar?.close(); var error = message.toString(); error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error; error = error.toLowerCase(); status.value = UpdateStatus.error; final completer = Completer(); - infoBarEntry = showRebootInfoBar( + SnackBar = SnackBar.open( translations.downloadDllError(error.toString(), "reboot.dll"), duration: infoBarLongDuration, severity: InfoBarSeverity.error, onDismissed: () => completer.complete(false), action: Button( onPressed: () async { - infoBarEntry?.close(); + SnackBar?.close(); final result = updateGameServerDll( force: true, silent: silent @@ -216,7 +216,7 @@ class DllController extends GetxController { Future download(GameDll dll, String filePath, {bool silent = false, bool force = false}) async { log("[DLL] Asking for $dll at $filePath(silent: $silent, force: $force)"); - InfoBarEntry? entry; + SnackBar? entry; try { if (dll == GameDll.gameServer) { return await updateGameServerDll(silent: silent); @@ -232,7 +232,7 @@ class DllController extends GetxController { final fileNameWithoutExtension = basenameWithoutExtension(filePath); if(!silent) { log("[DLL] Showing dialog while downloading $dll..."); - entry = showRebootInfoBar( + entry = SnackBar.open( translations.downloadingDll(fileNameWithoutExtension), loading: true, duration: null @@ -243,7 +243,7 @@ class DllController extends GetxController { final result = await downloadDependency(dll, filePath); if(!result) { entry?.close(); - showRebootInfoBar( + SnackBar.open( translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, dll.name), duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -254,7 +254,7 @@ class DllController extends GetxController { entry?.close(); if(!silent) { log("[DLL] Showing success dialog for $dll"); - entry = await showRebootInfoBar( + entry = await SnackBar.open( translations.downloadDllSuccess(fileNameWithoutExtension), severity: InfoBarSeverity.success, duration: infoBarShortDuration @@ -271,7 +271,7 @@ class DllController extends GetxController { error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error; error = error.toLowerCase(); final completer = Completer(); - await showRebootInfoBar( + await SnackBar.open( translations.downloadDllError(error.toString(), dll.name), duration: infoBarLongDuration, severity: InfoBarSeverity.error, @@ -317,7 +317,7 @@ class DllController extends GetxController { .instance .value ?.kill(); - showRebootInfoBar( + SnackBar.open( translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, injectable.name), duration: infoBarLongDuration, severity: InfoBarSeverity.error diff --git a/gui/lib/src/controller/settings_controller.dart b/gui/lib/src/controller/settings_controller.dart index 4a33880..a59a8cc 100644 --- a/gui/lib/src/controller/settings_controller.dart +++ b/gui/lib/src/controller/settings_controller.dart @@ -7,7 +7,7 @@ import 'package:http/http.dart' as http; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:version/version.dart'; import 'package:yaml/yaml.dart'; diff --git a/gui/lib/src/message/data.dart b/gui/lib/src/message/data.dart index 12d9efa..238711d 100644 --- a/gui/lib/src/message/data.dart +++ b/gui/lib/src/message/data.dart @@ -1,6 +1,6 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; Future showResetDialog(Function() onConfirm) => showRebootDialog( builder: (context) => InfoDialog( diff --git a/gui/lib/src/message/dll.dart b/gui/lib/src/message/dll.dart index c91a688..c45c187 100644 --- a/gui/lib/src/message/dll.dart +++ b/gui/lib/src/message/dll.dart @@ -1,6 +1,6 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; Future showDllDeletedDialog() => showRebootDialog( builder: (context) => InfoDialog( diff --git a/gui/lib/src/message/download_version.dart b/gui/lib/src/message/download_version.dart index 561ae5e..5b89051 100644 --- a/gui/lib/src/message/download_version.dart +++ b/gui/lib/src/message/download_version.dart @@ -9,8 +9,8 @@ import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/util/extensions.dart'; import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/button/file_selector.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; +import 'package:reboot_launcher/src/widget/file_selector.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:windows_taskbar/windows_taskbar.dart'; diff --git a/gui/lib/src/message/error.dart b/gui/lib/src/message/error.dart index 9f286e0..2f680d2 100644 --- a/gui/lib/src/message/error.dart +++ b/gui/lib/src/message/error.dart @@ -1,8 +1,8 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; String? lastError; diff --git a/gui/lib/src/message/import_version.dart b/gui/lib/src/message/import_version.dart index 82d0e5b..4f37dad 100644 --- a/gui/lib/src/message/import_version.dart +++ b/gui/lib/src/message/import_version.dart @@ -6,8 +6,8 @@ import 'package:path/path.dart' as path; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/button/file_selector.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; +import 'package:reboot_launcher/src/widget/file_selector.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; import 'package:version/version.dart'; class ImportVersionDialog extends StatefulWidget { diff --git a/gui/lib/src/message/onboard.dart b/gui/lib/src/message/onboard.dart index 789725d..f467092 100644 --- a/gui/lib/src/message/onboard.dart +++ b/gui/lib/src/message/onboard.dart @@ -5,16 +5,16 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/message/profile.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/page/backend_page.dart'; -import 'package:reboot_launcher/src/pager/pager.dart'; -import 'package:reboot_launcher/src/page/host_page.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; -import 'package:reboot_launcher/src/page/play_page.dart'; -import 'package:reboot_launcher/src/button/version_selector.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/section/backend/page.dart'; +import 'package:reboot_launcher/src/widget/page/pager.dart'; +import 'package:reboot_launcher/src/widget/page/host/page.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; +import 'package:reboot_launcher/src/widget/section/play/page.dart'; +import 'package:reboot_launcher/src/widget/version_selector.dart'; void startOnboarding() { final gameController = Get.find(); diff --git a/gui/lib/src/message/profile.dart b/gui/lib/src/message/profile.dart index f883a48..a86a032 100644 --- a/gui/lib/src/message/profile.dart +++ b/gui/lib/src/message/profile.dart @@ -3,7 +3,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/material.dart' show Icons; import 'package:get/get.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; Future showProfileForm(BuildContext context, TextEditingController username, TextEditingController password) async{ final showPassword = RxBool(false); diff --git a/gui/lib/src/messenger/dialog.dart b/gui/lib/src/messenger/dialog.dart deleted file mode 100644 index 4fa9f07..0000000 --- a/gui/lib/src/messenger/dialog.dart +++ /dev/null @@ -1,325 +0,0 @@ -import 'package:clipboard/clipboard.dart'; -import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog; -import 'package:fluent_ui/fluent_ui.dart'; -import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; - -import 'info_bar.dart'; - -bool inDialog = false; - -Future showRebootDialog({required WidgetBuilder builder, bool dismissWithEsc = true}) async { - inDialog = true; - pagesController.add(null); - try { - return await fluent.showDialog( - context: appNavigatorKey.currentContext!, - useRootNavigator: false, - dismissWithEsc: dismissWithEsc, - builder: builder - ); - }finally { - inDialog = false; - } -} - -abstract class AbstractDialog extends StatelessWidget { - const AbstractDialog({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context); -} - -class GenericDialog extends AbstractDialog { - final Widget header; - final List buttons; - final EdgeInsets? padding; - - const GenericDialog({Key? key, required this.header, required this.buttons, this.padding}) : super(key: key); - - @override - Widget build(BuildContext context) => ContentDialog( - style: ContentDialogThemeData( - padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0) - ), - content: header, - actions: buttons - ); -} - -class FormDialog extends AbstractDialog { - final Widget content; - final List buttons; - - const FormDialog({Key? key, required this.content, required this.buttons}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Form( - child: Builder( - builder: (context) { - final parsed = buttons.map((entry) => _createFormButton(entry, context)).toList(); - return GenericDialog( - header: content, - buttons: parsed - ); - } - ) - ); - } - - DialogButton _createFormButton(DialogButton entry, BuildContext context) { - if (entry.type != ButtonType.primary) { - return entry; - } - - return DialogButton( - text: entry.text, - type: entry.type, - onTap: () { - if(!Form.of(context).validate()) { - return; - } - - entry.onTap?.call(); - } - ); - } -} - -class InfoDialog extends AbstractDialog { - final String text; - final List? buttons; - - const InfoDialog({required this.text, this.buttons, Key? key}) : super(key: key); - - InfoDialog.ofOnly({required this.text, required DialogButton button, Key? key}) - : buttons = [button], super(key: key); - - @override - Widget build(BuildContext context) { - return GenericDialog( - header: SizedBox( - width: double.infinity, - child: Text(text, textAlign: TextAlign.center) - ), - buttons: buttons ?? [_defaultCloseButton], - padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0) - ); - } - - DialogButton get _defaultCloseButton =>DialogButton( - text: translations.defaultDialogSecondaryAction, - type: ButtonType.only - ); -} - -class ProgressDialog extends AbstractDialog { - final String text; - final Function()? onStop; - final bool showButton; - - const ProgressDialog({required this.text, this.onStop, this.showButton = true, Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return GenericDialog( - header: InfoLabel( - label: text, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 16.0), - width: double.infinity, - child: const ProgressBar() - ), - ), - buttons: [ - if(showButton) - DialogButton( - text: translations.defaultDialogSecondaryAction, - type: ButtonType.only, - onTap: onStop - ) - ] - ); - } -} - -class FutureBuilderDialog extends AbstractDialog { - final Future future; - final String loadingMessage; - final Widget successfulBody; - final Widget unsuccessfulBody; - final Function(Object) errorMessageBuilder; - final Function()? onError; - final bool closeAutomatically; - - const FutureBuilderDialog( - {Key? key, - required this.future, - required this.loadingMessage, - required this.successfulBody, - required this.unsuccessfulBody, - required this.errorMessageBuilder, - this.onError, - this.closeAutomatically = false}) : super(key: key); - - static Container ofMessage(String message) { - return Container( - width: double.infinity, - padding: const EdgeInsets.only(bottom: 16.0), - child: Text( - message, - textAlign: TextAlign.center - ) - ); - } - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: future, - builder: (context, snapshot) => GenericDialog( - header: _createBody(context, snapshot), - buttons: [_createButton(context, snapshot)] - ) - ); - } - - Widget _createBody(BuildContext context, AsyncSnapshot snapshot){ - if (snapshot.hasError) { - onError?.call(); - return ofMessage(errorMessageBuilder(snapshot.error!)); - } - - if(snapshot.connectionState == ConnectionState.done && (snapshot.data == null || (snapshot.data is bool && !snapshot.data))){ - return unsuccessfulBody; - } - - if (!snapshot.hasData) { - return _createLoadingBody(); - } - - if(closeAutomatically){ - WidgetsBinding.instance - .addPostFrameCallback((_) => Navigator.of(context).pop(true)); - return _createLoadingBody(); - } - - return successfulBody; - } - - InfoLabel _createLoadingBody() { - return InfoLabel( - label: loadingMessage, - child: Container( - padding: const EdgeInsets.only(bottom: 16.0), - width: double.infinity, - child: const ProgressBar()), - ); - } - - DialogButton _createButton(BuildContext context, AsyncSnapshot snapshot){ - return DialogButton( - text: snapshot.hasData - || snapshot.hasError - || (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? translations.defaultDialogSecondaryAction : translations.stopLoadingDialogAction, - type: ButtonType.only, - onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData) - ); - } -} - -class ErrorDialog extends AbstractDialog { - final Object exception; - final StackTrace? stackTrace; - final Function(Object) errorMessageBuilder; - - const ErrorDialog({Key? key, required this.exception, required this.errorMessageBuilder, this.stackTrace}) : super(key: key); - - static DialogButton createCopyErrorButton({required Object error, required StackTrace? stackTrace, required Function() onClick, ButtonType type = ButtonType.primary}) => DialogButton( - text: translations.copyErrorDialogTitle, - type: type, - onTap: () async { - FlutterClipboard.controlC("$error\n$stackTrace"); - showRebootInfoBar(translations.copyErrorDialogSuccess); - onClick(); - }, - ); - - @override - Widget build(BuildContext context) { - return InfoDialog( - text: errorMessageBuilder(exception), - buttons: [ - DialogButton( - type: stackTrace == null ? ButtonType.only : ButtonType.secondary - ), - - if(stackTrace != null) - createCopyErrorButton( - error: exception, - stackTrace: stackTrace, - onClick: () => Navigator.pop(context) - ) - ], - ); - } -} - -class DialogButton extends StatefulWidget { - final String? text; - final Function()? onTap; - final ButtonType type; - final Color? color; - - const DialogButton( - {Key? key, - this.text, - this.onTap, - this.color, - required this.type}) - : assert(type != ButtonType.primary || onTap != null, - "OnTap handler cannot be null for primary buttons"), - assert(type != ButtonType.primary || text != null, - "Text cannot be null for primary buttons"), - super(key: key); - - @override - State createState() => _DialogButtonState(); -} - -class _DialogButtonState extends State { - @override - Widget build(BuildContext context) => widget.type == ButtonType.only ? _onlyButton : _button; - - SizedBox get _onlyButton => SizedBox( - width: double.infinity, - child: _button - ); - - Widget get _button => widget.type == ButtonType.primary ? _primaryButton : _secondaryButton; - - Widget get _primaryButton => Button( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor) - ), - onPressed: widget.onTap!, - child: Text(widget.text!), - ); - - Widget get _secondaryButton => Button( - style: widget.color != null ? ButtonStyle( - backgroundColor: WidgetStateProperty.all(widget.color!) - ) : null, - onPressed: widget.onTap ?? _onDefaultSecondaryActionTap, - child: Text(widget.text ?? translations.defaultDialogSecondaryAction), - ); - - void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null); -} - -enum ButtonType { - primary, - secondary, - only -} \ No newline at end of file diff --git a/gui/lib/src/messenger/info_bar.dart b/gui/lib/src/messenger/info_bar.dart deleted file mode 100644 index 068d144..0000000 --- a/gui/lib/src/messenger/info_bar.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:fluent_ui/fluent_ui.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; - -const infoBarLongDuration = Duration(seconds: 4); -const infoBarShortDuration = Duration(seconds: 2); -const _height = 64.0; - -InfoBarEntry showRebootInfoBar(String text, { - InfoBarSeverity severity = InfoBarSeverity.info, - bool loading = false, - Duration? duration = infoBarShortDuration, - void Function()? onDismissed, - Widget? action -}) { - final overlay = _buildOverlay(text, action, loading, severity); - final overlayEntry = InfoBarEntry(overlay: overlay, onDismissed: onDismissed); - if(duration != null) { - Future.delayed(duration) - .then((_) => WidgetsBinding.instance.addPostFrameCallback((timeStamp) => overlayEntry.close())); - } - return overlayEntry; -} - -Widget _buildOverlay(text, Widget? action, bool loading, InfoBarSeverity severity) => ConstrainedBox( - constraints: BoxConstraints( - minHeight: _height - ), - child: Mica( - elevation: 1, - child: InfoBar( - title: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: text is Widget ? text : Text(text) - ), - if(action != null) - action - ], - ), - isLong: false, - isIconVisible: true, - content: SizedBox( - width: double.infinity, - child: loading ? const Padding( - padding: const EdgeInsets.only( - top: 8.0, - bottom: 2.0, - right: 6.0 - ), - child: ProgressBar(), - ) : const SizedBox() - ), - severity: severity - ) - ), -); - -class InfoBarEntry { - final Widget overlay; - final void Function()? onDismissed; - - InfoBarEntry({required this.overlay, required this.onDismissed}) { - final context = pageKey.currentContext; - if(context != null) { - infoBarAreaKey.currentState?.insertChild(overlay); - } - } - - bool close() { - final result = infoBarAreaKey.currentState?.removeChild(overlay) ?? false; - if(result) { - onDismissed?.call(); - } - return result; - } -} \ No newline at end of file diff --git a/gui/lib/src/messenger/info_bar_area.dart b/gui/lib/src/messenger/info_bar_area.dart deleted file mode 100644 index 9443bbc..0000000 --- a/gui/lib/src/messenger/info_bar_area.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:fluent_ui/fluent_ui.dart'; -import 'package:get/get.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; - -class InfoBarArea extends StatefulWidget { - const InfoBarArea({super.key}); - - @override - State createState() => InfoBarAreaState(); -} - -class InfoBarAreaState extends State { - final Rx> _children = Rx([]); - - void insertChild(Widget child) { - _children.value.add(child); - _children.refresh(); - } - - bool removeChild(Widget child) { - final result = _children.value.remove(child); - _children.refresh(); - return result; - } - - @override - Widget build(BuildContext context) => StreamBuilder( - stream: pagesController.stream, - builder: (context, _) => Obx(() => Padding( - padding: EdgeInsets.only( - bottom: hasPageButton ? 72.0 : 16.0 - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: _children.value.map((child) => Padding( - padding: EdgeInsets.only( - top: 12.0 - ), - child: child - )).toList(growable: false) - ), - )) - ); -} \ No newline at end of file diff --git a/gui/lib/src/tile/file_setting_tile.dart b/gui/lib/src/tile/file_setting_tile.dart deleted file mode 100644 index 61f61e0..0000000 --- a/gui/lib/src/tile/file_setting_tile.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'dart:io'; - -import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons; -import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; -import 'package:fluentui_system_icons/fluentui_system_icons.dart'; -import 'package:flutter/foundation.dart'; -import 'package:get/get.dart'; -import 'package:reboot_launcher/src/util/os.dart'; -import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/button/file_selector.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; - -const double _kButtonDimensions = 30; -const double _kButtonSpacing = 8; - -SettingTile createFileSetting({ - required GlobalKey key, - required String title, - required String description, - required TextEditingController controller, - required void Function() onReset -}) { - final obx = RxnString(); - final selecting = RxBool(false); - return SettingTile( - icon: Icon( - FluentIcons.document_24_regular - ), - title: Text(title), - subtitle: Text(description), - contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions, - content: Row( - children: [ - Expanded( - child: FileSelector( - placeholder: translations.selectPathPlaceholder, - windowTitle: translations.selectPathWindowTitle, - controller: controller, - validator: (text) { - final result = _checkDll(text); - obx.value = result; - return result; - }, - extension: "dll", - folder: false, - validatorMode: AutovalidateMode.always, - allowNavigator: false, - validatorKey: key - ), - ), - const SizedBox(width: _kButtonSpacing), - Obx(() => Padding( - padding: EdgeInsets.only( - bottom: obx.value == null ? 0.0 : 20.0 - ), - child: Tooltip( - message: translations.selectFile, - child: Button( - style: ButtonStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero) - ), - onPressed: () => _onPressed(selecting, controller), - child: SizedBox.square( - dimension: _kButtonDimensions, - child: Icon( - fluentIcons.FluentIcons.open_folder_horizontal - ), - ) - ), - ), - )), - const SizedBox(width: _kButtonSpacing), - Obx(() => Padding( - padding: EdgeInsets.only( - bottom: obx.value == null ? 0.0 : 20.0 - ), - child: Tooltip( - message: translations.reset, - child: Button( - style: ButtonStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero) - ), - onPressed: onReset, - child: SizedBox.square( - dimension: _kButtonDimensions, - child: Icon( - FluentIcons.arrow_reset_24_regular - ), - ) - ), - ), - )) - ], - ) - ); -} - -void _onPressed(RxBool selecting, TextEditingController controller) { - if(selecting.value){ - return; - } - - selecting.value = true; - compute(openFilePicker, "dll") - .then((value) => _updateText(controller, value)) - .then((_) => selecting.value = false); -} - -void _updateText(TextEditingController controller, String? value) { - final text = value ?? controller.text; - controller.text = text; - controller.selection = TextSelection.collapsed(offset: text.length); -} - -String? _checkDll(String? text) { - if (text == null || text.isEmpty) { - return translations.invalidDllPath; - } - - final file = File(text); - try { - file.readAsBytesSync(); - }catch(_) { - return translations.dllDoesNotExist; - } - - if (!text.endsWith(".dll")) { - return translations.invalidDllExtension; - } - - return null; -} \ No newline at end of file diff --git a/gui/lib/src/tile/info_tile.dart b/gui/lib/src/tile/info_tile.dart deleted file mode 100644 index 3ff6c66..0000000 --- a/gui/lib/src/tile/info_tile.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; -import 'package:fluentui_system_icons/fluentui_system_icons.dart'; - -class InfoTile extends StatelessWidget { - final Key? expanderKey; - final Text title; - final Text content; - - const InfoTile({ - this.expanderKey, - required this.title, - required this.content - }); - - @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.only( - bottom: 4.0 - ), - child: Expander( - key: expanderKey, - header: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - FluentIcons.info_24_regular - ), - const SizedBox(width: 16.0), - title - ], - ), - content: content, - ), - ); -} diff --git a/gui/lib/src/util/url_protocol.dart b/gui/lib/src/util/url_protocol.dart index 30b9599..40641b9 100644 --- a/gui/lib/src/util/url_protocol.dart +++ b/gui/lib/src/util/url_protocol.dart @@ -53,7 +53,9 @@ String _sanitize(String value) { } List _getArguments(List? arguments) { - if (arguments == null) return ['%s']; + if (arguments == null) { + return ['%s']; + } if (arguments.isEmpty && !arguments.any((e) => e.contains('%s'))) { throw ArgumentError('arguments must contain at least 1 instance of "%s"'); diff --git a/gui/lib/src/widget/dialog/dialog.dart b/gui/lib/src/widget/dialog/dialog.dart new file mode 100644 index 0000000..7dff194 --- /dev/null +++ b/gui/lib/src/widget/dialog/dialog.dart @@ -0,0 +1,20 @@ +import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/widget/section/sections.dart'; + +abstract class Dialog extends StatelessWidget { + static Future open({ + required WidgetBuilder builder, + bool dismissWithEsc = true + }) => fluent.showDialog( + context: appNavigatorKey.currentContext!, + useRootNavigator: false, + dismissWithEsc: dismissWithEsc, + builder: builder + ); + + const Dialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context); +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/dialog_button.dart b/gui/lib/src/widget/dialog/dialog_button.dart new file mode 100644 index 0000000..3e86633 --- /dev/null +++ b/gui/lib/src/widget/dialog/dialog_button.dart @@ -0,0 +1,58 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/util/translations.dart'; + +class DialogButton extends StatefulWidget { + final String? text; + final Function()? onTap; + final DialogButtonType type; + final Color? color; + + const DialogButton({ + Key? key, + this.text, + this.onTap, + this.color, + required this.type + }): assert(type != DialogButtonType.primary || onTap != null, "OnTap handler cannot be null for primary buttons"), + assert(type != DialogButtonType.primary || text != null, "Text cannot be null for primary buttons"), + super(key: key); + + @override + State createState() => _DialogButtonState(); +} + +class _DialogButtonState extends State { + @override + Widget build(BuildContext context) => widget.type == DialogButtonType.only ? _onlyButton : _button; + + SizedBox get _onlyButton => SizedBox( + width: double.infinity, + child: _button + ); + + Widget get _button => widget.type == DialogButtonType.primary ? _primaryButton : _secondaryButton; + + Widget get _primaryButton => Button( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor) + ), + onPressed: widget.onTap!, + child: Text(widget.text!), + ); + + Widget get _secondaryButton => Button( + style: widget.color != null ? ButtonStyle( + backgroundColor: WidgetStateProperty.all(widget.color!) + ) : null, + onPressed: widget.onTap ?? _onDefaultSecondaryActionTap, + child: Text(widget.text ?? translations.defaultDialogSecondaryAction), + ); + + void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null); +} + +enum DialogButtonType { + primary, + secondary, + only +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/error_dialog.dart b/gui/lib/src/widget/dialog/error_dialog.dart new file mode 100644 index 0000000..7edb212 --- /dev/null +++ b/gui/lib/src/widget/dialog/error_dialog.dart @@ -0,0 +1,39 @@ +import 'package:clipboard/clipboard.dart'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/util/translations.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; + +import 'dialog.dart'; +import 'dialog_button.dart'; +import 'info_dialog.dart'; + +class ErrorDialog extends Dialog { + final Object exception; + final StackTrace? stackTrace; + final Function(Object) errorMessageBuilder; + + const ErrorDialog({Key? key, required this.exception, required this.errorMessageBuilder, this.stackTrace}) : super(key: key); + + @override + Widget build(BuildContext context) { + return InfoDialog( + text: errorMessageBuilder(exception), + buttons: [ + DialogButton( + type: stackTrace == null ? DialogButtonType.only : DialogButtonType.secondary + ), + + if(stackTrace != null) + DialogButton( + text: translations.copyErrorDialogTitle, + type: DialogButtonType.primary, + onTap: () async { + FlutterClipboard.controlC("$exception\n$stackTrace"); + SnackBar.open(translations.copyErrorDialogSuccess); + Navigator.pop(context); + }, + ) + ], + ); + } +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/form_dialog.dart b/gui/lib/src/widget/dialog/form_dialog.dart new file mode 100644 index 0000000..9ee4e58 --- /dev/null +++ b/gui/lib/src/widget/dialog/form_dialog.dart @@ -0,0 +1,40 @@ +import 'package:fluent_ui/fluent_ui.dart'; + +import 'dialog.dart'; +import 'dialog_button.dart'; +import 'generic_dialog.dart'; + +class FormDialog extends Dialog { + final Widget content; + final List buttons; + + const FormDialog({Key? key, required this.content, required this.buttons}) : super(key: key); + + @override + Widget build(BuildContext context) => Form( + child: Builder( + builder: (context) => GenericDialog( + header: content, + buttons: buttons.map((entry) => _createFormButton(entry, context)).toList() + ) + ) + ); + + DialogButton _createFormButton(DialogButton entry, BuildContext context) { + if (entry.type != DialogButtonType.primary) { + return entry; + } + + return DialogButton( + text: entry.text, + type: entry.type, + onTap: () { + if(!Form.of(context).validate()) { + return; + } + + entry.onTap?.call(); + } + ); + } +} diff --git a/gui/lib/src/widget/dialog/future_dialog.dart b/gui/lib/src/widget/dialog/future_dialog.dart new file mode 100644 index 0000000..39be35d --- /dev/null +++ b/gui/lib/src/widget/dialog/future_dialog.dart @@ -0,0 +1,88 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/util/translations.dart'; + +import 'dialog.dart'; +import 'dialog_button.dart'; +import 'generic_dialog.dart'; + +class FutureDialog extends Dialog { + final Future future; + final String loadingMessage; + final Widget successfulBody; + final Widget unsuccessfulBody; + final Function(Object) errorMessageBuilder; + final Function()? onError; + final bool closeAutomatically; + + const FutureDialog( + {Key? key, + required this.future, + required this.loadingMessage, + required this.successfulBody, + required this.unsuccessfulBody, + required this.errorMessageBuilder, + this.onError, + this.closeAutomatically = false}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: future, + builder: (context, snapshot) => GenericDialog( + header: _createBody(context, snapshot), + buttons: [_createButton(context, snapshot)] + ) + ); + } + + Widget _createBody(BuildContext context, AsyncSnapshot snapshot){ + if (snapshot.hasError) { + onError?.call(); + return _buildData(errorMessageBuilder(snapshot.error!)); + } + + if(snapshot.connectionState == ConnectionState.done + && (snapshot.data == null || (snapshot.data is bool && !snapshot.data))){ + return unsuccessfulBody; + } + + if (!snapshot.hasData) { + return _loadingBody; + } + + if(closeAutomatically){ + WidgetsBinding.instance + .addPostFrameCallback((_) => Navigator.of(context).pop(true)); + return _loadingBody; + } + + return successfulBody; + } + + Widget _buildData(String message) { + return Container( + width: double.infinity, + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + message, + textAlign: TextAlign.center + ) + ); + } + + Widget get _loadingBody => InfoLabel( + label: loadingMessage, + child: Container( + padding: const EdgeInsets.only(bottom: 16.0), + width: double.infinity, + child: const ProgressBar()), + ); + + DialogButton _createButton(BuildContext context, AsyncSnapshot snapshot)=> DialogButton( + text: snapshot.hasData + || snapshot.hasError + || (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? translations.defaultDialogSecondaryAction : translations.stopLoadingDialogAction, + type: DialogButtonType.only, + onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData) + ); +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/generic_dialog.dart b/gui/lib/src/widget/dialog/generic_dialog.dart new file mode 100644 index 0000000..7ff3762 --- /dev/null +++ b/gui/lib/src/widget/dialog/generic_dialog.dart @@ -0,0 +1,21 @@ +import 'package:fluent_ui/fluent_ui.dart'; + +import 'dialog.dart'; +import 'dialog_button.dart'; + +class GenericDialog extends Dialog { + final Widget header; + final List buttons; + final EdgeInsets? padding; + + const GenericDialog({Key? key, required this.header, required this.buttons, this.padding}) : super(key: key); + + @override + Widget build(BuildContext context) => ContentDialog( + style: ContentDialogThemeData( + padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0) + ), + content: header, + actions: buttons + ); +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/info_dialog.dart b/gui/lib/src/widget/dialog/info_dialog.dart new file mode 100644 index 0000000..5736ea4 --- /dev/null +++ b/gui/lib/src/widget/dialog/info_dialog.dart @@ -0,0 +1,33 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/util/translations.dart'; + +import 'dialog.dart'; +import 'dialog_button.dart'; +import 'generic_dialog.dart'; + +class InfoDialog extends Dialog { + final String text; + final List? buttons; + + const InfoDialog({required this.text, this.buttons, Key? key}) : super(key: key); + + InfoDialog.ofOnly({required this.text, required DialogButton button, Key? key}) + : buttons = [button], super(key: key); + + @override + Widget build(BuildContext context) { + return GenericDialog( + header: SizedBox( + width: double.infinity, + child: Text(text, textAlign: TextAlign.center) + ), + buttons: buttons ?? [_defaultCloseButton], + padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0) + ); + } + + DialogButton get _defaultCloseButton =>DialogButton( + text: translations.defaultDialogSecondaryAction, + type: DialogButtonType.only + ); +} \ No newline at end of file diff --git a/gui/lib/src/widget/dialog/progress_dialog.dart b/gui/lib/src/widget/dialog/progress_dialog.dart new file mode 100644 index 0000000..4c3f7f5 --- /dev/null +++ b/gui/lib/src/widget/dialog/progress_dialog.dart @@ -0,0 +1,36 @@ +import 'package:fluent_ui/fluent_ui.dart'; + +import 'package:reboot_launcher/src/util/translations.dart'; +import 'dialog.dart'; +import 'dialog_button.dart'; +import 'generic_dialog.dart'; + +class ProgressDialog extends Dialog { + final String text; + final Function()? onStop; + final bool showButton; + + const ProgressDialog({required this.text, this.onStop, this.showButton = true, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GenericDialog( + header: InfoLabel( + label: text, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16.0), + width: double.infinity, + child: const ProgressBar() + ), + ), + buttons: [ + if(showButton) + DialogButton( + text: translations.defaultDialogSecondaryAction, + type: DialogButtonType.only, + onTap: onStop + ) + ] + ); + } +} \ No newline at end of file diff --git a/gui/lib/src/button/file_selector.dart b/gui/lib/src/widget/file_selector.dart similarity index 82% rename from gui/lib/src/button/file_selector.dart rename to gui/lib/src/widget/file_selector.dart index ea7bab5..2ad3aa6 100644 --- a/gui/lib/src/button/file_selector.dart +++ b/gui/lib/src/widget/file_selector.dart @@ -17,20 +17,19 @@ class FileSelector extends StatefulWidget { final bool folder; final void Function(String)? onSelected; - const FileSelector( - {required this.placeholder, - required this.windowTitle, - required this.controller, - required this.folder, - required this.allowNavigator, - this.validator, - this.validatorKey, - this.label, - this.extension, - this.validatorMode, - this.onSelected, - Key? key}) - : assert(folder || extension != null, "Missing extension for file selector"), + const FileSelector({ + required this.placeholder, + required this.windowTitle, + required this.controller, + required this.folder, + required this.allowNavigator, + this.validator, + this.validatorKey, + this.label, + this.extension, + this.validatorMode, + this.onSelected, + Key? key}) : assert(folder || extension != null, "Missing extension for file selector"), super(key: key); @override diff --git a/gui/lib/src/pager/abstract_page.dart b/gui/lib/src/widget/page/abstract_page.dart similarity index 97% rename from gui/lib/src/pager/abstract_page.dart rename to gui/lib/src/widget/page/abstract_page.dart index edd020d..b92cbdf 100644 --- a/gui/lib/src/pager/abstract_page.dart +++ b/gui/lib/src/widget/page/abstract_page.dart @@ -1,7 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:reboot_launcher/src/message/onboard.dart'; diff --git a/gui/lib/src/message/backend.dart b/gui/lib/src/widget/page/backend/message.dart similarity index 65% rename from gui/lib/src/message/backend.dart rename to gui/lib/src/widget/page/backend/message.dart index 7dd7440..081800a 100644 --- a/gui/lib/src/message/backend.dart +++ b/gui/lib/src/widget/page/backend/message.dart @@ -1,110 +1,110 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:reboot_common/common.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; import 'package:reboot_launcher/src/util/translations.dart'; import 'package:url_launcher/url_launcher.dart'; -InfoBarEntry? onBackendResult(AuthBackendType type, AuthBackendResult event) { +SnackBar? onBackendResult(AuthBackendType type, AuthBackendEvent event) { switch (event.type) { - case AuthBackendResultType.starting: - return showRebootInfoBar( + case AuthBackendEventType.starting: + return SnackBar.open( translations.startingServer, severity: InfoBarSeverity.info, loading: true, duration: null ); - case AuthBackendResultType.startSuccess: - return showRebootInfoBar( + case AuthBackendEventType.startSuccess: + return SnackBar.open( type == AuthBackendType.local ? translations.checkedServer : translations.startedServer, severity: InfoBarSeverity.success ); - case AuthBackendResultType.startError: - return showRebootInfoBar( + case AuthBackendEventType.startError: + return SnackBar.open( type == AuthBackendType.local ? translations.localServerError(event.error ?? translations.unknownError) : translations.startServerError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); - case AuthBackendResultType.stopping: - return showRebootInfoBar( + case AuthBackendEventType.stopping: + return SnackBar.open( translations.stoppingServer, severity: InfoBarSeverity.info, loading: true, duration: null ); - case AuthBackendResultType.stopSuccess: - return showRebootInfoBar( + case AuthBackendEventType.stopSuccess: + return SnackBar.open( translations.stoppedServer, severity: InfoBarSeverity.success ); - case AuthBackendResultType.stopError: - return showRebootInfoBar( + case AuthBackendEventType.stopError: + return SnackBar.open( translations.stopServerError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); - case AuthBackendResultType.startMissingHostError: - return showRebootInfoBar( + case AuthBackendEventType.startMissingHostError: + return SnackBar.open( translations.missingHostNameError, severity: InfoBarSeverity.error ); - case AuthBackendResultType.startMissingPortError: - return showRebootInfoBar( + case AuthBackendEventType.startMissingPortError: + return SnackBar.open( translations.missingPortError, severity: InfoBarSeverity.error ); - case AuthBackendResultType.startIllegalPortError: - return showRebootInfoBar( + case AuthBackendEventType.startIllegalPortError: + return SnackBar.open( translations.illegalPortError, severity: InfoBarSeverity.error ); - case AuthBackendResultType.startFreeingPort: - return showRebootInfoBar( + case AuthBackendEventType.startFreeingPort: + return SnackBar.open( translations.freeingPort, loading: true, duration: null ); - case AuthBackendResultType.startFreePortSuccess: - return showRebootInfoBar( + case AuthBackendEventType.startFreePortSuccess: + return SnackBar.open( translations.freedPort, severity: InfoBarSeverity.success, duration: infoBarShortDuration ); - case AuthBackendResultType.startFreePortError: - return showRebootInfoBar( + case AuthBackendEventType.startFreePortError: + return SnackBar.open( translations.freePortError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); - case AuthBackendResultType.startPingingRemote: - return showRebootInfoBar( + case AuthBackendEventType.startPingingRemote: + return SnackBar.open( translations.pingingServer(AuthBackendType.remote.name), severity: InfoBarSeverity.info, loading: true, duration: null ); - case AuthBackendResultType.startPingingLocal: - return showRebootInfoBar( + case AuthBackendEventType.startPingingLocal: + return SnackBar.open( translations.pingingServer(type.name), severity: InfoBarSeverity.info, loading: true, duration: null ); - case AuthBackendResultType.startPingError: - return showRebootInfoBar( + case AuthBackendEventType.startPingError: + return SnackBar.open( translations.pingError(type.name), severity: InfoBarSeverity.error ); - case AuthBackendResultType.startedImplementation: + case AuthBackendEventType.startedImplementation: return null; } } void onBackendError(Object error) { - showRebootInfoBar( + SnackBar.open( translations.backendErrorMessage, severity: InfoBarSeverity.error, duration: infoBarLongDuration, diff --git a/gui/lib/src/page/backend_page.dart b/gui/lib/src/widget/page/backend/page.dart similarity index 85% rename from gui/lib/src/page/backend_page.dart rename to gui/lib/src/widget/page/backend/page.dart index 2202301..4efb347 100644 --- a/gui/lib/src/page/backend_page.dart +++ b/gui/lib/src/widget/page/backend/page.dart @@ -5,22 +5,22 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/keyboard.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/section/setting_tile.dart'; import 'package:reboot_launcher/src/message/data.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; -import 'package:reboot_launcher/src/button/backend_start_button.dart'; -import 'package:reboot_launcher/src/button/server_type_selector.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/backend/state_toggle.dart'; +import 'package:reboot_launcher/src/widget/section/backend/type_selector.dart'; import 'package:url_launcher/url_launcher.dart'; -final GlobalKey backendTypeOverlayTargetKey = GlobalKey(); -final GlobalKey backendGameServerAddressOverlayTargetKey = GlobalKey(); -final GlobalKey backendUnrealEngineOverlayTargetKey = GlobalKey(); -final GlobalKey backendDetachedOverlayTargetKey = GlobalKey(); +final GlobalKey backendTypeOverlayTargetKey = GlobalKey(); +final GlobalKey backendGameServerAddressOverlayTargetKey = GlobalKey(); +final GlobalKey backendUnrealEngineOverlayTargetKey = GlobalKey(); +final GlobalKey backendDetachedOverlayTargetKey = GlobalKey(); class BackendPage extends AbstractPage { const BackendPage({Key? key}) : super(key: key); @@ -44,12 +44,12 @@ class BackendPage extends AbstractPage { class _BackendPageState extends AbstractPageState { final BackendController _backendController = Get.find(); - InfoBarEntry? _infoBarEntry; + SnackBar? _SnackBar; @override void initState() { ServicesBinding.instance.keyboard.addHandler((keyEvent) { - if(_infoBarEntry == null) { + if(_SnackBar == null) { return false; } @@ -57,8 +57,8 @@ class _BackendPageState extends AbstractPageState { _backendController.consoleKey.value = keyEvent.physicalKey; } - _infoBarEntry?.close(); - _infoBarEntry = null; + _SnackBar?.close(); + _SnackBar = null; return true; }); super.initState(); @@ -87,7 +87,7 @@ class _BackendPageState extends AbstractPageState { ), title: Text(translations.matchmakerConfigurationAddressName), subtitle: Text(translations.matchmakerConfigurationAddressDescription), - content: OverlayTarget( + content: TutorialOverlayTarget( key: backendGameServerAddressOverlayTargetKey, child: TextFormBox( placeholder: translations.matchmakerConfigurationAddressName, @@ -158,7 +158,7 @@ class _BackendPageState extends AbstractPageState { const SizedBox( width: 16.0 ), - OverlayTarget( + TutorialOverlayTarget( key: backendDetachedOverlayTargetKey, child: ToggleSwitch( checked: _backendController.detached(), @@ -182,11 +182,11 @@ class _BackendPageState extends AbstractPageState { title: Text(translations.settingsClientConsoleKeyName), subtitle: Text(translations.settingsClientConsoleKeyDescription), contentWidth: null, - content: OverlayTarget( + content: TutorialOverlayTarget( key: backendUnrealEngineOverlayTargetKey, child: Button( onPressed: () { - _infoBarEntry = showRebootInfoBar( + _SnackBar = SnackBar.open( translations.clickKey, loading: true, duration: null @@ -234,7 +234,7 @@ class _BackendPageState extends AbstractPageState { ), title: Text(translations.backendTypeName), subtitle: Text(translations.backendTypeDescription), - content: ServerTypeSelector( + content: BackendTypeSelector( overlayKey: backendTypeOverlayTargetKey ) ); diff --git a/gui/lib/src/button/backend_start_button.dart b/gui/lib/src/widget/page/backend/state_toggle.dart similarity index 62% rename from gui/lib/src/button/backend_start_button.dart rename to gui/lib/src/widget/page/backend/state_toggle.dart index 0658dbd..5721bd7 100644 --- a/gui/lib/src/button/backend_start_button.dart +++ b/gui/lib/src/widget/page/backend/state_toggle.dart @@ -6,11 +6,9 @@ import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; -import 'package:reboot_launcher/src/message/backend.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/section/backend/message.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import '../messenger/info_bar.dart'; class BackendButton extends StatefulWidget { const BackendButton({Key? key}) : super(key: key); @@ -50,40 +48,42 @@ class _BackendButtonState extends State { alignment: Alignment.center, child: StreamBuilder( stream: _textController.stream, - builder: (context, snapshot) => Obx(() => Text(_buttonText)) + builder: (context, snapshot) => Obx(() => Text(_text)) ), ), onPressed: () => _backendController.toggle( - eventHandler: (type, event) { - _backendController.started.value = event.type.isStart && !event.type.isError; - if(event.type == AuthBackendResultType.startedImplementation) { - _backendController.implementation = event.implementation; - } - return onBackendResult(type, event); - }, - errorHandler: (error) { - if(_backendController.started.value) { - _backendController.stop(); - _gameController.instance.value?.kill(); - _hostingController.instance.value?.kill(); - onBackendError(error); - } - } + eventHandler: _onEvent, + errorHandler: _onError ) ) ) ); + SnackBar? _onEvent(AuthBackendType type, AuthBackendEvent event) { + _backendController.started.value = event.type.isStart && !event.type.isError; + if(event.type == AuthBackendEventType.startedImplementation) { + _backendController.implementation = event.implementation; + } + return onBackendResult(type, event); + } - String get _buttonText { - if(_backendController.type.value == AuthBackendType.local && _backendController.port.text.trim() == kDefaultBackendPort.toString()){ + void _onError(String error) { + if(_backendController.started.value) { + _backendController.stop(); + _gameController.instance.value?.kill(); + _hostingController.instance.value?.kill(); + onBackendError(error); + } + } + + String get _text { + if(_backendController.type.value == AuthBackendType.local + && _backendController.port.text.trim() == kDefaultBackendPort.toString()){ return translations.checkServer; - } - - if(_backendController.started.value){ + }else if(_backendController.started.value){ return translations.stopServer; + }else { + return translations.startServer; } - - return translations.startServer; } } diff --git a/gui/lib/src/button/server_type_selector.dart b/gui/lib/src/widget/page/backend/type_selector.dart similarity index 52% rename from gui/lib/src/button/server_type_selector.dart rename to gui/lib/src/widget/page/backend/type_selector.dart index 183b561..0e9fb97 100644 --- a/gui/lib/src/button/server_type_selector.dart +++ b/gui/lib/src/widget/page/backend/type_selector.dart @@ -3,35 +3,34 @@ import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; -class ServerTypeSelector extends StatefulWidget { +class BackendTypeSelector extends StatefulWidget { final Key overlayKey; - const ServerTypeSelector({required this.overlayKey}); + const BackendTypeSelector({required this.overlayKey}); @override - State createState() => _ServerTypeSelectorState(); + State createState() => _BackendTypeSelectorState(); } -class _ServerTypeSelectorState extends State { +class _BackendTypeSelectorState extends State { late final BackendController _backendController = Get.find(); @override Widget build(BuildContext context) { - return Obx(() => OverlayTarget( + return Obx(() => TutorialOverlayTarget( key: widget.overlayKey, child: DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text(_backendController.type.value.label), - items: AuthBackendType.values - .map((type) => _createItem(type)) - .toList() + items: _items ), )); } + List get _items => AuthBackendType.values + .map((type) => _createItem(type)) + .toList(); + MenuFlyoutItem _createItem(AuthBackendType type) => MenuFlyoutItem( text: Text(type.label), onPressed: () async { @@ -42,9 +41,9 @@ class _ServerTypeSelectorState extends State { } extension _ServerTypeExtension on AuthBackendType { - String get label { - return this == AuthBackendType.embedded ? translations.embedded - : this == AuthBackendType.remote ? translations.remote - : translations.local; - } + String get label => switch(this) { + AuthBackendType.embedded => translations.embedded, + AuthBackendType.remote => translations.remote, + AuthBackendType.local => translations.local + }; } diff --git a/gui/lib/src/widget/page/browser/filter.dart b/gui/lib/src/widget/page/browser/filter.dart new file mode 100644 index 0000000..8ed39ee --- /dev/null +++ b/gui/lib/src/widget/page/browser/filter.dart @@ -0,0 +1,13 @@ +import 'package:reboot_launcher/src/util/translations.dart'; + +enum ServerBrowserFilter { + all, + accessible, + playable; + + String get translatedName => switch(this) { + ServerBrowserFilter.all => translations.all, + ServerBrowserFilter.accessible => translations.accessible, + ServerBrowserFilter.playable => translations.playable + }; +} \ No newline at end of file diff --git a/gui/lib/src/widget/page/browser/order.dart b/gui/lib/src/widget/page/browser/order.dart new file mode 100644 index 0000000..368ce41 --- /dev/null +++ b/gui/lib/src/widget/page/browser/order.dart @@ -0,0 +1,15 @@ +import 'package:reboot_launcher/src/util/translations.dart'; + +enum ServerBrowserOrder { + timeAscending, + timeDescending, + nameAscending, + nameDescending; + + String get translatedName => switch(this) { + ServerBrowserOrder.timeAscending => translations.timeAscending, + ServerBrowserOrder.timeDescending => translations.timeDescending, + ServerBrowserOrder.nameAscending => translations.nameAscending, + ServerBrowserOrder.nameDescending => translations.nameDescending + }; +} \ No newline at end of file diff --git a/gui/lib/src/page/browser_page.dart b/gui/lib/src/widget/page/browser/page.dart similarity index 85% rename from gui/lib/src/page/browser_page.dart rename to gui/lib/src/widget/page/browser/page.dart index 14959bf..8b2f842 100644 --- a/gui/lib/src/page/browser_page.dart +++ b/gui/lib/src/widget/page/browser/page.dart @@ -11,16 +11,20 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/server_browser_controller.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/dialog/dialog.dart'; +import 'package:reboot_launcher/src/widget/dialog/dialog_button.dart'; +import 'package:reboot_launcher/src/widget/dialog/form_dialog.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/section/browser/filter.dart'; +import 'package:reboot_launcher/src/widget/section/browser/order.dart'; +import 'package:reboot_launcher/src/widget/section/sections.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/cryptography.dart'; import 'package:reboot_launcher/src/util/extensions.dart'; import 'package:reboot_launcher/src/util/matchmaker.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; class BrowsePage extends AbstractPage { const BrowsePage({Key? key}) : super(key: key); @@ -49,8 +53,8 @@ class _BrowsePageState extends AbstractPageState { final TextEditingController _filterController = TextEditingController(); final StreamController _filterControllerStream = StreamController.broadcast(); - final Rx<_Filter> _filter = Rx(_Filter.all); - final Rx<_Sort> _sort = Rx(_Sort.timeDescending); + final Rx _filter = Rx(ServerBrowserFilter.all); + final Rx _sort = Rx(ServerBrowserOrder.timeDescending); @override void initState() { @@ -76,7 +80,7 @@ class _BrowsePageState extends AbstractPageState { if(server != null) { _joinServer(_hostingController.uuid, server); }else { - showRebootInfoBar( + SnackBar.open( translations.noServerFound, duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -124,11 +128,11 @@ class _BrowsePageState extends AbstractPageState { ), Row( children: [ - _buildFilter(context), + _buildFilterSection(context), const SizedBox( width: 16.0 ), - _buildSort(context), + _buildOrderSection(context), ], ), const SizedBox( @@ -142,7 +146,7 @@ class _BrowsePageState extends AbstractPageState { } ); - Widget _buildSort(BuildContext context) => Row( + Widget _buildOrderSection(BuildContext context) => Row( children: [ Icon( fluentIcons.FluentIcons.arrow_sort_24_regular, @@ -159,24 +163,27 @@ class _BrowsePageState extends AbstractPageState { Obx(() => SizedBox( width: 230, child: DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text( _sort.value.translatedName, textAlign: TextAlign.start ), title: const Spacer(), - items: _Sort.values.map((entry) => MenuFlyoutItem( - text: Text(entry.translatedName), - onPressed: () => _sort.value = entry - )).toList() + items: _orders ), )) ], ); - Row _buildFilter(BuildContext context) { - return Row( + List get _orders => ServerBrowserOrder.values + .map(_buildOrder) + .toList(); + + MenuFlyoutItem _buildOrder(ServerBrowserOrder entry) => MenuFlyoutItem( + text: Text(entry.translatedName), + onPressed: () => _sort.value = entry + ); + + Widget _buildFilterSection(BuildContext context) => Row( children: [ Icon( fluentIcons.FluentIcons.filter_24_regular, @@ -193,45 +200,48 @@ class _BrowsePageState extends AbstractPageState { Obx(() => SizedBox( width: 125, child: DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text( _filter.value.translatedName, textAlign: TextAlign.start ), title: const Spacer(), - items: _Filter.values.map((entry) => MenuFlyoutItem( - text: Text(entry.translatedName), - onPressed: () => _filter.value = entry - )).toList() + items: _filters ), )) ], ); - } + + List get _filters => ServerBrowserFilter.values + .map((entry) => _buildFilter(entry)) + .toList(); + + MenuFlyoutItem _buildFilter(ServerBrowserFilter entry) => MenuFlyoutItem( + text: Text(entry.translatedName), + onPressed: () => _filter.value = entry + ); Widget _buildPopulatedListBody(List? items) => Obx(() { final filter = _filter.value; final sorted = items?.where((element) { switch(filter) { - case _Filter.all: + case ServerBrowserFilter.all: return true; - case _Filter.accessible: + case ServerBrowserFilter.accessible: return element.password.isNotEmpty; - case _Filter.playable: + case ServerBrowserFilter.playable: return _gameController.getVersionByGame(element.version) != null; } }).toList(); final sort = _sort.value; sorted?.sort((first, second) { switch(sort) { - case _Sort.timeAscending: + case ServerBrowserOrder.timeAscending: return first.timestamp.compareTo(second.timestamp); - case _Sort.timeDescending: + case ServerBrowserOrder.timeDescending: return second.timestamp.compareTo(first.timestamp); - case _Sort.nameAscending: + case ServerBrowserOrder.nameAscending: return first.name.compareTo(second.name); - case _Sort.nameDescending: + case ServerBrowserOrder.nameDescending: return second.name.compareTo(first.name); } }); @@ -369,7 +379,7 @@ class _BrowsePageState extends AbstractPageState { Future _joinServer(String uuid, ServerBrowserEntry server) async { if(!kDebugMode && uuid == server.id) { - showRebootInfoBar( + SnackBar.open( translations.joinSelfServer, duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -379,7 +389,7 @@ class _BrowsePageState extends AbstractPageState { final version = _gameController.getVersionByGame(server.version.toString()); if(version == null) { - showRebootInfoBar( + SnackBar.open( translations.cannotJoinServerVersion(server.version.toString()), duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -407,7 +417,7 @@ class _BrowsePageState extends AbstractPageState { } if(!checkPassword(confirmPassword, hashedPassword)) { - showRebootInfoBar( + SnackBar.open( translations.wrongServerPassword, duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -425,7 +435,7 @@ class _BrowsePageState extends AbstractPageState { } Future _isServerValid(String name, String address) async { - final loadingBar = showRebootInfoBar( + final loadingBar = SnackBar.open( translations.joiningServer(name), duration: infoBarLongDuration, loading: true, @@ -438,7 +448,7 @@ class _BrowsePageState extends AbstractPageState { return true; } - showRebootInfoBar( + SnackBar.open( translations.offlineServer, duration: infoBarLongDuration, severity: InfoBarSeverity.error @@ -450,7 +460,7 @@ class _BrowsePageState extends AbstractPageState { final confirmPasswordController = TextEditingController(); final showPassword = RxBool(false); final showPasswordTrailing = RxBool(false); - return await showRebootDialog( + return await Dialog.open( builder: (context) => FormDialog( content: Column( mainAxisSize: MainAxisSize.min, @@ -486,12 +496,12 @@ class _BrowsePageState extends AbstractPageState { buttons: [ DialogButton( text: translations.serverPasswordCancel, - type: ButtonType.secondary + type: DialogButtonType.secondary ), DialogButton( text: translations.serverPasswordConfirm, - type: ButtonType.primary, + type: DialogButtonType.primary, onTap: () => Navigator.of(context).pop(confirmPasswordController.text) ) ] @@ -507,7 +517,7 @@ class _BrowsePageState extends AbstractPageState { FlutterClipboard.controlC(decryptedIp); } Get.find().selectedVersion.value = version; - WidgetsBinding.instance.addPostFrameCallback((_) => showRebootInfoBar( + WidgetsBinding.instance.addPostFrameCallback((_) => SnackBar.open( embedded ? translations.joinedServer(author) : translations.copiedIp, duration: infoBarLongDuration, severity: InfoBarSeverity.success @@ -519,41 +529,4 @@ class _BrowsePageState extends AbstractPageState { @override List get settings => []; -} - -enum _Filter { - all, - accessible, - playable; - - String get translatedName { - switch(this) { - case _Filter.all: - return translations.all; - case _Filter.accessible: - return translations.accessible; - case _Filter.playable: - return translations.playable; - } - } -} - -enum _Sort { - timeAscending, - timeDescending, - nameAscending, - nameDescending; - - String get translatedName { - switch(this) { - case _Sort.timeAscending: - return translations.timeAscending; - case _Sort.timeDescending: - return translations.timeDescending; - case _Sort.nameAscending: - return translations.nameAscending; - case _Sort.nameDescending: - return translations.nameDescending; - } - } } \ No newline at end of file diff --git a/gui/lib/src/page/host_page.dart b/gui/lib/src/widget/page/host/page.dart similarity index 88% rename from gui/lib/src/page/host_page.dart rename to gui/lib/src/widget/page/host/page.dart index 1b9b3f3..aa1a1c6 100644 --- a/gui/lib/src/page/host_page.dart +++ b/gui/lib/src/widget/page/host/page.dart @@ -9,22 +9,22 @@ import 'package:reboot_launcher/main.dart'; import 'package:reboot_launcher/src/controller/dll_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/server_browser_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/setting_tile.dart'; import 'package:reboot_launcher/src/message/data.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; -import 'package:reboot_launcher/src/button/game_start_button.dart'; -import 'package:reboot_launcher/src/button/version_selector.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/play/state_toggle.dart'; +import 'package:reboot_launcher/src/widget/version_selector.dart'; -final GlobalKey hostVersionOverlayTargetKey = GlobalKey(); -final GlobalKey hostInfoOverlayTargetKey = GlobalKey(); -final GlobalKey hostInfoNameOverlayTargetKey = GlobalKey(); -final GlobalKey hostInfoDescriptionOverlayTargetKey = GlobalKey(); -final GlobalKey hostInfoPasswordOverlayTargetKey = GlobalKey(); -final GlobalKey hostShareOverlayTargetKey = GlobalKey(); +final GlobalKey hostVersionOverlayTargetKey = GlobalKey(); +final GlobalKey hostInfoOverlayTargetKey = GlobalKey(); +final GlobalKey hostInfoNameOverlayTargetKey = GlobalKey(); +final GlobalKey hostInfoDescriptionOverlayTargetKey = GlobalKey(); +final GlobalKey hostInfoPasswordOverlayTargetKey = GlobalKey(); +final GlobalKey hostShareOverlayTargetKey = GlobalKey(); final GlobalKey hostInfoTileKey = GlobalKey(); class HostPage extends AbstractPage { @@ -100,7 +100,7 @@ class _HostingPageState extends AbstractPageState { ), title: Text(translations.hostGameServerNameName), subtitle: Text(translations.hostGameServerNameDescription), - content: OverlayTarget( + content: TutorialOverlayTarget( key: hostInfoNameOverlayTargetKey, child: TextFormBox( placeholder: translations.hostGameServerNameName, @@ -116,7 +116,7 @@ class _HostingPageState extends AbstractPageState { ), title: Text(translations.hostGameServerDescriptionName), subtitle: Text(translations.hostGameServerDescriptionDescription), - content: OverlayTarget( + content: TutorialOverlayTarget( key: hostInfoDescriptionOverlayTargetKey, child: TextFormBox( placeholder: translations.hostGameServerDescriptionName, @@ -132,7 +132,7 @@ class _HostingPageState extends AbstractPageState { ), title: Text(translations.hostGameServerPasswordName), subtitle: Text(translations.hostGameServerPasswordDescription), - content: Obx(() => OverlayTarget( + content: Obx(() => TutorialOverlayTarget( key: hostInfoPasswordOverlayTargetKey, child: TextFormBox( placeholder: translations.hostGameServerPasswordName, @@ -275,7 +275,7 @@ class _HostingPageState extends AbstractPageState { subtitle: Text(translations.hostShareIpDescription), content: Button( onPressed: () async { - InfoBarEntry? entry; + SnackBar? entry; try { entry = _showCopyingIp(); final ip = await Ipify.ipv4(); @@ -317,29 +317,29 @@ class _HostingPageState extends AbstractPageState { } } - void _showCopiedLink() => showRebootInfoBar( + void _showCopiedLink() => SnackBar.open( translations.hostShareLinkMessageSuccess, severity: InfoBarSeverity.success ); - InfoBarEntry _showCopyingIp() => showRebootInfoBar( + SnackBar _showCopyingIp() => SnackBar.open( translations.hostShareIpMessageLoading, loading: true, duration: null ); - void _showCopiedIp() => showRebootInfoBar( + void _showCopiedIp() => SnackBar.open( translations.hostShareIpMessageSuccess, severity: InfoBarSeverity.success ); - void _showCannotCopyIp(Object error) => showRebootInfoBar( + void _showCannotCopyIp(Object error) => SnackBar.open( translations.hostShareIpMessageError(error.toString()), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); - void _showCannotUpdateGameServer(Object error) => showRebootInfoBar( + void _showCannotUpdateGameServer(Object error) => SnackBar.open( translations.cannotUpdateGameServer(error.toString()), severity: InfoBarSeverity.success, duration: infoBarLongDuration diff --git a/gui/lib/src/page/info_page.dart b/gui/lib/src/widget/page/info/page.dart similarity index 91% rename from gui/lib/src/page/info_page.dart rename to gui/lib/src/widget/page/info/page.dart index 92201d4..c3950ab 100644 --- a/gui/lib/src/page/info_page.dart +++ b/gui/lib/src/widget/page/info/page.dart @@ -1,11 +1,11 @@ import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/section/setting_tile.dart'; import 'package:reboot_launcher/src/message/onboard.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; import 'package:url_launcher/url_launcher_string.dart'; class InfoPage extends AbstractPage { diff --git a/gui/lib/src/pager/page_suggestion.dart b/gui/lib/src/widget/page/page_suggestion.dart similarity index 100% rename from gui/lib/src/pager/page_suggestion.dart rename to gui/lib/src/widget/page/page_suggestion.dart diff --git a/gui/lib/src/pager/page_type.dart b/gui/lib/src/widget/page/page_type.dart similarity index 100% rename from gui/lib/src/pager/page_type.dart rename to gui/lib/src/widget/page/page_type.dart diff --git a/gui/lib/src/pager/pager.dart b/gui/lib/src/widget/page/pager.dart similarity index 94% rename from gui/lib/src/pager/pager.dart rename to gui/lib/src/widget/page/pager.dart index e77381c..c70fddd 100644 --- a/gui/lib/src/pager/pager.dart +++ b/gui/lib/src/widget/page/pager.dart @@ -13,22 +13,21 @@ import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/server_browser_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; -import 'package:reboot_launcher/src/pager/page_suggestion.dart'; +import 'package:reboot_launcher/src/widget/page/page_suggestion.dart'; import 'package:reboot_launcher/src/util/matchmaker.dart'; import 'package:reboot_launcher/src/util/os.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/profile_tile.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; +import 'package:reboot_launcher/src/widget/profile_tile.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; import 'package:reboot_launcher/src/util/updater.dart'; -import 'package:reboot_launcher/src/messenger/info_bar_area.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar_area.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:window_manager/window_manager.dart'; -final GlobalKey profileOverlayKey = GlobalKey(); +final GlobalKey profileOverlayKey = GlobalKey(); const double _kDefaultPadding = 12.0; class RebootPager extends StatefulWidget { @@ -92,7 +91,7 @@ class _RebootPagerState extends State with WindowListener, Automati } _backendController.gameServerAddress.text = kDefaultGameServerHost; - WidgetsBinding.instance.addPostFrameCallback((_) => showRebootInfoBar( + WidgetsBinding.instance.addPostFrameCallback((_) => SnackBar.open( translations.serverNoLongerAvailableUnnamed, severity: InfoBarSeverity.warning, duration: infoBarLongDuration @@ -106,8 +105,8 @@ class _RebootPagerState extends State with WindowListener, Automati void _checkUpdates() { checkLauncherUpdate( onUpdate: (latestVersion) { - late InfoBarEntry infoBar; - infoBar = showRebootInfoBar( + late SnackBar infoBar; + infoBar = SnackBar.open( translations.updateAvailable(latestVersion.toString()), duration: null, severity: InfoBarSeverity.warning, @@ -312,7 +311,7 @@ class _RebootPagerState extends State with WindowListener, Automati fit: StackFit.loose, children: [ _buildBodyContent(), - InfoBarArea( + SnackBarArea( key: infoBarAreaKey ) ], @@ -414,10 +413,6 @@ class _RebootPagerState extends State with WindowListener, Automati return TextSpan( text: name, recognizer: last ? null : (TapGestureRecognizer()..onTap = () { - if(inDialog) { - return; - } - var pops = length - 1 - index; while(pops-- > 0) { Navigator.of(pageKey.currentContext!).pop(); @@ -439,7 +434,7 @@ class _RebootPagerState extends State with WindowListener, Automati children: [ Obx(() { pageIndex.value; - return ProfileWidget( + return ProfileTile( overlayKey: profileOverlayKey ); }), @@ -470,7 +465,7 @@ class _RebootPagerState extends State with WindowListener, Automati Widget _buildNavigationItem(AbstractPage page) { final index = page.type.index; - return OverlayTarget( + return TutorialOverlayTarget( key: getOverlayTargetKeyByPage(index), child: HoverButton( onPressed: () { diff --git a/gui/lib/src/page/play_page.dart b/gui/lib/src/widget/page/play/page.dart similarity index 82% rename from gui/lib/src/page/play_page.dart rename to gui/lib/src/widget/page/play/page.dart index 2b49610..49f6d7a 100644 --- a/gui/lib/src/page/play_page.dart +++ b/gui/lib/src/widget/page/play/page.dart @@ -3,16 +3,16 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:get/get.dart'; import 'package:reboot_launcher/src/controller/dll_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/section/setting_tile.dart'; import 'package:reboot_launcher/src/message/data.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; -import 'package:reboot_launcher/src/button/game_start_button.dart'; -import 'package:reboot_launcher/src/button/version_selector.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/play/state_toggle.dart'; +import 'package:reboot_launcher/src/widget/section/version_selector.dart'; -final GlobalKey gameVersionOverlayTargetKey = GlobalKey(); +final GlobalKey gameVersionOverlayTargetKey = GlobalKey(); class PlayPage extends AbstractPage { const PlayPage({Key? key}) : super(key: key); diff --git a/gui/lib/src/button/game_start_button.dart b/gui/lib/src/widget/page/play/state_toggle.dart similarity index 95% rename from gui/lib/src/button/game_start_button.dart rename to gui/lib/src/widget/page/play/state_toggle.dart index b8dd4df..864d2cb 100644 --- a/gui/lib/src/button/game_start_button.dart +++ b/gui/lib/src/widget/page/play/state_toggle.dart @@ -14,11 +14,13 @@ import 'package:reboot_launcher/src/controller/dll_controller.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; import 'package:reboot_launcher/src/controller/server_browser_controller.dart'; -import 'package:reboot_launcher/src/message/backend.dart'; +import 'package:reboot_launcher/src/widget/dialog/dialog.dart'; +import 'package:reboot_launcher/src/widget/dialog/dialog_button.dart'; +import 'package:reboot_launcher/src/widget/dialog/info_dialog.dart'; +import 'package:reboot_launcher/src/widget/section/backend/message.dart'; import 'package:reboot_launcher/src/util/matchmaker.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:version/version.dart'; @@ -43,8 +45,8 @@ class _LaunchButtonState extends State { final DllController _dllController = Get.find(); final ServerBrowserController _serverBrowserController = Get.find(); - InfoBarEntry? _gameClientInfoBar; - InfoBarEntry? _gameServerInfoBar; + SnackBar? _gameClientInfoBar; + SnackBar? _gameServerInfoBar; CancelableOperation? _operation; Completer? _pingOperation; @@ -106,7 +108,7 @@ class _LaunchButtonState extends State { final backendResult = _backendController.started() || await _backendController.toggle( eventHandler: (type, event) { _backendController.started.value = event.type.isStart && !event.type.isError; - if(event.type == AuthBackendResultType.startedImplementation) { + if(event.type == AuthBackendEventType.startedImplementation) { _backendController.implementation = event.implementation; } return onBackendResult(type, event); @@ -211,16 +213,16 @@ class _LaunchButtonState extends State { return false; } - final result = await showRebootDialog( + final result = await Dialog.open( builder: (context) => InfoDialog( text: translations.automaticGameServerDialogContent, buttons: [ DialogButton( - type: ButtonType.secondary, + type: DialogButtonType.secondary, text: translations.automaticGameServerDialogIgnore ), DialogButton( - type: ButtonType.primary, + type: DialogButtonType.primary, text: translations.automaticGameServerDialogStart, onTap: () => Navigator.of(context).pop(true), ), @@ -469,7 +471,7 @@ class _LaunchButtonState extends State { void _onGameClientInjected() { _gameClientInfoBar?.close(); - showRebootInfoBar( + SnackBar.open( translations.gameClientStarted, severity: InfoBarSeverity.success, duration: infoBarLongDuration @@ -488,7 +490,7 @@ class _LaunchButtonState extends State { final started = await _checkLocalGameServer(gameServerPort); if(!started) { if (_hostingController.instance.value?.killed != true) { - showRebootInfoBar( + SnackBar.open( translations.gameServerStartWarning, severity: InfoBarSeverity.error, duration: infoBarLongDuration @@ -500,7 +502,7 @@ class _LaunchButtonState extends State { final accessible = await _checkPublicGameServer(gameServerPort); if (!accessible) { - showRebootInfoBar( + SnackBar.open( translations.gameServerStartLocalWarning, severity: InfoBarSeverity.warning, duration: infoBarLongDuration, @@ -514,7 +516,7 @@ class _LaunchButtonState extends State { final serverBrowserEntry = await _hostingController.createServerBrowserEntry(); await _serverBrowserController.addServer(serverBrowserEntry); - showRebootInfoBar( + SnackBar.open( translations.gameServerStarted, severity: InfoBarSeverity.success, duration: infoBarLongDuration @@ -526,7 +528,7 @@ class _LaunchButtonState extends State { Future _checkLocalGameServer(String gameServerPort) async { try { - _gameServerInfoBar = showRebootInfoBar( + _gameServerInfoBar = SnackBar.open( translations.waitingForGameServer, loading: true, duration: null @@ -549,7 +551,7 @@ class _LaunchButtonState extends State { Future _checkPublicGameServer(String gameServerPort) async { try { - _gameServerInfoBar = showRebootInfoBar( + _gameServerInfoBar = SnackBar.open( translations.checkingGameServer, loading: true, duration: null @@ -668,21 +670,21 @@ class _LaunchButtonState extends State { case _StopReason.normal: break; case _StopReason.missingVersionError: - showRebootInfoBar( + SnackBar.open( translations.missingVersionError, severity: InfoBarSeverity.error, duration: infoBarLongDuration, ); break; case _StopReason.missingExecutableError: - showRebootInfoBar( + SnackBar.open( translations.missingExecutableError, severity: InfoBarSeverity.error, duration: infoBarLongDuration, ); break; case _StopReason.multipleExecutablesError: - showRebootInfoBar( + SnackBar.open( translations.multipleExecutablesError(error ?? translations.unknown), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -691,7 +693,7 @@ class _LaunchButtonState extends State { case _StopReason.exitCode: if(instance != null && !instance.launched) { final injectedDlls = instance.injectedDlls; - showRebootInfoBar( + SnackBar.open( translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -700,7 +702,7 @@ class _LaunchButtonState extends State { break; case _StopReason.corruptedVersionError: final injectedDlls = instance?.injectedDlls ?? []; - showRebootInfoBar( + SnackBar.open( translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -711,14 +713,14 @@ class _LaunchButtonState extends State { ); break; case _StopReason.corruptedDllError: - showRebootInfoBar( + SnackBar.open( translations.corruptedDllError(error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration, ); break; case _StopReason.missingCustomDllError: - showRebootInfoBar( + SnackBar.open( translations.missingCustomDllError(error!), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -727,7 +729,7 @@ class _LaunchButtonState extends State { case _StopReason.tokenError: _backendController.stop(); final injectedDlls = instance?.injectedDlls; - showRebootInfoBar( + SnackBar.open( translations.tokenError(injectedDlls == null || injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -738,21 +740,21 @@ class _LaunchButtonState extends State { ); break; case _StopReason.crash: - showRebootInfoBar( + SnackBar.open( translations.fortniteCrashError(host ? translations.gameServer : translations.client), severity: InfoBarSeverity.error, duration: infoBarLongDuration, ); break; case _StopReason.unknownError: - showRebootInfoBar( + SnackBar.open( translations.unknownFortniteError(error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration, ); break; case _StopReason.gameServerPortError: - showRebootInfoBar( + SnackBar.open( translations.gameServerPortEqualsBackendPort(kDefaultBackendPort), severity: InfoBarSeverity.error, duration: infoBarLongDuration, @@ -829,14 +831,14 @@ class _LaunchButtonState extends State { return null; } - InfoBarEntry _showLaunchingGameServerWidget() => _gameServerInfoBar = showRebootInfoBar( + SnackBar _showLaunchingGameServerWidget() => _gameServerInfoBar = SnackBar.open( translations.launchingGameServer, loading: true, duration: null ); - InfoBarEntry _showLaunchingGameClientWidget(GameVersion version, bool headless, bool linkedHosting) { - return _gameClientInfoBar = showRebootInfoBar( + SnackBar _showLaunchingGameClientWidget(GameVersion version, bool headless, bool linkedHosting) { + return _gameClientInfoBar = SnackBar.open( linkedHosting ? translations.launchingGameClientAndServer : translations.launchingGameClientOnly, loading: true, duration: null, diff --git a/gui/lib/src/widget/page/settings/file_tile.dart b/gui/lib/src/widget/page/settings/file_tile.dart new file mode 100644 index 0000000..a56bed1 --- /dev/null +++ b/gui/lib/src/widget/page/settings/file_tile.dart @@ -0,0 +1,142 @@ +import 'dart:io'; + +import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons; +import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/foundation.dart'; +import 'package:reboot_launcher/src/util/os.dart'; +import 'package:reboot_launcher/src/util/translations.dart'; +import 'package:reboot_launcher/src/widget/file_selector.dart'; +import 'package:reboot_launcher/src/widget/section/setting_tile.dart'; + +class FileSetting extends StatefulWidget { + final GlobalKey validatorKey; + final String title; + final String description; + final TextEditingController controller; + final VoidCallback onReset; + final String extension; + final bool folder; + + const FileSetting({ + Key? key, + required this.validatorKey, + required this.title, + required this.description, + required this.controller, + required this.onReset, + this.extension = 'dll', + this.folder = false, + }) : super(key: key); + + @override + State createState() => _FileSettingState(); +} + +class _FileSettingState extends State { + static const double _kButtonDimensions = 30; + static const double _kButtonSpacing = 8; + + String? _validationMessage; + bool _selecting = false; + + @override + Widget build(BuildContext context) { + return SettingTile( + icon: const Icon(FluentIcons.document_24_regular), + title: Text(widget.title), + subtitle: Text(widget.description), + contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions, + content: Row( + children: [ + Expanded( + child: FileSelector( + placeholder: translations.selectPathPlaceholder, + windowTitle: translations.selectPathWindowTitle, + controller: widget.controller, + validator: (text) { + final result = _checkDll(text); + setState(() => _validationMessage = result); + return result; + }, + extension: widget.extension, + folder: widget.folder, + validatorMode: AutovalidateMode.always, + allowNavigator: false, + validatorKey: widget.validatorKey, + ), + ), + const SizedBox(width: _kButtonSpacing), + Padding( + padding: EdgeInsets.only(bottom: _validationMessage == null ? 0.0 : 20.0), + child: Tooltip( + message: translations.selectFile, + child: Button( + style: ButtonStyle(padding: WidgetStateProperty.all(EdgeInsets.zero)), + onPressed: _selecting ? null : _onSelectPressed, + child: SizedBox.square( + dimension: _kButtonDimensions, + child: const Icon(fluentIcons.FluentIcons.open_folder_horizontal), + ), + ), + ), + ), + const SizedBox(width: _kButtonSpacing), + Padding( + padding: EdgeInsets.only(bottom: _validationMessage == null ? 0.0 : 20.0), + child: Tooltip( + message: translations.reset, + child: Button( + style: ButtonStyle(padding: WidgetStateProperty.all(EdgeInsets.zero)), + onPressed: widget.onReset, + child: SizedBox.square( + dimension: _kButtonDimensions, + child: const Icon(FluentIcons.arrow_reset_24_regular), + ), + ), + ), + ), + ], + ), + ); + } + + Future _onSelectPressed() async { + if (_selecting) return; + setState(() => _selecting = true); + + try { + final picked = await compute(openFilePicker, widget.extension); + _updateText(widget.controller, picked); + } finally { + if (mounted) { + setState(() => _selecting = false); + } + } + } + + void _updateText(TextEditingController controller, String? value) { + final text = value ?? controller.text; + controller.text = text; + controller.selection = TextSelection.collapsed(offset: text.length); + } + + String? _checkDll(String? text) { + if (text == null || text.isEmpty) { + return translations.invalidDllPath; + } + + final file = File(text); + try { + file.readAsBytesSync(); + } catch (_) { + return translations.dllDoesNotExist; + } + + if (!text.endsWith('.dll')) { + return translations.invalidDllExtension; + } + + return null; + } +} diff --git a/gui/lib/src/page/settings_page.dart b/gui/lib/src/widget/page/settings/page.dart similarity index 86% rename from gui/lib/src/page/settings_page.dart rename to gui/lib/src/widget/page/settings/page.dart index e1546b7..6fef564 100644 --- a/gui/lib/src/page/settings_page.dart +++ b/gui/lib/src/widget/page/settings/page.dart @@ -8,12 +8,11 @@ import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/l10n/reboot_localizations.dart'; import 'package:reboot_launcher/src/controller/dll_controller.dart'; import 'package:reboot_launcher/src/controller/settings_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/file_setting_tile.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/settings/file_tile.dart'; +import 'package:reboot_launcher/src/widget/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; import 'package:url_launcher/url_launcher.dart'; final GlobalKey settingsConsoleDllInputKey = GlobalKey(); @@ -63,42 +62,42 @@ class _SettingsPageState extends AbstractPageState { title: Text(translations.settingsClientName), subtitle: Text(translations.settingsClientDescription), children: [ - createFileSetting( - key: settingsConsoleDllInputKey, - title: translations.settingsClientConsoleName, - description: translations.settingsClientConsoleDescription, - controller: _dllController.unrealEngineConsoleDll, - onReset: () async { + FileSetting( + validatorKey: settingsConsoleDllInputKey, + title: translations.settingsClientConsoleName, + description: translations.settingsClientConsoleDescription, + controller: _dllController.unrealEngineConsoleDll, + onReset: () async { final path = _dllController.getDefaultDllPath(GameDll.console); _dllController.unrealEngineConsoleDll.text = path; await _dllController.download(GameDll.console, path, force: true); settingsConsoleDllInputKey.currentState?.validate(); - } - ), - createFileSetting( - key: settingsAuthDllInputKey, - title: translations.settingsClientAuthName, - description: translations.settingsClientAuthDescription, - controller: _dllController.backendDll, - onReset: () async { + }, + ), + FileSetting( + validatorKey: settingsAuthDllInputKey, + title: translations.settingsClientAuthName, + description: translations.settingsClientAuthDescription, + controller: _dllController.backendDll, + onReset: () async { final path = _dllController.getDefaultDllPath(GameDll.auth); _dllController.backendDll.text = path; await _dllController.download(GameDll.auth, path, force: true); settingsAuthDllInputKey.currentState?.validate(); - } - ), - createFileSetting( - key: settingsMemoryDllInputKey, - title: translations.settingsClientMemoryName, - description: translations.settingsClientMemoryDescription, - controller: _dllController.memoryLeakDll, - onReset: () async { + }, + ), + FileSetting( + validatorKey: settingsMemoryDllInputKey, + title: translations.settingsClientMemoryName, + description: translations.settingsClientMemoryDescription, + controller: _dllController.memoryLeakDll, + onReset: () async { final path = _dllController.getDefaultDllPath(GameDll.memoryLeak); _dllController.memoryLeakDll.text = path; await _dllController.download(GameDll.memoryLeak, path, force: true); settingsAuthDllInputKey.currentState?.validate(); - } - ), + }, + ), _gameServer ], ); @@ -124,8 +123,6 @@ class _SettingsPageState extends AbstractPageState { subtitle: Text(translations.settingsServerTypeDescription), contentWidth: SettingTile.kDefaultContentWidth + 30, content: Obx(() => DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName), items: { false: translations.settingsServerTypeEmbeddedName, @@ -200,18 +197,18 @@ class _SettingsPageState extends AbstractPageState { ) ); }else { - return createFileSetting( - key: settingsGameServerDllInputKey, - title: translations.settingsOldServerFileName, - description: translations.settingsServerFileDescription, - controller: _dllController.customGameServerDll, - onReset: () async { + return FileSetting( + validatorKey: settingsGameServerDllInputKey, + title: translations.settingsOldServerFileName, + description: translations.settingsServerFileDescription, + controller: _dllController.customGameServerDll, + onReset: () async { final path = _dllController.getDefaultDllPath(GameDll.gameServer); _dllController.customGameServerDll.text = path; await _dllController.download(GameDll.gameServer, path); settingsGameServerDllInputKey.currentState?.validate(); - } - ); + }, + ); } }); @@ -295,8 +292,6 @@ class _SettingsPageState extends AbstractPageState { title: Text(translations.settingsUtilsLanguageName), subtitle: Text(translations.settingsUtilsLanguageDescription), content: Obx(() => DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text(_getLocaleName(_settingsController.language.value)), items: AppLocalizations.supportedLocales.map((locale) => MenuFlyoutItem( text: Text(_getLocaleName(locale.languageCode)), @@ -321,8 +316,6 @@ class _SettingsPageState extends AbstractPageState { title: Text(translations.settingsUtilsThemeName), subtitle: Text(translations.settingsUtilsThemeDescription), content: Obx(() => DropDownButton( - onOpen: () => inDialog = true, - onClose: () => inDialog = false, leading: Text(_settingsController.themeMode.value.title), items: ThemeMode.values.map((themeMode) => MenuFlyoutItem( text: Text(themeMode.title), diff --git a/gui/lib/src/tile/profile_tile.dart b/gui/lib/src/widget/profile_tile.dart similarity index 86% rename from gui/lib/src/tile/profile_tile.dart rename to gui/lib/src/widget/profile_tile.dart index 0f91919..2b7ef5c 100644 --- a/gui/lib/src/tile/profile_tile.dart +++ b/gui/lib/src/widget/profile_tile.dart @@ -3,25 +3,25 @@ import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/controller/hosting_controller.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; import 'package:reboot_launcher/src/message/profile.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; -class ProfileWidget extends StatefulWidget { - final GlobalKey overlayKey; - const ProfileWidget({required this.overlayKey}); +class ProfileTile extends StatefulWidget { + final GlobalKey overlayKey; + const ProfileTile({required this.overlayKey}); @override - State createState() => _ProfileWidgetState(); + State createState() => _ProfileTileState(); } -class _ProfileWidgetState extends State { +class _ProfileTileState extends State { final GameController _gameController = Get.find(); final HostingController _hostingController = Get.find(); @override - Widget build(BuildContext context) => OverlayTarget( + Widget build(BuildContext context) => TutorialOverlayTarget( key: widget.overlayKey, child: HoverButton( margin: const EdgeInsets.all(8.0), diff --git a/gui/lib/src/page/pages.dart b/gui/lib/src/widget/sections.dart similarity index 60% rename from gui/lib/src/page/pages.dart rename to gui/lib/src/widget/sections.dart index 6aa78e5..fb563ec 100644 --- a/gui/lib/src/page/pages.dart +++ b/gui/lib/src/widget/sections.dart @@ -3,16 +3,16 @@ import 'dart:collection'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; -import 'package:reboot_launcher/src/pager/page_type.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/page/backend_page.dart'; -import 'package:reboot_launcher/src/page/browser_page.dart'; -import 'package:reboot_launcher/src/page/host_page.dart'; -import 'package:reboot_launcher/src/page/info_page.dart'; -import 'package:reboot_launcher/src/pager/abstract_page.dart'; -import 'package:reboot_launcher/src/page/play_page.dart'; -import 'package:reboot_launcher/src/page/settings_page.dart'; -import 'package:reboot_launcher/src/messenger/info_bar_area.dart'; +import 'package:reboot_launcher/src/widget/section/host/page.dart'; +import 'package:reboot_launcher/src/widget/section/info/page.dart'; +import 'package:reboot_launcher/src/widget/section/settings/page.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar_area.dart'; +import 'package:reboot_launcher/src/widget/page/page_type.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; +import 'package:reboot_launcher/src/widget/section/backend/page.dart'; +import 'package:reboot_launcher/src/widget/section/browser/page.dart'; +import 'package:reboot_launcher/src/widget/page/abstract_page.dart'; +import 'package:reboot_launcher/src/widget/section/play/page.dart'; final StreamController pagesController = StreamController.broadcast(); bool hitBack = false; @@ -26,7 +26,7 @@ final List pages = [ const SettingsPage() ]; -final List> _flyoutPageControllers = List.generate(pages.length, (_) => GlobalKey()); +final List> _flyoutPageControllers = List.generate(pages.length, (_) => GlobalKey()); final RxInt pageIndex = RxInt(PageType.play.index); @@ -36,7 +36,7 @@ final GlobalKey appNavigatorKey = GlobalKey(); final GlobalKey appOverlayKey = GlobalKey(); -final GlobalKey infoBarAreaKey = GlobalKey(); +final GlobalKey infoBarAreaKey = GlobalKey(); GlobalKey get pageKey => getPageKeyByIndex(pageIndex.value); @@ -82,6 +82,6 @@ void addSubPageToCurrent(String pageName) { pagesController.add(null); } -GlobalKey getOverlayTargetKeyByPage(int pageIndex) => _flyoutPageControllers[pageIndex]; +GlobalKey getOverlayTargetKeyByPage(int pageIndex) => _flyoutPageControllers[pageIndex]; -GlobalKey get pageOverlayTargetKey => _flyoutPageControllers[pageIndex.value]; +GlobalKey get pageOverlayTargetKey => _flyoutPageControllers[pageIndex.value]; diff --git a/gui/lib/src/tile/setting_tile.dart b/gui/lib/src/widget/setting_tile.dart similarity index 96% rename from gui/lib/src/tile/setting_tile.dart rename to gui/lib/src/widget/setting_tile.dart index e0c00d5..00398d4 100644 --- a/gui/lib/src/tile/setting_tile.dart +++ b/gui/lib/src/widget/setting_tile.dart @@ -1,7 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; import 'package:skeletons/skeletons.dart'; class SettingTile extends StatefulWidget { @@ -78,7 +78,7 @@ class SettingTileState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ if(widget.overlayKey != null) - OverlayTarget( + TutorialOverlayTarget( key: widget.overlayKey, child: isSkeleton ? _skeletonIcon : icon, ) diff --git a/gui/lib/src/widget/snackbar/snackbar.dart b/gui/lib/src/widget/snackbar/snackbar.dart new file mode 100644 index 0000000..6b83458 --- /dev/null +++ b/gui/lib/src/widget/snackbar/snackbar.dart @@ -0,0 +1,81 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; + +const infoBarLongDuration = Duration(seconds: 4); +const infoBarShortDuration = Duration(seconds: 2); +const _height = 64.0; + +class SnackBar { + static SnackBar open(String text, { + InfoBarSeverity severity = InfoBarSeverity.info, + bool loading = false, + Duration? duration = infoBarShortDuration, + void Function()? onDismissed, + Widget? action + }) { + final overlayEntry = SnackBar._internal( + overlay: ConstrainedBox( + constraints: BoxConstraints( + minHeight: _height + ), + child: Mica( + elevation: 1, + child: InfoBar( + title: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text(text) + ), + if(action != null) + action + ], + ), + isLong: false, + isIconVisible: true, + content: SizedBox( + width: double.infinity, + child: loading ? const Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 2.0, + right: 6.0 + ), + child: ProgressBar(), + ) : const SizedBox() + ), + severity: severity + ) + ), + ), + onDismissed: onDismissed + ); + if(duration != null) { + Future.delayed(duration) + .then((_) => WidgetsBinding.instance.addPostFrameCallback((timeStamp) => overlayEntry.close())); + } + return overlayEntry; + } + + final Widget overlay; + final void Function()? onDismissed; + + SnackBar._internal({ + required this.overlay, + required this.onDismissed + }) { + final context = pageKey.currentContext; + if(context != null) { + infoBarAreaKey.currentState?.insertChild(overlay); + } + } + + bool close() { + final result = infoBarAreaKey.currentState?.removeChild(overlay) ?? false; + if(result) { + onDismissed?.call(); + } + return result; + } +} \ No newline at end of file diff --git a/gui/lib/src/widget/snackbar/snackbar_area.dart b/gui/lib/src/widget/snackbar/snackbar_area.dart new file mode 100644 index 0000000..15533df --- /dev/null +++ b/gui/lib/src/widget/snackbar/snackbar_area.dart @@ -0,0 +1,53 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; + +class SnackBarArea extends StatefulWidget { + const SnackBarArea({super.key}); + + @override + State createState() => SnackBarAreaState(); +} + +class SnackBarAreaState extends State { + final List _children = []; + + void insertChild(Widget child) { + setState(() { + _children.add(child); + }); + } + + bool removeChild(Widget child) { + var result = false; + setState(() { + result = _children.remove(child); + }); + return result; + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: pagesController.stream, + builder: (context, _) => Padding( + padding: EdgeInsets.only( + bottom: hasPageButton ? 72.0 : 16.0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: _snackbars, + ), + ), + ); + } + + List get _snackbars => _children + .map((child) => _buildSnackbar(child)) + .toList(growable: false); + + Widget _buildSnackbar(Widget child) => Padding( + padding: const EdgeInsets.only(top: 12.0), + child: child, + ); +} \ No newline at end of file diff --git a/gui/lib/src/messenger/overlay.dart b/gui/lib/src/widget/tutorial/tutorial_overlay.dart similarity index 85% rename from gui/lib/src/messenger/overlay.dart rename to gui/lib/src/widget/tutorial/tutorial_overlay.dart index dc80b35..f420009 100644 --- a/gui/lib/src/messenger/overlay.dart +++ b/gui/lib/src/widget/tutorial/tutorial_overlay.dart @@ -1,20 +1,22 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/rendering.dart'; -import 'package:reboot_launcher/src/page/pages.dart'; +import 'package:reboot_launcher/src/widget/sections.dart'; + +import 'tutorial_overlay_attach_mode.dart'; typedef WidgetBuilder = Widget Function(BuildContext, void Function()); -class OverlayTarget extends StatefulWidget { +class TutorialOverlayTarget extends StatefulWidget { final Widget child; - const OverlayTarget({super.key, required this.child}); + const TutorialOverlayTarget({super.key, required this.child}); @override - State createState() => OverlayTargetState(); + State createState() => TutorialOverlayTargetState(); - OverlayTargetState of(BuildContext context) => context.findAncestorStateOfType()!; + TutorialOverlayTargetState of(BuildContext context) => context.findAncestorStateOfType()!; } -class OverlayTargetState extends State { +class TutorialOverlayTargetState extends State { @override Widget build(BuildContext context) => widget.child; @@ -23,7 +25,7 @@ class OverlayTargetState extends State { required WidgetBuilder actionBuilder, Offset offset = Offset.zero, bool ignoreTargetPointers = true, - AttachMode attachMode = AttachMode.start + TutorialOverlayAttachMode attachMode = TutorialOverlayAttachMode.start }) { final renderBox = context.findRenderObject() as RenderBox; final position = renderBox.localToGlobal(Offset.zero); @@ -40,10 +42,10 @@ class OverlayTargetState extends State { ) ), Positioned( - left: position.dx - (attachMode != AttachMode.start ? renderBox.size.width : 0) + offset.dx, + left: position.dx - (attachMode != TutorialOverlayAttachMode.start ? renderBox.size.width : 0) + offset.dx, top: position.dy + (renderBox.size.height / 2) + offset.dy, child: CustomPaint( - painter: _CallOutShape(color, attachMode != AttachMode.start), + painter: _CallOutShape(color, attachMode != TutorialOverlayAttachMode.start), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( @@ -65,12 +67,6 @@ class OverlayTargetState extends State { } } -enum AttachMode { - start, - middle, - end; -} - // Harder than one would think class _CallOutShape extends CustomPainter { final Color color; diff --git a/gui/lib/src/widget/tutorial/tutorial_overlay_attach_mode.dart b/gui/lib/src/widget/tutorial/tutorial_overlay_attach_mode.dart new file mode 100644 index 0000000..d1ff494 --- /dev/null +++ b/gui/lib/src/widget/tutorial/tutorial_overlay_attach_mode.dart @@ -0,0 +1,5 @@ +enum TutorialOverlayAttachMode { + start, + middle, + end; +} \ No newline at end of file diff --git a/gui/lib/src/button/version_selector.dart b/gui/lib/src/widget/version_selector.dart similarity index 96% rename from gui/lib/src/button/version_selector.dart rename to gui/lib/src/widget/version_selector.dart index ee68cdf..5a724d5 100644 --- a/gui/lib/src/button/version_selector.dart +++ b/gui/lib/src/widget/version_selector.dart @@ -7,10 +7,10 @@ import 'package:get/get.dart'; import 'package:reboot_common/common.dart'; import 'package:reboot_launcher/src/controller/game_controller.dart'; import 'package:reboot_launcher/src/util/translations.dart'; -import 'package:reboot_launcher/src/tile/setting_tile.dart'; -import 'package:reboot_launcher/src/messenger/dialog.dart'; -import 'package:reboot_launcher/src/messenger/info_bar.dart'; -import 'package:reboot_launcher/src/messenger/overlay.dart'; +import 'package:reboot_launcher/src/widget/setting_tile.dart'; +import 'package:reboot_launcher/src/widget/overlay/dialog.dart'; +import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart'; +import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart'; import 'package:reboot_launcher/src/message/download_version.dart'; import 'package:reboot_launcher/src/message/import_version.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -19,7 +19,7 @@ class VersionSelector extends StatefulWidget { const VersionSelector({Key? key}) : super(key: key); static SettingTile buildTile({ - required GlobalKey key + required GlobalKey key }) => SettingTile( icon: Icon( FluentIcons.play_24_regular @@ -221,7 +221,7 @@ class _VersionSelectorState extends State { } bool _onExplorerError() { - showRebootInfoBar( + SnackBar.open( translations.missingVersionError, severity: InfoBarSeverity.error, duration: infoBarLongDuration,