Reboot v3

This commit is contained in:
Alessandro Autiero
2023-06-03 18:30:50 +02:00
parent 5eafcae616
commit 30f1b0f162
44 changed files with 1041 additions and 613 deletions

View File

@@ -4,6 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_name_input.dart';
import '../../util/checks.dart';
import '../widget/shared/file_selector.dart';
@@ -38,12 +39,12 @@ class AddLocalVersion extends StatelessWidget {
height: 16.0
),
TextFormBox(
controller: _nameController,
header: "Name",
placeholder: "Type the version's name",
autofocus: true,
validator: (text) => checkVersion(text, _gameController.versions.value)
VersionNameInput(
controller: _nameController
),
const SizedBox(
height: 16.0
),
FileSelector(
@@ -53,6 +54,10 @@ class AddLocalVersion extends StatelessWidget {
controller: _gamePathController,
validator: checkGameFolder,
folder: true
),
const SizedBox(
height: 16.0
)
],
),

View File

@@ -32,16 +32,16 @@ class _AddServerVersionState extends State<AddServerVersion> {
final BuildController _buildController = Get.find<BuildController>();
final TextEditingController _nameController = TextEditingController();
final TextEditingController _pathController = TextEditingController();
final Rx<DownloadStatus> _status = Rx(DownloadStatus.form);
final GlobalKey<FormState> _formKey = GlobalKey();
final Rxn<String> _timeLeft = Rxn();
final Rxn<double> _downloadProgress = Rxn();
late DiskSpace _diskSpace;
late Future _fetchFuture;
late Future _diskFuture;
DownloadStatus _status = DownloadStatus.form;
String _timeLeft = "00:00:00";
double _downloadProgress = 0;
CancelableOperation? _manifestDownloadProcess;
CancelableOperation? _driveDownloadOperation;
Object? _error;
StackTrace? _stackTrace;
@@ -54,7 +54,6 @@ class _AddServerVersionState extends State<AddServerVersion> {
_diskSpace = DiskSpace();
_diskFuture = _diskSpace.scan()
.then((_) => _updateFormDefaults());
_buildController.addOnBuildChangedListener(() => _updateFormDefaults());
super.initState();
}
@@ -62,57 +61,74 @@ class _AddServerVersionState extends State<AddServerVersion> {
void dispose() {
_pathController.dispose();
_nameController.dispose();
_buildController.removeOnBuildChangedListener();
_onDisposed();
_cancelDownload();
super.dispose();
}
void _onDisposed() {
if (_status != DownloadStatus.downloading) {
void _cancelDownload() {
if (_status.value != DownloadStatus.extracting && _status.value != DownloadStatus.extracting) {
return;
}
if (_manifestDownloadProcess != null) {
_manifestDownloadProcess?.cancel();
_buildController.cancelledDownload(true);
if (_manifestDownloadProcess == null) {
return;
}
if (_driveDownloadOperation == null) {
return;
}
_driveDownloadOperation!.cancel();
_buildController.cancelledDownload(true);
Process.run('${assetsDirectory.path}\\builds\\stop.bat', []);
_manifestDownloadProcess?.cancel();
}
@override
Widget build(BuildContext context) {
switch(_status){
case DownloadStatus.form:
return _createFormDialog();
case DownloadStatus.downloading:
return GenericDialog(
header: _createDownloadBody(),
buttons: _createCloseButton()
);
case DownloadStatus.extracting:
return GenericDialog(
header: _createExtractingBody(),
buttons: _createCloseButton()
);
case DownloadStatus.error:
return ErrorDialog(
exception: _error ?? Exception("unknown error"),
stackTrace: _stackTrace,
errorMessageBuilder: (exception) => "Cannot download version: $exception"
);
case DownloadStatus.done:
return const InfoDialog(
text: "The download was completed successfully!",
);
}
}
Widget build(BuildContext context) => Form(
key: _formKey,
child: Obx(() {
switch(_status.value){
case DownloadStatus.form:
return FutureBuilder(
future: Future.wait([_fetchFuture, _diskFuture]),
builder: (context, snapshot) {
if (snapshot.hasError) {
WidgetsBinding.instance
.addPostFrameCallback((_) =>
_onDownloadError(snapshot.error, snapshot.stackTrace));
}
if (!snapshot.hasData) {
return ProgressDialog(
text: "Fetching builds and disks...",
onStop: () => Navigator.of(context).pop()
);
}
return FormDialog(
content: _createFormBody(),
buttons: _createFormButtons()
);
}
);
case DownloadStatus.downloading:
return GenericDialog(
header: _createDownloadBody(),
buttons: _createCloseButton()
);
case DownloadStatus.extracting:
return GenericDialog(
header: _createExtractingBody(),
buttons: _createCloseButton()
);
case DownloadStatus.error:
return ErrorDialog(
exception: _error ?? Exception("unknown error"),
stackTrace: _stackTrace,
errorMessageBuilder: (exception) => "Cannot download version: $exception"
);
case DownloadStatus.done:
return const InfoDialog(
text: "The download was completed successfully!",
);
}
})
);
List<DialogButton> _createFormButtons() {
return [
@@ -127,61 +143,56 @@ class _AddServerVersionState extends State<AddServerVersion> {
void _startDownload(BuildContext context) async {
try {
setState(() => _status = DownloadStatus.downloading);
var build = _buildController.selectedBuildRx.value;
if(build == null){
return;
}
_status.value = DownloadStatus.downloading;
var future = downloadArchiveBuild(
_buildController.selectedBuild.link,
build.link,
Directory(_pathController.text),
_onDownloadProgress,
_onUnrar
(progress, eta) => _onDownloadProgress(progress, eta, false),
(progress, eta) => _onDownloadProgress(progress, eta, true),
);
future.then((value) => _onDownloadComplete());
future.onError((error, stackTrace) => _onDownloadError(error, stackTrace));
_manifestDownloadProcess = CancelableOperation.fromFuture(future);
} catch (exception, stackTrace) {
_onDownloadError(exception, stackTrace);
}
}
void _onUnrar() {
setState(() => _status = DownloadStatus.extracting);
}
Future<void> _onDownloadComplete() async {
if (!mounted) {
return;
}
setState(() {
_status = DownloadStatus.done;
_gameController.addVersion(FortniteVersion(
name: _nameController.text,
location: Directory(_pathController.text)
));
});
_status.value = DownloadStatus.done;
_gameController.addVersion(FortniteVersion(
name: _nameController.text,
location: Directory(_pathController.text)
));
}
void _onDownloadError(Object? error, StackTrace? stackTrace) {
print("Error");
if (!mounted) {
return;
}
setState(() {
_status = DownloadStatus.error;
_error = error;
_stackTrace = stackTrace;
});
_status.value = DownloadStatus.error;
_error = error;
_stackTrace = stackTrace;
}
void _onDownloadProgress(double progress, String timeLeft) {
void _onDownloadProgress(double? progress, String? timeLeft, bool extracting) {
if (!mounted) {
return;
}
setState(() {
_status = DownloadStatus.downloading;
_timeLeft = timeLeft;
_downloadProgress = progress;
});
_status.value = extracting ? DownloadStatus.extracting : DownloadStatus.downloading;
_timeLeft.value = timeLeft;
_downloadProgress.value = progress;
}
Widget _createDownloadBody() => Column(
@@ -204,14 +215,15 @@ class _AddServerVersionState extends State<AddServerVersion> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${_downloadProgress.round()}%",
"${(_downloadProgress.value ?? 0).round()}%",
style: FluentTheme.maybeOf(context)?.typography.body,
),
Text(
"Time left: $_timeLeft",
style: FluentTheme.maybeOf(context)?.typography.body,
)
if(_timeLeft.value != null)
Text(
"Time left: ${_timeLeft.value}",
style: FluentTheme.maybeOf(context)?.typography.body,
)
],
),
@@ -221,7 +233,7 @@ class _AddServerVersionState extends State<AddServerVersion> {
SizedBox(
width: double.infinity,
child: ProgressBar(value: _downloadProgress.toDouble())
child: ProgressBar(value: (_downloadProgress.value ?? 0).toDouble())
),
const SizedBox(
@@ -257,39 +269,27 @@ class _AddServerVersionState extends State<AddServerVersion> {
],
);
Widget _createFormDialog() {
return FutureBuilder(
future: Future.wait([_fetchFuture, _diskFuture]),
builder: (context, snapshot) {
if (snapshot.hasError) {
WidgetsBinding.instance
.addPostFrameCallback((_) =>
_onDownloadError(snapshot.error, snapshot.stackTrace));
}
if (!snapshot.hasData) {
return ProgressDialog(
text: "Fetching builds and disks...",
onStop: () => Navigator.of(context).pop()
);
}
return FormDialog(
content: _createFormBody(),
buttons: _createFormButtons()
);
}
);
}
Widget _createFormBody() {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const BuildSelector(),
const SizedBox(height: 20.0),
VersionNameInput(controller: _nameController),
BuildSelector(
onSelected: _updateFormDefaults
),
const SizedBox(
height: 16.0
),
VersionNameInput(
controller: _nameController
),
const SizedBox(
height: 16.0
),
FileSelector(
label: "Path",
placeholder: "Type the download destination",
@@ -298,6 +298,10 @@ class _AddServerVersionState extends State<AddServerVersion> {
validator: checkDownloadDestination,
folder: true
),
const SizedBox(
height: 16.0
)
],
);
}
@@ -319,9 +323,15 @@ class _AddServerVersionState extends State<AddServerVersion> {
await _fetchFuture;
var bestDisk = _diskSpace.disks
.reduce((first, second) => first.availableSpace > second.availableSpace ? first : second);
var build = _buildController.selectedBuildRx.value;
if(build== null){
return;
}
_pathController.text = "${bestDisk.devicePath}\\FortniteBuilds\\Fortnite "
"${_buildController.selectedBuild.version.toString()}";
_nameController.text = _buildController.selectedBuild.version.toString();
"${build.version}";
_nameController.text = build.version.toString();
_formKey.currentState?.validate();
}
}

View File

@@ -70,7 +70,7 @@ class FormDialog extends AbstractDialog {
text: entry.text,
type: entry.type,
onTap: () {
if(!Form.of(context)!.validate()) {
if(!Form.of(context).validate()) {
return;
}

View File

@@ -5,15 +5,13 @@ import '../../../main.dart';
import 'dialog.dart';
const String _unsupportedServerError = "The build you are currently using is not supported by Reboot. "
"This means that you cannot currently host this version of the game. "
"For a list of supported versions, check #info in the Discord server. "
"If you are unsure which version works best, use build 7.40. "
"If you are a passionate programmer you can add support by opening a PR on Github. ";
const String _corruptedBuildError = "The build you are currently using is corrupted. "
"This means that some critical files are missing for the game to launch. "
"Download the build again from the launcher or, if it's not available there, from another source. "
"Occasionally some files might get corrupted if there isn't enough space on your drive.";
const String _corruptedBuildError = "An unknown error happened while launching Fortnite. "
"Some critical could be missing in your installation. "
"Download the build again from the launcher, not locally, or from a different source. "
"Alternatively, something could have gone wrong in the launcher. ";
Future<void> showBrokenError() async {
showDialog(
@@ -82,7 +80,7 @@ Future<void> showCorruptedBuildError(bool server, [Object? error, StackTrace? st
builder: (context) => ErrorDialog(
exception: error,
stackTrace: stackTrace,
errorMessageBuilder: (exception) => _corruptedBuildError
errorMessageBuilder: (exception) => "${_corruptedBuildError}Error message: $exception"
)
);
}

View File

@@ -125,11 +125,6 @@ extension ServerControllerDialog on ServerController {
return false;
}
var result = await _showPortTakenDialog(3551);
if (!result) {
return false;
}
await freeLawinPort();
await stop();
return _toggle(newResultType);
@@ -139,11 +134,6 @@ extension ServerControllerDialog on ServerController {
return false;
}
var result = await _showPortTakenDialog(8080);
if (!result) {
return false;
}
await freeMatchmakerPort();
await stop();
return _toggle(newResultType);
@@ -203,14 +193,13 @@ extension ServerControllerDialog on ServerController {
Future<Uri?> _pingRemoteInteractive() async {
try {
var mainFuture = ping(host.text, port.text).then((value) => value != null);
var future = _waitFutureOrTime(mainFuture);
var result = await showDialog<bool>(
var future = ping(host.text, port.text);
await showDialog<bool>(
context: appKey.currentContext!,
builder: (context) =>
FutureBuilderDialog(
future: future,
closeAutomatically: false,
closeAutomatically: true,
loadingMessage: "Pinging remote server...",
successfulBody: FutureBuilderDialog.ofMessage(
"The server at ${host.text}:${port
@@ -220,8 +209,8 @@ extension ServerControllerDialog on ServerController {
.text} doesn't work. Check the hostname and/or the port and try again."),
errorMessageBuilder: (exception) => "An error occurred while pining the server: $exception"
)
) ?? false;
return result ? await future : null;
);
return await future;
} catch (_) {
return null;
}
@@ -236,27 +225,6 @@ extension ServerControllerDialog on ServerController {
);
}
Future<bool> _showPortTakenDialog(int port) async {
return await showDialog<bool>(
context: appKey.currentContext!,
builder: (context) =>
InfoDialog(
text: "Port $port is already in use, do you want to kill the associated process?",
buttons: [
DialogButton(
type: ButtonType.secondary,
onTap: () => Navigator.of(context).pop(false),
),
DialogButton(
text: "Kill",
type: ButtonType.primary,
onTap: () => Navigator.of(context).pop(true),
),
],
)
) ?? false;
}
void _showCannotStopError() {
if(!started.value){
return;
@@ -298,7 +266,7 @@ extension ServerControllerDialog on ServerController {
)
);
void _showIllegalPortError() => showMessage("Illegal port for backend server, use only numbers");
void _showIllegalPortError() => showMessage("Invalid port for backend server");
void _showMissingPortError() => showMessage("Missing port for backend server");