Final version

This commit is contained in:
Alessandro Autiero
2023-09-21 16:48:31 +02:00
parent 4bba21c038
commit 73c1cc8526
90 changed files with 3204 additions and 2608 deletions

View File

@@ -2,7 +2,8 @@ import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/translations.dart';
import 'dialog_button.dart';
@@ -92,17 +93,15 @@ class InfoDialog extends AbstractDialog {
width: double.infinity,
child: Text(text, textAlign: TextAlign.center)
),
buttons: buttons ?? [_createDefaultButton()],
buttons: buttons ?? [_defaultCloseButton],
padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0)
);
}
DialogButton _createDefaultButton() {
return DialogButton(
text: "Close",
type: ButtonType.only
);
}
DialogButton get _defaultCloseButton =>DialogButton(
text: translations.defaultDialogSecondaryAction,
type: ButtonType.only
);
}
class ProgressDialog extends AbstractDialog {
@@ -124,7 +123,7 @@ class ProgressDialog extends AbstractDialog {
),
buttons: [
DialogButton(
text: "Close",
text: translations.defaultDialogSecondaryAction,
type: ButtonType.only,
onTap: onStop
)
@@ -211,7 +210,7 @@ class FutureBuilderDialog extends AbstractDialog {
return DialogButton(
text: snapshot.hasData
|| snapshot.hasError
|| (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? "Close" : "Stop",
|| (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? translations.defaultDialogSecondaryAction : translations.stopLoadingDialogAction,
type: ButtonType.only,
onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData)
);
@@ -226,11 +225,11 @@ class ErrorDialog extends AbstractDialog {
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: "Copy error",
text: translations.copyErrorDialogTitle,
type: type,
onTap: () async {
FlutterClipboard.controlC("An error occurred: $error\nStacktrace:\n $stackTrace");
showInfoBar("Copied error to clipboard");
FlutterClipboard.controlC("$error\n$stackTrace");
showInfoBar(translations.copyErrorDialogSuccess);
onClick();
},
);

View File

@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/util/translations.dart';
class DialogButton extends StatefulWidget {
final String? text;
@@ -41,7 +42,7 @@ class _DialogButtonState extends State<DialogButton> {
Widget get _secondaryButton {
return Button(
onPressed: widget.onTap ?? _onDefaultSecondaryActionTap,
child: Text(widget.text ?? "Close"),
child: Text(widget.text ?? translations.defaultDialogSecondaryAction),
);
}

View File

@@ -1,15 +1,16 @@
import 'dart:collection';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:sync/semaphore.dart';
Semaphore _semaphore = Semaphore();
HashMap<int, OverlayEntry?> _overlays = HashMap();
void restoreMessage(int lastIndex) {
removeMessage(lastIndex);
var overlay = _overlays[pageIndex.value];
void restoreMessage(int pageIndex, int lastIndex) {
removeMessageByPage(lastIndex);
var overlay = _overlays[pageIndex];
if(overlay == null) {
return;
}
@@ -17,48 +18,62 @@ void restoreMessage(int lastIndex) {
Overlay.of(pageKey.currentContext!).insert(overlay);
}
void showInfoBar(dynamic text, {InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = snackbarShortDuration, Widget? action}) {
OverlayEntry showInfoBar(dynamic text,
{RebootPageType? pageType,
InfoBarSeverity severity = InfoBarSeverity.info,
bool loading = false,
Duration? duration = snackbarShortDuration,
Widget? action}) {
try {
_semaphore.acquire();
var index = pageIndex.value;
removeMessage(index);
var overlay = showSnackbar(
pageKey.currentContext!,
SizedBox(
width: double.infinity,
child: Mica(
child: InfoBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if(text is Widget)
text,
if(text is String)
Text(text),
if(action != null)
action
],
var index = pageType?.index ?? pageIndex.value;
removeMessageByPage(index);
var overlay = OverlayEntry(
builder: (context) => Padding(
padding: EdgeInsets.only(
right: 12.0,
left: 12.0,
bottom: pagesWithButtonIndexes.contains(index) ? 72.0 : 16.0
),
child: Align(
alignment: AlignmentDirectional.bottomCenter,
child: Container(
width: double.infinity,
constraints: const BoxConstraints(
maxWidth: 1000
),
child: Mica(
child: InfoBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if(text is Widget)
text,
if(text is String)
Text(text),
if(action != null)
action
],
),
isLong: false,
isIconVisible: true,
content: SizedBox(
width: double.infinity,
child: loading ? const Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 2.0),
child: ProgressBar(),
) : const SizedBox()
),
severity: severity
),
isLong: false,
isIconVisible: true,
content: SizedBox(
width: double.infinity,
child: loading ? const Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 2.0),
child: ProgressBar(),
) : const SizedBox()
),
severity: severity
),
),
),
),
margin: EdgeInsets.only(
right: 12.0,
left: 12.0,
bottom: index == 0 || index == 1 || index == 3 || index == 4 ? 72.0 : 16.0
),
duration: duration
)
);
if(index == pageIndex.value) {
Overlay.of(pageKey.currentContext!).insert(overlay);
}
_overlays[index] = overlay;
if(duration != null) {
Future.delayed(duration).then((_) {
@@ -73,17 +88,24 @@ void showInfoBar(dynamic text, {InfoBarSeverity severity = InfoBarSeverity.info,
});
});
}
return overlay;
}finally {
_semaphore.release();
}
}
void removeMessage(int index) {
void removeMessageByPage(int index) {
var lastOverlay = _overlays[index];
if(lastOverlay != null) {
removeMessageByOverlay(lastOverlay);
_overlays[index] = null;
}
}
void removeMessageByOverlay(OverlayEntry? overlay) {
try {
var lastOverlay = _overlays[index];
if(lastOverlay != null) {
lastOverlay.remove();
_overlays[index] = null;
if(overlay != null) {
overlay.remove();
}
}catch(_) {
// Do not use .isMounted

View File

@@ -0,0 +1,24 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/util/translations.dart';
Future<void> showResetDialog(Function() onConfirm) => showAppDialog(
builder: (context) => InfoDialog(
text: translations.resetDefaultsDialogTitle,
buttons: [
DialogButton(
type: ButtonType.secondary,
text: translations.resetDefaultsDialogSecondaryAction,
),
DialogButton(
type: ButtonType.primary,
text: translations.resetDefaultsDialogPrimaryAction,
onTap: () {
onConfirm();
Navigator.of(context).pop();
},
)
],
)
);

View File

@@ -1,12 +1,14 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/translations.dart';
String? lastError;
void onError(Object? exception, StackTrace? stackTrace, bool framework) {
if(exception == null){
void onError(Object exception, StackTrace? stackTrace, bool framework) {
if(!kDebugMode) {
return;
}
@@ -29,7 +31,7 @@ void onError(Object? exception, StackTrace? stackTrace, bool framework) {
ErrorDialog(
exception: exception,
stackTrace: stackTrace,
errorMessageBuilder: (exception) => framework ? "An error was thrown by Flutter: $exception" : "An uncaught error was thrown: $exception"
errorMessageBuilder: (exception) => translations.uncaughtErrorMessage(exception.toString())
)
));
}

View File

@@ -1,69 +0,0 @@
import 'package:reboot_common/common.dart';
import '../abstract/dialog.dart';
const String _unsupportedServerError = "The build you are currently using is not supported by Reboot. "
"If you are unsure which version works best, use build 7.40. "
"If you are a passionate programmer you can add support by opening a PR on Github. ";
const String _corruptedBuildError = "An unknown occurred while launching Fortnite. "
"Some critical files could be missing in your installation. "
"Download the build again from the launcher, not locally, or from a different source. "
"Alternatively, something could have gone wrong in the launcher. ";
Future<void> showMissingDllError(String name) async {
showAppDialog(
builder: (context) => InfoDialog(
text: "$name dll is not a valid dll, fix it in the settings tab"
)
);
}
Future<void> showTokenErrorFixable() async {
showAppDialog(
builder: (context) => const InfoDialog(
text: "A token error occurred. "
"The backend server has been automatically restarted to fix the issue. "
"The game has been restarted automatically. "
)
);
}
Future<void> showTokenErrorUnfixable() async {
showAppDialog(
builder: (context) => const InfoDialog(
text: "A token error occurred. "
"This issue cannot be resolved automatically as the server isn't embedded."
"Please restart the server manually, then relaunch your game to check if the issue has been fixed. "
"Otherwise, open an issue on Discord."
)
);
}
Future<void> showCorruptedBuildError(bool server, [Object? error, StackTrace? stackTrace]) async {
if(error == null) {
showAppDialog(
builder: (context) => InfoDialog(
text: server ? _unsupportedServerError : _corruptedBuildError
)
);
return;
}
showAppDialog(
builder: (context) => ErrorDialog(
exception: error,
stackTrace: stackTrace,
errorMessageBuilder: (exception) => "${_corruptedBuildError}Error message: $exception"
)
);
}
Future<void> showMissingBuildError(FortniteVersion version) async {
showAppDialog(
builder: (context) => InfoDialog(
text: "${version.location.path} no longer contains a Fortnite executable. "
"This probably means that you deleted it or move it somewhere else."
)
);
}

View File

@@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/util/translations.dart';
final GameController _gameController = Get.find<GameController>();
@@ -20,9 +21,9 @@ Future<bool> showProfileForm(BuildContext context) async{
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoLabel(
label: "Username/Email",
label: translations.usernameOrEmail,
child: TextFormBox(
placeholder: "Type your username or email",
placeholder: translations.usernameOrEmailPlaceholder,
controller: _gameController.username,
autovalidateMode: AutovalidateMode.always,
enableSuggestions: true,
@@ -32,9 +33,9 @@ Future<bool> showProfileForm(BuildContext context) async{
),
const SizedBox(height: 16.0),
InfoLabel(
label: "Password",
label: translations.password,
child: TextFormBox(
placeholder: "Type your password, if you have one",
placeholder: translations.passwordPlaceholder,
controller: _gameController.password,
autovalidateMode: AutovalidateMode.always,
obscureText: !showPassword.value,
@@ -59,16 +60,14 @@ Future<bool> showProfileForm(BuildContext context) async{
),
buttons: [
DialogButton(
text: "Cancel",
text: translations.cancelProfileChanges,
type: ButtonType.secondary
),
DialogButton(
text: "Save",
text: translations.saveProfileChanges,
type: ButtonType.primary,
onTap: () {
Navigator.of(context).pop(true);
}
onTap: () => Navigator.of(context).pop(true)
)
]
))

View File

@@ -12,29 +12,27 @@ import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/cryptography.dart';
import 'package:reboot_launcher/src/util/matchmaker.dart';
import 'package:reboot_launcher/src/util/translations.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
extension ServerControllerDialog on ServerController {
Future<bool> restartInteractive() async {
var stream = restart();
return await _handleStream(stream, false);
}
Future<bool> toggleInteractive([bool showSuccessMessage = true]) async {
Future<bool> toggleInteractive(RebootPageType caller, [bool showSuccessMessage = true]) async {
var stream = toggle();
return await _handleStream(stream, showSuccessMessage);
return await _handleStream(caller, stream, showSuccessMessage);
}
Future<bool> _handleStream(Stream<ServerResult> stream, bool showSuccessMessage) async {
Future<bool> _handleStream(RebootPageType caller, Stream<ServerResult> stream, bool showSuccessMessage) async {
var completer = Completer<bool>();
stream.listen((event) {
worker = stream.listen((event) {
switch (event.type) {
case ServerResultType.starting:
showInfoBar(
"Starting the $controllerName...",
translations.startingServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -43,7 +41,8 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.startSuccess:
if(showSuccessMessage) {
showInfoBar(
"The $controllerName was started successfully",
translations.startedServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.success
);
}
@@ -51,14 +50,17 @@ extension ServerControllerDialog on ServerController {
break;
case ServerResultType.startError:
showInfoBar(
"An error occurred while starting the $controllerName: ${event.error ?? "unknown error"}",
translations.startServerError(
event.error ?? translations.unknownError, controllerName),
pageType: caller,
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
break;
case ServerResultType.stopping:
showInfoBar(
"Stopping the $controllerName...",
translations.stoppingServer,
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -67,7 +69,8 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.stopSuccess:
if(showSuccessMessage) {
showInfoBar(
"The $controllerName was stopped successfully",
translations.stoppedServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.success
);
}
@@ -75,46 +78,54 @@ extension ServerControllerDialog on ServerController {
break;
case ServerResultType.stopError:
showInfoBar(
"An error occurred while stopping the $controllerName: ${event.error ?? "unknown error"}",
translations.stopServerError(
event.error ?? translations.unknownError, controllerName),
pageType: caller,
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
break;
case ServerResultType.missingHostError:
showInfoBar(
"Missing hostname in $controllerName configuration",
translations.missingHostNameError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.missingPortError:
showInfoBar(
"Missing port in $controllerName configuration",
translations.missingPortError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.illegalPortError:
showInfoBar(
"Invalid port in $controllerName configuration",
translations.illegalPortError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.freeingPort:
showInfoBar(
"Freeing port $defaultPort...",
translations.freeingPort(defaultPort),
pageType: caller,
loading: true,
duration: null
);
break;
case ServerResultType.freePortSuccess:
showInfoBar(
"Port $defaultPort was freed successfully",
translations.freedPort(defaultPort),
pageType: caller,
severity: InfoBarSeverity.success,
duration: snackbarShortDuration
);
break;
case ServerResultType.freePortError:
showInfoBar(
"An error occurred while freeing port $defaultPort: ${event.error ?? "unknown error"}",
translations.freePortError(event.error ?? translations.unknownError, controllerName),
pageType: caller,
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
@@ -122,7 +133,8 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.pingingRemote:
if(started.value) {
showInfoBar(
"Pinging the remote $controllerName...",
translations.pingingRemoteServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -132,7 +144,8 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.pingingLocal:
if(started.value) {
showInfoBar(
"Pinging the ${type().name} $controllerName...",
translations.pingingLocalServer(controllerName, type().name),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -141,7 +154,8 @@ extension ServerControllerDialog on ServerController {
break;
case ServerResultType.pingError:
showInfoBar(
"Cannot ping ${type().name} $controllerName",
translations.pingError(controllerName, type().name),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
@@ -175,7 +189,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
var id = entry["id"];
if(uuid == id) {
showInfoBar(
"You can't join your own server",
translations.joinSelfServer,
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
@@ -204,7 +218,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
if(!checkPassword(confirmPassword, hashedPassword)) {
showInfoBar(
"Wrong password: please try again",
translations.wrongServerPassword,
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
@@ -227,7 +241,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
}
showInfoBar(
"This server isn't online right now: please try again later",
translations.offlineServer,
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
@@ -246,9 +260,9 @@ extension MatchmakerControllerExtension on MatchmakerController {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoLabel(
label: "Password",
label: translations.serverPassword,
child: Obx(() => TextFormBox(
placeholder: "Type the server's password",
placeholder: translations.serverPasswordPlaceholder,
controller: confirmPasswordController,
autovalidateMode: AutovalidateMode.always,
obscureText: !showPassword.value,
@@ -274,12 +288,12 @@ extension MatchmakerControllerExtension on MatchmakerController {
),
buttons: [
DialogButton(
text: "Cancel",
text: translations.serverPasswordCancel,
type: ButtonType.secondary
),
DialogButton(
text: "Confirm",
text: translations.serverPasswordConfirm,
type: ButtonType.primary,
onTap: () => Navigator.of(context).pop(confirmPasswordController.text)
)
@@ -297,7 +311,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
FlutterClipboard.controlC(decryptedIp);
}
WidgetsBinding.instance.addPostFrameCallback((_) => showInfoBar(
embedded ? "You joined $author's server successfully!" : "Copied IP to the clipboard",
embedded ? translations.joinedServer(author) : translations.copiedIp,
duration: snackbarLongDuration,
severity: InfoBarSeverity.success
));