mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 11:12:23 +01:00
Added headless switch
Fixed UI
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
|
|
||||||
class GameController extends GetxController {
|
class GameController extends GetxController {
|
||||||
late final GetStorage _storage;
|
late final GetStorage _storage;
|
||||||
@@ -12,8 +13,9 @@ class GameController extends GetxController {
|
|||||||
late final TextEditingController version;
|
late final TextEditingController version;
|
||||||
late final Rx<List<FortniteVersion>> versions;
|
late final Rx<List<FortniteVersion>> versions;
|
||||||
late final Rxn<FortniteVersion> _selectedVersion;
|
late final Rxn<FortniteVersion> _selectedVersion;
|
||||||
late final RxBool host;
|
late final Rx<GameType> type;
|
||||||
late final RxBool started;
|
late final RxBool started;
|
||||||
|
Future? updater;
|
||||||
Process? gameProcess;
|
Process? gameProcess;
|
||||||
Process? launcherProcess;
|
Process? launcherProcess;
|
||||||
Process? eacProcess;
|
Process? eacProcess;
|
||||||
@@ -34,15 +36,15 @@ class GameController extends GetxController {
|
|||||||
(element) => element.name == decodedSelectedVersionName);
|
(element) => element.name == decodedSelectedVersionName);
|
||||||
_selectedVersion = Rxn(decodedSelectedVersion);
|
_selectedVersion = Rxn(decodedSelectedVersion);
|
||||||
|
|
||||||
host = RxBool(_storage.read("host") ?? false);
|
type = Rx(GameType.values.elementAt(_storage.read("type") ?? 0));
|
||||||
host.listen((value) {
|
type.listen((value) {
|
||||||
_storage.write("host", value);
|
_storage.write("type", value.index);
|
||||||
username.text = _storage.read("${host.value ? 'host' : 'game'}_username") ?? "";
|
username.text = _storage.read("${type.value == GameType.client ? 'game' : 'host'}_username") ?? "";
|
||||||
});
|
});
|
||||||
|
|
||||||
username = TextEditingController(text: _storage.read("${host.value ? 'host' : 'game'}_username") ?? "");
|
username = TextEditingController(text: _storage.read("${type.value == GameType.client ? 'game' : 'host'}_username") ?? "");
|
||||||
username.addListener(() async {
|
username.addListener(() async {
|
||||||
await _storage.write("${host.value ? 'host' : 'game'}_username", username.text);
|
await _storage.write("${type.value == GameType.client ? 'game' : 'host'}_username", username.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
started = RxBool(false);
|
started = RxBool(false);
|
||||||
|
|||||||
5
lib/src/model/game_type.dart
Normal file
5
lib/src/model/game_type.dart
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
enum GameType {
|
||||||
|
client,
|
||||||
|
server,
|
||||||
|
headlessServer
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:get_storage/get_storage.dart';
|
|
||||||
import 'package:reboot_launcher/src/page/info_page.dart';
|
import 'package:reboot_launcher/src/page/info_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/launcher_page.dart';
|
import 'package:reboot_launcher/src/page/launcher_page.dart';
|
||||||
import 'package:reboot_launcher/src/page/server_page.dart';
|
import 'package:reboot_launcher/src/page/server_page.dart';
|
||||||
@@ -10,8 +8,6 @@ import 'package:reboot_launcher/src/widget/window_border.dart';
|
|||||||
import 'package:reboot_launcher/src/widget/window_buttons.dart';
|
import 'package:reboot_launcher/src/widget/window_buttons.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../util/reboot.dart';
|
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -20,17 +16,12 @@ class HomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> with WindowListener {
|
class _HomePageState extends State<HomePage> with WindowListener {
|
||||||
late final Future _future;
|
|
||||||
bool _focused = true;
|
bool _focused = true;
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
windowManager.addListener(this);
|
windowManager.addListener(this);
|
||||||
var storage = GetStorage("update");
|
|
||||||
int? lastUpdateMs = storage.read("last_update");
|
|
||||||
_future = compute(downloadRebootDll, lastUpdateMs);
|
|
||||||
_future.then((value) => storage.write("last_update", value));
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +38,7 @@ class _HomePageState extends State<HomePage> with WindowListener {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onWindowBlur() {
|
void onWindowBlur() {
|
||||||
setState(() => _focused = false);
|
setState(() => _focused = !_focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -66,12 +57,13 @@ class _HomePageState extends State<HomePage> with WindowListener {
|
|||||||
_createPane("Info", FluentIcons.info),
|
_createPane("Info", FluentIcons.info),
|
||||||
],
|
],
|
||||||
trailing: WindowTitleBar(focused: _focused)),
|
trailing: WindowTitleBar(focused: _focused)),
|
||||||
content: FutureBuilder(
|
content: NavigationBody(
|
||||||
future: _future,
|
index: _index,
|
||||||
builder: (context, snapshot) => NavigationBody(
|
children: [
|
||||||
index: _index,
|
const LauncherPage(),
|
||||||
children: _createPages(snapshot)
|
ServerPage(),
|
||||||
)
|
const InfoPage()
|
||||||
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -81,19 +73,6 @@ class _HomePageState extends State<HomePage> with WindowListener {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _createPages(AsyncSnapshot snapshot) {
|
|
||||||
|
|
||||||
return [
|
|
||||||
LauncherPage(
|
|
||||||
ready: snapshot.hasData,
|
|
||||||
error: snapshot.error,
|
|
||||||
stackTrace: snapshot.stackTrace
|
|
||||||
),
|
|
||||||
ServerPage(),
|
|
||||||
const InfoPage()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
PaneItem _createPane(String label, IconData icon) {
|
PaneItem _createPane(String label, IconData icon) {
|
||||||
return PaneItem(icon: Icon(icon), title: Text(label));
|
return PaneItem(icon: Icon(icon), title: Text(label));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class InfoPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomLeft, child: Text("Version 3.8${kDebugMode ? '-DEBUG' : ''}")))
|
alignment: Alignment.bottomLeft, child: Text("Version 3.10${kDebugMode ? '-DEBUG' : ''}")))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:get_storage/get_storage.dart';
|
||||||
import 'package:reboot_launcher/src/controller/build_controller.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/os.dart';
|
import 'package:reboot_launcher/src/util/os.dart';
|
||||||
import 'package:reboot_launcher/src/widget/deployment_selector.dart';
|
import 'package:reboot_launcher/src/widget/host_checkbox.dart';
|
||||||
import 'package:reboot_launcher/src/widget/launch_button.dart';
|
import 'package:reboot_launcher/src/widget/launch_button.dart';
|
||||||
import 'package:reboot_launcher/src/widget/username_box.dart';
|
import 'package:reboot_launcher/src/widget/username_box.dart';
|
||||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../util/binary.dart';
|
||||||
|
import '../util/reboot.dart';
|
||||||
import '../widget/warning_info.dart';
|
import '../widget/warning_info.dart';
|
||||||
|
|
||||||
class LauncherPage extends StatefulWidget {
|
class LauncherPage extends StatefulWidget {
|
||||||
final bool ready;
|
|
||||||
final Object? error;
|
|
||||||
final StackTrace? stackTrace;
|
|
||||||
|
|
||||||
const LauncherPage(
|
const LauncherPage(
|
||||||
{Key? key, required this.ready, required this.error, this.stackTrace})
|
{Key? key})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -26,68 +27,94 @@ class LauncherPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LauncherPageState extends State<LauncherPage> {
|
class _LauncherPageState extends State<LauncherPage> {
|
||||||
|
final GameController _gameController = Get.find<GameController>();
|
||||||
final BuildController _buildController = Get.find<BuildController>();
|
final BuildController _buildController = Get.find<BuildController>();
|
||||||
bool shouldWriteError = true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_buildController.cancelledDownload
|
if(_gameController.updater == null) {
|
||||||
.listen((value) => value ? _onCancelWarning() : {});
|
_gameController.updater = compute(downloadRebootDll, _updateTime)
|
||||||
|
..then((value) => _updateTime = value)
|
||||||
|
..onError(_saveError);
|
||||||
|
_buildController.cancelledDownload
|
||||||
|
.listen((value) => value ? _onCancelWarning() : {});
|
||||||
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int? get _updateTime {
|
||||||
|
var storage = GetStorage("update");
|
||||||
|
return storage.read("last_update");
|
||||||
|
}
|
||||||
|
|
||||||
|
set _updateTime(int? updateTime) {
|
||||||
|
var storage = GetStorage("update");
|
||||||
|
storage.write("last_update", updateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveError(Object? error, StackTrace stackTrace) async {
|
||||||
|
var errorFile = await loadBinary("error.txt", true);
|
||||||
|
errorFile.writeAsString(
|
||||||
|
"Error: $error\nStacktrace: $stackTrace", mode: FileMode.write);
|
||||||
|
}
|
||||||
|
|
||||||
void _onCancelWarning() {
|
void _onCancelWarning() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if(!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
showSnackbar(context,
|
showSnackbar(context,
|
||||||
const Snackbar(content: Text("Download cancelled")));
|
const Snackbar(content: Text("Download cancelled")));
|
||||||
_buildController.cancelledDownload.value = false;
|
_buildController.cancelledDownload(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!widget.ready && widget.error == null) {
|
return FutureBuilder(
|
||||||
return Row(
|
future: _gameController.updater,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
builder: (context, snapshot) {
|
||||||
children: [
|
if (!snapshot.hasData && !snapshot.hasError) {
|
||||||
Column(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: const [
|
children: [
|
||||||
ProgressRing(),
|
Column(
|
||||||
SizedBox(height: 16.0),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Text("Updating Reboot DLL...")
|
children: const [
|
||||||
|
ProgressRing(),
|
||||||
|
SizedBox(height: 16.0),
|
||||||
|
Text("Updating Reboot DLL...")
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if(snapshot.hasError)
|
||||||
|
_createUpdateError(snapshot),
|
||||||
|
UsernameBox(),
|
||||||
|
const VersionSelector(),
|
||||||
|
const DeploymentSelector(),
|
||||||
|
const LaunchButton()
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
],
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
Widget _createUpdateError(AsyncSnapshot<Object?> snapshot) {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
return WarningInfo(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
text: "Cannot update Reboot DLL",
|
||||||
children: [
|
icon: FluentIcons.info,
|
||||||
if(widget.error != null)
|
severity: InfoBarSeverity.warning,
|
||||||
WarningInfo(
|
onPressed: () => loadBinary("error.txt", true)
|
||||||
text: "Cannot update Reboot DLL",
|
.then((file) => launchUrl(file.uri))
|
||||||
icon: FluentIcons.info,
|
|
||||||
severity: InfoBarSeverity.warning,
|
|
||||||
onPressed: () async {
|
|
||||||
if (shouldWriteError) {
|
|
||||||
await errorFile.writeAsString(
|
|
||||||
"Error: ${widget.error}\nStacktrace: ${widget.stackTrace}",
|
|
||||||
mode: FileMode.write
|
|
||||||
);
|
|
||||||
shouldWriteError = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
launchUrl(errorFile.uri);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
UsernameBox(),
|
|
||||||
VersionSelector(),
|
|
||||||
DeploymentSelector(enabled: true),
|
|
||||||
const LaunchButton()
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ Future<List<FortniteBuild>> _fetchManifests() async {
|
|||||||
var children = tableEntry.querySelectorAll("td");
|
var children = tableEntry.querySelectorAll("td");
|
||||||
|
|
||||||
var name = children[0].text;
|
var name = children[0].text;
|
||||||
var separator = name.indexOf("-") + 1;
|
var minifiedName = name.substring(name.indexOf("-") + 1, name.lastIndexOf("-"));
|
||||||
var version = parser
|
var version = parser
|
||||||
.tryParse(name.substring(separator, name.indexOf("-", separator)));
|
.tryParse(minifiedName.replaceFirst("-CL", ""));
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import 'dart:io';
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
File errorFile = File("${Platform.environment["Temp"]}/error.txt");
|
|
||||||
|
|
||||||
const int appBarSize = 2;
|
const int appBarSize = 2;
|
||||||
final RegExp _regex = RegExp(r'(?<=\(Build )(.*)(?=\))');
|
final RegExp _regex = RegExp(r'(?<=\(Build )(.*)(?=\))');
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Future<int> downloadRebootDll(int? lastUpdateMs) async {
|
|||||||
|
|
||||||
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) {
|
||||||
outputDir.delete();
|
outputDir.delete();
|
||||||
return lastUpdateMs!;
|
return lastUpdateMs ?? now.millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
|
|
||||||
await rebootDll.rename(oldRebootDll.path);
|
await rebootDll.rename(oldRebootDll.path);
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Future<HttpServer?> changeReverseProxyState(BuildContext context, String host, S
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await shelf_io.serve(proxyHandler(uri), 'localhost', 3551);
|
return await shelf_io.serve(proxyHandler(uri), "127.0.0.1", 3551);
|
||||||
}catch(error){
|
}catch(error){
|
||||||
_showStartProxyError(context, error);
|
_showStartProxyError(context, error);
|
||||||
return null;
|
return null;
|
||||||
@@ -129,10 +129,8 @@ Future<Uri?> _showReverseProxyCheck(BuildContext context, String host, String po
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
@@ -175,10 +173,8 @@ void _showStartProxyError(BuildContext context, Object error) {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -198,10 +194,8 @@ void _showStopProxyError(BuildContext context, Object error) {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -285,10 +279,8 @@ Future<bool> _showServerDownloadInfo(BuildContext context, bool portable) async
|
|||||||
future: nodeFuture,
|
future: nodeFuture,
|
||||||
builder: (builder, snapshot) => SizedBox(
|
builder: (builder, snapshot) => SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(snapshot.hasData && !snapshot.hasError),
|
onPressed: () => Navigator.of(context).pop(snapshot.hasData && !snapshot.hasError),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: Text(!snapshot.hasData && !snapshot.hasError ? 'Stop' : 'Close'),
|
child: Text(!snapshot.hasData && !snapshot.hasError ? 'Stop' : 'Close'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -310,10 +302,8 @@ void _showEmbeddedError(BuildContext context, String path) {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ class AddLocalVersion extends StatelessWidget {
|
|||||||
|
|
||||||
List<Widget> _createLocalVersionActions(BuildContext context) {
|
List<Widget> _createLocalVersionActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
FilledButton(
|
Button(
|
||||||
onPressed: () => _closeLocalVersionDialog(context, false),
|
onPressed: () => _closeLocalVersionDialog(context, false),
|
||||||
style: ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
|
|||||||
@@ -94,9 +94,8 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
switch (_status) {
|
switch (_status) {
|
||||||
case DownloadStatus.none:
|
case DownloadStatus.none:
|
||||||
return [
|
return [
|
||||||
FilledButton(
|
Button(
|
||||||
onPressed: () => _onClose(),
|
onPressed: () => _onClose(),
|
||||||
style: ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close')),
|
child: const Text('Close')),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => _startDownload(context),
|
onPressed: () => _startDownload(context),
|
||||||
@@ -108,20 +107,16 @@ class _AddServerVersionState extends State<AddServerVersion> {
|
|||||||
return [
|
return [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => _onClose(),
|
onPressed: () => _onClose(),
|
||||||
style:
|
|
||||||
ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close')))
|
child: const Text('Close')))
|
||||||
];
|
];
|
||||||
default:
|
default:
|
||||||
return [
|
return [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => _onClose(),
|
onPressed: () => _onClose(),
|
||||||
style:
|
|
||||||
ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
_status == DownloadStatus.downloading ? 'Stop' : 'Close')),
|
_status == DownloadStatus.downloading ? 'Stop' : 'Close')),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
|
||||||
import 'package:reboot_launcher/src/widget/smart_switch.dart';
|
|
||||||
|
|
||||||
class DeploymentSelector extends StatelessWidget {
|
|
||||||
final GameController _gameController = Get.find<GameController>();
|
|
||||||
final bool enabled;
|
|
||||||
|
|
||||||
DeploymentSelector({Key? key, required this.enabled}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Tooltip(
|
|
||||||
message: enabled ? "Whether the launched client should be used to host multiplayer games or not" : "Hosting is not allowed",
|
|
||||||
child: _buildSwitch(context)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SmartSwitch _buildSwitch(BuildContext context) {
|
|
||||||
return SmartSwitch(
|
|
||||||
value: _gameController.host,
|
|
||||||
onDisabledPress: !enabled
|
|
||||||
? () => showSnackbar(context,
|
|
||||||
const Snackbar(content: Text("Hosting is not allowed")))
|
|
||||||
: null,
|
|
||||||
label: "Host",
|
|
||||||
enabled: enabled
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
74
lib/src/widget/host_checkbox.dart
Normal file
74
lib/src/widget/host_checkbox.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
|
import 'package:reboot_launcher/src/widget/smart_switch.dart';
|
||||||
|
|
||||||
|
class DeploymentSelector extends StatefulWidget {
|
||||||
|
const DeploymentSelector({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DeploymentSelector> createState() => _DeploymentSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeploymentSelectorState extends State<DeploymentSelector> {
|
||||||
|
final Map<GameType, String> _options = {
|
||||||
|
GameType.client: "Client",
|
||||||
|
GameType.server: "Server",
|
||||||
|
GameType.headlessServer: "Headless Server"
|
||||||
|
};
|
||||||
|
final Map<GameType, String> _descriptions = {
|
||||||
|
GameType.client: "A fortnite client will be launched to play multiplayer games",
|
||||||
|
GameType.server: "A fortnite client will be launched to host multiplayer games",
|
||||||
|
GameType.headlessServer: "A fortnite client will be launched in the background to host multiplayer games",
|
||||||
|
};
|
||||||
|
final GameController _gameController = Get.find<GameController>();
|
||||||
|
bool? _value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
switch(_gameController.type.value){
|
||||||
|
case GameType.client:
|
||||||
|
_value = false;
|
||||||
|
break;
|
||||||
|
case GameType.server:
|
||||||
|
_value = true;
|
||||||
|
break;
|
||||||
|
case GameType.headlessServer:
|
||||||
|
_value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Tooltip(
|
||||||
|
message: _descriptions[_gameController.type.value]!,
|
||||||
|
child: InfoLabel(
|
||||||
|
label: _options[_gameController.type.value]!,
|
||||||
|
child: Checkbox(
|
||||||
|
checked: _value,
|
||||||
|
onChanged: _onSelected
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSelected(bool? value){
|
||||||
|
if(value == null){
|
||||||
|
_gameController.type(GameType.client);
|
||||||
|
setState(() => _value = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value){
|
||||||
|
_gameController.type(GameType.server);
|
||||||
|
setState(() => _value = true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameController.type(GameType.headlessServer);
|
||||||
|
setState(() => _value = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:process_run/shell.dart';
|
import 'package:process_run/shell.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
import 'package:reboot_launcher/src/controller/server_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
import 'package:reboot_launcher/src/util/binary.dart';
|
import 'package:reboot_launcher/src/util/binary.dart';
|
||||||
import 'package:reboot_launcher/src/util/injector.dart';
|
import 'package:reboot_launcher/src/util/injector.dart';
|
||||||
import 'package:reboot_launcher/src/util/patcher.dart';
|
import 'package:reboot_launcher/src/util/patcher.dart';
|
||||||
@@ -13,6 +15,8 @@ import 'package:reboot_launcher/src/util/server.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
import 'package:win32_suspend_process/win32_suspend_process.dart';
|
||||||
|
|
||||||
|
import '../util/os.dart';
|
||||||
|
|
||||||
class LaunchButton extends StatefulWidget {
|
class LaunchButton extends StatefulWidget {
|
||||||
const LaunchButton(
|
const LaunchButton(
|
||||||
{Key? key})
|
{Key? key})
|
||||||
@@ -25,7 +29,15 @@ class LaunchButton extends StatefulWidget {
|
|||||||
class _LaunchButtonState extends State<LaunchButton> {
|
class _LaunchButtonState extends State<LaunchButton> {
|
||||||
final GameController _gameController = Get.find<GameController>();
|
final GameController _gameController = Get.find<GameController>();
|
||||||
final ServerController _serverController = Get.find<ServerController>();
|
final ServerController _serverController = Get.find<ServerController>();
|
||||||
bool _lawinFail = false;
|
File? _logFile;
|
||||||
|
bool _fail = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
loadBinary("log.txt", true)
|
||||||
|
.then((value) => _logFile = value);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -67,7 +79,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
try {
|
try {
|
||||||
_updateServerState(true);
|
_updateServerState(true);
|
||||||
var version = _gameController.selectedVersionObs.value!;
|
var version = _gameController.selectedVersionObs.value!;
|
||||||
var hosting = _gameController.host.value;
|
var hosting = _gameController.type.value == GameType.headlessServer;
|
||||||
if (version.launcher != null) {
|
if (version.launcher != null) {
|
||||||
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
|
_gameController.launcherProcess = await Process.start(version.launcher!.path, []);
|
||||||
Win32Process(_gameController.launcherProcess!.pid).suspend();
|
Win32Process(_gameController.launcherProcess!.pid).suspend();
|
||||||
@@ -88,7 +100,18 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gameController.gameProcess = await Process.start(version.executable!.path, _createProcessArguments())
|
if(_logFile != null && await _logFile!.exists()){
|
||||||
|
await _logFile!.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
var gamePath = version.executable?.path;
|
||||||
|
if(gamePath == null){
|
||||||
|
_onError("${version.location.path} no longer contains a Fortnite executable. Did you delete it?", null);
|
||||||
|
_onStop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameController.gameProcess = await Process.start(gamePath, _createProcessArguments())
|
||||||
..exitCode.then((_) => _onEnd())
|
..exitCode.then((_) => _onEnd())
|
||||||
..outLines.forEach(_onGameOutput);
|
..outLines.forEach(_onGameOutput);
|
||||||
await _injectOrShowError("cranium.dll");
|
await _injectOrShowError("cranium.dll");
|
||||||
@@ -96,9 +119,10 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
if(hosting){
|
if(hosting){
|
||||||
await _showServerLaunchingWarning();
|
await _showServerLaunchingWarning();
|
||||||
}
|
}
|
||||||
} catch (exception) {
|
} catch (exception, stacktrace) {
|
||||||
_closeDialogIfOpen();
|
_closeDialogIfOpen(false);
|
||||||
_onError(exception);
|
_onError(exception, stacktrace);
|
||||||
|
_onStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,15 +164,15 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onEnd() {
|
void _onEnd() {
|
||||||
if(_lawinFail){
|
if(_fail){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_closeDialogIfOpen();
|
_closeDialogIfOpen(false);
|
||||||
_onStop();
|
_onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _closeDialogIfOpen() {
|
void _closeDialogIfOpen(bool success) {
|
||||||
if(!mounted){
|
if(!mounted){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -158,7 +182,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.of(context).pop(false);
|
Navigator.of(context).pop(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showBrokenServerWarning() async {
|
Future<void> _showBrokenServerWarning() async {
|
||||||
@@ -176,10 +200,33 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Close'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showUnsupportedHeadless() async {
|
||||||
|
if(!mounted){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ContentDialog(
|
||||||
|
content: const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text("This version of Fortnite doesn't support headless hosting", textAlign: TextAlign.center)
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -197,7 +244,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ContentDialog(
|
builder: (context) => ContentDialog(
|
||||||
content: const InfoLabel(
|
content: const InfoLabel(
|
||||||
label: "Launching reboot server...",
|
label: "Launching headless reboot server...",
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ProgressBar()
|
child: ProgressBar()
|
||||||
@@ -206,13 +253,11 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(false);
|
Navigator.of(context).pop(false);
|
||||||
_onStop();
|
_onStop();
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -228,30 +273,52 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onGameOutput(String line) {
|
void _onGameOutput(String line) {
|
||||||
|
if(kDebugMode){
|
||||||
|
print(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_logFile != null){
|
||||||
|
_logFile!.writeAsString("$line\n", mode: FileMode.append);
|
||||||
|
}
|
||||||
|
|
||||||
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) {
|
if (line.contains("FOnlineSubsystemGoogleCommon::Shutdown()")) {
|
||||||
_onStop();
|
_onStop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(line.contains("port 3551 failed: Connection refused")){
|
if(line.contains("port 3551 failed: Connection refused")){
|
||||||
_lawinFail = true;
|
_fail = true;
|
||||||
_closeDialogIfOpen();
|
_closeDialogIfOpen(false);
|
||||||
_showBrokenServerWarning();
|
_showBrokenServerWarning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.contains("Game Engine Initialized") && !_gameController.host.value) {
|
if(line.contains("HTTP 400 response from ")){
|
||||||
|
_fail = true;
|
||||||
|
_closeDialogIfOpen(false);
|
||||||
|
_showUnsupportedHeadless();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.contains("Game Engine Initialized") && _gameController.type.value == GameType.client) {
|
||||||
_injectOrShowError("console.dll");
|
_injectOrShowError("console.dll");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(line.contains("added to UI Party led ") && _gameController.host.value){
|
if(line.contains("Region") && _gameController.type.value != GameType.client){
|
||||||
_injectOrShowError("reboot.dll")
|
_injectOrShowError("reboot.dll")
|
||||||
.then((value) => Navigator.of(context).pop(true));
|
.then((value) => _closeDialogIfOpen(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Object?> _onError(Object exception) {
|
Future<Object?> _onError(Object exception, StackTrace? stackTrace) async {
|
||||||
|
if (stackTrace != null) {
|
||||||
|
var errorFile = await loadBinary("error.txt", true);
|
||||||
|
errorFile.writeAsString(
|
||||||
|
"Error: $exception\nStacktrace: $stackTrace", mode: FileMode.write);
|
||||||
|
launchUrl(errorFile.uri);
|
||||||
|
}
|
||||||
|
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ContentDialog(
|
builder: (context) => ContentDialog(
|
||||||
@@ -262,10 +329,8 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
@@ -316,8 +381,8 @@ class _LaunchButtonState extends State<LaunchButton> {
|
|||||||
"-AUTH_TYPE=epic"
|
"-AUTH_TYPE=epic"
|
||||||
];
|
];
|
||||||
|
|
||||||
if(_gameController.host.value){
|
if(_gameController.type.value == GameType.headlessServer){
|
||||||
args.addAll(["-log", "-nullrhi", "-nosplash", "-nosound", "-unattended"]);
|
args.addAll(["-nullrhi", "-nosplash", "-nosound"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ class _ScanLocalVersionState extends State<ScanLocalVersion> {
|
|||||||
List<Widget> _createLocalVersionActions(BuildContext context) {
|
List<Widget> _createLocalVersionActions(BuildContext context) {
|
||||||
if(_future == null) {
|
if(_future == null) {
|
||||||
return [
|
return [
|
||||||
FilledButton(
|
Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
@@ -53,9 +52,8 @@ class _ScanLocalVersionState extends State<ScanLocalVersion> {
|
|||||||
if(!snapshot.hasData || snapshot.hasError) {
|
if(!snapshot.hasData || snapshot.hasError) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
style: ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Close'),
|
child: const Text('Close'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
27
lib/src/widget/smart_check_box.dart
Normal file
27
lib/src/widget/smart_check_box.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
|
|
||||||
|
class SmartCheckBox extends StatefulWidget {
|
||||||
|
final CheckboxController controller;
|
||||||
|
final Widget? content;
|
||||||
|
const SmartCheckBox({Key? key, required this.controller, this.content}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SmartCheckBox> createState() => _SmartCheckBoxState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SmartCheckBoxState extends State<SmartCheckBox> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Checkbox(
|
||||||
|
checked: widget.controller.value,
|
||||||
|
onChanged: (checked) => setState(() => widget.controller.value = checked ?? false),
|
||||||
|
content: widget.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckboxController {
|
||||||
|
bool value;
|
||||||
|
|
||||||
|
CheckboxController({this.value = false});
|
||||||
|
}
|
||||||
@@ -3,17 +3,17 @@ import 'package:get/get.dart';
|
|||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
|
|
||||||
class SmartSwitch extends StatefulWidget {
|
class SmartSwitch extends StatefulWidget {
|
||||||
final String label;
|
final String? label;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
final Function()? onDisabledPress;
|
final Function()? onDisabledPress;
|
||||||
final Rx<bool> value;
|
final Rx<bool> value;
|
||||||
|
|
||||||
const SmartSwitch(
|
const SmartSwitch(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.label,
|
required this.value,
|
||||||
required this.value,
|
this.label,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.onDisabledPress})
|
this.onDisabledPress})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -23,22 +23,32 @@ class SmartSwitch extends StatefulWidget {
|
|||||||
class _SmartSwitchState extends State<SmartSwitch> {
|
class _SmartSwitchState extends State<SmartSwitch> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return widget.label == null ? _createSwitch() : _createLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoLabel _createLabel() {
|
||||||
return InfoLabel(
|
return InfoLabel(
|
||||||
label: widget.label,
|
label: widget.label!,
|
||||||
child: Obx(() => ToggleSwitch(
|
child: _createSwitch()
|
||||||
enabled: widget.enabled,
|
);
|
||||||
onDisabledPress: widget.onDisabledPress,
|
}
|
||||||
checked: widget.value.value,
|
|
||||||
onChanged: _onChanged,
|
Widget _createSwitch() {
|
||||||
style: ToggleSwitchThemeData.standard(ThemeData(
|
return Obx(() => ToggleSwitch(
|
||||||
checkedColor: _toolTipColor.withOpacity(_checkedOpacity),
|
enabled: widget.enabled,
|
||||||
uncheckedColor: _toolTipColor.withOpacity(_uncheckedOpacity),
|
onDisabledPress: widget.onDisabledPress,
|
||||||
borderInputColor: _toolTipColor.withOpacity(_uncheckedOpacity),
|
checked: widget.value.value,
|
||||||
accentColor: _bodyColor
|
onChanged: _onChanged,
|
||||||
.withOpacity(widget.value.value
|
style: ToggleSwitchThemeData.standard(ThemeData(
|
||||||
? _checkedOpacity
|
checkedColor: _toolTipColor.withOpacity(_checkedOpacity),
|
||||||
: _uncheckedOpacity)
|
uncheckedColor: _toolTipColor.withOpacity(_uncheckedOpacity),
|
||||||
.toAccentColor())))));
|
borderInputColor: _toolTipColor.withOpacity(_uncheckedOpacity),
|
||||||
|
accentColor: _bodyColor
|
||||||
|
.withOpacity(widget.value.value
|
||||||
|
? _checkedOpacity
|
||||||
|
: _uncheckedOpacity)
|
||||||
|
.toAccentColor())))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get _toolTipColor =>
|
Color get _toolTipColor =>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||||
|
import 'package:reboot_launcher/src/model/game_type.dart';
|
||||||
import 'package:reboot_launcher/src/widget/smart_input.dart';
|
import 'package:reboot_launcher/src/widget/smart_input.dart';
|
||||||
|
|
||||||
class UsernameBox extends StatelessWidget {
|
class UsernameBox extends StatelessWidget {
|
||||||
@@ -11,10 +12,10 @@ class UsernameBox extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(() => Tooltip(
|
return Obx(() => Tooltip(
|
||||||
message: _gameController.host.value ? "The username of the game hoster" : "The in-game username of your player",
|
message: _gameController.type.value != GameType.client ? "The username of the game hoster" : "The in-game username of your player",
|
||||||
child: SmartInput(
|
child: SmartInput(
|
||||||
label: "Username",
|
label: "Username",
|
||||||
placeholder: "Type your ${_gameController.host.value ? 'hosting' : "in-game"} username",
|
placeholder: "Type your ${_gameController.type.value != GameType.client ? 'hosting' : "in-game"} username",
|
||||||
controller: _gameController.username,
|
controller: _gameController.username,
|
||||||
populate: true
|
populate: true
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:reboot_launcher/src/model/fortnite_version.dart';
|
|||||||
import 'package:reboot_launcher/src/widget/add_local_version.dart';
|
import 'package:reboot_launcher/src/widget/add_local_version.dart';
|
||||||
import 'package:reboot_launcher/src/widget/add_server_version.dart';
|
import 'package:reboot_launcher/src/widget/add_server_version.dart';
|
||||||
import 'package:reboot_launcher/src/widget/scan_local_version.dart';
|
import 'package:reboot_launcher/src/widget/scan_local_version.dart';
|
||||||
|
import 'package:reboot_launcher/src/widget/smart_check_box.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class VersionSelector extends StatefulWidget {
|
class VersionSelector extends StatefulWidget {
|
||||||
@@ -23,6 +24,7 @@ class VersionSelector extends StatefulWidget {
|
|||||||
|
|
||||||
class _VersionSelectorState extends State<VersionSelector> {
|
class _VersionSelectorState extends State<VersionSelector> {
|
||||||
final GameController _gameController = Get.find<GameController>();
|
final GameController _gameController = Get.find<GameController>();
|
||||||
|
final CheckboxController _deleteFilesController = CheckboxController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -146,55 +148,78 @@ class _VersionSelectorState extends State<VersionSelector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
launchUrl(version.location.uri);
|
launchUrl(version.location.uri)
|
||||||
|
.onError((error, stackTrace) => _onExplorerError());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
_gameController.removeVersion(version);
|
|
||||||
|
|
||||||
if(!mounted){
|
if(!mounted){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _openDeleteDialog(context, version);
|
var result = await _openDeleteDialog(context, version) ?? false;
|
||||||
if(!mounted){
|
if(!mounted || !result){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
_gameController.removeVersion(version);
|
||||||
if (_gameController.selectedVersionObs.value?.name == version.name || _gameController.hasNoVersions) {
|
if (_gameController.selectedVersionObs.value?.name == version.name || _gameController.hasNoVersions) {
|
||||||
_gameController.selectedVersionObs.value = null;
|
_gameController.selectedVersionObs.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_deleteFilesController.value && await version.location.exists()) {
|
||||||
|
version.location.delete(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _openDeleteDialog(BuildContext context, FortniteVersion version) {
|
bool _onExplorerError() {
|
||||||
return showDialog(
|
showSnackbar(
|
||||||
|
context,
|
||||||
|
const Snackbar(
|
||||||
|
content: Text("This version doesn't exist on the local machine", textAlign: TextAlign.center),
|
||||||
|
extended: true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> _openDeleteDialog(BuildContext context, FortniteVersion version) {
|
||||||
|
return showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ContentDialog(
|
builder: (context) => ContentDialog(
|
||||||
content: const SizedBox(
|
content: Column(
|
||||||
width: double.infinity,
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Text("Do you want to also delete the files for this version?",
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
textAlign: TextAlign.center)),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text("Are you sure you want to delete this version?")),
|
||||||
|
|
||||||
|
const SizedBox(height: 12.0),
|
||||||
|
|
||||||
|
SmartCheckBox(
|
||||||
|
controller: _deleteFilesController,
|
||||||
|
content: const Text("Delete version files from disk")
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
FilledButton(
|
Button(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: const Text('Keep'),
|
child: const Text('Keep'),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
Navigator.of(context).pop();
|
|
||||||
if (await version.location.exists()) {
|
|
||||||
version.location.delete(recursive: true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style:
|
|
||||||
ButtonStyle(backgroundColor: ButtonState.all(Colors.red)),
|
|
||||||
child: const Text('Delete'),
|
child: const Text('Delete'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: reboot_launcher
|
name: reboot_launcher
|
||||||
description: Launcher for project reboot
|
description: Launcher for project reboot
|
||||||
version: "3.8.0"
|
version: "3.10.0"
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ msix_config:
|
|||||||
display_name: Reboot Launcher
|
display_name: Reboot Launcher
|
||||||
publisher_display_name: Auties00
|
publisher_display_name: Auties00
|
||||||
identity_name: 31868Auties00.RebootLauncher
|
identity_name: 31868Auties00.RebootLauncher
|
||||||
msix_version: 3.8.0.0
|
msix_version: 3.10.0.0
|
||||||
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
|
publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029
|
||||||
logo_path: ./assets/icons/reboot.ico
|
logo_path: ./assets/icons/reboot.ico
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|||||||
Reference in New Issue
Block a user