<feat: New release>

This commit is contained in:
Alessandro Autiero
2023-09-09 12:46:16 +02:00
parent badf41b044
commit 485e757e83
424 changed files with 37224 additions and 818815 deletions

View File

@@ -1,26 +1,28 @@
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
import 'package:reboot_launcher/src/controller/update_controller.dart';
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/interactive/error.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/dialog/implementation/error.dart';
import 'package:reboot_launcher/src/controller/build_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/authenticator_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/interactive/server.dart';
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/util/watch.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:system_theme/system_theme.dart';
import 'package:window_manager/window_manager.dart';
import 'package:url_protocol/url_protocol.dart';
import 'package:window_manager/window_manager.dart';
const double kDefaultWindowWidth = 1536;
const double kDefaultWindowHeight = 1024;
@@ -56,44 +58,57 @@ Future<Object?> _initUrlHandler() async {
var appLinks = AppLinks();
var initialUrl = await appLinks.getInitialAppLink();
if(initialUrl != null) {
_joinServer(initialUrl);
}
var gameController = Get.find<GameController>();
var matchmakerController = Get.find<MatchmakerController>();
appLinks.uriLinkStream.listen((uri) {
var uuid = _parseCustomUrl(uri);
var server = gameController.findServerById(uuid);
if(server != null) {
matchmakerController.joinServer(server);
return;
}
showMessage(
"No server found: invalid or expired link",
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
});
appLinks.uriLinkStream.listen(_joinServer);
return null;
}catch(error) {
return error;
}
}
void _joinServer(Uri uri) {
var gameController = Get.find<GameController>();
var matchmakerController = Get.find<MatchmakerController>();
var uuid = _parseCustomUrl(uri);
var server = gameController.findServerById(uuid);
if(server != null) {
matchmakerController.joinServer(server);
}else {
showInfoBar(
"No server found: invalid or expired link",
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
}
}
String _parseCustomUrl(Uri uri) => uri.host;
Future<Object?> _initWindow() async {
try {
await windowManager.ensureInitialized();
await Window.initialize();
var settingsController = Get.find<SettingsController>();
var size = Size(settingsController.width, settingsController.height);
await windowManager.setSize(size);
if(settingsController.offsetX != null && settingsController.offsetY != null){
await windowManager.setPosition(Offset(settingsController.offsetX!, settingsController.offsetY!));
appWindow.size = size;
var offsetX = settingsController.offsetX;
var offsetY = settingsController.offsetY;
if(offsetX != null && offsetY != null){
appWindow.position = Offset(
offsetX,
offsetY
);
}else {
await windowManager.setAlignment(Alignment.center);
appWindow.alignment = Alignment.center;
}
await Window.setEffect(
effect: WindowEffect.acrylic,
color: Colors.transparent,
dark: true
);
return null;
}catch(error) {
return error;
@@ -105,9 +120,11 @@ Object? _initObservers() {
var gameController = Get.find<GameController>();
var gameInstance = gameController.instance.value;
gameInstance?.startObserver();
gameController.saveInstance();
var hostingController = Get.find<HostingController>();
var hostingInstance = hostingController.instance.value;
hostingInstance?.startObserver();
hostingController.saveInstance();
return null;
}catch(error) {
return error;
@@ -133,7 +150,6 @@ Future<Object?> _initStorage() async {
updateController.update();
return null;
}catch(error) {
print(error);
return error;
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
@@ -21,4 +23,10 @@ class AuthenticatorController extends ServerController {
@override
Future<bool> freePort() => freeAuthenticatorPort();
@override
Future<int> startEmbeddedInternal() => startEmbeddedAuthenticator(detached.value);
@override
Future<Uri?> pingServer(String host, String port) => pingAuthenticator(host, port);
}

View File

@@ -1,10 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart';
import 'package:supabase/src/supabase_stream_builder.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:uuid/uuid.dart';
@@ -47,7 +49,7 @@ class GameController extends GetxController {
servers = Rxn();
supabase.from('hosts')
.stream(primaryKey: ['id'])
.map((event) => event.where((element) => element["ip"] != null).toSet())
.map((event) => _parseValidServers(event))
.listen((event) {
if(servers.value == null) {
servers.value = event;
@@ -57,9 +59,17 @@ class GameController extends GetxController {
});
var serializedInstance = _storage.read("instance");
instance = Rxn(serializedInstance != null ? GameInstance.fromJson(jsonDecode(serializedInstance)) : null);
instance.listen((value) => _storage.write("instance", jsonEncode(value?.toJson())));
instance.listen((_) => saveInstance());
}
Set<Map<String, dynamic>> _parseValidServers(SupabaseStreamEvent event) => event.where((element) => _isValidServer(element)).toSet();
bool _isValidServer(Map<String, dynamic> element) =>
(kDebugMode || element["id"] != uuid) && element["ip"] != null;
Future<void> saveInstance() =>
_storage.write("instance", jsonEncode(instance.value?.toJson()));
void reset() {
username.text = kDefaultPlayerName;
password.text = "";
@@ -116,7 +126,6 @@ class GameController extends GetxController {
Map<String, dynamic>? findServerById(String uuid) {
try {
print(uuid);
return servers.value?.firstWhere((element) => element["id"] == uuid);
} on StateError catch(_) {
return null;

View File

@@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/util/watch.dart';
const String kDefaultServerName = "Reboot Game Server";
const String kDefaultDescription = "Just another server";
@@ -32,9 +33,11 @@ class HostingController extends GetxController {
showPassword = RxBool(false);
var serializedInstance = _storage.read("instance");
instance = Rxn(serializedInstance != null ? GameInstance.fromJson(jsonDecode(serializedInstance)) : null);
instance.listen((value) => _storage.write("instance", jsonEncode(value?.toJson())));
instance.listen((_) => saveInstance());
}
Future<void> saveInstance() => _storage.write("instance", jsonEncode(instance.value?.toJson()));
void reset() {
name.text = kDefaultServerName;
description.text = kDefaultDescription;

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
@@ -7,7 +7,11 @@ class MatchmakerController extends ServerController {
MatchmakerController() : super() {
gameServerAddress = TextEditingController(text: storage.read("game_server_address") ?? kDefaultMatchmakerHost);
gameServerAddress.addListener(() => storage.write("game_server_address", gameServerAddress.text));
writeMatchmakingIp(gameServerAddress.text);
gameServerAddress.addListener(() {
storage.write("game_server_address", gameServerAddress.text);
writeMatchmakingIp(gameServerAddress.text);
});
}
@override
@@ -27,4 +31,10 @@ class MatchmakerController extends ServerController {
@override
Future<bool> freePort() => freeMatchmakerPort();
@override
Future<int> startEmbeddedInternal() => startEmbeddedMatchmaker(detached.value);
@override
Future<Uri?> pingServer(String host, String port) => pingMatchmaker(host, port);
}

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart';
@@ -15,7 +15,7 @@ abstract class ServerController extends GetxController {
late final Semaphore semaphore;
late RxBool started;
late RxBool detached;
Process? embeddedServer;
int? embeddedServerPid;
HttpServer? localServer;
HttpServer? remoteServer;
@@ -52,12 +52,17 @@ abstract class ServerController extends GetxController {
String get defaultPort;
Future<Uri?> pingServer(String host, String port);
Future<bool> get isPortFree;
Future<bool> get isPortTaken async => !(await isPortFree);
Future<bool> freePort();
@protected
Future<int> startEmbeddedInternal();
void reset() async {
type.value = ServerType.values.elementAt(0);
for (var type in ServerType.values) {
@@ -80,22 +85,31 @@ abstract class ServerController extends GetxController {
storage.read("${type.value.name}_port") ?? defaultPort;
Stream<ServerResult> start() async* {
if(started.value) {
return;
}
yield ServerResult(ServerResultType.starting);
started.value = true;
try {
var host = this.host.text.trim();
if (host.isEmpty) {
yield ServerResult(ServerResultType.missingHostError);
started.value = false;
return;
}
var port = this.port.text.trim();
if (port.isEmpty) {
yield ServerResult(ServerResultType.missingPortError);
started.value = false;
return;
}
var portNumber = int.tryParse(port);
if (portNumber == null) {
yield ServerResult(ServerResultType.illegalPortError);
started.value = false;
return;
}
@@ -104,18 +118,20 @@ abstract class ServerController extends GetxController {
var result = await freePort();
yield ServerResult(result ? ServerResultType.freePortSuccess : ServerResultType.freePortError);
if(!result) {
started.value = false;
return;
}
}
switch(type()){
case ServerType.embedded:
embeddedServer = await startEmbeddedAuthenticator(detached());
embeddedServerPid = await startEmbeddedInternal();
break;
case ServerType.remote:
yield ServerResult(ServerResultType.pingingRemote);
var uriResult = await ping(host, port);
var uriResult = await pingServer(host, port);
if(uriResult == null) {
yield ServerResult(ServerResultType.pingError);
started.value = false;
return;
}
@@ -130,29 +146,35 @@ abstract class ServerController extends GetxController {
}
yield ServerResult(ServerResultType.pingingLocal);
var uriResult = await pingSelf(defaultPort);
var uriResult = await pingServer(defaultHost, defaultPort);
if(uriResult == null) {
yield ServerResult(ServerResultType.pingError);
started.value = false;
return;
}
yield ServerResult(ServerResultType.startSuccess);
started.value = true;
}catch(error, stackTrace) {
yield ServerResult(
ServerResultType.startError,
error: error,
stackTrace: stackTrace
);
started.value = false;
}
}
Future<bool> stop() async {
Stream<ServerResult> stop() async* {
if(!started.value) {
return;
}
yield ServerResult(ServerResultType.stopping);
started.value = false;
try{
switch(type()){
case ServerType.embedded:
freePort();
Process.killPid(embeddedServerPid!, ProcessSignal.sigabrt);
break;
case ServerType.remote:
await remoteServer?.close(force: true);
@@ -163,17 +185,21 @@ abstract class ServerController extends GetxController {
localServer = null;
break;
}
return true;
}catch(_){
yield ServerResult(ServerResultType.stopSuccess);
}catch(error, stackTrace){
yield ServerResult(
ServerResultType.stopError,
error: error,
stackTrace: stackTrace
);
started.value = true;
return false;
}
}
Stream<ServerResult> restart() async* {
await resetWinNat();
if(started()) {
await stop();
yield* stop();
}
yield* start();
@@ -181,7 +207,7 @@ abstract class ServerController extends GetxController {
Stream<ServerResult> toggle() async* {
if(started()) {
await stop();
yield* stop();
}else {
yield* start();
}

View File

@@ -1,18 +1,16 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_common/common.dart';
import 'package:window_manager/window_manager.dart';
class SettingsController extends GetxController {
static const String _kDefaultIp = "127.0.0.1";
late final GetStorage _storage;
late final String originalDll;
late final TextEditingController rebootDll;
late final TextEditingController consoleDll;
late final TextEditingController authDll;
late final TextEditingController gameServerDll;
late final TextEditingController unrealEngineConsoleDll;
late final TextEditingController authenticatorDll;
late final TextEditingController gameServerPort;
late final RxBool firstRun;
late double width;
late double height;
@@ -22,9 +20,11 @@ class SettingsController extends GetxController {
SettingsController() {
_storage = GetStorage("reboot_settings");
rebootDll = _createController("reboot", "reboot.dll");
consoleDll = _createController("console", "console.dll");
authDll = _createController("cobalt", "cobalt.dll");
gameServerDll = _createController("game_server", "reboot.dll");
unrealEngineConsoleDll = _createController("unreal_engine_console", "console.dll");
authenticatorDll = _createController("authenticator", "cobalt.dll");
gameServerPort = TextEditingController(text: _storage.read("game_server_port") ?? kDefaultGameServerPort);
gameServerPort.addListener(() => _storage.write("game_server_port", gameServerPort.text));
width = _storage.read("width") ?? kDefaultWindowWidth;
height = _storage.read("height") ?? kDefaultWindowHeight;
offsetX = _storage.read("offset_x");
@@ -40,8 +40,7 @@ class SettingsController extends GetxController {
return controller;
}
void saveWindowSize() async {
var size = await windowManager.getSize();
void saveWindowSize(Size size) {
_storage.write("width", size.width);
_storage.write("height", size.height);
}
@@ -52,11 +51,10 @@ class SettingsController extends GetxController {
}
void reset(){
rebootDll.text = _controllerDefaultPath("reboot.dll");
consoleDll.text = _controllerDefaultPath("console.dll");
authDll.text = _controllerDefaultPath("cobalt.dll");
gameServerDll.text = _controllerDefaultPath("reboot.dll");
unrealEngineConsoleDll.text = _controllerDefaultPath("console.dll");
authenticatorDll.text = _controllerDefaultPath("cobalt.dll");
firstRun.value = true;
writeMatchmakingIp(_kDefaultIp);
}
String _controllerDefaultPath(String name) => "${assetsDirectory.path}\\dlls\\$name";

View File

@@ -1,7 +1,8 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
class UpdateController {
late final GetStorage _storage;
@@ -22,18 +23,44 @@ class UpdateController {
status = Rx(UpdateStatus.waiting);
}
Future<void> update() async {
Future<void> update([bool force = false]) async {
if(timer.value == UpdateTimer.never) {
status.value = UpdateStatus.success;
return;
}
showInfoBar(
"Downloading reboot dll...",
loading: true,
duration: null
);
try {
timestamp.value = await downloadRebootDll(url.text, timestamp.value);
timestamp.value = await downloadRebootDll(
url.text,
timestamp.value,
hours: timer.value.hours,
force: force
);
status.value = UpdateStatus.success;
}catch(_) {
showInfoBar(
"The reboot dll was downloaded successfully",
severity: InfoBarSeverity.success,
duration: snackbarShortDuration
);
}catch(message) {
var error = message.toString();
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
error = error.toLowerCase();
status.value = UpdateStatus.error;
rethrow;
showInfoBar(
"An error occurred while downloading the reboot dll: $error",
duration: snackbarLongDuration,
severity: InfoBarSeverity.error,
action: Button(
onPressed: () => update(true),
child: const Text("Retry"),
)
);
}
}
@@ -44,4 +71,19 @@ class UpdateController {
status.value = UpdateStatus.waiting;
update();
}
}
extension _UpdateTimerExtension on UpdateTimer {
int get hours {
switch(this) {
case UpdateTimer.never:
return -1;
case UpdateTimer.hour:
return 1;
case UpdateTimer.day:
return 24;
case UpdateTimer.week:
return 24 * 7;
}
}
}

View File

@@ -1,13 +1,13 @@
import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'dialog_button.dart';
Future<T?> showDialog<T extends Object?>({required WidgetBuilder builder}) => fluent.showDialog(
context: pageKey.currentContext!,
Future<T?> showAppDialog<T extends Object?>({required WidgetBuilder builder}) => fluent.showDialog(
context: appKey.currentContext!,
useRootNavigator: false,
builder: builder
);
@@ -24,7 +24,7 @@ class GenericDialog extends AbstractDialog {
final List<DialogButton> buttons;
final EdgeInsets? padding;
const GenericDialog({super.key, required this.header, required this.buttons, this.padding});
const GenericDialog({Key? key, required this.header, required this.buttons, this.padding}) : super(key: key);
@override
Widget build(BuildContext context) => ContentDialog(
@@ -40,7 +40,7 @@ class FormDialog extends AbstractDialog {
final Widget content;
final List<DialogButton> buttons;
const FormDialog({super.key, required this.content, required this.buttons});
const FormDialog({Key? key, required this.content, required this.buttons}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -80,10 +80,10 @@ class InfoDialog extends AbstractDialog {
final String text;
final List<DialogButton>? buttons;
const InfoDialog({required this.text, this.buttons, super.key});
const InfoDialog({required this.text, this.buttons, Key? key}) : super(key: key);
InfoDialog.ofOnly({required this.text, required DialogButton button, super.key})
: buttons = [button];
InfoDialog.ofOnly({required this.text, required DialogButton button, Key? key})
: buttons = [button], super(key: key);
@override
Widget build(BuildContext context) {
@@ -109,7 +109,7 @@ class ProgressDialog extends AbstractDialog {
final String text;
final Function()? onStop;
const ProgressDialog({required this.text, this.onStop, super.key});
const ProgressDialog({required this.text, this.onStop, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -143,14 +143,14 @@ class FutureBuilderDialog extends AbstractDialog {
final bool closeAutomatically;
const FutureBuilderDialog(
{super.key,
{Key? key,
required this.future,
required this.loadingMessage,
required this.successfulBody,
required this.unsuccessfulBody,
required this.errorMessageBuilder,
this.onError,
this.closeAutomatically = false});
this.closeAutomatically = false}) : super(key: key);
static Container ofMessage(String message) {
return Container(
@@ -223,14 +223,14 @@ class ErrorDialog extends AbstractDialog {
final StackTrace? stackTrace;
final Function(Object) errorMessageBuilder;
const ErrorDialog({super.key, required this.exception, required this.errorMessageBuilder, this.stackTrace});
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",
type: type,
onTap: () async {
FlutterClipboard.controlC("An error occurred: $error\nStacktrace:\n $stackTrace");
showMessage("Copied error to clipboard");
showInfoBar("Copied error to clipboard");
onClick();
},
);

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
class DialogButton extends StatefulWidget {
final String? text;

View File

@@ -0,0 +1,80 @@
import 'dart:collection';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/page/home_page.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];
if(overlay == null) {
return;
}
Overlay.of(pageKey.currentContext!).insert(overlay);
}
void showInfoBar(String text, {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: Text(text),
isLong: action == null,
isIconVisible: true,
content: action ?? SizedBox(
width: double.infinity,
child: loading ? const 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
);
_overlays[index] = overlay;
if(duration != null) {
Future.delayed(duration).then((_) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if(_overlays[index] == overlay) {
if(overlay.mounted) {
overlay.remove();
}
_overlays[index] = null;
}
});
});
}
}finally {
_semaphore.release();
}
}
void removeMessage(int index) {
try {
var lastOverlay = _overlays[index];
if(lastOverlay != null) {
lastOverlay.remove();
_overlays[index] = null;
}
}catch(_) {
// Do not use .isMounted
// This is intended behaviour
}
}

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
String? lastError;
@@ -25,7 +25,7 @@ void onError(Object? exception, StackTrace? stackTrace, bool framework) {
Navigator.of(pageKey.currentContext!).pop(false);
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => showDialog(
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => showAppDialog(
builder: (context) =>
ErrorDialog(
exception: exception,

View File

@@ -1,6 +1,6 @@
import 'package:reboot_common/common.dart';
import '../dialog/dialog.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. "
@@ -11,16 +11,8 @@ const String _corruptedBuildError = "An unknown occurred while launching Fortnit
"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> showBrokenError() async {
showDialog(
builder: (context) => const InfoDialog(
text: "The backend server is not working correctly"
)
);
}
Future<void> showMissingDllError(String name) async {
showDialog(
showAppDialog(
builder: (context) => InfoDialog(
text: "$name dll is not a valid dll, fix it in the settings tab"
)
@@ -28,7 +20,7 @@ Future<void> showMissingDllError(String name) async {
}
Future<void> showTokenErrorFixable() async {
showDialog(
showAppDialog(
builder: (context) => const InfoDialog(
text: "A token error occurred. "
"The backend server has been automatically restarted to fix the issue. "
@@ -37,17 +29,8 @@ Future<void> showTokenErrorFixable() async {
);
}
Future<void> showTokenErrorCouldNotFix() async {
showDialog(
builder: (context) => const InfoDialog(
text: "A token error occurred. "
"The game couldn't be recovered, open an issue on Discord."
)
);
}
Future<void> showTokenErrorUnfixable() async {
showDialog(
showAppDialog(
builder: (context) => const InfoDialog(
text: "A token error occurred. "
"This issue cannot be resolved automatically as the server isn't embedded."
@@ -59,7 +42,7 @@ Future<void> showTokenErrorUnfixable() async {
Future<void> showCorruptedBuildError(bool server, [Object? error, StackTrace? stackTrace]) async {
if(error == null) {
showDialog(
showAppDialog(
builder: (context) => InfoDialog(
text: server ? _unsupportedServerError : _corruptedBuildError
)
@@ -67,7 +50,7 @@ Future<void> showCorruptedBuildError(bool server, [Object? error, StackTrace? st
return;
}
showDialog(
showAppDialog(
builder: (context) => ErrorDialog(
exception: error,
stackTrace: stackTrace,
@@ -77,7 +60,7 @@ Future<void> showCorruptedBuildError(bool server, [Object? error, StackTrace? st
}
Future<void> showMissingBuildError(FortniteVersion version) async {
showDialog(
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

@@ -1,9 +1,9 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
final GameController _gameController = Get.find<GameController>();
@@ -12,7 +12,7 @@ Future<bool> showProfileForm(BuildContext context) async{
var oldUsername = _gameController.username.text;
var showPasswordTrailing = RxBool(oldUsername.isNotEmpty);
var oldPassword = _gameController.password.text;
var result = await showDialog<bool?>(
var result = await showAppDialog<bool?>(
builder: (context) => Obx(() => FormDialog(
content: Column(
mainAxisSize: MainAxisSize.min,

View File

@@ -1,19 +1,20 @@
import 'dart:async';
import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/message.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_common/common.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:reboot_launcher/src/util/cryptography.dart';
import 'package:reboot_launcher/src/util/matchmaker.dart';
extension ServerControllerDialog on ServerController {
Future<bool> restartInteractive() async {
@@ -26,63 +27,21 @@ extension ServerControllerDialog on ServerController {
return await _handleStream(stream, showSuccessMessage);
}
Future<bool> _handleStream(Stream<ServerResult> stream, bool showSuccessMessage) async {
var completer = Completer<bool>();
stream.listen((event) {
switch (event.type) {
case ServerResultType.missingHostError:
showMessage(
"Cannot launch game: missing hostname in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.missingPortError:
showMessage(
"Cannot launch game: missing port in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.illegalPortError:
showMessage(
"Cannot launch game: invalid port in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.freeingPort:
case ServerResultType.freePortSuccess:
case ServerResultType.freePortError:
showMessage(
"Message",
loading: event.type == ServerResultType.freeingPort,
severity: event.type == ServerResultType.freeingPort ? InfoBarSeverity.info : event.type == ServerResultType.freePortSuccess ? InfoBarSeverity.success : InfoBarSeverity.error
);
break;
case ServerResultType.pingingRemote:
showMessage(
"Pinging remote server...",
case ServerResultType.starting:
showInfoBar(
"Starting the $controllerName...",
severity: InfoBarSeverity.info,
loading: true,
duration: const Duration(seconds: 10)
);
break;
case ServerResultType.pingingLocal:
showMessage(
"Pinging ${type().name} server...",
severity: InfoBarSeverity.info,
loading: true,
duration: const Duration(seconds: 10)
);
break;
case ServerResultType.pingError:
showMessage(
"Cannot ping ${type().name} server",
severity: InfoBarSeverity.error
duration: null
);
break;
case ServerResultType.startSuccess:
if(showSuccessMessage) {
showMessage(
showInfoBar(
"The $controllerName was started successfully",
severity: InfoBarSeverity.success
);
@@ -90,8 +49,98 @@ extension ServerControllerDialog on ServerController {
completer.complete(true);
break;
case ServerResultType.startError:
showMessage(
showInfoBar(
"An error occurred while starting the $controllerName: ${event.error ?? "unknown error"}",
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
break;
case ServerResultType.stopping:
showInfoBar(
"Stopping the $controllerName...",
severity: InfoBarSeverity.info,
loading: true,
duration: null
);
break;
case ServerResultType.stopSuccess:
if(showSuccessMessage) {
showInfoBar(
"The $controllerName was stopped successfully",
severity: InfoBarSeverity.success
);
}
completer.complete(true);
break;
case ServerResultType.stopError:
showInfoBar(
"An error occurred while stopping the $controllerName: ${event.error ?? "unknown error"}",
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
break;
case ServerResultType.missingHostError:
showInfoBar(
"Missing hostname in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.missingPortError:
showInfoBar(
"Missing port in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.illegalPortError:
showInfoBar(
"Invalid port in $controllerName configuration",
severity: InfoBarSeverity.error
);
break;
case ServerResultType.freeingPort:
showInfoBar(
"Freeing port $defaultPort...",
loading: true,
duration: null
);
break;
case ServerResultType.freePortSuccess:
showInfoBar(
"Port $defaultPort was freed successfully",
severity: InfoBarSeverity.success,
duration: snackbarShortDuration
);
break;
case ServerResultType.freePortError:
showInfoBar(
"An error occurred while freeing port $defaultPort: ${event.error ?? "unknown error"}",
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
break;
case ServerResultType.pingingRemote:
if(started.value) {
showInfoBar(
"Pinging the remote $controllerName...",
severity: InfoBarSeverity.info,
loading: true,
duration: null
);
}
break;
case ServerResultType.pingingLocal:
if(started.value) {
showInfoBar(
"Pinging the ${type().name} $controllerName...",
severity: InfoBarSeverity.info,
loading: true,
duration: null
);
}
break;
case ServerResultType.pingError:
showInfoBar(
"Cannot ping ${type().name} $controllerName",
severity: InfoBarSeverity.error
);
break;
@@ -104,15 +153,9 @@ extension ServerControllerDialog on ServerController {
var result = await completer.future;
if(result && type() == ServerType.embedded) {
watchProcess(embeddedServer!.pid).then((value) {
watchProcess(embeddedServerPid!).then((value) {
if(started()) {
pageIndex.value = 3;
started.value = false;
WidgetsBinding.instance.addPostFrameCallback((_) => showMessage(
"The $controllerName was terminated unexpectedly: if this wasn't intentional, file a bug report",
severity: InfoBarSeverity.warning,
duration: snackbarLongDuration
));
}
});
}
@@ -129,6 +172,11 @@ extension MatchmakerControllerExtension on MatchmakerController {
var author = entry["author"];
var encryptedIp = entry["ip"];
if(!hasPassword) {
var valid = await _isServerValid(encryptedIp);
if(!valid) {
return;
}
_onSuccess(embedded, encryptedIp, author);
return;
}
@@ -139,7 +187,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
}
if(!checkPassword(confirmPassword, hashedPassword)) {
showMessage(
showInfoBar(
"Wrong password: please try again",
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
@@ -148,15 +196,33 @@ extension MatchmakerControllerExtension on MatchmakerController {
}
var decryptedIp = aes256Decrypt(encryptedIp, confirmPassword);
var valid = await _isServerValid(decryptedIp);
if(!valid) {
return;
}
_onSuccess(embedded, decryptedIp, author);
}
Future<bool> _isServerValid(String address) async {
var result = await pingGameServer(address);
if(result) {
return true;
}
showInfoBar(
"This server isn't online right now: please try again later",
duration: snackbarLongDuration,
severity: InfoBarSeverity.error
);
return false;
}
Future<String?> _askForPassword() async {
var confirmPasswordController = TextEditingController();
var showPassword = RxBool(false);
var showPasswordTrailing = RxBool(false);
return await showDialog<String?>(
return await showAppDialog<String?>(
builder: (context) => FormDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
@@ -213,7 +279,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
}else {
FlutterClipboard.controlC(decryptedIp);
}
WidgetsBinding.instance.addPostFrameCallback((_) => showMessage(
WidgetsBinding.instance.addPostFrameCallback((_) => showInfoBar(
embedded ? "You joined $author's server successfully!" : "Copied IP to the clipboard",
duration: snackbarLongDuration,
severity: InfoBarSeverity.success

View File

@@ -1,38 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:sync/semaphore.dart';
Semaphore _semaphore = Semaphore();
OverlayEntry? _lastOverlay;
void showMessage(String text, {InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = snackbarShortDuration}) {
try {
_semaphore.acquire();
if(_lastOverlay?.mounted == true) {
_lastOverlay?.remove();
}
var pageIndexValue = pageIndex.value;
_lastOverlay = showSnackbar(
pageKey.currentContext!,
InfoBar(
title: Text(text),
isLong: true,
isIconVisible: true,
content: SizedBox(
width: double.infinity,
child: loading ? const ProgressBar() : const SizedBox()
),
severity: severity
),
margin: EdgeInsets.only(
left: 330.0,
right: 16.0,
bottom: pageIndexValue == 0 || pageIndexValue == 1 || pageIndexValue == 3 || pageIndexValue == 4 ? 72 : 16
),
duration: duration
);
}finally {
_semaphore.release();
}
}

View File

@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/authenticator_controller.dart';
@@ -8,8 +9,8 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
class AuthenticatorPage extends StatefulWidget {
const AuthenticatorPage({Key? key}) : super(key: key);
@@ -46,8 +47,7 @@ class _AuthenticatorPageState extends State<AuthenticatorPage> with AutomaticKee
isChild: true,
content: TextFormBox(
placeholder: "Host",
controller: _authenticatorController.host,
readOnly: !_isRemote
controller: _authenticatorController.host
)
),
if(_authenticatorController.type.value != ServerType.embedded)
@@ -58,7 +58,10 @@ class _AuthenticatorPageState extends State<AuthenticatorPage> with AutomaticKee
content: TextFormBox(
placeholder: "Port",
controller: _authenticatorController.port,
readOnly: !_isRemote
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
]
)
),
if(_authenticatorController.type.value == ServerType.embedded)
@@ -92,7 +95,7 @@ class _AuthenticatorPageState extends State<AuthenticatorPage> with AutomaticKee
title: "Reset authenticator",
subtitle: "Resets the authenticator's settings to their default values",
content: Button(
onPressed: () => showDialog(
onPressed: () => showAppDialog(
builder: (context) => InfoDialog(
text: "Do you want to reset all the setting in this tab to their default values? This action is irreversible",
buttons: [

View File

@@ -7,7 +7,7 @@ import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
import 'package:reboot_launcher/src/interactive/server.dart';
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
import 'package:skeletons/skeletons.dart';

View File

@@ -1,23 +1,24 @@
import 'dart:collection';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show MaterialPage;
import 'package:get/get.dart';
import 'package:reboot_launcher/src/page/browse_page.dart';
import 'package:reboot_launcher/src/page/authenticator_page.dart';
import 'package:reboot_launcher/src/page/matchmaker_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/util/os.dart';
import 'package:reboot_launcher/src/widget/home/pane.dart';
import 'package:reboot_launcher/src/widget/home/profile.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/widget/os/border.dart';
import 'package:reboot_launcher/src/widget/os/title_bar.dart';
import 'package:reboot_launcher/src/page/hosting_page.dart';
import 'package:reboot_launcher/src/page/info_page.dart';
import 'package:window_manager/window_manager.dart';
import 'hosting_page.dart';
import 'info_page.dart';
GlobalKey appKey = GlobalKey();
const int pagesLength = 7;
final RxInt pageIndex = RxInt(0);
final Queue<int> _pagesStack = Queue();
@@ -39,14 +40,12 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
final FocusNode _searchFocusNode = FocusNode();
final TextEditingController _searchController = TextEditingController();
final RxBool _focused = RxBool(true);
final RxBool _fullScreen = RxBool(false);
@override
bool get wantKeepAlive => true;
@override
void initState() {
windowManager.show();
windowManager.addListener(this);
_searchController.addListener(_onSearch);
super.initState();
@@ -58,22 +57,12 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override
void dispose() {
windowManager.removeListener(this);
_searchFocusNode.dispose();
_searchController.dispose();
windowManager.removeListener(this);
super.dispose();
}
@override
void onWindowEnterFullScreen() {
_fullScreen.value = true;
}
@override
void onWindowLeaveFullScreen() {
_fullScreen.value = false;
}
@override
void onWindowFocus() {
_focused.value = true;
@@ -86,63 +75,63 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override
void onWindowResized() {
_settingsController.saveWindowSize();
super.onWindowResized();
_settingsController.saveWindowSize(appWindow.size);
}
@override
void onWindowMoved() {
windowManager.getPosition()
.then((value) => _settingsController.saveWindowOffset(value));
super.onWindowMoved();
_settingsController.saveWindowOffset(appWindow.position);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Stack(
children: [
Obx(() => NavigationPaneTheme(
data: NavigationPaneThemeData(
backgroundColor: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.9),
),
child: NavigationView(
paneBodyBuilder: (pane, body) => Padding(
padding: const EdgeInsets.all(_kDefaultPadding),
child: SizedBox(
key: pageKey,
child: body
)
),
appBar: NavigationAppBar(
height: 32,
title: _draggableArea,
actions: WindowTitleBar(focused: _focused()),
leading: _backButton,
automaticallyImplyLeading: false,
),
pane: NavigationPane(
selected: pageIndex.value,
onChanged: (index) {
_pagesStack.add(pageIndex.value);
pageIndex.value = index;
},
menuButton: const SizedBox(),
displayMode: PaneDisplayMode.open,
items: _items,
header: const ProfileWidget(),
autoSuggestBox: _autoSuggestBox,
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
),
contentShape: const RoundedRectangleBorder(),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: (child, animation) => child
)
)),
if (isWin11)
Obx(() => !_fullScreen.value && _focused.value ? const WindowBorder() : const SizedBox())
]
);
windowManager.show();
return Obx(() => NavigationPaneTheme(
data: NavigationPaneThemeData(
backgroundColor: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.93),
),
child: NavigationView(
paneBodyBuilder: (pane, body) => Navigator(
onPopPage: (page, data) => false,
pages: [
MaterialPage(
child: Padding(
padding: const EdgeInsets.all(_kDefaultPadding),
child: SizedBox(
key: pageKey,
child: body
)
)
)
],
),
appBar: NavigationAppBar(
height: 32,
title: _draggableArea,
actions: WindowTitleBar(focused: _focused()),
leading: _backButton,
automaticallyImplyLeading: false,
),
pane: NavigationPane(
key: appKey,
selected: pageIndex.value,
onChanged: (index) {
_pagesStack.add(pageIndex.value);
pageIndex.value = index;
},
menuButton: const SizedBox(),
displayMode: PaneDisplayMode.open,
items: _items,
header: const ProfileWidget(),
autoSuggestBox: _autoSuggestBox,
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
),
contentShape: const RoundedRectangleBorder(),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: (child, animation) => child
)
));
}
Widget get _backButton => Obx(() {
@@ -159,9 +148,9 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
});
GestureDetector get _draggableArea => GestureDetector(
onDoubleTap: () async => await windowManager.isMaximized() ? await windowManager.restore() : await windowManager.maximize(),
onHorizontalDragStart: (event) => windowManager.startDragging(),
onVerticalDragStart: (event) => windowManager.startDragging()
onDoubleTap: appWindow.maximizeOrRestore,
onHorizontalDragStart: (_) => appWindow.startDragging(),
onVerticalDragStart: (_) => appWindow.startDragging()
);
Widget get _autoSuggestBox => Padding(

View File

@@ -1,12 +1,12 @@
import 'package:clipboard/clipboard.dart';
import 'package:dart_ipify/dart_ipify.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/main.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/update_controller.dart';
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
import 'package:flutter/material.dart' show Icons;
@@ -38,17 +38,6 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
Expanded(
child: ListView(
children: [
Obx(() => Column(
children: _updateController.status.value != UpdateStatus.error ? [] : [
SizedBox(
width: double.infinity,
child: _updateError
),
const SizedBox(
height: 8.0
),
],
)),
SettingTile(
title: "Game Server",
subtitle: "Provide basic information about your server",
@@ -150,7 +139,7 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
content: Button(
onPressed: () async {
FlutterClipboard.controlC("$kCustomUrlSchema://${_gameController.uuid}");
showMessage(
showInfoBar(
"Copied your link to the clipboard",
severity: InfoBarSeverity.success
);
@@ -165,19 +154,19 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
content: Button(
onPressed: () async {
try {
showMessage(
showInfoBar(
"Obtaining your public IP...",
loading: true,
duration: null
);
var ip = await Ipify.ipv4();
FlutterClipboard.controlC(ip);
showMessage(
showInfoBar(
"Copied your IP to the clipboard",
severity: InfoBarSeverity.success
);
}catch(error) {
showMessage(
showInfoBar(
"An error occurred while obtaining your public IP: $error",
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
@@ -201,15 +190,4 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
],
);
}
Widget get _updateError => MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: _updateController.update,
child: const InfoBar(
title: Text("The reboot dll couldn't be downloaded: click here to try again"),
severity: InfoBarSeverity.info
),
),
);
}

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';

View File

@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
@@ -7,8 +8,8 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.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/widget/server/start_button.dart';
class MatchmakerPage extends StatefulWidget {
@@ -46,8 +47,7 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
isChild: true,
content: TextFormBox(
placeholder: "Host",
controller: _matchmakerController.host,
readOnly: _matchmakerController.type.value != ServerType.remote
controller: _matchmakerController.host
)
),
if(_matchmakerController.type.value != ServerType.embedded)
@@ -58,7 +58,10 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
content: TextFormBox(
placeholder: "Port",
controller: _matchmakerController.port,
readOnly: _matchmakerController.type.value != ServerType.remote
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
]
)
),
if(_matchmakerController.type.value == ServerType.embedded)
@@ -91,7 +94,7 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
title: "Installation directory",
subtitle: "Opens the folder where the embedded matchmaker is located",
content: Button(
onPressed: () => launchUrl(authenticatorDirectory.uri),
onPressed: () => launchUrl(matchmakerDirectory.uri),
child: const Text("Show Files")
)
),
@@ -102,7 +105,7 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
title: "Reset matchmaker",
subtitle: "Resets the authenticator's settings to their default values",
content: Button(
onPressed: () => showDialog(
onPressed: () => showAppDialog(
builder: (context) => InfoDialog(
text: "Do you want to reset all the setting in this tab to their default values? This action is irreversible",
buttons: [

View File

@@ -1,5 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart';

View File

@@ -1,4 +1,5 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/build_controller.dart';
@@ -7,12 +8,12 @@ import 'package:reboot_launcher/src/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/controller/authenticator_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/controller/update_controller.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/widget/common/file_selector.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
class SettingsPage extends StatefulWidget {
@@ -45,12 +46,12 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
_createFileSetting(
title: "Unreal engine console",
description: "This file is injected to unlock the Unreal Engine Console",
controller: _settingsController.consoleDll
controller: _settingsController.unrealEngineConsoleDll
),
_createFileSetting(
title: "Authentication patcher",
description: "This file is injected to redirect all HTTP requests to the launcher's authenticator",
controller: _settingsController.authDll
controller: _settingsController.authenticatorDll
),
SettingTile(
title: "Custom launch arguments",
@@ -67,13 +68,26 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
height: 8.0,
),
SettingTile(
title: "Server settings",
title: "Game server settings",
subtitle: "This section contains settings related to the game server implementation",
expandedContent: [
_createFileSetting(
title: "Game server",
title: "Implementation",
description: "This file is injected to create a game server & host matches",
controller: _settingsController.rebootDll
controller: _settingsController.gameServerDll
),
SettingTile(
title: "Port",
subtitle: "The port used by the game server dll",
content: TextFormBox(
placeholder: "Port",
controller: _settingsController.gameServerPort,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
]
),
isChild: true
),
SettingTile(
title: "Update mirror",
@@ -92,7 +106,10 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
leading: Text(_updateController.timer.value.text),
items: UpdateTimer.values.map((entry) => MenuFlyoutItem(
text: Text(entry.text),
onPressed: () => _updateController.timer.value = entry
onPressed: () {
_updateController.timer.value = entry;
_updateController.update(true);
}
)).toList()
)),
isChild: true
@@ -129,7 +146,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
subtitle: "Resets the launcher's settings to their default values",
isChild: true,
content: Button(
onPressed: () => showDialog(
onPressed: () => showAppDialog(
builder: (context) => InfoDialog(
text: "Do you want to reset all the launcher's settings to their default values? This action is irreversible",
buttons: [

View File

@@ -0,0 +1,34 @@
import 'dart:io';
import 'package:reboot_common/common.dart';
final File _script = File("${assetsDirectory.path}\\misc\\udp.ps1");
Future<bool> pingGameServer(String address, {Duration? timeout}) async {
var start = DateTime.now();
var firstTime = true;
while (firstTime || (timeout != null && DateTime.now().millisecondsSinceEpoch - start.millisecondsSinceEpoch < timeout.inMilliseconds)) {
var split = address.split(":");
var hostname = split[0];
var port = split.length > 1 ? split[1] : kDefaultGameServerPort;
var result = await Process.run(
"powershell",
[
_script.path,
hostname,
port
]
);
if (result.exitCode == 0) {
return true;
}
if(firstTime) {
firstTime = false;
}else {
await Future.delayed(const Duration(seconds: 2));
}
}
return false;
}

View File

@@ -9,11 +9,12 @@ import 'package:supabase_flutter/supabase_flutter.dart';
final SupabaseClient _supabase = Supabase.instance.client;
final GameController _gameController = Get.find<GameController>();
final HostingController _hostingController = Get.find<HostingController>();
final File _executable = File("${assetsDirectory.path}\\misc\\watch.exe");
extension GameInstanceWatcher on GameInstance {
Future<void> startObserver() async {
if(watchPid != null) {
Process.killPid(watchPid!, ProcessSignal.sigabrt);
if(observerPid != null) {
Process.killPid(observerPid!, ProcessSignal.sigabrt);
}
watchProcess(gamePid).then((value) async {
@@ -24,9 +25,15 @@ extension GameInstanceWatcher on GameInstance {
_onGameStopped();
});
watchPid = startBackgroundProcess(
'${assetsDirectory.path}\\misc\\watch.exe',
[_gameController.uuid, gamePid.toString(), launcherPid?.toString() ?? "-1", eacPid?.toString() ?? "-1", hosting.toString()]
observerPid = await startBackgroundProcess(
executable: _executable,
args: [
_gameController.uuid,
gamePid.toString(),
launcherPid?.toString() ?? "-1",
eacPid?.toString() ?? "-1",
hosting.toString()
]
);
}

View File

@@ -1,6 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/util/picker.dart';
class FileSelector extends StatefulWidget {
@@ -56,7 +56,7 @@ class _FileSelectorState extends State<FileSelector> {
void _onPressed() {
if(_selecting){
showMessage("Folder selector is already opened");
showInfoBar("Folder selector is already opened");
return;
}

View File

@@ -1,10 +1,9 @@
import 'package:auto_animated_list/auto_animated_list.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:skeletons/skeletons.dart';
class SettingTile extends StatefulWidget {
static const double kDefaultContentWidth = 200.0;
static const double kDefaultSpacing = 8.0;
static const double kDefaultHeaderHeight = 72;
final String? title;
@@ -15,7 +14,6 @@ class SettingTile extends StatefulWidget {
final double? contentWidth;
final List<Widget>? expandedContent;
final double expandedContentHeaderHeight;
final double expandedContentSpacing;
final bool isChild;
const SettingTile(
@@ -27,7 +25,6 @@ class SettingTile extends StatefulWidget {
this.content,
this.contentWidth = kDefaultContentWidth,
this.expandedContentHeaderHeight = kDefaultHeaderHeight,
this.expandedContentSpacing = kDefaultSpacing,
this.expandedContent,
this.isChild = false})
: assert(
@@ -63,7 +60,7 @@ class _SettingTileState extends State<SettingTile> {
Widget get _expandedContent {
var expandedContents = widget.expandedContent!;
var separatedContents = List.generate(expandedContents.length * 2, (index) => index % 2 == 0 ? expandedContents[index ~/ 2] : SizedBox(height: widget.expandedContentSpacing));
var separatedContents = List.generate(expandedContents.length, (index) => expandedContents[index]);
return AutoAnimatedList<Widget>(
scrollDirection: Axis.vertical,
shrinkWrap: true,
@@ -103,7 +100,7 @@ class _SettingTileState extends State<SettingTile> {
Widget get _title => Text(
widget.title!,
style:
widget.titleStyle ?? FluentTheme.of(context).typography.subtitle,
widget.titleStyle ?? FluentTheme.of(context).typography.subtitle
);
Widget get _skeletonTitle => const SkeletonLine(

View File

@@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:dart_ipify/dart_ipify.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as path;
import 'package:process_run/shell.dart';
@@ -10,35 +10,37 @@ 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/controller/authenticator_controller.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/interactive/game.dart';
import 'package:reboot_launcher/src/interactive/server.dart';
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/dialog/implementation/game.dart';
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/util/cryptography.dart';
import 'package:reboot_launcher/src/util/matchmaker.dart';
import 'package:reboot_launcher/src/util/watch.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class LaunchButton extends StatefulWidget {
final bool host;
final String? startLabel;
final String? stopLabel;
final bool Function()? onTap;
const LaunchButton({Key? key, required this.host, this.startLabel, this.stopLabel, this.onTap}) : super(key: key);
const LaunchButton({Key? key, required this.host, this.startLabel, this.stopLabel}) : super(key: key);
@override
State<LaunchButton> createState() => _LaunchButtonState();
}
class _LaunchButtonState extends State<LaunchButton> {
final SupabaseClient _supabase = Supabase.instance.client;
final GameController _gameController = Get.find<GameController>();
final HostingController _hostingController = Get.find<HostingController>();
final AuthenticatorController _authenticatorController = Get.find<AuthenticatorController>();
final MatchmakerController _matchmakerController = Get.find<MatchmakerController>();
final SettingsController _settingsController = Get.find<SettingsController>();
final File _logFile = File("${logsDirectory.path}\\game.log");
Completer<bool> _completer = Completer();
bool _fail = false;
Future? _executor;
@override
Widget build(BuildContext context) => Align(
@@ -48,11 +50,11 @@ class _LaunchButtonState extends State<LaunchButton> {
child: Obx(() => SizedBox(
height: 48,
child: Button(
onPressed: _start,
child: Align(
alignment: Alignment.center,
child: Text(_hasStarted ? _stopMessage : _startMessage)
),
onPressed: () => _executor = _start()
)
),
)),
),
@@ -67,18 +69,16 @@ class _LaunchButtonState extends State<LaunchButton> {
String get _stopMessage => widget.stopLabel ?? (widget.host ? "Stop hosting" : "Close fortnite");
Future<void> _start() async {
if(widget.onTap != null && !widget.onTap!()){
return;
}
if (_hasStarted) {
_onStop(widget.host);
_onStop(widget.host, false);
removeMessage(widget.host ? 1 : 0);
return;
}
_fail = false;
if(_gameController.selectedVersion == null){
showMessage("Select a Fortnite version before continuing");
_onStop(widget.host);
showInfoBar("Select a Fortnite version before continuing");
_onStop(widget.host, false);
return;
}
@@ -94,24 +94,33 @@ class _LaunchButtonState extends State<LaunchButton> {
var executable = await version.executable;
if(executable == null){
showMissingBuildError(version);
_onStop(widget.host);
_onStop(widget.host, false);
return;
}
var result = _authenticatorController.started() || await _authenticatorController.toggleInteractive(false);
if(!result){
_onStop(widget.host);
var authenticatorResult = _authenticatorController.started() || await _authenticatorController.toggleInteractive(false);
if(!authenticatorResult){
_onStop(widget.host, false);
return;
}
var matchmakerResult = _matchmakerController.started() || await _matchmakerController.toggleInteractive(false);
if(!matchmakerResult){
_onStop(widget.host, false);
return;
}
var automaticallyStartedServer = await _startMatchMakingServer();
await _startGameProcesses(version, widget.host, automaticallyStartedServer);
if(widget.host){
await _showServerLaunchingWarning();
showInfoBar(
"Launching the headless server...",
loading: true,
duration: null
);
}
} catch (exception, stacktrace) {
_closeLaunchingWidget(false);
_onStop(widget.host);
_onStop(widget.host, false);
showCorruptedBuildError(widget.host, exception, stacktrace);
}
}
@@ -121,19 +130,16 @@ class _LaunchButtonState extends State<LaunchButton> {
var launcherProcess = await _createLauncherProcess(version);
var eacProcess = await _createEacProcess(version);
var executable = await version.executable;
if(executable == null){
showMissingBuildError(version);
_onStop(widget.host);
return;
}
var gameProcess = await _createGameProcess(executable.path, host);
var gameProcess = await _createGameProcess(executable!.path, host);
var instance = GameInstance(gameProcess, launcherProcess, eacProcess, host, linkedHosting);
instance.startObserver();
if(host){
_removeHostEntry();
_hostingController.instance.value = instance;
_hostingController.saveInstance();
}else{
_gameController.instance.value = instance;
_gameController.saveInstance();
}
_injectOrShowError(Injectable.sslBypass, host);
}
@@ -219,62 +225,89 @@ class _LaunchButtonState extends State<LaunchButton> {
return;
}
_closeLaunchingWidget(false);
_onStop(widget.host);
_onStop(widget.host, false);
}
void _closeLaunchingWidget(bool success) {
showMessage(
success ? "The headless server was started successfully" : "An error occurred while starting the headless server",
severity: success ? InfoBarSeverity.success : InfoBarSeverity.error
);
if(!_completer.isCompleted) {
_completer.complete(success);
void _closeLaunchingWidget(bool host, bool message) async {
if(!message) {
return;
}
}
Future<void> _showServerLaunchingWarning() async {
showMessage(
"Launching headless server...",
if(_fail) {
showInfoBar(
"An error occurred while starting the headless server",
severity: InfoBarSeverity.error
);
return;
}
showInfoBar(
"Waiting for the game server to become available...",
loading: true,
duration: null
);
var result = await _completer.future;
if(!result){
_onStop(true);
var gameServerPort = _settingsController.gameServerPort.text;
var localPingResult = await pingGameServer(
"localhost:$gameServerPort",
timeout: const Duration(minutes: 1)
);
if(!localPingResult) {
showInfoBar(
"The headless server was started successfully, but the game server isn't available",
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
);
return;
}
if(!_hostingController.discoverable.value){
showInfoBar(
"Checking if the game server is accessible...",
loading: true,
duration: null
);
var publicIp = await Ipify.ipv4();
var result = await pingGameServer("$publicIp:$gameServerPort");
if(!result) {
showInfoBar(
"The game server was started successfully, but nobody outside your network can join",
severity: InfoBarSeverity.warning,
duration: null,
action: Button(
onPressed: () => launchUrl(Uri.parse("https://github.com/Auties00/reboot_launcher/documentation/PortForwarding.md")),
child: Text("Open port $gameServerPort")
)
);
return;
}
var password = _hostingController.password.text;
var hasPassword = password.isNotEmpty;
var ip = await Ipify.ipv4();
if(hasPassword) {
ip = aes256Encrypt(ip, password);
}
if(_hostingController.discoverable.value){
var password = _hostingController.password.text;
var hasPassword = password.isNotEmpty;
var ip = await Ipify.ipv4();
if(hasPassword) {
ip = aes256Encrypt(ip, password);
}
var supabase = Supabase.instance.client;
await supabase.from('hosts').insert({
'id': _gameController.uuid,
'name': _hostingController.name.text,
'description': _hostingController.description.text,
'author': _gameController.username.text,
'ip': ip,
'version': _gameController.selectedVersion?.name,
'password': hasPassword ? hashPassword(password) : null,
'timestamp': DateTime.now().toIso8601String(),
'discoverable': _hostingController.discoverable.value
});
var supabase = Supabase.instance.client;
await supabase.from('hosts').insert({
'id': _gameController.uuid,
'name': _hostingController.name.text,
'description': _hostingController.description.text,
'author': _gameController.username.text,
'ip': ip,
'version': _gameController.selectedVersion?.name,
'password': hasPassword ? hashPassword(password) : null,
'timestamp': DateTime.now().toIso8601String(),
'discoverable': _hostingController.discoverable.value
});
}
}
void _onGameOutput(String line, bool host) {
_logFile.createSync(recursive: true);
_logFile.writeAsString("$line\n", mode: FileMode.append);
if (line.contains(shutdownLine)) {
_onStop(host);
_onStop(host, false);
return;
}
@@ -285,7 +318,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_fail = true;
showCorruptedBuildError(host);
_onStop(host);
_onStop(host, false);
return;
}
@@ -294,8 +327,6 @@ class _LaunchButtonState extends State<LaunchButton> {
return;
}
_fail = true;
_closeLaunchingWidget(false);
_showTokenError(host);
return;
}
@@ -305,7 +336,7 @@ class _LaunchButtonState extends State<LaunchButton> {
_injectOrShowError(Injectable.console, host);
}else {
_injectOrShowError(Injectable.reboot, host)
.then((value) => _closeLaunchingWidget(true));
.then((value) => _closeLaunchingWidget(host, true));
}
_injectOrShowError(Injectable.memoryFix, host);
@@ -315,6 +346,7 @@ class _LaunchButtonState extends State<LaunchButton> {
}
Future<void> _showTokenError(bool host) async {
_fail = true;
var instance = host ? _hostingController.instance.value : _gameController.instance.value;
if(_authenticatorController.type() != ServerType.embedded) {
showTokenErrorUnfixable();
@@ -324,19 +356,15 @@ class _LaunchButtonState extends State<LaunchButton> {
await _authenticatorController.restartInteractive();
showTokenErrorFixable();
_onStop(host);
_onStop(host, false);
_start();
}
void _onStop(bool host) async {
if(_executor != null){
await _executor;
}
void _onStop(bool host, bool showMessage) async {
var instance = host ? _hostingController.instance.value : _gameController.instance.value;
if(instance != null){
if(instance.linkedHosting){
_onStop(true);
_onStop(true, showMessage);
}
instance.kill();
@@ -350,13 +378,16 @@ class _LaunchButtonState extends State<LaunchButton> {
_setStarted(host, false);
if(host){
var supabase = Supabase.instance.client;
await supabase.from('hosts')
.delete()
.match({'id': _gameController.uuid});
await _removeHostEntry();
}
_completer = Completer();
_closeLaunchingWidget(host, showMessage);
}
Future<void> _removeHostEntry() async {
await _supabase.from('hosts')
.delete()
.match({'id': _gameController.uuid});
}
Future<void> _injectOrShowError(Injectable injectable, bool hosting) async {
@@ -374,8 +405,8 @@ class _LaunchButtonState extends State<LaunchButton> {
await injectDll(gameProcess, dllPath.path);
} catch (exception) {
showMessage("Cannot inject $injectable.dll: $exception");
_onStop(hosting);
showInfoBar("Cannot inject $injectable.dll: $exception");
_onStop(hosting, false);
}
}
@@ -383,11 +414,11 @@ class _LaunchButtonState extends State<LaunchButton> {
Future<File> getPath(Injectable injectable) async {
switch(injectable){
case Injectable.reboot:
return File(_settingsController.rebootDll.text);
return File(_settingsController.gameServerDll.text);
case Injectable.console:
return File(_settingsController.consoleDll.text);
return File(_settingsController.unrealEngineConsoleDll.text);
case Injectable.sslBypass:
return File(_settingsController.authDll.text);
return File(_settingsController.authenticatorDll.text);
case Injectable.memoryFix:
return File("${assetsDirectory.path}\\dlls\\memoryleak.dll");
}
@@ -405,9 +436,8 @@ class _LaunchButtonState extends State<LaunchButton> {
void _onDllFail(File dllPath, bool hosting) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_fail = true;
_closeLaunchingWidget(false);
showMissingDllError(path.basename(dllPath.path));
_onStop(hosting);
_onStop(hosting, false);
});
}
}

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
class RebootPaneItem extends PaneItem {
RebootPaneItem({required super.title, required super.icon, required super.body});

View File

@@ -1,9 +1,9 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
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/interactive/profile.dart';
import 'package:reboot_launcher/src/dialog/implementation/profile.dart';
class ProfileWidget extends StatefulWidget {
const ProfileWidget({Key? key}) : super(key: key);

View File

@@ -1,5 +1,5 @@
import 'package:bitsdojo_window/bitsdojo_window.dart' show appWindow;
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import 'icons.dart';
import 'mouse.dart';
@@ -132,7 +132,7 @@ class MinimizeWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
MinimizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => windowManager.minimize());
onPressed: onPressed ?? () => appWindow.minimize());
}
class MaximizeWindowButton extends WindowButton {
@@ -148,27 +148,7 @@ class MaximizeWindowButton extends WindowButton {
iconBuilder: (buttonContext) =>
MaximizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ??
() async => await windowManager.isMaximized()
? await windowManager.restore()
: await windowManager.maximize());
}
class RestoreWindowButton extends WindowButton {
RestoreWindowButton(
{Key? key,
WindowButtonColors? colors,
VoidCallback? onPressed,
bool? animate})
: super(
key: key,
colors: colors,
animate: animate ?? false,
iconBuilder: (buttonContext) =>
RestoreIcon(color: buttonContext.iconColor),
onPressed: onPressed ??
() async => await windowManager.isMaximized()
? await windowManager.restore()
: await windowManager.maximize());
() => appWindow.maximizeOrRestore());
}
final _defaultCloseButtonColors = WindowButtonColors(
@@ -189,5 +169,5 @@ class CloseWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
CloseIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => windowManager.close());
onPressed: onPressed ?? () => appWindow.close());
}

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/widget/os/buttons.dart';
import 'package:system_theme/system_theme.dart';

View File

@@ -1,10 +1,10 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/authenticator_controller.dart';
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/interactive/server.dart';
import 'package:reboot_launcher/src/dialog/implementation/server.dart';
class ServerButton extends StatefulWidget {
final bool authenticator;
@@ -25,10 +25,10 @@ class _ServerButtonState extends State<ServerButton> {
child: Obx(() => SizedBox(
height: 48,
child: Button(
child: Align(
alignment: Alignment.center,
child: Text(_buttonText),
),
child: Align(
alignment: Alignment.center,
child: Text(_buttonText),
),
onPressed: () => _controller.toggleInteractive()
),
)),

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_common/src/model/server_type.dart';
import 'package:reboot_launcher/src/controller/authenticator_controller.dart';
@@ -35,7 +35,7 @@ class _ServerTypeSelectorState extends State<ServerTypeSelector> {
child: Text(type.label)
),
onPressed: () async {
await _controller.stop();
_controller.stop();
_controller.type.value = type;
}
);

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
@@ -8,8 +8,8 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/widget/common/file_selector.dart';
import 'package:reboot_launcher/src/widget/version/version_name_input.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:path/path.dart' as path;
class AddLocalVersion extends StatefulWidget {

View File

@@ -2,7 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
@@ -14,8 +14,8 @@ import 'package:universal_disk_space/universal_disk_space.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/controller/build_controller.dart';
import 'package:reboot_launcher/src/widget/common/file_selector.dart';
import '../../dialog/dialog.dart';
import '../../dialog/dialog_button.dart';
import '../../dialog/abstract/dialog.dart';
import '../../dialog/abstract/dialog_button.dart';
class AddServerVersion extends StatefulWidget {
const AddServerVersion({Key? key}) : super(key: key);
@@ -63,7 +63,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
}
void _cancelDownload() {
Process.run('${assetsDirectory.path}\\misc\\stop.bat', []);
Process.run('${assetsDirectory.path}\\build\\stop.bat', []);
_downloadPort?.send("kill");
}
@@ -152,18 +152,19 @@ class _AddServerVersionState extends State<AddServerVersion> {
var errorPort = ReceivePort();
errorPort.listen((message) => _onDownloadError(message, null));
var exitPort = ReceivePort();
exitPort.listen((message) {
if(_status.value != DownloadStatus.error) {
_onDownloadComplete();
}
});
await Isolate.spawn(
var isolate = await Isolate.spawn(
downloadArchiveBuild,
options,
onError: errorPort.sendPort,
onExit: exitPort.sendPort,
errorsAreFatal: true
);
exitPort.listen((message) {
isolate.kill(priority: Isolate.immediate);
if(_status.value != DownloadStatus.error) {
_onDownloadComplete();
}
});
} catch (exception, stackTrace) {
_onDownloadError(exception, stackTrace);
}

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/build_controller.dart';

View File

@@ -1,4 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';

View File

@@ -1,16 +1,16 @@
import 'dart:async';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
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/widget/version/add_local_version.dart';
import 'package:reboot_launcher/src/widget/version/add_server_version.dart';
import 'package:reboot_launcher/src/dialog/dialog.dart';
import 'package:reboot_launcher/src/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/message.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/util/checks.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -19,11 +19,11 @@ import 'package:reboot_launcher/src/widget/common/file_selector.dart';
class VersionSelector extends StatefulWidget {
const VersionSelector({Key? key}) : super(key: key);
static Future<void> openDownloadDialog() => showDialog<bool>(
static Future<void> openDownloadDialog() => showAppDialog<bool>(
builder: (context) => const AddServerVersion(),
);
static Future<void> openAddDialog() => showDialog<bool>(
static Future<void> openAddDialog() => showAppDialog<bool>(
builder: (context) => const AddLocalVersion(),
);
@@ -78,9 +78,9 @@ class _VersionSelectorState extends State<VersionSelector> {
return;
}
var result = await _flyoutController.showFlyout<ContextualOption?>(
var result = await _flyoutController.showFlyout<_ContextualOption?>(
builder: (context) => MenuFlyout(
items: ContextualOption.values
items: _ContextualOption.values
.map((entry) => _createOption(context, entry))
.toList()
)
@@ -90,9 +90,9 @@ class _VersionSelectorState extends State<VersionSelector> {
child: child
);
void _handleResult(ContextualOption? result, FortniteVersion version, bool close) async {
void _handleResult(_ContextualOption? result, FortniteVersion version, bool close) async {
switch (result) {
case ContextualOption.openExplorer:
case _ContextualOption.openExplorer:
if(!mounted){
return;
}
@@ -104,7 +104,7 @@ class _VersionSelectorState extends State<VersionSelector> {
launchUrl(version.location.uri)
.onError((error, stackTrace) => _onExplorerError());
break;
case ContextualOption.modify:
case _ContextualOption.modify:
if(!mounted){
return;
}
@@ -115,7 +115,7 @@ class _VersionSelectorState extends State<VersionSelector> {
await _openRenameDialog(context, version);
break;
case ContextualOption.delete:
case _ContextualOption.delete:
if(!mounted){
return;
}
@@ -140,7 +140,7 @@ class _VersionSelectorState extends State<VersionSelector> {
}
}
MenuFlyoutItem _createOption(BuildContext context, ContextualOption entry) {
MenuFlyoutItem _createOption(BuildContext context, _ContextualOption entry) {
return MenuFlyoutItem(
text: Text(entry.name),
onPressed: () => Navigator.of(context).pop(entry)
@@ -148,12 +148,12 @@ class _VersionSelectorState extends State<VersionSelector> {
}
bool _onExplorerError() {
showMessage("This version doesn't exist on the local machine");
showInfoBar("This version doesn't exist on the local machine");
return false;
}
Future<bool?> _openDeleteDialog(BuildContext context, FortniteVersion version) {
return showDialog<bool>(
return showAppDialog<bool>(
builder: (context) => ContentDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
@@ -190,7 +190,7 @@ class _VersionSelectorState extends State<VersionSelector> {
Future<String?> _openRenameDialog(BuildContext context, FortniteVersion version) {
var nameController = TextEditingController(text: version.name);
var pathController = TextEditingController(text: version.location.path);
return showDialog<String?>(
return showAppDialog<String?>(
builder: (context) => FormDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
@@ -245,14 +245,16 @@ class _VersionSelectorState extends State<VersionSelector> {
}
}
enum ContextualOption {
enum _ContextualOption {
openExplorer,
modify,
delete;
delete
}
extension _ContextualOptionExtension on _ContextualOption {
String get name {
return this == ContextualOption.openExplorer ? "Open in explorer"
: this == ContextualOption.modify ? "Modify"
return this == _ContextualOption.openExplorer ? "Open in explorer"
: this == _ContextualOption.modify ? "Modify"
: "Delete";
}
}