diff --git a/lib/main.dart b/lib/main.dart index 1b05ba4..6b19b19 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart' show WinDesktopWindow; @@ -7,11 +9,14 @@ import 'package:get_storage/get_storage.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/server_controller.dart'; +import 'package:reboot_launcher/src/util/binary.dart'; import 'package:reboot_launcher/src/util/os.dart'; import 'package:system_theme/system_theme.dart'; import 'package:reboot_launcher/src/page/home_page.dart'; void main() async { + await Directory(safeBinariesDirectory) + .create(recursive: true); WidgetsFlutterBinding.ensureInitialized(); await SystemTheme.accentColor.load(); await GetStorage.init("game"); @@ -22,7 +27,7 @@ void main() async { Get.put(BuildController()); SystemTheme.accentColor.load(); doWhenWindowReady(() { - const size = Size(600, 380); + const size = Size(600, 365); var window = appWindow as WinDesktopWindow; window.setWindowCutOnMaximize(appBarSize * 2); appWindow.size = size; diff --git a/lib/src/controller/server_controller.dart b/lib/src/controller/server_controller.dart index e29b4aa..f7a6f0b 100644 --- a/lib/src/controller/server_controller.dart +++ b/lib/src/controller/server_controller.dart @@ -5,13 +5,14 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:reboot_launcher/src/util/binary.dart'; +import 'package:reboot_launcher/src/util/server.dart'; class ServerController extends GetxController { late final TextEditingController host; late final TextEditingController port; late final RxBool embedded; - late final RxBool started; - Process? process; + late final RxBool warning; + late RxBool started; ServerController() { var storage = GetStorage("server"); @@ -24,7 +25,12 @@ class ServerController extends GetxController { embedded = RxBool(storage.read("embedded") ?? true); embedded.listen((value) => storage.write("embedded", value)); + warning = RxBool(storage.read("warning") ?? true); + warning.listen((value) => storage.write("warning", value)); + started = RxBool(false); + isLawinPortFree() + .then((value) => started = RxBool(!value)); } Future kill() async { diff --git a/lib/src/page/home_page.dart b/lib/src/page/home_page.dart index 2f7ef1e..969f1ec 100644 --- a/lib/src/page/home_page.dart +++ b/lib/src/page/home_page.dart @@ -1,5 +1,6 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder; 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/launcher_page.dart'; import 'package:reboot_launcher/src/page/server_page.dart'; @@ -25,7 +26,10 @@ class _HomePageState extends State with WindowListener { @override void initState() { windowManager.addListener(this); - _future = downloadRebootDll(); + var storage = GetStorage("update"); + int? lastUpdateMs = storage.read("last_update"); + _future = compute(downloadRebootDll, lastUpdateMs); + _future.then((value) => storage.write("last_update", value)); super.initState(); } @@ -56,8 +60,8 @@ class _HomePageState extends State with WindowListener { displayMode: PaneDisplayMode.top, indicator: const EndNavigationIndicator(), items: [ - _createPane("Launcher", FluentIcons.game), - _createPane("Server", FluentIcons.server_enviroment), + _createPane("Home", FluentIcons.game), + _createPane("Lawin", FluentIcons.server_enviroment), _createPane("Info", FluentIcons.info), ], trailing: WindowTitleBar(focused: _focused)), @@ -70,6 +74,7 @@ class _HomePageState extends State with WindowListener { "An error occurred while loading the launcher: ${snapshot.error}", textAlign: TextAlign.center)); } + return NavigationBody( index: _index, children: _createPages(snapshot.hasData)); diff --git a/lib/src/page/info_page.dart b/lib/src/page/info_page.dart index 6f4af7e..385bfb5 100644 --- a/lib/src/page/info_page.dart +++ b/lib/src/page/info_page.dart @@ -31,7 +31,7 @@ class InfoPage extends StatelessWidget { ), const Expanded( child: Align( - alignment: Alignment.bottomLeft, child: Text("Version 3.1${kDebugMode ? '-DEBUG' : ''}"))) + alignment: Alignment.bottomLeft, child: Text("Version 3.4${kDebugMode ? '-DEBUG' : ''}"))) ], ); } diff --git a/lib/src/page/launcher_page.dart b/lib/src/page/launcher_page.dart index 07f1457..33e8328 100644 --- a/lib/src/page/launcher_page.dart +++ b/lib/src/page/launcher_page.dart @@ -16,7 +16,7 @@ class LauncherPage extends StatelessWidget { children: [ UsernameBox(), VersionSelector(), - DeploymentSelector(enabled: true), + DeploymentSelector(enabled: false), const LaunchButton() ], ); diff --git a/lib/src/page/server_page.dart b/lib/src/page/server_page.dart index ff7a672..cd9d3e2 100644 --- a/lib/src/page/server_page.dart +++ b/lib/src/page/server_page.dart @@ -1,14 +1,58 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; import 'package:reboot_launcher/src/widget/local_server_switch.dart'; import 'package:reboot_launcher/src/widget/port_input.dart'; import 'package:reboot_launcher/src/widget/host_input.dart'; import 'package:reboot_launcher/src/widget/server_button.dart'; -class ServerPage extends StatelessWidget { +import '../controller/server_controller.dart'; + +class ServerPage extends StatefulWidget { const ServerPage({Key? key}) : super(key: key); + @override + State createState() => _ServerPageState(); +} + +class _ServerPageState extends State { + final ServerController _serverController = Get.find(); + + @override + void initState() { + if (_serverController.warning.value) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await _showAdvancedUserWarning(); + _serverController.warning.value = false; + }); + } + + super.initState(); + } + + Future _showAdvancedUserWarning() async { + await showDialog( + context: context, + builder: (context) => ContentDialog( + content: const SizedBox( + width: double.infinity, + child: Text("This section is reserved for advanced users", + textAlign: TextAlign.center), + ), + actions: [ + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('I understand'), + ), + ) + ], + ) + ); + } + @override Widget build(BuildContext context) { return Column( diff --git a/lib/src/util/binary.dart b/lib/src/util/binary.dart index a71b6d4..768c3be 100644 --- a/lib/src/util/binary.dart +++ b/lib/src/util/binary.dart @@ -25,5 +25,8 @@ File _locateInternalBinary(String binary){ String get internalBinariesDirectory => "${File(Platform.resolvedExecutable).parent.path}\\data\\flutter_assets\\assets\\binaries"; +Directory get tempDirectory => + Directory("${Platform.environment["Temp"]}"); + String get safeBinariesDirectory => "${Platform.environment["UserProfile"]}\\.reboot_launcher"; \ No newline at end of file diff --git a/lib/src/util/build.dart b/lib/src/util/build.dart index 52e6127..e3f53d8 100644 --- a/lib/src/util/build.dart +++ b/lib/src/util/build.dart @@ -15,7 +15,7 @@ const _userAgent = final _cookieRegex = RegExp("cookie=\"(.*?);"); final _manifestSourceUrl = Uri.parse( - "https://github.com/VastBlast/FortniteManifestArchive/blob/main/INSTALLATION.md"); + "https://github.com/VastBlast/FortniteManifestArchive/blob/main/README.md"); final _archiveCookieUrl = Uri.parse("http://allinstaller.xyz/rel"); final _archiveSourceUrl = Uri.parse("http://allinstaller.xyz/rel?i=1"); diff --git a/lib/src/util/reboot.dart b/lib/src/util/reboot.dart index 7590307..3197890 100644 --- a/lib/src/util/reboot.dart +++ b/lib/src/util/reboot.dart @@ -1,47 +1,47 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; -import 'package:get_storage/get_storage.dart'; +import 'package:get/get.dart'; import 'package:reboot_launcher/src/util/binary.dart'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:path/path.dart' as path; const _rebootUrl = "https://nightly.link/Milxnor/Universal-Walking-Simulator/workflows/msbuild/master/Release.zip"; -final GetStorage _storage = GetStorage("update"); -Future _getLastUpdate() async { - int? timeInMillis = _storage.read("last_update"); - return timeInMillis != null ? DateTime.fromMillisecondsSinceEpoch(timeInMillis) : null; +Future _getLastUpdate(int? lastUpdateMs) async { + return lastUpdateMs != null ? DateTime.fromMillisecondsSinceEpoch(lastUpdateMs) : null; } -Future downloadRebootDll() async { +Future downloadRebootDll(int? lastUpdateMs) async { var now = DateTime.now(); var oldRebootDll = await loadBinary("reboot.dll", true); - var lastUpdate = await _getLastUpdate(); + var lastUpdate = await _getLastUpdate(lastUpdateMs); var exists = await oldRebootDll.exists(); - if(lastUpdate != null && now.difference(lastUpdate).inHours <= 24 && exists){ - return oldRebootDll; + if(lastUpdate != null && now.difference(lastUpdate).inHours <= 24 && await oldRebootDll.exists()){ + return lastUpdateMs!; } var response = await http.get(Uri.parse(_rebootUrl)); - var tempZip = File("${Platform.environment["Temp"]}/reboot.zip"); + var tempZip = File("${tempDirectory.path}/reboot.zip"); await tempZip.writeAsBytes(response.bodyBytes); - await extractFileToDisk(tempZip.path, safeBinariesDirectory); - var pdb = await loadBinary("Project Reboot.pdb", true); - pdb.delete(); - var rebootDll = await loadBinary("Project Reboot.dll", true); - if (!(await rebootDll.exists())) { + + var outputDir = await tempDirectory.createTemp("reboot"); + await extractFileToDisk(tempZip.path, outputDir.path); + + var rebootDll = outputDir.listSync() + .firstWhereOrNull((element) => path.extension(element.path) == ".dll"); + if(rebootDll == null){ throw Exception("Missing reboot dll"); } - _storage.write("last_update", now.millisecondsSinceEpoch); - if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await rebootDll.readAsBytes())) { - rebootDll.delete(); - return oldRebootDll; + if (exists && sha1.convert(await oldRebootDll.readAsBytes()) == sha1.convert(await File(rebootDll.path).readAsBytes())) { + outputDir.delete(); + return lastUpdateMs!; } await rebootDll.rename(oldRebootDll.path); - return oldRebootDll; + outputDir.delete(); + return now.millisecondsSinceEpoch; } diff --git a/lib/src/util/server.dart b/lib/src/util/server.dart index 736771b..293fc83 100644 --- a/lib/src/util/server.dart +++ b/lib/src/util/server.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; import 'package:process_run/shell.dart'; @@ -13,8 +14,6 @@ const String _serverUrl = "https://github.com/Lawin0129/LawinServer/archive/refs/heads/main.zip"; const String _portableServerUrl = "https://cdn.discordapp.com/attachments/998020695223193673/1019999251994005504/LawinServer.exe"; -const String _nodeUrl = - "https://nodejs.org/dist/v16.16.0/node-v16.16.0-x64.msi"; Future downloadServer(bool portable) async { if(!portable){ @@ -41,17 +40,7 @@ Future updateEngineConfig() async { await engine.writeAsString(await patchedEngine.readAsString()); } -Future downloadNode() async { - var client = HttpClient(); - client.badCertificateCallback = ((cert, host, port) => true); - var request = await client.getUrl(Uri.parse(_nodeUrl)); - var response = await request.close(); - var file = File("${Platform.environment["Temp"]}\\node.msi"); - await response.pipe(file.openWrite()); - return file; -} - -Future isPortFree() async { +Future isLawinPortFree() async { var portBat = await loadBinary("port.bat", false); var process = await Process.run(portBat.path, []); return !process.outText.contains(" LISTENING "); // Goofy way, best we got @@ -104,33 +93,23 @@ Future _pingAddress(String host, String port) async { && process.outText.contains("TcpTestSucceeded : True"); } -Future startEmbedded(BuildContext context, bool running, bool needsFreePort) async { - var releaseBat = await loadBinary("release.bat", false); +Future changeEmbeddedServerState(BuildContext context, bool running) async { if (running) { + var releaseBat = await loadBinary("release.bat", false); await Process.run(releaseBat.path, []); - return null; - } - - var free = await isPortFree(); - if (!free && needsFreePort) { - var shouldKill = await _showAlreadyBindPortWarning(context); - if (!shouldKill) { - return null; - } - - await Process.run(releaseBat.path, []); + return false; } var nodeProcess = await Process.run("where", ["node"]); if(nodeProcess.exitCode == 0) { if(!(await serverLocation.exists()) && !(await _showServerDownloadInfo(context, false))){ - return null; + return false; } var serverRunner = File("${serverLocation.path}/start.bat"); if (!(await serverRunner.exists())) { _showEmbeddedError(context, serverRunner.path); - return null; + return false; } var nodeModules = Directory("${serverLocation.path}/node_modules"); @@ -139,20 +118,21 @@ Future startEmbedded(BuildContext context, bool running, bool needsFre workingDirectory: serverLocation.path); } - return await Process.start(serverRunner.path, [], - workingDirectory: serverLocation.path); + await Process.start(serverRunner.path, [], workingDirectory: serverLocation.path); + return true; } var portableServer = await loadBinary("LawinServer.exe", true); if(!(await portableServer.exists()) && !(await _showServerDownloadInfo(context, true))){ - return null; + return false; } - return await Process.start(portableServer.path, []); + await Process.start(portableServer.path, []); + return true; } Future _showServerDownloadInfo(BuildContext context, bool portable) async { - var nodeFuture = downloadServer(portable); + var nodeFuture = compute(downloadServer, portable); var result = await showDialog( context: context, builder: (context) => ContentDialog( @@ -221,27 +201,4 @@ void _showEmbeddedError(BuildContext context, String path) { )) ], )); -} - - -Future _showAlreadyBindPortWarning(BuildContext context) async { - return await showDialog( - context: context, - builder: (context) => ContentDialog( - content: const Text( - "Port 3551 is already in use, do you want to kill the associated process?", - textAlign: TextAlign.center), - actions: [ - FilledButton( - onPressed: () => Navigator.of(context).pop(false), - style: ButtonStyle( - backgroundColor: ButtonState.all(Colors.red)), - child: const Text('Close'), - ), - FilledButton( - child: const Text('Kill'), - onPressed: () => Navigator.of(context).pop(true)), - ], - )) ?? - false; -} +} \ No newline at end of file diff --git a/lib/src/widget/add_server_version.dart b/lib/src/widget/add_server_version.dart index 03f1fb1..312b96e 100644 --- a/lib/src/widget/add_server_version.dart +++ b/lib/src/widget/add_server_version.dart @@ -4,6 +4,7 @@ import 'package:async/async.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; +import 'package:process_run/shell.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/build.dart'; @@ -12,6 +13,7 @@ import 'package:reboot_launcher/src/widget/select_file.dart'; import 'package:reboot_launcher/src/widget/version_name_input.dart'; import 'package:reboot_launcher/src/model/fortnite_version.dart'; +import '../model/fortnite_build.dart'; import 'build_selector.dart'; class AddServerVersion extends StatefulWidget { @@ -40,7 +42,7 @@ class _AddServerVersionState extends State { _future = _buildController.builds != null ? Future.value(true) : compute(fetchBuilds, null) - .then((value) => _buildController.builds = value); + .then((value) => _buildController.builds = value); super.initState(); } diff --git a/lib/src/widget/deployment_selector.dart b/lib/src/widget/deployment_selector.dart index e19e20c..3c42f5f 100644 --- a/lib/src/widget/deployment_selector.dart +++ b/lib/src/widget/deployment_selector.dart @@ -12,14 +12,21 @@ class DeploymentSelector extends StatelessWidget { @override Widget build(BuildContext context) { - return SmartSwitch( - value: _gameController.host, - onDisabledPress: !enabled - ? () => showSnackbar(context, - const Snackbar(content: Text("Hosting is not allowed"))) - : null, - label: "Host", - enabled: enabled + 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 + ); + } } diff --git a/lib/src/widget/host_input.dart b/lib/src/widget/host_input.dart index 38fe86c..8acb359 100644 --- a/lib/src/widget/host_input.dart +++ b/lib/src/widget/host_input.dart @@ -11,17 +11,26 @@ class HostInput extends StatelessWidget { @override Widget build(BuildContext context) { - return Obx(() => SmartInput( - label: "Host", - placeholder: "Type the host name", - controller: _serverController.host, - enabled: !_serverController.embedded.value, - onTap: () => _serverController.embedded.value - ? showSnackbar( - context, - const Snackbar( - content: Text("The host is locked when embedded is on"))) - : {}, + return Obx(() => Tooltip( + message: _serverController.embedded.value + ? "The remote lawin host cannot be set when running on embedded" + : "The remote host of the lawin server to use for authentication", + child: _buildInput(context), )); } + + SmartInput _buildInput(BuildContext context) { + return SmartInput( + label: "Host", + placeholder: "Type the host name", + controller: _serverController.host, + enabled: !_serverController.embedded.value, + onTap: () => _serverController.embedded.value + ? showSnackbar( + context, + const Snackbar( + content: Text("The host is locked when embedded is on"))) + : {}, + ); + } } diff --git a/lib/src/widget/launch_button.dart b/lib/src/widget/launch_button.dart index 2aee811..5d86438 100644 --- a/lib/src/widget/launch_button.dart +++ b/lib/src/widget/launch_button.dart @@ -32,12 +32,13 @@ class _LaunchButtonState extends State { alignment: AlignmentDirectional.bottomCenter, child: SizedBox( width: double.infinity, - child: Listener( - child: Obx(() => Button( + child: Obx(() => Tooltip( + message: _gameController.started.value ? "Close the running Fortnite instance" : "Launch a new Fortnite instance", + child: Button( onPressed: () => _onPressed(context), child: Text(_gameController.started.value ? "Close" : "Launch") - )), - ), + ), + )), ), ); } @@ -63,10 +64,9 @@ class _LaunchButtonState extends State { } _updateServerState(true); - if (!_serverController.started.value && _serverController.embedded.value && await isPortFree()) { - var process = await startEmbedded(context, false, false); - _serverController.process = process; - _serverController.started(process != null); + if (!_serverController.started.value && _serverController.embedded.value && await isLawinPortFree()) { + var result = await changeEmbeddedServerState(context, false); + _serverController.started(result); } _onStart(); diff --git a/lib/src/widget/local_server_switch.dart b/lib/src/widget/local_server_switch.dart index 49ea743..ef546ec 100644 --- a/lib/src/widget/local_server_switch.dart +++ b/lib/src/widget/local_server_switch.dart @@ -11,9 +11,12 @@ class LocalServerSwitch extends StatelessWidget { @override Widget build(BuildContext context) { - return SmartSwitch( - value: _serverController.embedded, - label: "Embedded" + return Tooltip( + message: "Determines whether an embedded or remote lawin server should be used", + child: SmartSwitch( + value: _serverController.embedded, + label: "Embedded" + ), ); } } diff --git a/lib/src/widget/port_input.dart b/lib/src/widget/port_input.dart index 066306b..7dd641e 100644 --- a/lib/src/widget/port_input.dart +++ b/lib/src/widget/port_input.dart @@ -11,17 +11,25 @@ class PortInput extends StatelessWidget { @override Widget build(BuildContext context) { - return Obx(() => SmartInput( + return Obx(() => Tooltip( + message: _serverController.embedded.value + ? "The remote lawin port cannot be set when running on embedded" + : "The remote port of the lawin server to use for authentication", + child: _buildInput(context))); + } + + SmartInput _buildInput(BuildContext context) { + return SmartInput( label: "Port", placeholder: "Type the host port", controller: _serverController.port, enabled: !_serverController.embedded.value, onTap: () => _serverController.embedded.value ? showSnackbar( - context, - const Snackbar( - content: Text("The port is locked when embedded is on"))) + context, + const Snackbar( + content: Text("The port is locked when embedded is on"))) : {}, - )); + ); } } diff --git a/lib/src/widget/server_button.dart b/lib/src/widget/server_button.dart index 829159f..93d8a47 100644 --- a/lib/src/widget/server_button.dart +++ b/lib/src/widget/server_button.dart @@ -6,6 +6,7 @@ import 'package:reboot_launcher/src/util/server.dart'; class ServerButton extends StatelessWidget { final ServerController _serverController = Get.find(); + ServerButton({Key? key}) : super(key: key); @override @@ -14,31 +15,36 @@ class ServerButton extends StatelessWidget { alignment: AlignmentDirectional.bottomCenter, child: SizedBox( width: double.infinity, - child: Obx(() => Button( - onPressed: () => _onPressed(context), - child: Text(_serverController.embedded.value - ? !_serverController.started.value - ? "Start" - : "Stop" - : "Check address"))), + child: Obx(() => Tooltip( + message: !_serverController.embedded.value + ? "Check the address of the remote Lawin server" + : _serverController.started.value + ? "Stop the running Lawin server instance" + : "Start a new Lawin server instance", + child: Button( + onPressed: () => _onPressed(context), + child: Text(_serverController.embedded.value + ? !_serverController.started.value + ? "Start" + : "Stop" + : "Check address")), + )), ), ); } void _onPressed(BuildContext context) async { if (!_serverController.embedded.value) { - checkAddress(context, _serverController.host.text, _serverController.port.text); + checkAddress( + context, _serverController.host.text, _serverController.port.text); return; } var running = _serverController.started.value; - _serverController.started(!running); - var process = await startEmbedded(context, running, true); - var updatedRunning = process != null; + _serverController.started.value = !running; + var updatedRunning = await changeEmbeddedServerState(context, running); if (updatedRunning != _serverController.started.value) { _serverController.started.value = updatedRunning; } - - _serverController.process = process; } } diff --git a/lib/src/widget/username_box.dart b/lib/src/widget/username_box.dart index 8b17f05..f5ec437 100644 --- a/lib/src/widget/username_box.dart +++ b/lib/src/widget/username_box.dart @@ -11,11 +11,14 @@ class UsernameBox extends StatelessWidget { @override Widget build(BuildContext context) { - return Obx(() => SmartInput( - label: "Username", - placeholder: "Type your ${_gameController.host.value ? 'hosting' : "in-game"} username", - controller: _gameController.username, - populate: true + return Obx(() => Tooltip( + message: _gameController.host.value ? "The username of the game hoster" : "The in-game username of your player", + child: SmartInput( + label: "Username", + placeholder: "Type your ${_gameController.host.value ? 'hosting' : "in-game"} username", + controller: _gameController.username, + populate: true + ), )); } } diff --git a/lib/src/widget/version_selector.dart b/lib/src/widget/version_selector.dart index daca65f..4a0775e 100644 --- a/lib/src/widget/version_selector.dart +++ b/lib/src/widget/version_selector.dart @@ -22,33 +22,36 @@ class VersionSelector extends StatelessWidget { @override Widget build(BuildContext context) { - return InfoLabel( - label: "Version", - child: Align( - alignment: AlignmentDirectional.centerStart, - child: Row( - children: [ - Expanded(child: _createSelector(context)), - const SizedBox( - width: 16, - ), - Tooltip( - message: "Add a local fortnite build to the versions list", - child: Button( - child: const Icon(FluentIcons.open_file), - onPressed: () => _openLocalVersionDialog(context)), - ), - const SizedBox( - width: 16, - ), - Tooltip( - message: "Download a fortnite build from the archive", - child: Button( - child: const Icon(FluentIcons.download), - onPressed: () => _openDownloadVersionDialog(context)), - ) - ], - ))); + return Tooltip( + message: "The version of Fortnite to launch", + child: InfoLabel( + label: "Version", + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + Expanded(child: _createSelector(context)), + const SizedBox( + width: 16, + ), + Tooltip( + message: "Add a local fortnite build to the versions list", + child: Button( + child: const Icon(FluentIcons.open_file), + onPressed: () => _openLocalVersionDialog(context)), + ), + const SizedBox( + width: 16, + ), + Tooltip( + message: "Download a fortnite build from the archive", + child: Button( + child: const Icon(FluentIcons.download), + onPressed: () => _openDownloadVersionDialog(context)), + ) + ], + ))), + ); } Widget _createSelector(BuildContext context) { diff --git a/pubspec.yaml b/pubspec.yaml index 92df561..7f2fc55 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: reboot_launcher description: Launcher for project reboot -version: "3.1.0" +version: "3.4.0" publish_to: 'none' environment: - sdk: ">=2.17.6 <3.0.5" + sdk: ">=2.17.6 <=3.3.2" dependencies: flutter: @@ -48,7 +48,7 @@ msix_config: display_name: Reboot Launcher publisher_display_name: Auties00 identity_name: 31868Auties00.RebootLauncher - msix_version: 3.1.0.0 + msix_version: 3.4.0.0 publisher: CN=E6CD08C6-DECF-4034-A3EB-2D5FA2CA8029 logo_path: ./assets/icons/reboot.ico architecture: x64