This commit is contained in:
Alessandro Autiero
2023-08-24 19:28:31 +02:00
parent cd7db4cf71
commit 69f2dcd527
70 changed files with 677 additions and 1087 deletions

BIN
assets/images/auth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

BIN
assets/images/cloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

BIN
assets/images/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/host.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

BIN
assets/images/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/images/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,45 +0,0 @@
## 0.1.5
- Runs on Windows 7
## 0.1.4
- Updated win32 to 3.0.0
## 0.1.3
- Updated ffi to 2.0.0
## 0.1.2
- Flutter 3.0 support
## 0.1.1+1
- Added Linux usage instructions
## 0.1.1
- Linux support now stable
## 0.1.0+1
- Fix gtk library name on Linux
## 0.1.0
- Added null safety support
## 0.0.9
- Linux support added
## 0.0.8
- Added macOS readme instructions
## 0.0.7
- macOS support added
## 0.0.6
- Works with latest Flutter version (master channel)
## 0.0.5
- Works with latest Flutter version (dev channel)
## 0.0.4
- Better integration with other plugins
## 0.0.3
- Using dpi-aware values for title bar and buttons dimensions
- Dynamically calculating default button padding instead of fixed one
## 0.0.2
- Added video tutorial link
## 0.0.1
* Initial release
- Custom window frame - remove standard Windows titlebar and buttons
- Hide window on startup
- Show/hide window
- Minimize/Maximize/Restore/Close window
- Move window using Flutter widget
- Set window size, minimum size and maximum size
- Set window position
- Set window alignment on screen (center/topLeft/topRight/bottomLeft/bottomRight)
- Set window title

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020-2021 Bogdan Hobeanu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,262 +0,0 @@
# bitsdojo_window
A [Flutter package](https://pub.dev/packages/bitsdojo_window) that makes it easy to customize and work with your Flutter desktop app window **on Windows, macOS and Linux**.
Watch the tutorial to get started. Click the image below to watch the video:
[![IMAGE ALT TEXT](https://img.youtube.com/vi/bee2AHQpGK4/0.jpg)](https://www.youtube.com/watch?v=bee2AHQpGK4 "Click to open")
<img src="https://raw.githubusercontent.com/bitsdojo/bitsdojo_window/master/resources/screenshot.png">
**Features**:
- Custom window frame - remove standard Windows/macOS/Linux titlebar and buttons
- Hide window on startup
- Show/hide window
- Move window using Flutter widget
- Minimize/Maximize/Restore/Close window
- Set window size, minimum size and maximum size
- Set window position
- Set window alignment on screen (center/topLeft/topRight/bottomLeft/bottomRight)
- Set window title
# Getting Started
Install the package using `pubspec.yaml`
# For Windows apps
Inside your application folder, go to `windows\runner\main.cpp` and add these two lines at the beginning of the file:
```cpp
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
```
# For macOS apps
Inside your application folder, go to `macos\runner\MainFlutterWindow.swift` and add this line after the one saying `import FlutterMacOS` :
```swift
import FlutterMacOS
import bitsdojo_window_macos // Add this line
```
Then change this line from:
```swift
class MainFlutterWindow: NSWindow {
```
to this:
```swift
class MainFlutterWindow: BitsdojoWindow {
```
After changing `NSWindow` to `BitsdojoWindow` add these lines below the line you changed:
```swift
override func bitsdojo_window_configure() -> UInt {
return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
}
```
Your code should now look like this:
```swift
class MainFlutterWindow: BitsdojoWindow {
override func bitsdojo_window_configure() -> UInt {
return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
}
override func awakeFromNib() {
... //rest of your code
```
#
If you don't want to use a custom frame and prefer the standard window titlebar and buttons, you can remove the `BDW_CUSTOM_FRAME` flag from the code above.
If you don't want to hide the window on startup, you can remove the `BDW_HIDE_ON_STARTUP` flag from the code above.
# For Linux apps
Inside your application folder, go to `linux\my_application.cc` and add this line at the beginning of the file:
```cpp
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
```
Then look for these two lines:
```cpp
gtk_window_set_default_size(window, 1280, 720);
gtk_widget_show(GTK_WIDGET(window));
```
and change them to this:
```cpp
auto bdw = bitsdojo_window_from(window); // <--- add this line
bdw->setCustomFrame(true); // <-- add this line
//gtk_window_set_default_size(window, 1280, 720); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window));
```
As you can see, we commented the line calling `gtk_window_set_default_size` and added these two lines before `gtk_widget_show(GTK_WIDGET(window));`
```cpp
auto bdw = bitsdojo_window_from(window);
bdw->setCustomFrame(true);
```
# Flutter app integration
Now go to `lib\main.dart` and add this code in the `main` function right after `runApp(MyApp());` :
```dart
void main() {
runApp(MyApp());
// Add this code below
doWhenWindowReady(() {
const initialSize = Size(600, 450);
appWindow.minSize = initialSize;
appWindow.size = initialSize;
appWindow.alignment = Alignment.center;
appWindow.show();
});
}
```
This will set an initial size and a minimum size for your application window, center it on the screen and show it on the screen.
You can find examples in the `example` folder.
Here is an example that displays this window:
<details>
<summary>Click to expand</summary>
```dart
import 'package:flutter/material.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
void main() {
runApp(const MyApp());
doWhenWindowReady(() {
final win = appWindow;
const initialSize = Size(600, 450);
win.minSize = initialSize;
win.size = initialSize;
win.alignment = Alignment.center;
win.title = "Custom window with Flutter";
win.show();
});
}
const borderColor = Color(0xFF805306);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: WindowBorder(
color: borderColor,
width: 1,
child: Row(
children: const [LeftSide(), RightSide()],
),
),
),
);
}
}
const sidebarColor = Color(0xFFF6A00C);
class LeftSide extends StatelessWidget {
const LeftSide({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
child: Container(
color: sidebarColor,
child: Column(
children: [
WindowTitleBarBox(child: MoveWindow()),
Expanded(child: Container())
],
)));
}
}
const backgroundStartColor = Color(0xFFFFD500);
const backgroundEndColor = Color(0xFFF6A00C);
class RightSide extends StatelessWidget {
const RightSide({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [backgroundStartColor, backgroundEndColor],
stops: [0.0, 1.0]),
),
child: Column(children: [
WindowTitleBarBox(
child: Row(
children: [Expanded(child: MoveWindow()), const WindowButtons()],
),
)
]),
),
);
}
}
final buttonColors = WindowButtonColors(
iconNormal: const Color(0xFF805306),
mouseOver: const Color(0xFFF6A00C),
mouseDown: const Color(0xFF805306),
iconMouseOver: const Color(0xFF805306),
iconMouseDown: const Color(0xFFFFD500));
final closeButtonColors = WindowButtonColors(
mouseOver: const Color(0xFFD32F2F),
mouseDown: const Color(0xFFB71C1C),
iconNormal: const Color(0xFF805306),
iconMouseOver: Colors.white);
class WindowButtons extends StatelessWidget {
const WindowButtons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
MinimizeWindowButton(colors: buttonColors),
MaximizeWindowButton(colors: buttonColors),
CloseWindowButton(colors: closeButtonColors),
],
);
}
}
```
</details>
#
# ❤️ **Sponsors - friends helping this package**
I am developing this package in my spare time and any help is appreciated.
If you want to help you can [become a sponsor](https://github.com/sponsors/bitsdojo).
🙏 Thank you!
Want to help? [Become a sponsor](https://github.com/sponsors/bitsdojo)

View File

@@ -1,5 +0,0 @@
export 'src/widgets/window_border.dart';
export 'src/widgets/window_button.dart';
export 'src/widgets/window_caption.dart';
export 'src/icons/icons.dart';
export 'src/app_window.dart';

View File

@@ -1,43 +0,0 @@
import 'package:bitsdojo_window_platform_interface/bitsdojo_window_platform_interface.dart';
import 'package:bitsdojo_window_platform_interface/method_channel_bitsdojo_window.dart';
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart';
import 'package:bitsdojo_window_macos/bitsdojo_window_macos.dart';
import 'package:bitsdojo_window_linux/bitsdojo_window_linux.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
bool _platformInstanceNeedsInit = true;
void initPlatformInstance() {
if (!kIsWeb) {
if (BitsdojoWindowPlatform.instance is MethodChannelBitsdojoWindow) {
if (Platform.isWindows) {
BitsdojoWindowPlatform.instance = BitsdojoWindowWindows();
} else if (Platform.isMacOS) {
BitsdojoWindowPlatform.instance = BitsdojoWindowMacOS();
} else if (Platform.isLinux) {
BitsdojoWindowPlatform.instance = BitsdojoWindowLinux();
}
}
} else {
BitsdojoWindowPlatform.instance = BitsdojoWindowPlatformNotImplemented();
}
}
BitsdojoWindowPlatform get _platform {
var needsInit = _platformInstanceNeedsInit;
if (needsInit) {
initPlatformInstance();
_platformInstanceNeedsInit = false;
}
return BitsdojoWindowPlatform.instance;
}
void doWhenWindowReady(VoidCallback callback) {
_platform.doWhenWindowReady(callback);
}
DesktopWindow get appWindow {
return _platform.appWindow;
}

View File

@@ -1,49 +0,0 @@
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart'
show WinDesktopWindow;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import '../app_window.dart';
class WindowBorder extends StatelessWidget {
final Widget child;
final Color color;
final double? width;
WindowBorder({Key? key, required this.child, required this.color, this.width})
: super(key: key);
@override
Widget build(BuildContext context) {
bool isWindowsApp =
(!kIsWeb) && (defaultTargetPlatform == TargetPlatform.windows);
bool isLinuxApp =
(!kIsWeb) && (defaultTargetPlatform == TargetPlatform.linux);
// Only show border on Windows and Linux
if (!(isWindowsApp || isLinuxApp)) {
return child;
}
var borderWidth = width ?? 1;
var topBorderWidth = width ?? 1;
if (appWindow is WinDesktopWindow) {
appWindow as WinDesktopWindow..setWindowCutOnMaximize(borderWidth.ceil());
}
if (isWindowsApp) {
topBorderWidth += 1 / appWindow.scaleFactor;
}
final topBorderSide = BorderSide(color: this.color, width: topBorderWidth);
final borderSide = BorderSide(color: this.color, width: borderWidth);
return Container(
child: child,
decoration: BoxDecoration(
border: Border(
top: topBorderSide,
left: borderSide,
right: borderSide,
bottom: borderSide)));
}
}

View File

@@ -1,48 +0,0 @@
import 'package:flutter/widgets.dart';
import '../app_window.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
class _MoveWindow extends StatelessWidget {
_MoveWindow({Key? key, this.child, this.onDoubleTap}) : super(key: key);
final Widget? child;
final VoidCallback? onDoubleTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onPanStart: (details) {
appWindow.startDragging();
},
onDoubleTap: this.onDoubleTap ?? () => appWindow.maximizeOrRestore(),
child: this.child ?? Container());
}
}
class MoveWindow extends StatelessWidget {
final Widget? child;
final VoidCallback? onDoubleTap;
MoveWindow({Key? key, this.child, this.onDoubleTap}) : super(key: key);
@override
Widget build(BuildContext context) {
if (child == null) return _MoveWindow(onDoubleTap: this.onDoubleTap);
return _MoveWindow(
onDoubleTap: this.onDoubleTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [Expanded(child: this.child!)]),
);
}
}
class WindowTitleBarBox extends StatelessWidget {
final Widget? child;
WindowTitleBarBox({Key? key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Container();
}
final titlebarHeight = appWindow.titleBarHeight;
return SizedBox(height: titlebarHeight, child: this.child ?? Container());
}
}

View File

@@ -1,31 +0,0 @@
name: bitsdojo_window
description: A package to help with creating custom windows with Flutter desktop (custom border, titlebar and minimize/maximize/close buttons) and common desktop window operations (show/hide/position on screen) for Windows and macOS
version: 0.1.5
homepage: https://www.bitsdojo.com
repository: https://github.com/bitsdojo/bitsdojo_window
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=1.20.0"
flutter:
plugin:
platforms:
windows:
default_package: bitsdojo_window_windows
macos:
default_package: bitsdojo_window_macos
linux:
default_package: bitsdojo_window_linux
dependencies:
flutter:
sdk: flutter
bitsdojo_window_platform_interface: ^0.1.2
#path: ../bitsdojo_window_platform_interface
bitsdojo_window_windows: ^0.1.5
#path: ../bitsdojo_window_windows
bitsdojo_window_macos: ^0.1.3
#path: ../bitsdojo_window_macos
bitsdojo_window_linux: ^0.1.3
#path: ../bitsdojo_window_linux

View File

@@ -68,12 +68,11 @@ void main(List<String> args) async {
}
stdout.writeln("Launching game...");
if(version.executable == null){
var executable = await version.executable;
if(executable == null){
throw Exception("Missing game executable at: ${version.location.path}");
}
await patchHeadless(version.executable!);
var serverType = getServerType(result);
var serverHost = result["server-host"] ?? serverJson["${serverType.id}_host"];
var serverPort = result["server-port"] ?? serverJson["${serverType.id}_port"];

View File

@@ -1,8 +1,5 @@
import 'dart:async';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:bitsdojo_window_windows/bitsdojo_window_windows.dart'
show WinDesktopWindow;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
@@ -17,6 +14,8 @@ import 'package:reboot_launcher/src/ui/page/home_page.dart';
import 'package:reboot_launcher/supabase.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:system_theme/system_theme.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:window_manager/window_manager.dart';
const double kDefaultWindowWidth = 1024;
const double kDefaultWindowHeight = 1024;
@@ -42,21 +41,21 @@ void main() async {
Get.put(BuildController());
Get.put(SettingsController());
Get.put(HostingController());
doWhenWindowReady(() {
await windowManager.ensureInitialized();
var controller = Get.find<SettingsController>();
var size = Size(controller.width, controller.height);
var window = appWindow as WinDesktopWindow;
window.setWindowCutOnMaximize(appBarSize * 2);
appWindow.size = size;
await windowManager.setSize(size);
if(controller.offsetX != null && controller.offsetY != null){
appWindow.position = Offset(controller.offsetX!, controller.offsetY!);
await windowManager.setPosition(Offset(controller.offsetX!, controller.offsetY!));
}else {
appWindow.alignment = Alignment.center;
}
appWindow.title = "Reboot Launcher";
appWindow.show();
});
await windowManager.setAlignment(Alignment.center);
};
await Window.initialize();
await Window.setEffect(
effect: WindowEffect.acrylic,
color: Colors.transparent,
dark: SystemTheme.isDarkMode
);
var supabase = Supabase.instance.client;
await supabase.from('hosts')
.delete()
@@ -91,6 +90,7 @@ class _RebootApplicationState extends State<RebootApplication> {
FluentThemeData _createTheme(Brightness brightness) => FluentThemeData(
brightness: brightness,
accentColor: SystemTheme.accentColor.accent.toAccentColor(),
visualDensity: VisualDensity.standard
visualDensity: VisualDensity.standard,
scaffoldBackgroundColor: Colors.transparent
);
}

View File

@@ -1,8 +1,7 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

View File

@@ -1,9 +1,8 @@
import 'dart:convert';
import 'package:args/args.dart';
import '../model/fortnite_version.dart';
import '../model/server_type.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
Iterable<String> getServerTypes() => ServerType.values.map((entry) => entry.id);

View File

@@ -2,12 +2,11 @@ import 'dart:io';
import 'package:process_run/shell.dart';
import 'package:reboot_launcher/cli.dart';
import '../model/fortnite_version.dart';
import '../util/injector.dart';
import '../util/os.dart';
import '../util/process.dart';
import '../util/server.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/util/injector.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/process.dart';
import 'package:reboot_launcher/src/util/server.dart';
final List<String> _errorStrings = [
"port 3551 failed: Connection refused",
@@ -25,10 +24,9 @@ Future<void> startGame() async {
await _startLauncherProcess(version);
await _startEacProcess(version);
var gamePath = version.executable?.path;
if (gamePath == null) {
throw Exception("${version.location
.path} no longer contains a Fortnite executable, did you delete or move it?");
var executable = await version.executable;
if (executable == null) {
throw Exception("${version.location.path} no longer contains a Fortnite executable, did you delete or move it?");
}
if (username == null) {
@@ -36,7 +34,7 @@ Future<void> startGame() async {
stdout.writeln("No username was specified, using $username by default. Use --username to specify one");
}
_gameProcess = await Process.start(gamePath, createRebootArgs(username!, "", host, ""))
_gameProcess = await Process.start(executable.path, createRebootArgs(username!, "", host, ""))
..exitCode.then((_) => _onClose())
..outLines.forEach((line) => _onGameOutput(line, dll, host, verbose));
}

View File

@@ -1,10 +1,9 @@
import 'dart:io';
import 'package:archive/archive_io.dart';
import 'package:reboot_launcher/src/util/server.dart';
import '../util/os.dart';
import 'package:http/http.dart' as http;
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/server.dart';
const String _baseDownload = "https://cdn.discordapp.com/attachments/1095351875961901057/1110968021373169674/cobalt.dll";
const String _consoleDownload = "https://cdn.discordapp.com/attachments/1095351875961901057/1110968095033524234/console.dll";

View File

@@ -1,9 +1,8 @@
import 'dart:io';
import 'package:process_run/shell.dart';
import '../model/server_type.dart';
import '../util/server.dart' as server;
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/server.dart' as server;
Future<bool> startServer(String? host, String? port, ServerType type) async {
stdout.writeln("Starting backend server...");

View File

@@ -1,6 +1,8 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/util/patcher.dart';
class FortniteVersion {
String name;
@@ -22,8 +24,22 @@ class FortniteVersion {
}
}
File? get executable {
return findExecutable(location, "FortniteClient-Win64-Shipping.exe");
Future<File?> get executable async {
var result = findExecutable(location, "FortniteClient-Win64-Shipping-Reboot.exe");
if(result != null) {
return result;
}
var original = findExecutable(location, "FortniteClient-Win64-Shipping.exe");
if(original == null) {
return null;
}
await Future.wait([
compute(patchMatchmaking, original),
compute(patchHeadless, original)
]);
return original;
}
File? get launcher {

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'package:fluent_ui/fluent_ui.dart';
@@ -9,7 +8,6 @@ import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import 'package:uuid/uuid.dart';
import '../../model/update_status.dart';
const String kDefaultPlayerName = "Player";

View File

@@ -4,9 +4,9 @@ import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/controller/update_controller.dart';
import '../../model/game_instance.dart';
import '../../model/update_status.dart';
import '../../util/reboot.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import 'package:reboot_launcher/src/model/update_status.dart';
import 'package:reboot_launcher/src/util/reboot.dart';
const String kDefaultServerName = "Reboot Game Server";

View File

@@ -4,8 +4,8 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../model/server_type.dart';
import '../../util/server.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/server.dart';
class ServerController extends GetxController {
static const String _kDefaultServerHost = "127.0.0.1";

View File

@@ -1,3 +1,4 @@
import 'dart:ui';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
@@ -5,9 +6,8 @@ import 'package:get_storage/get_storage.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'dart:ui';
import '../../util/reboot.dart';
import 'package:reboot_launcher/src/util/reboot.dart';
class SettingsController extends GetxController {
static const String _kDefaultIp = "127.0.0.1";
@@ -49,9 +49,9 @@ class SettingsController extends GetxController {
autoUpdate = RxBool(_storage.read("auto_update") ?? _kDefaultAutoUpdate);
autoUpdate.listen((value) => _storage.write("auto_update", value));
scrollingDistance = 0.0;
firstRun = RxBool(_storage.read("fr") ?? true);
firstRun.listen((value) => _storage.write("fr", value));
index = RxInt(firstRun() ? 0 : 1);
firstRun = RxBool(_storage.read("first_run") ?? true);
firstRun.listen((value) => _storage.write("first_run", value));
index = RxInt(firstRun() ? 3 : 0);
}
TextEditingController _createController(String key, String name) {

View File

@@ -6,9 +6,8 @@ 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';
import '../widget/shared/smart_check_box.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/ui/widget/shared/file_selector.dart';
import 'dialog.dart';
import 'dialog_button.dart';

View File

@@ -6,17 +6,16 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/util/error.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/build.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/util/build.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:universal_disk_space/universal_disk_space.dart';
import '../../util/checks.dart';
import '../controller/build_controller.dart';
import '../widget/home/build_selector.dart';
import '../widget/home/version_name_input.dart';
import '../widget/shared/file_selector.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/ui/controller/build_controller.dart';
import 'package:reboot_launcher/src/ui/widget/home/build_selector.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_name_input.dart';
import 'package:reboot_launcher/src/ui/widget/shared/file_selector.dart';
import 'dialog.dart';
import 'dialog_button.dart';

View File

@@ -1,7 +1,5 @@
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'dialog_button.dart';
@@ -24,10 +22,6 @@ class GenericDialog extends AbstractDialog {
Widget build(BuildContext context) {
return Stack(
children: [
MoveWindow(
child: const SizedBox.expand(),
),
ContentDialog(
style: ContentDialogThemeData(
padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0)

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import '../../../main.dart';
import 'package:reboot_launcher/main.dart';
import 'dialog.dart';
const String _unsupportedServerError = "The build you are currently using is not supported by Reboot. "

View File

@@ -1,14 +1,12 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:sync/semaphore.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../main.dart';
import '../../util/server.dart';
import '../controller/server_controller.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
import 'dialog.dart';
import 'dialog_button.dart';

View File

@@ -1,6 +1,6 @@
import 'package:fluent_ui/fluent_ui.dart';
import '../../../main.dart';
import 'package:reboot_launcher/main.dart';
void showMessage(String text){
showSnackbar(

View File

@@ -1,8 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/ui/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

View File

@@ -1,18 +1,16 @@
import 'package:bitsdojo_window/bitsdojo_window.dart' hide WindowBorder;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/page/launcher_page.dart';
import 'package:reboot_launcher/src/ui/page/server_page.dart';
import 'package:reboot_launcher/src/ui/page/settings_page.dart';
import 'package:window_manager/window_manager.dart';
import 'package:reboot_launcher/src/ui/widget/shared/profile_widget.dart';
import '../controller/game_controller.dart';
import '../controller/settings_controller.dart';
import '../widget/os/window_border.dart';
import '../widget/os/window_buttons.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/widget/os/window_border.dart';
import 'package:reboot_launcher/src/ui/widget/os/window_title_bar.dart';
import 'package:window_manager/window_manager.dart';
import 'hosting_page.dart';
import 'info_page.dart';
@@ -25,7 +23,7 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepAliveClientMixin {
static const double _kDefaultPadding = 12.0;
static const int _kPagesLength = 5;
static const int _kPagesLength = 6;
final SettingsController _settingsController = Get.find<SettingsController>();
final GlobalKey _searchKey = GlobalKey();
@@ -41,6 +39,7 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override
void initState() {
windowManager.show();
windowManager.addListener(this);
_searchController.addListener(_onSearch);
super.initState();
@@ -52,7 +51,8 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
return;
}
_searchItems.value = _allItems.whereType<PaneItem>()
_searchItems.value = _allItems
.whereType<PaneItem>()
.where((item) => (item.title as Text).data!.toLowerCase().contains(searchValue.toLowerCase()))
.toList()
.cast<NavigationPaneItem>();
@@ -84,22 +84,27 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
@override
void onWindowMoved() {
_settingsController.saveWindowOffset(appWindow.position);
windowManager.getPosition()
.then((value) => _settingsController.saveWindowOffset(value));
super.onWindowMoved();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Stack(
children: [
return Stack(children: [
LayoutBuilder(
builder: (context, specs) => Obx(() => NavigationView(
builder: (context, specs) => Obx(() => NavigationPaneTheme(
data: NavigationPaneThemeData(
backgroundColor: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.9),
),
child: NavigationView(
paneBodyBuilder: (pane, body) => Padding(
padding: const EdgeInsets.all(_kDefaultPadding),
child: body
),
appBar: NavigationAppBar(
height: 32,
title: _draggableArea,
actions: WindowTitleBar(focused: _focused()),
automaticallyImplyLeading: false,
@@ -109,48 +114,58 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
key: appKey,
selected: _selectedIndex,
onChanged: _onIndexChanged,
displayMode: specs.biggest.width <= 1536 ? PaneDisplayMode.compact : PaneDisplayMode.open,
menuButton: const SizedBox(),
displayMode: PaneDisplayMode.open,
items: _items,
header: ProfileWidget(),
footerItems: _footerItems,
autoSuggestBox: _autoSuggestBox,
autoSuggestBoxReplacement: const Icon(FluentIcons.search),
),
contentShape: const RoundedRectangleBorder(),
onOpenSearch: () => _searchFocusNode.requestFocus(),
transitionBuilder: (child, animation) => child
))
transitionBuilder: (child, animation) => child),
)
)
),
if(isWin11)
if (isWin11)
Obx(() => _focused.value ? const WindowBorder() : const SizedBox())
]
);
]);
}
GestureDetector get _draggableArea => GestureDetector(
onDoubleTap: () async => await windowManager.isMaximized() ? await windowManager.restore() : await windowManager.maximize(),
onHorizontalDragStart: (event) => windowManager.startDragging(),
onVerticalDragStart: (event) => windowManager.startDragging()
);
Widget get _backButton => Obx(() {
for(var entry in _navigationStatus){
for (var entry in _navigationStatus) {
entry.value;
}
var onBack = _onBack();
return PaneItem(
enabled: onBack != null,
icon: const Icon(FluentIcons.back, size: 14.0),
body: const SizedBox.shrink(),
).build(
context,
false,
onBack,
displayMode: PaneDisplayMode.compact
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Button(
onPressed: onBack,
style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.transparent),
border: ButtonState.all(BorderSide(color: Colors.transparent))
),
child: const Icon(FluentIcons.back, size: 13.0)
)
);
});
Function()? _onBack() {
var navigator = _navigators[_settingsController.index.value].currentState;
if(navigator == null || !navigator.mounted || !navigator.canPop()){
if (navigator == null || !navigator.mounted || !navigator.canPop()) {
return null;
}
var status = _navigationStatus[_settingsController.index.value];
if(status.value <= 0){
if (status.value <= 0) {
return null;
}
@@ -165,17 +180,15 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
_settingsController.index.value = index;
}
TextBox get _autoSuggestBox => TextBox(
Widget get _autoSuggestBox => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TextBox(
key: _searchKey,
controller: _searchController,
placeholder: 'Search',
focusNode: _searchFocusNode
);
GestureDetector get _draggableArea => GestureDetector(
onDoubleTap: () => appWindow.maximizeOrRestore(),
onHorizontalDragStart: (event) => appWindow.startDragging(),
onVerticalDragStart: (event) => appWindow.startDragging()
placeholder: 'Find a setting',
focusNode: _searchFocusNode,
autofocus: true
),
);
int? get _selectedIndex {
@@ -184,11 +197,12 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
return _settingsController.index();
}
if(_settingsController.index() >= _allItems.length){
if (_settingsController.index() >= _allItems.length) {
return null;
}
var indexOnScreen = searchItems.indexOf(_allItems[_settingsController.index()]);
var indexOnScreen =
searchItems.indexOf(_allItems[_settingsController.index()]);
if (indexOnScreen.isNegative) {
return null;
}
@@ -199,36 +213,75 @@ class _HomePageState extends State<HomePage> with WindowListener, AutomaticKeepA
List<NavigationPaneItem> get _allItems => [..._items, ..._footerItems];
List<NavigationPaneItem> get _footerItems => searchValue.isNotEmpty ? [] : [
PaneItem(
title: const Text("Downloads"),
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/download.png")
)
),
body: const SettingsPage()
),
PaneItem(
title: const Text("Settings"),
icon: const Icon(FluentIcons.settings),
body: SettingsPage()
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/settings.png")
)
),
body: const SettingsPage()
)
];
List<NavigationPaneItem> get _items => _searchItems() ?? [
PaneItem(
title: const Text("Tutorial"),
icon: const Icon(FluentIcons.info),
body: InfoPage(_navigators[0], _navigationStatus[0])
),
PaneItem(
title: const Text("Play"),
icon: const Icon(FluentIcons.game),
body: LauncherPage(_navigators[1], _navigationStatus[1])
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/play.png")
)
),
body: LauncherPage(_navigators[0], _navigationStatus[0])
),
PaneItem(
title: const Text("Host"),
icon: const Icon(FluentIcons.server_processes),
body: HostingPage(_navigators[2], _navigationStatus[2])
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/host.png")
)
),
body: HostingPage(_navigators[1], _navigationStatus[1])
),
PaneItem(
title: const Text("Backend"),
icon: const Icon(FluentIcons.user_window),
body: ServerPage(_navigators[3], _navigationStatus[3])
title: const Text("Authenticator"),
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/cloud.png")
)
),
body: ServerPage(_navigators[2], _navigationStatus[2])
),
PaneItem(
title: const Text("Tutorial"),
icon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox.square(
dimension: 24,
child: Image.asset("assets/images/info.png")
)
),
body: InfoPage(_navigators[3], _navigationStatus[3])
)
];
String get searchValue => _searchController.text;

View File

@@ -3,10 +3,10 @@ import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
import '../../model/update_status.dart';
import 'package:reboot_launcher/src/model/update_status.dart';
import 'browse_page.dart';
class HostingPage extends StatefulWidget {
@@ -38,7 +38,7 @@ class _HostingPageState extends State<HostingPage> with AutomaticKeepAliveClient
mainAxisAlignment: MainAxisAlignment.center,
children: [
ProgressRing(),
SizedBox(height: 16.0),
SizedBox(height: 8.0),
Text("Updating Reboot DLL...")
],
),
@@ -97,7 +97,7 @@ class _HostPageState extends State<_HostPage> with AutomaticKeepAliveClientMixin
child: _hostingController.updateStatus.value == UpdateStatus.error ? _updateError : _rebootGuiInfo,
)),
const SizedBox(
height: 16.0
height: 8.0
),
SettingTile(
title: "Game Server",
@@ -135,7 +135,7 @@ class _HostPageState extends State<_HostPage> with AutomaticKeepAliveClientMixin
],
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Version",
@@ -163,7 +163,7 @@ class _HostPageState extends State<_HostPage> with AutomaticKeepAliveClientMixin
]
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Browse available servers",

View File

@@ -1,20 +1,16 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:reboot_launcher/src/ui/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/util/server.dart';
import '../../util/os.dart';
import '../controller/game_controller.dart';
import '../controller/settings_controller.dart';
import '../dialog/dialog.dart';
import '../dialog/dialog_button.dart';
import '../widget/home/version_selector.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
class InfoPage extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
@@ -219,7 +215,7 @@ class _PlayPageState extends State<_PlayPage> {
],
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: '2. Download Fortnite',
@@ -255,7 +251,7 @@ class _PlayPageState extends State<_PlayPage> {
],
),
const SizedBox(
height: 16.0,
height: 8.0,
),
StreamBuilder(
stream: _remoteGameServerStream.stream,

View File

@@ -1,20 +1,8 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:reboot_launcher/src/ui/page/browse_page.dart';
import 'package:reboot_launcher/src/ui/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/ui/page/play_page.dart';
import '../../util/checks.dart';
import '../../util/os.dart';
class LauncherPage extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
@@ -48,7 +36,7 @@ class _LauncherPageState extends State<LauncherPage> with AutomaticKeepAliveClie
Widget _createScreen(String? name) {
switch(name){
case "home":
return _GamePage(widget.navigatorKey, widget.nestedNavigation);
return PlayPage(widget.navigatorKey, widget.nestedNavigation);
case "browse":
return const BrowsePage();
default:
@@ -56,177 +44,3 @@ class _LauncherPageState extends State<LauncherPage> with AutomaticKeepAliveClie
}
}
}
class _GamePage extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
final RxInt nestedNavigation;
const _GamePage(this.navigatorKey, this.nestedNavigation, {Key? key}) : super(key: key);
@override
State<_GamePage> createState() => _GamePageState();
}
class _GamePageState extends State<_GamePage> {
final GameController _gameController = Get.find<GameController>();
final SettingsController _settingsController = Get.find<SettingsController>();
late final RxBool _showPasswordTrailing = RxBool(_gameController.password.text.isNotEmpty);
final StreamController _matchmakingStream = StreamController();
@override
void initState() {
_gameController.password.addListener(() => _matchmakingStream.add(null));
_settingsController.matchmakingIp.addListener(() => _matchmakingStream.add(null));
super.initState();
}
@override
Widget build(BuildContext context) => Column(
children: [
Expanded(
child: ListView(
children: [
SettingTile(
title: "Credentials",
subtitle: "Your in-game login credentials",
expandedContentSpacing: 0,
expandedContent: [
SettingTile(
title: "Username",
subtitle: "The username that other players will see when you are in game",
isChild: true,
content: TextFormBox(
placeholder: "Username",
controller: _gameController.username,
autovalidateMode: AutovalidateMode.always
),
),
SettingTile(
title: "Password",
subtitle: "The password of your account, only used if the backend requires it",
isChild: true,
content: Obx(() => TextFormBox(
placeholder: "Password",
controller: _gameController.password,
autovalidateMode: AutovalidateMode.always,
obscureText: !_gameController.showPassword.value,
enableSuggestions: false,
autocorrect: false,
onChanged: (text) => _showPasswordTrailing.value = text.isNotEmpty,
suffix: Button(
onPressed: () => _gameController.showPassword.value = !_gameController.showPassword.value,
style: ButtonStyle(
shape: ButtonState.all(const CircleBorder()),
backgroundColor: ButtonState.all(Colors.transparent)
),
child: Icon(
_gameController.showPassword.value ? Icons.visibility_off : Icons.visibility,
color: _showPasswordTrailing.value ? null : Colors.transparent
),
)
))
)
],
),
const SizedBox(
height: 16.0,
),
StreamBuilder(
stream: _matchmakingStream.stream,
builder: (context, value) =>
SettingTile(
title: "Matchmaking host",
subtitle: "Enter the IP address of the game server hosting the match",
content: TextFormBox(
placeholder: "IP:PORT",
controller: _settingsController.matchmakingIp,
validator: checkMatchmaking,
autovalidateMode: AutovalidateMode.always
),
expandedContent: [
SettingTile(
title: "Automatically start game server",
subtitle: "This option is available when the matchmaker is set to localhost",
contentWidth: null,
content: !isLocalHost(_settingsController.matchmakingIp.text) || _gameController.password.text.isNotEmpty ? _disabledAutoGameServerSwitch : _autoGameServerSwitch,
isChild: true
),
SettingTile(
title: "Browse available servers",
subtitle: "Discover new game servers that fit your play-style",
content: Button(
onPressed: () {
widget.navigatorKey.currentState?.pushNamed('browse');
widget.nestedNavigation.value += 1;
},
child: const Text("Browse")
),
isChild: true
)
]
)
),
const SizedBox(
height: 16.0,
),
SettingTile(
title: "Version",
subtitle: "Select the version of Fortnite you want to play",
content: const VersionSelector(),
expandedContent: [
SettingTile(
title: "Add a version from this PC's local storage",
subtitle: "Versions coming from your local disk are not guaranteed to work",
content: Button(
onPressed: () => VersionSelector.openAddDialog(context),
child: const Text("Add build"),
),
isChild: true
),
SettingTile(
title: "Download any version from the cloud",
subtitle: "A curated list of supported versions by Project Reboot",
content: Button(
onPressed: () => VersionSelector.openDownloadDialog(context),
child: const Text("Download"),
),
isChild: true
)
]
)
],
),
),
const SizedBox(
height: 8.0,
),
const LaunchButton(
host: false
)
],
);
Widget get _disabledAutoGameServerSwitch => Container(
foregroundDecoration: const BoxDecoration(
color: Colors.grey,
backgroundBlendMode: BlendMode.saturation,
),
child: _autoGameServerSwitch,
);
Widget get _autoGameServerSwitch => Obx(() => ToggleSwitch(
checked: _gameController.autoStartGameServer() && isLocalHost(_settingsController.matchmakingIp.text) && _gameController.password.text.isEmpty,
onChanged: (value) {
if(!isLocalHost(_settingsController.matchmakingIp.text)){
showMessage("This option isn't available when the matchmaker isn't set to 127.0.0.1");
return;
}
if(_gameController.password.text.isNotEmpty){
showMessage("This option isn't available when the password isn't empty(LawinV2)");
return;
}
_gameController.autoStartGameServer.value = value;
}
));
}

View File

@@ -0,0 +1,154 @@
import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:reboot_launcher/src/ui/widget/home/launch_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/ui/widget/home/version_selector.dart';
class PlayPage extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
final RxInt nestedNavigation;
const PlayPage(this.navigatorKey, this.nestedNavigation, {Key? key}) : super(key: key);
@override
State<PlayPage> createState() => _PlayPageState();
}
class _PlayPageState extends State<PlayPage> {
final GameController _gameController = Get.find<GameController>();
final SettingsController _settingsController = Get.find<SettingsController>();
final StreamController _matchmakingStream = StreamController();
@override
void initState() {
_gameController.password.addListener(() => _matchmakingStream.add(null));
_settingsController.matchmakingIp.addListener(() =>
_matchmakingStream.add(null));
super.initState();
}
@override
Widget build(BuildContext context) => Column(
children: [
Expanded(
child: ListView(
children: [
SettingTile(
title: "Version",
subtitle: "Select the version of Fortnite you want to play",
content: const VersionSelector(),
expandedContent: [
SettingTile(
title: "Add a version from this PC's local storage",
subtitle: "Versions coming from your local disk are not guaranteed to work",
content: Button(
onPressed: () =>
VersionSelector.openAddDialog(context),
child: const Text("Add build"),
),
isChild: true
),
SettingTile(
title: "Download any version from the cloud",
subtitle: "A curated list of supported versions by Project Reboot",
content: Button(
onPressed: () =>
VersionSelector.openDownloadDialog(context),
child: const Text("Download"),
),
isChild: true
)
]
),
const SizedBox(
height: 8.0,
),
StreamBuilder(
stream: _matchmakingStream.stream,
builder: (context, value) =>
SettingTile(
title: "Matchmaking host",
subtitle: "Enter the IP address of the game server hosting the match",
content: TextFormBox(
placeholder: "IP:PORT",
controller: _settingsController.matchmakingIp,
validator: checkMatchmaking,
autovalidateMode: AutovalidateMode.always
),
expandedContent: [
SettingTile(
title: "Automatically start game server",
subtitle: "This option is available when the matchmaker is set to localhost",
contentWidth: null,
content: !isLocalHost(
_settingsController.matchmakingIp.text) ||
_gameController.password.text.isNotEmpty
? _disabledAutoGameServerSwitch
: _autoGameServerSwitch,
isChild: true
),
SettingTile(
title: "Browse available servers",
subtitle: "Discover new game servers that fit your play-style",
content: Button(
onPressed: () {
widget.navigatorKey.currentState
?.pushNamed('browse');
widget.nestedNavigation.value += 1;
},
child: const Text("Browse")
),
isChild: true
)
]
)
),
],
),
),
const SizedBox(
height: 8.0,
),
const LaunchButton(
host: false
)
],
);
Widget get _disabledAutoGameServerSwitch => Container(
foregroundDecoration: const BoxDecoration(
color: Colors.grey,
backgroundBlendMode: BlendMode.saturation,
),
child: _autoGameServerSwitch,
);
Widget get _autoGameServerSwitch => Obx(() => ToggleSwitch(
checked: _gameController.autoStartGameServer() &&
isLocalHost(_settingsController.matchmakingIp.text) &&
_gameController.password.text.isEmpty,
onChanged: (value) {
if (!isLocalHost(_settingsController.matchmakingIp.text)) {
showMessage(
"This option isn't available when the matchmaker isn't set to 127.0.0.1");
return;
}
if (_gameController.password.text.isNotEmpty) {
showMessage(
"This option isn't available when the password isn't empty(LawinV2)");
return;
}
_gameController.autoStartGameServer.value = value;
}
));
}

View File

@@ -1,15 +1,15 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
import 'package:reboot_launcher/src/ui/widget/server/server_type_selector.dart';
import 'package:reboot_launcher/src/ui/widget/server/server_button.dart';
import 'package:reboot_launcher/src/ui/widget/server/server_type_selector.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'package:url_launcher/url_launcher.dart';
import '../dialog/dialog.dart';
import '../dialog/dialog_button.dart';
import '../widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
class ServerPage extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
@@ -43,7 +43,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
),
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Host",
@@ -55,7 +55,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Port",
@@ -67,7 +67,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Type",
@@ -75,7 +75,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
content: ServerTypeSelector()
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Detached",
@@ -87,7 +87,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
))
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Server files",
@@ -98,7 +98,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Reset Backend",

View File

@@ -1,5 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
@@ -7,12 +6,10 @@ import 'package:reboot_launcher/src/ui/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/ui/widget/shared/file_selector.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../util/checks.dart';
import '../../util/os.dart';
import '../../util/selector.dart';
import '../dialog/dialog.dart';
import '../widget/home/setting_tile.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog.dart';
import 'package:reboot_launcher/src/ui/widget/home/setting_tile.dart';
class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}) : super(key: key);
@@ -56,7 +53,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
],
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Automatic updates",
@@ -82,7 +79,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
]
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Custom launch arguments",
@@ -93,7 +90,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Create a bug report",
@@ -104,7 +101,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Reset settings",
@@ -134,7 +131,7 @@ class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClie
)
),
const SizedBox(
height: 16.0,
height: 8.0,
),
SettingTile(
title: "Version status",

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/build_controller.dart';
import 'package:reboot_launcher/src/model/fortnite_build.dart';
import 'package:reboot_launcher/src/ui/controller/build_controller.dart';
class BuildSelector extends StatefulWidget {
final Function() onSelected;

View File

@@ -1,32 +1,29 @@
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as path;
import 'package:process_run/shell.dart';
import 'package:reboot_launcher/src/../main.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/ui/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog.dart';
import 'package:reboot_launcher/src/ui/dialog/game_dialogs.dart';
import 'package:reboot_launcher/src/ui/dialog/server_dialogs.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/injector.dart';
import 'package:reboot_launcher/src/util/patcher.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/../main.dart';
import 'package:reboot_launcher/src/ui/controller/settings_controller.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:reboot_launcher/src/model/game_instance.dart';
import 'package:reboot_launcher/src/util/injector.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/server.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../../../util/process.dart';
import 'package:reboot_launcher/src/util/process.dart';
class LaunchButton extends StatefulWidget {
final bool host;
@@ -124,7 +121,8 @@ class _LaunchButtonState extends State<LaunchButton> {
try {
var version = _gameController.selectedVersion!;
if(version.executable?.path == null){
var executable = await version.executable;
if(executable == null){
showMissingBuildError(version);
_onStop(widget.host);
return;
@@ -136,9 +134,6 @@ class _LaunchButtonState extends State<LaunchButton> {
return;
}
await compute(patchHeadless, version.executable!);
// Is this needed? await compute(patchMatchmaking, version.executable!);
var automaticallyStartedServer = await _startMatchMakingServer();
await _startGameProcesses(version, widget.host, automaticallyStartedServer);
@@ -156,7 +151,14 @@ class _LaunchButtonState extends State<LaunchButton> {
_setStarted(host, true);
var launcherProcess = await _createLauncherProcess(version);
var eacProcess = await _createEacProcess(version);
var gameProcess = await _createGameProcess(version.executable!.path, host);
var executable = await version.executable;
if(executable == null){
showMissingBuildError(version);
_onStop(widget.host);
return;
}
var gameProcess = await _createGameProcess(executable.path, host);
var watchDogProcess = _createWatchdogProcess(gameProcess, launcherProcess, eacProcess);
var instance = GameInstance(gameProcess, launcherProcess, eacProcess, watchDogProcess, hasChildServer);
if(host){

View File

@@ -1,5 +1,4 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/ui/widget/shared/fluent_card.dart';
class SettingTile extends StatefulWidget {
static const double kDefaultContentWidth = 200.0;
@@ -42,19 +41,17 @@ class _SettingTileState extends State<SettingTile> {
return _contentCard;
}
return Mica(
elevation: 1,
child: Expander(
return Expander(
initiallyExpanded: true,
contentBackgroundColor: FluentTheme.of(context).menuColor,
headerShape: (open) => const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(4.0)),
),
header: _header,
headerHeight: widget.expandedContentHeaderHeight,
header: SizedBox(
height: widget.expandedContentHeaderHeight,
child: _header
),
trailing: _trailing,
content: _content
),
);
}
@@ -90,8 +87,9 @@ class _SettingTileState extends State<SettingTile> {
);
}
return FluentCard(
child: _contentCardBody,
return Card(
borderRadius: const BorderRadius.vertical(top: Radius.circular(4.0)),
child: _contentCardBody
);
}

View File

@@ -4,18 +4,18 @@ import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.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/dialog/add_local_version.dart';
import 'package:reboot_launcher/src/ui/dialog/add_server_version.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog_button.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
import 'package:reboot_launcher/src/ui/dialog/add_local_version.dart';
import 'package:reboot_launcher/src/ui/widget/shared/smart_check_box.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:reboot_launcher/src/ui/dialog/add_server_version.dart';
import 'package:reboot_launcher/src/util/checks.dart';
import '../shared/file_selector.dart';
import 'package:reboot_launcher/src/ui/widget/shared/file_selector.dart';
class VersionSelector extends StatefulWidget {
const VersionSelector({Key? key}) : super(key: key);

View File

@@ -7,7 +7,9 @@ import 'package:flutter/widgets.dart';
/// Close
class CloseIcon extends StatelessWidget {
final Color color;
CloseIcon({Key? key, required this.color}) : super(key: key);
const CloseIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => Align(
alignment: Alignment.topLeft,
@@ -28,13 +30,16 @@ class CloseIcon extends StatelessWidget {
/// Maximize
class MaximizeIcon extends StatelessWidget {
final Color color;
MaximizeIcon({Key? key, required this.color}) : super(key: key);
const MaximizeIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
}
class _MaximizePainter extends _IconPainter {
_MaximizePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
@@ -45,22 +50,25 @@ class _MaximizePainter extends _IconPainter {
/// Restore
class RestoreIcon extends StatelessWidget {
final Color color;
RestoreIcon({
const RestoreIcon({
Key? key,
required this.color,
}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
}
class _RestorePainter extends _IconPainter {
_RestorePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
canvas.drawLine(Offset(2, 2), Offset(2, 0), p);
canvas.drawLine(Offset(2, 0), Offset(size.width, 0), p);
canvas.drawLine(const Offset(2, 2), const Offset(2, 0), p);
canvas.drawLine(const Offset(2, 0), Offset(size.width, 0), p);
canvas.drawLine(
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
canvas.drawLine(Offset(size.width, size.height - 2),
@@ -71,13 +79,16 @@ class _RestorePainter extends _IconPainter {
/// Minimize
class MinimizeIcon extends StatelessWidget {
final Color color;
MinimizeIcon({Key? key, required this.color}) : super(key: key);
const MinimizeIcon({Key? key, required this.color}) : super(key: key);
@override
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
}
class _MinimizePainter extends _IconPainter {
_MinimizePainter(Color color) : super(color);
@override
void paint(Canvas canvas, Size size) {
Paint p = getPaint(color);
@@ -89,6 +100,7 @@ class _MinimizePainter extends _IconPainter {
/// Helpers
abstract class _IconPainter extends CustomPainter {
_IconPainter(this.color);
final Color color;
@override
@@ -103,7 +115,7 @@ class _AlignedPaint extends StatelessWidget {
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: CustomPaint(size: Size(10, 10), painter: painter));
child: CustomPaint(size: const Size(10, 10), painter: painter));
}
}

View File

@@ -6,26 +6,29 @@ typedef MouseStateBuilderCB = Widget Function(
class MouseState {
bool isMouseOver = false;
bool isMouseDown = false;
MouseState();
@override
String toString() {
return "isMouseDown: ${this.isMouseDown} - isMouseOver: ${this.isMouseOver}";
return "isMouseDown: $isMouseDown - isMouseOver: $isMouseOver";
}
}
T? _ambiguate<T>(T? value) => value;
class MouseStateBuilder extends StatefulWidget {
final MouseStateBuilderCB builder;
final VoidCallback? onPressed;
MouseStateBuilder({Key? key, required this.builder, this.onPressed})
const MouseStateBuilder({Key? key, required this.builder, this.onPressed})
: super(key: key);
@override
_MouseStateBuilderState createState() => _MouseStateBuilderState();
State<MouseStateBuilder> createState() => _MouseStateBuilderState();
}
class _MouseStateBuilderState extends State<MouseStateBuilder> {
late MouseState _mouseState;
_MouseStateBuilderState() {
_mouseState = MouseState();
}
@@ -59,7 +62,7 @@ class _MouseStateBuilderState extends State<MouseStateBuilder> {
_mouseState.isMouseDown = false;
_mouseState.isMouseOver = false;
});
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.onPressed != null) {
widget.onPressed!();
}

View File

@@ -1,4 +1,3 @@
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:system_theme/system_theme.dart';
@@ -10,8 +9,8 @@ class WindowBorder extends StatelessWidget {
Widget build(BuildContext context) {
return IgnorePointer(
child: Padding(
padding: EdgeInsets.only(
top: 1 / appWindow.scaleFactor
padding: const EdgeInsets.only(
top: 1
),
child: Container(
decoration: BoxDecoration(
@@ -22,6 +21,7 @@ class WindowBorder extends StatelessWidget {
)
)
),
));
)
);
}
}

View File

@@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
import './mouse_state_builder.dart';
import '../icons/icons.dart';
import '../app_window.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
import 'icons.dart';
import 'mouse_state_builder.dart';
typedef WindowButtonIconBuilder = Widget Function(
WindowButtonContext buttonContext);
@@ -16,6 +14,7 @@ class WindowButtonContext {
MouseState mouseState;
Color? backgroundColor;
Color iconColor;
WindowButtonContext(
{required this.context,
required this.mouseState,
@@ -30,6 +29,7 @@ class WindowButtonColors {
late Color iconNormal;
late Color iconMouseOver;
late Color iconMouseDown;
WindowButtonColors(
{Color? normal,
Color? mouseOver,
@@ -48,11 +48,11 @@ class WindowButtonColors {
final _defaultButtonColors = WindowButtonColors(
normal: Colors.transparent,
iconNormal: Color(0xFF805306),
mouseOver: Color(0xFF404040),
mouseDown: Color(0xFF202020),
iconMouseOver: Color(0xFFFFFFFF),
iconMouseDown: Color(0xFFF0F0F0));
iconNormal: const Color(0xFF805306),
mouseOver: const Color(0xFF404040),
mouseDown: const Color(0xFF202020),
iconMouseOver: const Color(0xFFFFFFFF),
iconMouseDown: const Color(0xFFF0F0F0));
class WindowButton extends StatelessWidget {
final WindowButtonBuilder? builder;
@@ -88,15 +88,6 @@ class WindowButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return Container();
} else {
// Don't show button on macOS
if (Platform.isMacOS) {
return Container();
}
}
final buttonSize = appWindow.titleBarButtonSize;
return MouseStateBuilder(
builder: (context, mouseState) {
WindowButtonContext buttonContext = WindowButtonContext(
@@ -105,34 +96,27 @@ class WindowButton extends StatelessWidget {
backgroundColor: getBackgroundColor(mouseState),
iconColor: getIconColor(mouseState));
var icon = (this.iconBuilder != null)
? this.iconBuilder!(buttonContext)
: Container();
double borderSize = appWindow.borderSize;
double defaultPadding =
(appWindow.titleBarHeight - borderSize) / 3 - (borderSize / 2);
// Used when buttonContext.backgroundColor is null, allowing the AnimatedContainer to fade-out smoothly.
var icon =
(iconBuilder != null) ? iconBuilder!(buttonContext) : Container();
var fadeOutColor =
getBackgroundColor(MouseState()..isMouseOver = true).withOpacity(0);
getBackgroundColor(MouseState()..isMouseOver = true)
.withOpacity(0);
var padding = this.padding ?? EdgeInsets.zero;
var animationMs =
mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0);
var animationMs = mouseState.isMouseOver
? (animate ? 100 : 0)
: (animate ? 200 : 0);
Widget iconWithPadding = Padding(padding: padding, child: icon);
iconWithPadding = AnimatedContainer(
curve: Curves.easeOut,
duration: Duration(milliseconds: animationMs),
color: buttonContext.backgroundColor ?? fadeOutColor,
child: iconWithPadding);
var button = (this.builder != null)
? this.builder!(buttonContext, icon)
var button = (builder != null)
? builder!(buttonContext, icon)
: iconWithPadding;
return SizedBox(
width: 48, height: 48, child: button);
return SizedBox.square(dimension: 45, child: button);
},
onPressed: () {
if (this.onPressed != null) this.onPressed!();
},
);
onPressed: onPressed);
}
}
@@ -148,7 +132,7 @@ class MinimizeWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
MinimizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.minimize());
onPressed: onPressed ?? () => windowManager.minimize());
}
class MaximizeWindowButton extends WindowButton {
@@ -163,7 +147,10 @@ class MaximizeWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
MaximizeIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.maximizeOrRestore());
onPressed: onPressed ??
() async => await windowManager.isMaximized()
? await windowManager.restore()
: await windowManager.maximize());
}
class RestoreWindowButton extends WindowButton {
@@ -178,14 +165,17 @@ class RestoreWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
RestoreIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.maximizeOrRestore());
onPressed: onPressed ??
() async => await windowManager.isMaximized()
? await windowManager.restore()
: await windowManager.maximize());
}
final _defaultCloseButtonColors = WindowButtonColors(
mouseOver: Color(0xFFD32F2F),
mouseDown: Color(0xFFB71C1C),
iconNormal: Color(0xFF805306),
iconMouseOver: Color(0xFFFFFFFF));
mouseOver: const Color(0xFFD32F2F),
mouseDown: const Color(0xFFB71C1C),
iconNormal: const Color(0xFF805306),
iconMouseOver: const Color(0xFFFFFFFF));
class CloseWindowButton extends WindowButton {
CloseWindowButton(
@@ -199,5 +189,5 @@ class CloseWindowButton extends WindowButton {
animate: animate ?? false,
iconBuilder: (buttonContext) =>
CloseIcon(color: buttonContext.iconColor),
onPressed: onPressed ?? () => appWindow.close());
onPressed: onPressed ?? () => windowManager.close());
}

View File

@@ -1,5 +1,5 @@
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/ui/widget/os/window_button.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:system_theme/system_theme.dart';

View File

@@ -1,8 +1,8 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
import 'package:reboot_launcher/src/ui/dialog/server_dialogs.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
class ServerButton extends StatefulWidget {
const ServerButton({Key? key}) : super(key: key);

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/ui/controller/server_controller.dart';
class ServerTypeSelector extends StatelessWidget {
final ServerController _serverController = Get.find<ServerController>();

View File

@@ -1,11 +1,6 @@
import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:reboot_launcher/src/ui/dialog/snackbar.dart';
import 'package:reboot_launcher/src/util/selector.dart';
class FileSelector extends StatefulWidget {

View File

@@ -1,16 +0,0 @@
import 'package:fluent_ui/fluent_ui.dart';
class FluentCard extends StatelessWidget {
final Widget child;
const FluentCard({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) => Mica(
elevation: 1,
child: Card(
backgroundColor: FluentTheme.of(context).menuColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4.0)),
child: child
)
);
}

View File

@@ -0,0 +1,56 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:get/get.dart';
import '../../controller/game_controller.dart';
class ProfileWidget extends StatelessWidget {
final GameController _gameController = Get.find<GameController>();
ProfileWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 12.0
),
child: GestureDetector(
child: Row(
children: [
Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
shape: BoxShape.circle
),
child: Image.asset("assets/images/user.png")
),
const SizedBox(
width: 12.0,
),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Auties00",
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w600
),
),
Text(
"alautiero@gmail.com",
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: FontWeight.w100
),
)
],
)
],
),
),
);
}
}

View File

@@ -2,13 +2,12 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:archive/archive_io.dart';
import 'package:html/parser.dart' show parse;
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:reboot_launcher/src/model/fortnite_build.dart';
import 'package:reboot_launcher/src/util/time.dart';
import 'package:reboot_launcher/src/util/version.dart' as parser;
import 'package:path/path.dart' as path;
import 'os.dart';

View File

@@ -1,9 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'package:reboot_launcher/src/util/server.dart';
import '../model/fortnite_version.dart';
import 'package:reboot_launcher/src/model/fortnite_version.dart';
String? checkVersion(String? text, List<FortniteVersion> versions) {
if (text == null || text.isEmpty) {

View File

@@ -1,7 +1,7 @@
import 'package:fluent_ui/fluent_ui.dart';
import '../../../main.dart';
import '../ui/dialog/dialog.dart';
import 'package:reboot_launcher/main.dart';
import 'package:reboot_launcher/src/ui/dialog/dialog.dart';
String? lastError;

View File

@@ -2,8 +2,8 @@
import 'dart:ffi';
import 'package:win32/win32.dart';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
final _kernel32 = DynamicLibrary.open('kernel32.dll');
final _CreateRemoteThread = _kernel32.lookupFunction<

View File

@@ -1,8 +1,8 @@
import 'dart:ffi';
import 'dart:io';
import 'package:win32/win32.dart';
import 'package:ffi/ffi.dart';
import 'dart:ffi';
import 'package:win32/win32.dart';
const int appBarSize = 2;

View File

@@ -1,6 +1,5 @@
import 'dart:ffi';
import 'package:win32/src/kernel32.dart';
import 'package:win32/win32.dart';
final _ntdll = DynamicLibrary.open('ntdll.dll');

View File

@@ -2,15 +2,14 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:ini/ini.dart';
import 'package:process_run/shell.dart';
import 'package:reboot_launcher/src/model/server_type.dart';
import 'package:reboot_launcher/src/ui/controller/game_controller.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:shelf_proxy/shelf_proxy.dart';
import 'package:shelf/shelf_io.dart';
import 'package:http/http.dart' as http;
import 'package:shelf_proxy/shelf_proxy.dart';
final serverLogFile = File("${logsDirectory.path}\\server.log");
final serverDirectory = Directory("${assetsDirectory.path}\\lawin");

View File

@@ -11,9 +11,7 @@ dependencies:
flutter:
sdk: flutter
bitsdojo_window:
path: ./dependencies/bitsdojo_window-0.1.5
fluent_ui: ^4.6.2
fluent_ui: ^4.7.3
bitsdojo_window_windows: ^0.1.5
system_theme: ^2.0.0
http: ^0.13.5
@@ -43,6 +41,7 @@ dependencies:
supabase_flutter: ^1.10.0
supabase: ^1.9.1
fluentui_system_icons: ^1.1.202
flutter_acrylic: ^1.1.3
dev_dependencies:
flutter_test:

View File

@@ -8,6 +8,7 @@
#include <app_links/app_links_plugin_c_api.h>
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
#include <flutter_acrylic/flutter_acrylic_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <system_theme/system_theme_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
@@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
BitsdojoWindowPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
FlutterAcrylicPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterAcrylicPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
SystemThemePluginRegisterWithRegistrar(

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
app_links
bitsdojo_window_windows
flutter_acrylic
screen_retriever
system_theme
url_launcher_windows

View File

@@ -0,0 +1,36 @@
[Setup]
AppId={{APP_ID}}
AppVersion={{APP_VERSION}}
AppName={{DISPLAY_NAME}}
AppPublisher={{PUBLISHER_NAME}}
AppPublisherURL={{PUBLISHER_URL}}
AppSupportURL={{PUBLISHER_URL}}
AppUpdatesURL={{PUBLISHER_URL}}
DefaultDirName={{INSTALL_DIR_NAME}}
DisableProgramGroupPage=yes
OutputBaseFilename={{OUTPUT_BASE_FILENAME}}
Compression=lzma
SolidCompression=yes
SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern
PrivilegesRequired=admin
ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "reboot_launcher-8.1.0+8.1.0-windows-setup_exe\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Run]
Filename: "{app}\\{{EXECUTABLE_NAME}}"; Description: "{cm:LaunchProgram,{{DISPLAY_NAME}}}"; Flags: runascurrentuser nowait postinstall skipifsilent
[Icons]
Name: "{autoprograms}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"
Name: "{autodesktop}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"; Tasks: desktopicon
Name: "{userstartup}\{{DISPLAY_NAME}}"; Filename: "{app}\{{EXECUTABLE_NAME}}"; WorkingDir: "{app}"; Tasks: launchAtStartup

View File

@@ -1,6 +1,9 @@
script_template: "custom-inno-setup-script.iss"
app_id: 31868Auties00.RebootLauncher
publisher_name: Auties00
publisher_url: https://github.com/Auties00
display_name: Reboot Launcher
install_dir_name: Reboot Launcher
create_desktop_icon: true
locales:
- en

View File

@@ -1,5 +1,5 @@
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME);
#include <cstdlib>
@@ -15,19 +15,20 @@ auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
#include <stdio.h>
#include <fcntl.h>
bool CheckOneInstance()
{
HANDLE m_hStartEvent = CreateEventW( NULL, FALSE, FALSE, L"reboot_launcher");
if(m_hStartEvent == NULL)
{
CloseHandle( m_hStartEvent );
bool CheckOneInstance(){
HANDLE hMutex = CreateMutexW(NULL, TRUE, L"RebootLauncherMutex");
if (hMutex == NULL) {
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle( m_hStartEvent );
m_hStartEvent = NULL;
if (GetLastError() == ERROR_ALREADY_EXISTS) {
HWND hwndExisting = FindWindowW(NULL, L"Reboot Launcher");
if (hwndExisting != NULL) {
ShowWindow(hwndExisting, SW_RESTORE);
SetForegroundWindow(hwndExisting);
}
CloseHandle(hMutex);
return false;
}
@@ -58,7 +59,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"reboot_launcher", origin, size)) {
if (!window.CreateAndShow(L"Reboot Launcher", origin, size)) {
return EXIT_FAILURE;
}

View File

@@ -4,6 +4,8 @@
#include "resource.h"
#include <dwmapi.h>
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
@@ -117,10 +119,18 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
window_class,
title.c_str(),
WS_OVERLAPPED & ~WS_VISIBLE,
Scale(origin.x, scale_factor),
Scale(origin.y, scale_factor),
Scale(size.width, scale_factor),
Scale(size.height, scale_factor),
nullptr,
nullptr,
GetModuleHandle(nullptr),
this
);
if (!window) {
return false;