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,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||
|
||||
@@ -10,7 +8,7 @@ class AuthenticatorController extends ServerController {
|
||||
String get controllerName => "authenticator";
|
||||
|
||||
@override
|
||||
String get storageName => "reboot_authenticator";
|
||||
String get storageName => "authenticator";
|
||||
|
||||
@override
|
||||
String get defaultHost => kDefaultAuthenticatorHost;
|
||||
|
||||
@@ -2,16 +2,11 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
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';
|
||||
|
||||
class GameController extends GetxController {
|
||||
late final String uuid;
|
||||
late final GetStorage _storage;
|
||||
late final TextEditingController username;
|
||||
late final TextEditingController password;
|
||||
@@ -20,53 +15,39 @@ class GameController extends GetxController {
|
||||
late final Rxn<FortniteVersion> _selectedVersion;
|
||||
late final RxBool started;
|
||||
late final RxBool autoStartGameServer;
|
||||
late final Rxn<Set<Map<String, dynamic>>> servers;
|
||||
late final Rxn<GameInstance> instance;
|
||||
|
||||
GameController() {
|
||||
_storage = GetStorage("reboot_game");
|
||||
Iterable decodedVersionsJson = jsonDecode(_storage.read("versions") ?? "[]");
|
||||
_storage = GetStorage("game");
|
||||
Iterable decodedVersionsJson = jsonDecode(
|
||||
_storage.read("versions") ?? "[]");
|
||||
var decodedVersions = decodedVersionsJson
|
||||
.map((entry) => FortniteVersion.fromJson(entry))
|
||||
.toList();
|
||||
versions = Rx(decodedVersions);
|
||||
versions.listen((data) => _saveVersions());
|
||||
var decodedSelectedVersionName = _storage.read("version");
|
||||
var decodedSelectedVersion = decodedVersions.firstWhereOrNull((element) => element.name == decodedSelectedVersionName);
|
||||
uuid = _storage.read("uuid") ?? const Uuid().v4();
|
||||
_storage.write("uuid", uuid);
|
||||
var decodedSelectedVersion = decodedVersions.firstWhereOrNull((
|
||||
element) => element.name == decodedSelectedVersionName);
|
||||
_selectedVersion = Rxn(decodedSelectedVersion);
|
||||
username = TextEditingController(text: _storage.read("username") ?? kDefaultPlayerName);
|
||||
username = TextEditingController(
|
||||
text: _storage.read("username") ?? kDefaultPlayerName);
|
||||
username.addListener(() => _storage.write("username", username.text));
|
||||
password = TextEditingController(text: _storage.read("password") ?? "");
|
||||
password.addListener(() => _storage.write("password", password.text));
|
||||
customLaunchArgs = TextEditingController(text: _storage.read("custom_launch_args") ?? "");
|
||||
customLaunchArgs.addListener(() => _storage.write("custom_launch_args", customLaunchArgs.text));
|
||||
customLaunchArgs =
|
||||
TextEditingController(text: _storage.read("custom_launch_args") ?? "");
|
||||
customLaunchArgs.addListener(() =>
|
||||
_storage.write("custom_launch_args", customLaunchArgs.text));
|
||||
started = RxBool(false);
|
||||
autoStartGameServer = RxBool(_storage.read("auto_game_server") ?? true);
|
||||
autoStartGameServer.listen((value) => _storage.write("auto_game_server", value));
|
||||
var supabase = Supabase.instance.client;
|
||||
servers = Rxn();
|
||||
supabase.from('hosts')
|
||||
.stream(primaryKey: ['id'])
|
||||
.map((event) => _parseValidServers(event))
|
||||
.listen((event) {
|
||||
if(servers.value == null) {
|
||||
servers.value = event;
|
||||
}else {
|
||||
servers.value?.addAll(event);
|
||||
}
|
||||
});
|
||||
autoStartGameServer.listen((value) =>
|
||||
_storage.write("auto_game_server", value));
|
||||
var serializedInstance = _storage.read("instance");
|
||||
instance = Rxn(serializedInstance != null ? GameInstance.fromJson(jsonDecode(serializedInstance)) : null);
|
||||
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()));
|
||||
|
||||
@@ -123,12 +104,4 @@ class GameController extends GetxController {
|
||||
void updateVersion(FortniteVersion version, Function(FortniteVersion) function) {
|
||||
versions.update((val) => function(version));
|
||||
}
|
||||
|
||||
Map<String, dynamic>? findServerById(String uuid) {
|
||||
try {
|
||||
return servers.value?.firstWhere((element) => element["id"] == uuid);
|
||||
} on StateError catch(_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,29 @@ 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';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const String kDefaultServerName = "Reboot Game Server";
|
||||
const String kDefaultDescription = "Just another server";
|
||||
|
||||
class HostingController extends GetxController {
|
||||
late final GetStorage _storage;
|
||||
late final String uuid;
|
||||
late final TextEditingController name;
|
||||
late final TextEditingController description;
|
||||
late final TextEditingController password;
|
||||
late final RxBool showPassword;
|
||||
late final RxBool discoverable;
|
||||
late final RxBool started;
|
||||
late final RxBool published;
|
||||
late final Rxn<GameInstance> instance;
|
||||
late final Rxn<Set<Map<String, dynamic>>> servers;
|
||||
|
||||
HostingController() {
|
||||
_storage = GetStorage("reboot_hosting");
|
||||
_storage = GetStorage("hosting");
|
||||
uuid = _storage.read("uuid") ?? const Uuid().v4();
|
||||
_storage.write("uuid", uuid);
|
||||
name = TextEditingController(text: _storage.read("name") ?? kDefaultServerName);
|
||||
name.addListener(() => _storage.write("name", name.text));
|
||||
description = TextEditingController(text: _storage.read("description") ?? kDefaultDescription);
|
||||
@@ -30,12 +36,30 @@ class HostingController extends GetxController {
|
||||
discoverable = RxBool(_storage.read("discoverable") ?? true);
|
||||
discoverable.listen((value) => _storage.write("discoverable", value));
|
||||
started = RxBool(false);
|
||||
published = RxBool(false);
|
||||
showPassword = RxBool(false);
|
||||
var serializedInstance = _storage.read("instance");
|
||||
instance = Rxn(serializedInstance != null ? GameInstance.fromJson(jsonDecode(serializedInstance)) : null);
|
||||
instance.listen((_) => saveInstance());
|
||||
var supabase = Supabase.instance.client;
|
||||
servers = Rxn();
|
||||
supabase.from('hosts')
|
||||
.stream(primaryKey: ['id'])
|
||||
.map((event) => _parseValidServers(event))
|
||||
.listen((event) {
|
||||
if(servers.value == null) {
|
||||
servers.value = event;
|
||||
}else {
|
||||
servers.value?.addAll(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Set<Map<String, dynamic>> _parseValidServers(event) => event.where((element) => _isValidServer(element)).toSet();
|
||||
|
||||
bool _isValidServer(Map<String, dynamic> element) =>
|
||||
element["id"] != uuid && element["ip"] != null;
|
||||
|
||||
Future<void> saveInstance() => _storage.write("instance", jsonEncode(instance.value?.toJson()));
|
||||
|
||||
void reset() {
|
||||
@@ -46,4 +70,12 @@ class HostingController extends GetxController {
|
||||
started.value = false;
|
||||
instance.value = null;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? findServerById(String uuid) {
|
||||
try {
|
||||
return servers.value?.firstWhere((element) => element["id"] == uuid);
|
||||
} on StateError catch(_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||
|
||||
class MatchmakerController extends ServerController {
|
||||
late final TextEditingController gameServerAddress;
|
||||
late final FocusNode gameServerAddressFocusNode;
|
||||
late final RxnString gameServerOwner;
|
||||
|
||||
MatchmakerController() : super() {
|
||||
gameServerAddress = TextEditingController(text: storage.read("game_server_address") ?? kDefaultMatchmakerHost);
|
||||
writeMatchmakingIp(gameServerAddress.text);
|
||||
var lastValue = gameServerAddress.text;
|
||||
writeMatchmakingIp(lastValue);
|
||||
gameServerAddress.addListener(() {
|
||||
storage.write("game_server_address", gameServerAddress.text);
|
||||
writeMatchmakingIp(gameServerAddress.text);
|
||||
var newValue = gameServerAddress.text;
|
||||
if(newValue.trim().toLowerCase() == lastValue.trim().toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastValue = newValue;
|
||||
gameServerAddress.selection = TextSelection.collapsed(offset: newValue.length);
|
||||
storage.write("game_server_address", newValue);
|
||||
writeMatchmakingIp(newValue);
|
||||
});
|
||||
watchMatchmakingIp().listen((event) {
|
||||
if(event != null && gameServerAddress.text != event) {
|
||||
gameServerAddress.text = event;
|
||||
}
|
||||
});
|
||||
gameServerAddressFocusNode = FocusNode();
|
||||
gameServerOwner = RxnString(storage.read("game_server_owner"));
|
||||
gameServerOwner.listen((value) => storage.write("game_server_owner", value));
|
||||
}
|
||||
|
||||
@override
|
||||
String get controllerName => "matchmaker";
|
||||
|
||||
@override
|
||||
String get storageName => "reboot_matchmaker";
|
||||
String get storageName => "matchmaker";
|
||||
|
||||
@override
|
||||
String get defaultHost => kDefaultMatchmakerHost;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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:reboot_launcher/main.dart';
|
||||
|
||||
class SettingsController extends GetxController {
|
||||
late final GetStorage _storage;
|
||||
@@ -19,7 +19,7 @@ class SettingsController extends GetxController {
|
||||
late double scrollingDistance;
|
||||
|
||||
SettingsController() {
|
||||
_storage = GetStorage("reboot_settings");
|
||||
_storage = GetStorage("settings");
|
||||
gameServerDll = _createController("game_server", "reboot.dll");
|
||||
unrealEngineConsoleDll = _createController("unreal_engine_console", "console.dll");
|
||||
authenticatorDll = _createController("authenticator", "cobalt.dll");
|
||||
@@ -54,6 +54,7 @@ class SettingsController extends GetxController {
|
||||
gameServerDll.text = _controllerDefaultPath("reboot.dll");
|
||||
unrealEngineConsoleDll.text = _controllerDefaultPath("console.dll");
|
||||
authenticatorDll.text = _controllerDefaultPath("cobalt.dll");
|
||||
gameServerPort.text = kDefaultGameServerPort;
|
||||
firstRun.value = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class UpdateController {
|
||||
late final TextEditingController url;
|
||||
|
||||
UpdateController() {
|
||||
_storage = GetStorage("reboot_update");
|
||||
_storage = GetStorage("update");
|
||||
timestamp = RxnInt(_storage.read("ts"));
|
||||
timestamp.listen((value) => _storage.write("ts", value));
|
||||
var timerIndex = _storage.read("timer");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/page/home_page.dart';
|
||||
import 'package:sync/semaphore.dart';
|
||||
|
||||
@@ -18,7 +17,7 @@ void restoreMessage(int lastIndex) {
|
||||
Overlay.of(pageKey.currentContext!).insert(overlay);
|
||||
}
|
||||
|
||||
void showInfoBar(String text, {InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = snackbarShortDuration, Widget? action}) {
|
||||
void showInfoBar(dynamic text, {InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = snackbarShortDuration, Widget? action}) {
|
||||
try {
|
||||
_semaphore.acquire();
|
||||
var index = pageIndex.value;
|
||||
@@ -29,12 +28,25 @@ void showInfoBar(String text, {InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
width: double.infinity,
|
||||
child: Mica(
|
||||
child: InfoBar(
|
||||
title: Text(text),
|
||||
isLong: action == null,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if(text is Widget)
|
||||
text,
|
||||
if(text is String)
|
||||
Text(text),
|
||||
if(action != null)
|
||||
action
|
||||
],
|
||||
),
|
||||
isLong: false,
|
||||
isIconVisible: true,
|
||||
content: action ?? SizedBox(
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: loading ? const ProgressBar() : const SizedBox()
|
||||
child: loading ? const Padding(
|
||||
padding: EdgeInsets.only(top: 8.0, bottom: 2.0),
|
||||
child: ProgressBar(),
|
||||
) : const SizedBox()
|
||||
),
|
||||
severity: severity
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/page/home_page.dart';
|
||||
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/home_page.dart';
|
||||
|
||||
|
||||
String? lastError;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:dart_ipify/dart_ipify.dart';
|
||||
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:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_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/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';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
extension ServerControllerDialog on ServerController {
|
||||
Future<bool> restartInteractive() async {
|
||||
@@ -165,7 +166,22 @@ extension ServerControllerDialog on ServerController {
|
||||
}
|
||||
|
||||
extension MatchmakerControllerExtension on MatchmakerController {
|
||||
Future<void> joinServer(Map<String, dynamic> entry) async {
|
||||
void joinLocalHost() {
|
||||
gameServerAddress.text = kDefaultGameServerHost;
|
||||
gameServerOwner.value = null;
|
||||
}
|
||||
|
||||
Future<void> joinServer(String uuid, Map<String, dynamic> entry) async {
|
||||
var id = entry["id"];
|
||||
if(uuid == id) {
|
||||
showInfoBar(
|
||||
"You can't join your own server",
|
||||
duration: snackbarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var hashedPassword = entry["password"];
|
||||
var hasPassword = hashedPassword != null;
|
||||
var embedded = type.value == ServerType.embedded;
|
||||
@@ -275,6 +291,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
|
||||
void _onSuccess(bool embedded, String decryptedIp, String author) {
|
||||
if(embedded) {
|
||||
gameServerAddress.text = decryptedIp;
|
||||
gameServerOwner.value = author;
|
||||
pageIndex.value = 0;
|
||||
}else {
|
||||
FlutterClipboard.controlC(decryptedIp);
|
||||
@@ -285,4 +302,44 @@ extension MatchmakerControllerExtension on MatchmakerController {
|
||||
severity: InfoBarSeverity.success
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
extension HostingControllerExtension on HostingController {
|
||||
Future<void> publishServer(String author, String version) async {
|
||||
var passwordText = password.text;
|
||||
var hasPassword = passwordText.isNotEmpty;
|
||||
var ip = await Ipify.ipv4();
|
||||
if(hasPassword) {
|
||||
ip = aes256Encrypt(ip, passwordText);
|
||||
}
|
||||
|
||||
var supabase = Supabase.instance.client;
|
||||
var hosts = supabase.from('hosts');
|
||||
var payload = {
|
||||
'name': name.text,
|
||||
'description': description.text,
|
||||
'author': author,
|
||||
'ip': ip,
|
||||
'version': version,
|
||||
'password': hasPassword ? hashPassword(passwordText) : null,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
'discoverable': discoverable.value
|
||||
};
|
||||
if(published()) {
|
||||
await hosts.update(payload).eq("id", uuid);
|
||||
}else {
|
||||
payload["id"] = uuid;
|
||||
await hosts.insert(payload);
|
||||
}
|
||||
|
||||
published.value = true;
|
||||
}
|
||||
|
||||
Future<void> discardServer() async {
|
||||
var supabase = Supabase.instance.client;
|
||||
await supabase.from('hosts')
|
||||
.delete()
|
||||
.match({'id': uuid});
|
||||
published.value = false;
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,13 @@ 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';
|
||||
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/common/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/type_selector.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.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);
|
||||
|
||||
@@ -70,9 +68,19 @@ class _AuthenticatorPageState extends State<AuthenticatorPage> with AutomaticKee
|
||||
subtitle: "Whether the embedded authenticator should be started as a separate process, useful for debugging",
|
||||
contentWidth: null,
|
||||
isChild: true,
|
||||
content: Obx(() => ToggleSwitch(
|
||||
checked: _authenticatorController.detached(),
|
||||
onChanged: (value) => _authenticatorController.detached.value = value
|
||||
content: Obx(() => Row(
|
||||
children: [
|
||||
Text(
|
||||
_authenticatorController.detached.value ? "On" : "Off"
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16.0
|
||||
),
|
||||
ToggleSwitch(
|
||||
checked: _authenticatorController.detached(),
|
||||
onChanged: (value) => _authenticatorController.detached.value = value
|
||||
),
|
||||
],
|
||||
))
|
||||
),
|
||||
],
|
||||
|
||||
@@ -4,15 +4,12 @@ import 'dart:async';
|
||||
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/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/matchmaker_controller.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';
|
||||
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
|
||||
class BrowsePage extends StatefulWidget {
|
||||
const BrowsePage({Key? key}) : super(key: key);
|
||||
|
||||
@@ -21,7 +18,7 @@ class BrowsePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BrowsePageState extends State<BrowsePage> with AutomaticKeepAliveClientMixin {
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final HostingController _hostingController = Get.find<HostingController>();
|
||||
final MatchmakerController _matchmakerController = Get.find<MatchmakerController>();
|
||||
final TextEditingController _filterController = TextEditingController();
|
||||
final StreamController<String> _filterControllerStream = StreamController();
|
||||
@@ -33,7 +30,10 @@ class _BrowsePageState extends State<BrowsePage> with AutomaticKeepAliveClientMi
|
||||
future: Future.delayed(const Duration(seconds: 1)), // Fake delay to show loading
|
||||
builder: (context, futureSnapshot) => Obx(() {
|
||||
var ready = futureSnapshot.connectionState == ConnectionState.done;
|
||||
var data = _gameController.servers.value;
|
||||
var data = _hostingController.servers
|
||||
.value
|
||||
?.where((entry) => entry["discoverable"] ?? false)
|
||||
.toSet();
|
||||
if(ready && data?.isEmpty == true) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -108,7 +108,7 @@ class _BrowsePageState extends State<BrowsePage> with AutomaticKeepAliveClientMi
|
||||
title: "${_formatName(entry)} • ${entry["author"]}",
|
||||
subtitle: "${_formatDescription(entry)} • ${_formatVersion(entry)}",
|
||||
content: Button(
|
||||
onPressed: () => _matchmakerController.joinServer(entry),
|
||||
onPressed: () => _matchmakerController.joinServer(_hostingController.uuid, entry),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -146,7 +146,7 @@ class _BrowsePageState extends State<BrowsePage> with AutomaticKeepAliveClientMi
|
||||
}
|
||||
|
||||
bool _isValidItem(Map<String, dynamic> entry, String? filter) =>
|
||||
(entry["discoverable"] ?? false) && (filter == null || _filterServer(entry, filter));
|
||||
filter == null || _filterServer(entry, filter);
|
||||
|
||||
bool _filterServer(Map<String, dynamic> element, String filter) {
|
||||
String? id = element["id"];
|
||||
|
||||
@@ -4,18 +4,17 @@ 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/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/page/authenticator_page.dart';
|
||||
import 'package:reboot_launcher/src/page/browse_page.dart';
|
||||
import 'package:reboot_launcher/src/page/hosting_page.dart';
|
||||
import 'package:reboot_launcher/src/page/info_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/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/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';
|
||||
|
||||
GlobalKey appKey = GlobalKey();
|
||||
@@ -48,6 +47,13 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
void initState() {
|
||||
windowManager.addListener(this);
|
||||
_searchController.addListener(_onSearch);
|
||||
var lastValue = pageIndex.value;
|
||||
pageIndex.listen((value) {
|
||||
if(value != lastValue) {
|
||||
_pagesStack.add(lastValue);
|
||||
lastValue = value;
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -116,10 +122,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
|
||||
pane: NavigationPane(
|
||||
key: appKey,
|
||||
selected: pageIndex.value,
|
||||
onChanged: (index) {
|
||||
_pagesStack.add(pageIndex.value);
|
||||
pageIndex.value = index;
|
||||
},
|
||||
onChanged: (index) => pageIndex.value = index,
|
||||
menuButton: const SizedBox(),
|
||||
displayMode: PaneDisplayMode.open,
|
||||
items: _items,
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:dart_ipify/dart_ipify.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
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/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/dialog/implementation/server.dart';
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/widget/game/start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector.dart';
|
||||
import 'package:sync/semaphore.dart';
|
||||
|
||||
class HostingPage extends StatefulWidget {
|
||||
const HostingPage({Key? key}) : super(key: key);
|
||||
@@ -24,7 +25,7 @@ class HostingPage extends StatefulWidget {
|
||||
class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClientMixin {
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final HostingController _hostingController = Get.find<HostingController>();
|
||||
final UpdateController _updateController = Get.find<UpdateController>();
|
||||
final Semaphore _semaphore = Semaphore();
|
||||
late final RxBool _showPasswordTrailing = RxBool(_hostingController.password.text.isNotEmpty);
|
||||
|
||||
@override
|
||||
@@ -48,7 +49,8 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
isChild: true,
|
||||
content: TextFormBox(
|
||||
placeholder: "Name",
|
||||
controller: _hostingController.name
|
||||
controller: _hostingController.name,
|
||||
onChanged: (_) => _updateServer()
|
||||
)
|
||||
),
|
||||
SettingTile(
|
||||
@@ -56,8 +58,9 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
subtitle: "The description of your game server",
|
||||
isChild: true,
|
||||
content: TextFormBox(
|
||||
placeholder: "Description",
|
||||
controller: _hostingController.description
|
||||
placeholder: "Description",
|
||||
controller: _hostingController.description,
|
||||
onChanged: (_) => _updateServer()
|
||||
)
|
||||
),
|
||||
SettingTile(
|
||||
@@ -71,7 +74,10 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
obscureText: !_hostingController.showPassword.value,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
onChanged: (text) => _showPasswordTrailing.value = text.isNotEmpty,
|
||||
onChanged: (text) {
|
||||
_showPasswordTrailing.value = text.isNotEmpty;
|
||||
_updateServer();
|
||||
},
|
||||
suffix: Button(
|
||||
onPressed: () => _hostingController.showPassword.value = !_hostingController.showPassword.value,
|
||||
style: ButtonStyle(
|
||||
@@ -90,9 +96,22 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
subtitle: "Make your server available to other players on the server browser",
|
||||
isChild: true,
|
||||
contentWidth: null,
|
||||
content: Obx(() => ToggleSwitch(
|
||||
checked: _hostingController.discoverable(),
|
||||
onChanged: (value) => _hostingController.discoverable.value = value
|
||||
content: Obx(() => Row(
|
||||
children: [
|
||||
Text(
|
||||
_hostingController.discoverable.value ? "On" : "Off"
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16.0
|
||||
),
|
||||
ToggleSwitch(
|
||||
checked: _hostingController.discoverable(),
|
||||
onChanged: (value) async {
|
||||
_hostingController.discoverable.value = value;
|
||||
await _updateServer();
|
||||
}
|
||||
),
|
||||
],
|
||||
))
|
||||
)
|
||||
],
|
||||
@@ -138,7 +157,7 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
isChild: true,
|
||||
content: Button(
|
||||
onPressed: () async {
|
||||
FlutterClipboard.controlC("$kCustomUrlSchema://${_gameController.uuid}");
|
||||
FlutterClipboard.controlC("$kCustomUrlSchema://${_hostingController.uuid}");
|
||||
showInfoBar(
|
||||
"Copied your link to the clipboard",
|
||||
severity: InfoBarSeverity.success
|
||||
@@ -177,6 +196,35 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
SettingTile(
|
||||
title: "Reset game server",
|
||||
subtitle: "Resets the game server's settings to their default values",
|
||||
content: Button(
|
||||
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: [
|
||||
DialogButton(
|
||||
type: ButtonType.secondary,
|
||||
text: "Close",
|
||||
),
|
||||
DialogButton(
|
||||
type: ButtonType.primary,
|
||||
text: "Reset",
|
||||
onTap: () {
|
||||
_hostingController.reset();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
child: const Text("Reset"),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -190,4 +238,26 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateServer() async {
|
||||
if(!_hostingController.published()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_semaphore.acquire();
|
||||
_hostingController.publishServer(
|
||||
_gameController.username.text,
|
||||
_hostingController.instance.value!.versionName
|
||||
);
|
||||
} catch(error) {
|
||||
showInfoBar(
|
||||
"An error occurred while updating the game server: $error",
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: snackbarLongDuration
|
||||
);
|
||||
} finally {
|
||||
_semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/util/tutorial.dart';
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
|
||||
|
||||
class InfoPage extends StatefulWidget {
|
||||
const InfoPage({Key? key}) : super(key: key);
|
||||
@@ -123,6 +124,33 @@ class _InfoPageState extends State<InfoPage> with AutomaticKeepAliveClientMixin
|
||||
.typography
|
||||
.title,
|
||||
contentWidth: null,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
SettingTile(
|
||||
title: 'How can I make my game server accessible for other players?',
|
||||
subtitle: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Follow ',
|
||||
style: FluentTheme.of(context).typography.body
|
||||
),
|
||||
TextSpan(
|
||||
text: 'this tutorial',
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
style: FluentTheme.of(context).typography.body?.copyWith(color: FluentTheme.of(context).accentColor),
|
||||
recognizer: TapGestureRecognizer()..onTap = openPortTutorial
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
titleStyle: FluentTheme
|
||||
.of(context)
|
||||
.typography
|
||||
.title,
|
||||
contentWidth: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -3,14 +3,12 @@ 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';
|
||||
import 'package:reboot_launcher/src/widget/server/type_selector.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.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/common/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/server/type_selector.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MatchmakerPage extends StatefulWidget {
|
||||
const MatchmakerPage({Key? key}) : super(key: key);
|
||||
@@ -71,7 +69,8 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
|
||||
isChild: true,
|
||||
content: TextFormBox(
|
||||
placeholder: "Address",
|
||||
controller: _matchmakerController.gameServerAddress
|
||||
controller: _matchmakerController.gameServerAddress,
|
||||
focusNode: _matchmakerController.gameServerAddressFocusNode
|
||||
)
|
||||
),
|
||||
if(_matchmakerController.type.value == ServerType.embedded)
|
||||
@@ -80,9 +79,19 @@ class _MatchmakerPageState extends State<MatchmakerPage> with AutomaticKeepAlive
|
||||
subtitle: "Whether the embedded matchmaker should be started as a separate process, useful for debugging",
|
||||
contentWidth: null,
|
||||
isChild: true,
|
||||
content: Obx(() => ToggleSwitch(
|
||||
checked: _matchmakerController.detached.value,
|
||||
onChanged: (value) => _matchmakerController.detached.value = value
|
||||
content: Obx(() => Row(
|
||||
children: [
|
||||
Text(
|
||||
_matchmakerController.detached.value ? "On" : "Off"
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16.0
|
||||
),
|
||||
ToggleSwitch(
|
||||
checked: _matchmakerController.detached.value,
|
||||
onChanged: (value) => _matchmakerController.detached.value = value
|
||||
),
|
||||
],
|
||||
)),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/matchmaker_controller.dart';
|
||||
import 'package:reboot_launcher/src/page/home_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/game/start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/game/start_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_selector.dart';
|
||||
|
||||
|
||||
@@ -71,10 +71,26 @@ class _PlayPageState extends State<PlayPage> {
|
||||
SettingTile(
|
||||
title: "Game Server",
|
||||
subtitle: "Helpful shortcuts to find the server where you want to play",
|
||||
content: IgnorePointer(
|
||||
child: Button(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: ButtonState.all(FluentTheme.of(context).resources.controlFillColorDefault)
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Obx(() {
|
||||
var address = _matchmakerController.gameServerAddress.text;
|
||||
var owner = _matchmakerController.gameServerOwner.value;
|
||||
return Text(
|
||||
isLocalHost(address) ? "Your server" : owner != null ? "$owner's server" : address,
|
||||
textAlign: TextAlign.start
|
||||
);
|
||||
})
|
||||
),
|
||||
),
|
||||
expandedContent: [
|
||||
SettingTile(
|
||||
title: "Host a server",
|
||||
subtitle: "Do you want to play with your friends? Host a server for them!",
|
||||
subtitle: "Do you want to create a game server for yourself or your friends? Host one!",
|
||||
content: Button(
|
||||
onPressed: () => pageIndex.value = 1,
|
||||
child: const Text("Host")
|
||||
@@ -82,13 +98,25 @@ class _PlayPageState extends State<PlayPage> {
|
||||
isChild: true
|
||||
),
|
||||
SettingTile(
|
||||
title: "Join a server",
|
||||
subtitle: "Find a server where you can play on the launcher's server browser",
|
||||
title: "Join a Reboot server",
|
||||
subtitle: "Find a discoverable server hosted on the Reboot Launcher in the server browser",
|
||||
content: Button(
|
||||
onPressed: () => pageIndex.value = 2,
|
||||
child: const Text("Browse")
|
||||
),
|
||||
isChild: true
|
||||
),
|
||||
SettingTile(
|
||||
title: "Join a custom server",
|
||||
subtitle: "Type the address of any server, whether it was hosted on the Reboot Launcher or not",
|
||||
content: Button(
|
||||
onPressed: () {
|
||||
pageIndex.value = 4;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _matchmakerController.gameServerAddressFocusNode.requestFocus());
|
||||
},
|
||||
child: const Text("Join")
|
||||
),
|
||||
isChild: true
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
@@ -2,19 +2,16 @@ 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';
|
||||
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/controller/update_controller.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/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:reboot_launcher/src/widget/common/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/common/setting_tile.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({Key? key}) : super(key: key);
|
||||
@@ -24,10 +21,7 @@ class SettingsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClientMixin {
|
||||
final BuildController _buildController = Get.find<BuildController>();
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final HostingController _hostingController = Get.find<HostingController>();
|
||||
final AuthenticatorController _authenticatorController = Get.find<AuthenticatorController>();
|
||||
final SettingsController _settingsController = Get.find<SettingsController>();
|
||||
final UpdateController _updateController = Get.find<UpdateController>();
|
||||
|
||||
@@ -108,6 +102,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
|
||||
text: Text(entry.text),
|
||||
onPressed: () {
|
||||
_updateController.timer.value = entry;
|
||||
removeMessage(6);
|
||||
_updateController.update(true);
|
||||
}
|
||||
)).toList()
|
||||
@@ -148,7 +143,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
|
||||
content: Button(
|
||||
onPressed: () => showAppDialog(
|
||||
builder: (context) => InfoDialog(
|
||||
text: "Do you want to reset all the launcher's settings to their default values? This action is irreversible",
|
||||
text: "Do you want to reset all the setting in this tab to their default values? This action is irreversible",
|
||||
buttons: [
|
||||
DialogButton(
|
||||
type: ButtonType.secondary,
|
||||
@@ -158,12 +153,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
|
||||
type: ButtonType.primary,
|
||||
text: "Reset",
|
||||
onTap: () {
|
||||
_buildController.reset();
|
||||
_gameController.reset();
|
||||
_hostingController.reset();
|
||||
_authenticatorController.reset();
|
||||
_settingsController.reset();
|
||||
_updateController.reset();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bcrypt/bcrypt.dart';
|
||||
import 'package:pointycastle/export.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
const int _ivLength = 16;
|
||||
const int _keyLength = 32;
|
||||
|
||||
3
gui/lib/src/util/tutorial.dart
Normal file
3
gui/lib/src/util/tutorial.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
Future<void> openPortTutorial() => launchUrl(Uri.parse("https://github.com/Auties00/reboot_launcher/blob/master/documentation/PortForwarding.md"));
|
||||
@@ -28,7 +28,7 @@ extension GameInstanceWatcher on GameInstance {
|
||||
observerPid = await startBackgroundProcess(
|
||||
executable: _executable,
|
||||
args: [
|
||||
_gameController.uuid,
|
||||
_hostingController.uuid,
|
||||
gamePid.toString(),
|
||||
launcherPid?.toString() ?? "-1",
|
||||
eacPid?.toString() ?? "-1",
|
||||
@@ -50,6 +50,6 @@ extension GameInstanceWatcher on GameInstance {
|
||||
_hostingController.instance.value?.kill();
|
||||
await _supabase.from('hosts')
|
||||
.delete()
|
||||
.match({'id': _gameController.uuid});
|
||||
.match({'id': _hostingController.uuid});
|
||||
}
|
||||
}
|
||||
@@ -63,13 +63,19 @@ class _FileSelectorState extends State<FileSelector> {
|
||||
_selecting = true;
|
||||
if(widget.folder) {
|
||||
compute(openFolderPicker, widget.windowTitle)
|
||||
.then((value) => widget.controller.text = value ?? widget.controller.text)
|
||||
.then(_updateText)
|
||||
.then((_) => _selecting = false);
|
||||
return;
|
||||
}
|
||||
|
||||
compute(openFilePicker, widget.extension!)
|
||||
.then((value) => widget.controller.text = value ?? widget.controller.text)
|
||||
.then(_updateText)
|
||||
.then((_) => _selecting = false);
|
||||
}
|
||||
|
||||
void _updateText(String? value) {
|
||||
var text = value ?? widget.controller.text;
|
||||
widget.controller.text = value ?? widget.controller.text;
|
||||
widget.controller.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class SettingTile extends StatefulWidget {
|
||||
|
||||
final String? title;
|
||||
final TextStyle? titleStyle;
|
||||
final String? subtitle;
|
||||
final dynamic subtitle;
|
||||
final TextStyle? subtitleStyle;
|
||||
final Widget? content;
|
||||
final double? contentWidth;
|
||||
@@ -27,10 +27,9 @@ class SettingTile extends StatefulWidget {
|
||||
this.expandedContentHeaderHeight = kDefaultHeaderHeight,
|
||||
this.expandedContent,
|
||||
this.isChild = false})
|
||||
: assert(
|
||||
(title == null && subtitle == null) ||
|
||||
(title != null && subtitle != null),
|
||||
"Title and subtitle can only be null together"),
|
||||
: assert((title == null && subtitle == null) || (title != null && subtitle != null), "title and subtitle can only be null together"),
|
||||
assert(subtitle == null || subtitle is String || subtitle is Widget, "subtitle can only be null, String or Widget"),
|
||||
assert(subtitle is! Widget || subtitleStyle == null, "subtitleStyle must be null if subtitle is a widget"),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@@ -112,7 +111,7 @@ class _SettingTileState extends State<SettingTile> {
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _subtitle => Text(
|
||||
Widget get _subtitle => widget.subtitle is Widget ? widget.subtitle : Text(
|
||||
widget.subtitle!,
|
||||
style: widget.subtitleStyle ?? FluentTheme.of(context).typography.body
|
||||
);
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:dart_ipify/dart_ipify.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:process_run/shell.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/authenticator_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/matchmaker_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/dialog/abstract/info_bar.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/tutorial.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;
|
||||
@@ -131,7 +132,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
var eacProcess = await _createEacProcess(version);
|
||||
var executable = await version.executable;
|
||||
var gameProcess = await _createGameProcess(executable!.path, host);
|
||||
var instance = GameInstance(gameProcess, launcherProcess, eacProcess, host, linkedHosting);
|
||||
var instance = GameInstance(version.name, gameProcess, launcherProcess, eacProcess, host, linkedHosting);
|
||||
instance.startObserver();
|
||||
if(host){
|
||||
_removeHostEntry();
|
||||
@@ -149,8 +150,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// var matchmakingIp = _settingsController.matchmakingIp.text;
|
||||
var matchmakingIp = "127.0.0.1";
|
||||
var matchmakingIp = _matchmakerController.gameServerAddress.text;
|
||||
if(!isLocalHost(matchmakingIp)) {
|
||||
return false;
|
||||
}
|
||||
@@ -241,8 +241,9 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return;
|
||||
}
|
||||
|
||||
var theme = FluentTheme.of(context);
|
||||
showInfoBar(
|
||||
"Waiting for the game server to become available...",
|
||||
"Waiting for the game server to boot up...",
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
@@ -253,54 +254,84 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
);
|
||||
if(!localPingResult) {
|
||||
showInfoBar(
|
||||
"The headless server was started successfully, but the game server isn't available",
|
||||
"The headless server was started successfully, but the game server didn't boot",
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: snackbarLongDuration
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
_matchmakerController.joinLocalHost();
|
||||
var accessible = await _checkAccessible(theme, gameServerPort);
|
||||
if(!accessible) {
|
||||
showInfoBar(
|
||||
"The game server was started successfully, but nobody outside your network can join",
|
||||
"The game server was started successfully, but other players can't 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")
|
||||
)
|
||||
duration: snackbarLongDuration
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_hostingController.discoverable.value){
|
||||
var password = _hostingController.password.text;
|
||||
var hasPassword = password.isNotEmpty;
|
||||
var ip = await Ipify.ipv4();
|
||||
if(hasPassword) {
|
||||
ip = aes256Encrypt(ip, password);
|
||||
}
|
||||
await _hostingController.publishServer(
|
||||
_gameController.username.text,
|
||||
_hostingController.instance.value!.versionName,
|
||||
);
|
||||
showInfoBar(
|
||||
"The game server was started successfully",
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: snackbarLongDuration
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
Future<bool> _checkAccessible(FluentThemeData theme, String gameServerPort) async {
|
||||
showInfoBar(
|
||||
"Checking if other players can join the game server...",
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
var publicIp = await Ipify.ipv4();
|
||||
var externalResult = await pingGameServer("$publicIp:$gameServerPort");
|
||||
if(externalResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var future = CancelableOperation.fromFuture(pingGameServer(
|
||||
"$publicIp:$gameServerPort",
|
||||
timeout: const Duration(days: 365)
|
||||
));
|
||||
showInfoBar(
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: "Other players can't join the game server currently: please follow "
|
||||
),
|
||||
TextSpan(
|
||||
text: "this tutorial",
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
style: TextStyle(
|
||||
color: theme.accentColor.dark
|
||||
),
|
||||
recognizer: TapGestureRecognizer()..onTap = openPortTutorial
|
||||
),
|
||||
const TextSpan(
|
||||
text: " to fix this problem"
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
action: Button(
|
||||
onPressed: () {
|
||||
future.cancel();
|
||||
removeMessage(1);
|
||||
},
|
||||
child: const Text("Ignore"),
|
||||
),
|
||||
severity: InfoBarSeverity.warning,
|
||||
duration: null,
|
||||
loading: true
|
||||
);
|
||||
return await future.valueOrCancellation() ?? false;
|
||||
}
|
||||
|
||||
void _onGameOutput(String line, bool host) {
|
||||
@@ -387,7 +418,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
Future<void> _removeHostEntry() async {
|
||||
await _supabase.from('hosts')
|
||||
.delete()
|
||||
.match({'id': _gameController.uuid});
|
||||
.match({'id': _hostingController.uuid});
|
||||
}
|
||||
|
||||
Future<void> _injectOrShowError(Injectable injectable, bool hosting) async {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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/dialog/implementation/profile.dart';
|
||||
|
||||
|
||||
@@ -2,15 +2,14 @@ import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.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/abstract/dialog.dart';
|
||||
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class AddLocalVersion extends StatefulWidget {
|
||||
const AddLocalVersion({Key? key})
|
||||
@@ -31,7 +30,9 @@ class _AddLocalVersionState extends State<AddLocalVersion> {
|
||||
var file = Directory(_gamePathController.text);
|
||||
if(await file.exists()) {
|
||||
if(_nameController.text.isEmpty) {
|
||||
_nameController.text = path.basename(_gamePathController.text);
|
||||
var text = path.basename(_gamePathController.text);
|
||||
_nameController.text = text;
|
||||
_nameController.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,15 +5,15 @@ import 'dart:isolate';
|
||||
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';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/build_controller.dart';
|
||||
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_build_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/version/version_name_input.dart';
|
||||
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/abstract/dialog.dart';
|
||||
import '../../dialog/abstract/dialog_button.dart';
|
||||
|
||||
@@ -334,8 +334,12 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
||||
return;
|
||||
}
|
||||
|
||||
_pathController.text = "${bestDisk.devicePath}\\FortniteBuilds\\${build.version}";
|
||||
_nameController.text = build.version.toString();
|
||||
var pathText = "${bestDisk.devicePath}\\FortniteBuilds\\${build.version}";
|
||||
_pathController.text = pathText;
|
||||
_pathController.selection = TextSelection.collapsed(offset: pathText.length);
|
||||
var buildName = build.version.toString();
|
||||
_nameController.text = buildName;
|
||||
_nameController.selection = TextSelection.collapsed(offset: buildName.length);
|
||||
_formKey.currentState?.validate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@ 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/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';
|
||||
|
||||
import 'package:reboot_launcher/src/widget/common/file_selector.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:url_launcher/url_launcher.dart';
|
||||
|
||||
class VersionSelector extends StatefulWidget {
|
||||
const VersionSelector({Key? key}) : super(key: key);
|
||||
|
||||
Reference in New Issue
Block a user