Refactored GUI

This commit is contained in:
Alessandro Autiero
2025-08-10 19:43:57 +01:00
parent 52abf5eb95
commit 4ea73d17c7
75 changed files with 2020 additions and 2011 deletions

View File

@@ -13,12 +13,12 @@ import 'package:reboot_launcher/src/controller/backend_controller.dart';
import 'package:reboot_launcher/src/controller/dll_controller.dart';
import 'package:reboot_launcher/src/controller/game_controller.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
import 'package:reboot_launcher/src/controller/server_browser_controller.dart';
import 'package:reboot_launcher/src/controller/settings_controller.dart';
import 'package:reboot_launcher/src/widget/message/error.dart';
import 'package:reboot_launcher/src/widget/page/home_page.dart';
import 'package:reboot_launcher/src/message/error.dart';
import 'package:reboot_launcher/src/pager/pager.dart';
import 'package:reboot_launcher/src/util/os.dart';
import 'package:reboot_launcher/src/util/url_protocol.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:system_theme/system_theme.dart';
import 'package:version/version.dart';
import 'package:window_manager/window_manager.dart';
@@ -35,207 +35,140 @@ bool appWithNoStorage = false;
void main() {
log("[APP] Called");
runZonedGuarded(
() => _startApp(),
(error, stack) => onError(error, stack, false),
() => _startApp(),
(error, stack) => onError(error, stack, false),
zoneSpecification: ZoneSpecification(
handleUncaughtError: (self, parent, zone, error, stacktrace) => onError(error, stacktrace, false)
)
);
}
// If anything fails here, the app won't start
// Be extremely careful
Future<void> _startApp() async {
_overrideHttpCertificate();
final errors = <Object>[];
final errors = <String>[];
Future<T?> runCatching<T>({
required FutureOr<T> Function() callable,
required String Function(Object) errorFormatter
}) async {
try {
return callable();
}catch(error) {
errors.add(errorFormatter(error));
return null;
}
}
log("[APP] Starting application");
try {
log("[APP] Starting application");
final pathError = await _initPath();
if(pathError != null) {
errors.add(pathError);
}
final databaseError = await _initDatabase();
if(databaseError != null) {
errors.add(databaseError);
}
final notificationsError = await _initNotifications();
if(notificationsError != null) {
errors.add(notificationsError);
}
final versionError = await _initVersion();
if(versionError != null) {
errors.add(versionError);
}
final storageErrors = await _initStorage();
errors.addAll(storageErrors);
WidgetsFlutterBinding.ensureInitialized();
_initWindow();
final urlError = await _initUrlHandler();
if(urlError != null) {
errors.add(urlError);
}
}catch(uncaughtError) {
errors.add(uncaughtError);
} finally{
await runCatching(
callable: () => installationDirectory.create(
recursive: true
),
errorFormatter: (error) => "Cannot create installation directory: $error"
);
await runCatching(
callable: () => localNotifier.setup(
appName: 'Reboot Launcher',
shortcutPolicy: ShortcutPolicy.ignore
),
errorFormatter: (error) => "Cannot create installation directory: $error"
);
await runCatching(
callable: () async {
final packageInfo = await PackageInfo.fromPlatform();
appVersion = Version.parse(packageInfo.version);
},
errorFormatter: (error) => "Cannot parse version: $error"
);
await runCatching(
callable: () async {
await GetStorage(GameController.storageName, settingsDirectory.path).initStorage;
await GetStorage(BackendController.storageName, settingsDirectory.path).initStorage;
await GetStorage(SettingsController.storageName, settingsDirectory.path).initStorage;
await GetStorage(HostingController.storageName, settingsDirectory.path).initStorage;
await GetStorage(DllController.storageName, settingsDirectory.path).initStorage;
},
errorFormatter: (error) {
appWithNoStorage = true;
return "Cannot access storage: $error";
}
);
await runCatching(
callable: () => Get.put(GameController(), permanent: true),
errorFormatter: (error) => "Cannot create game controller: $error"
);
await runCatching(
callable: () => Get.put(BackendController(), permanent: true),
errorFormatter: (error) => "Cannot create backend controller: $error"
);
await runCatching(
callable: () => Get.put(HostingController(), permanent: true),
errorFormatter: (error) => "Cannot create backend controller: $error"
);
await runCatching(
callable: () => Get.put(ServerBrowserController(), permanent: true),
errorFormatter: (error) => "Cannot create browser controller: $error"
);
final settingsController = await runCatching(
callable: () => Get.put(SettingsController(), permanent: true),
errorFormatter: (error) => "Cannot create settings controller: $error"
);
await runCatching(
callable: () => Get.put(DllController(), permanent: true),
errorFormatter: (error) => "Cannot create dll controller: $error"
);
await runCatching(
callable: () async {
try {
await SystemTheme.accentColor.load();
await windowManager.ensureInitialized();
await Window.initialize();
if(settingsController != null) {
final size = Size(settingsController.width, settingsController.height);
await windowManager.setSize(size);
final offsetX = settingsController.offsetX;
final offsetY = settingsController.offsetY;
if (offsetX != null && offsetY != null) {
final position = Offset(
offsetX,
offsetY
);
await windowManager.setPosition(position);
} else {
await windowManager.setAlignment(Alignment.center);
}
}
await windowManager.setPreventClose(true);
await windowManager.setResizable(true);
if(isWin11) {
await Window.setEffect(
effect: WindowEffect.acrylic,
color: Colors.green,
dark: isDarkMode
);
}
} finally {
windowManager.show();
}
},
errorFormatter: (error) => "Cannot configure window: $error"
);
runCatching(
callable: () => registerUrlProtocol(kCustomUrlSchema, arguments: ['%s']),
errorFormatter: (error) => "Cannot configure custom url scheme: $error"
);
}catch(error) {
errors.add("Uncaught error: $error");
}finally {
log("[APP] Started applications with errors: $errors");
runApp(RebootApplication(errors: errors));
}
}
class _MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
}
}
void _overrideHttpCertificate() {
HttpOverrides.global = _MyHttpOverrides(); // Not safe, but necessary
}
Future<Object?> _initNotifications() async {
try {
await localNotifier.setup(
appName: 'Reboot Launcher',
shortcutPolicy: ShortcutPolicy.ignore
);
return null;
}catch(error) {
return error;
}
}
Future<Object?> _initDatabase() async {
try {
await Supabase.initialize(
url: supabaseUrl,
anonKey: supabaseAnonKey
);
return null;
}catch(error) {
return error;
}
}
Future<Object?> _initPath() async {
try {
await installationDirectory.create(recursive: true);
return null;
}catch(error) {
return error;
}
}
Future<Object?> _initVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
appVersion = Version.parse(packageInfo.version);
return null;
}catch(error) {
return error;
}
}
Future<Object?> _initUrlHandler() async {
try {
registerUrlProtocol(kCustomUrlSchema, arguments: ['%s']);
return null;
}catch(error) {
return error;
}
}
Future<void> _initWindow() async {
try {
await SystemTheme.accentColor.load();
await windowManager.ensureInitialized();
await Window.initialize();
var settingsController = Get.find<SettingsController>();
var size = Size(settingsController.width, settingsController.height);
await windowManager.setSize(size);
var offsetX = settingsController.offsetX;
var offsetY = settingsController.offsetY;
if(offsetX != null && offsetY != null) {
final position = Offset(
offsetX,
offsetY
);
await windowManager.setPosition(position);
}else {
await windowManager.setAlignment(Alignment.center);
}
await windowManager.setPreventClose(true);
await windowManager.setResizable(true);
if(isWin11) {
await Window.setEffect(
effect: WindowEffect.acrylic,
color: Colors.green,
dark: isDarkMode
);
}
}catch(error, stackTrace) {
onError(error, stackTrace, false);
}finally {
windowManager.show();
}
}
Future<List<Object>> _initStorage() async {
final errors = <Object>[];
try {
await GetStorage(GameController.storageName, settingsDirectory.path).initStorage;
await GetStorage(BackendController.storageName, settingsDirectory.path).initStorage;
await GetStorage(SettingsController.storageName, settingsDirectory.path).initStorage;
await GetStorage(HostingController.storageName, settingsDirectory.path).initStorage;
await GetStorage(DllController.storageName, settingsDirectory.path).initStorage;
}catch(error) {
appWithNoStorage = true;
errors.add("The Reboot Launcher configuration in ${settingsDirectory.path} cannot be accessed: running with in memory storage");
}
try {
Get.put(GameController());
}catch(error) {
errors.add(error);
}
try {
Get.put(BackendController());
}catch(error) {
errors.add(error);
}
try {
final controller = HostingController();
Get.put(controller);
controller.discardServer();
}catch(error) {
errors.add(error);
}
try {
Get.put(SettingsController());
}catch(error) {
errors.add(error);
}
try {
Get.put(DllController());
}catch(error) {
errors.add(error);
}
return errors;
}
class RebootApplication extends StatefulWidget {
final List<Object> errors;
final List<String> errors;
const RebootApplication({Key? key, required this.errors}) : super(key: key);
@override
@@ -248,15 +181,12 @@ class _RebootApplicationState extends State<RebootApplication> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => _handleErrors(widget.errors));
}
void _handleErrors(List<Object?> errors) {
for(final error in errors) {
if(error != null) {
// Not pretty but make sure the errors are shown
Future.delayed(const Duration(seconds: 5)).then((_) {
for(final error in widget.errors) {
onError(error, null, false);
}
}
});
}
@override
@@ -272,7 +202,7 @@ class _RebootApplicationState extends State<RebootApplication> {
color: SystemTheme.accentColor.accent.toAccentColor(),
darkTheme: _createTheme(Brightness.dark),
theme: _createTheme(Brightness.light),
home: const HomePage()
home: const RebootPager()
));
FluentThemeData _createTheme(Brightness brightness) => FluentThemeData(