mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
<feat: New release>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
},
|
||||
);
|
||||
@@ -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;
|
||||
80
gui/lib/src/dialog/abstract/info_bar.dart
Normal file
80
gui/lib/src/dialog/abstract/info_bar.dart
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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."
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: [
|
||||
|
||||
34
gui/lib/src/util/matchmaker.dart
Normal file
34
gui/lib/src/util/matchmaker.dart
Normal 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;
|
||||
}
|
||||
@@ -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()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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()
|
||||
),
|
||||
)),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user