mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 03:02:22 +01:00
refactor
This commit is contained in:
@@ -17,7 +17,7 @@ String? _lastPort;
|
||||
|
||||
typedef BackendErrorHandler = void Function(String);
|
||||
|
||||
Stream<AuthBackendResult> startAuthBackend({
|
||||
Stream<AuthBackendEvent> startAuthBackend({
|
||||
required AuthBackendType type,
|
||||
required String host,
|
||||
required String port,
|
||||
@@ -30,80 +30,80 @@ Stream<AuthBackendResult> startAuthBackend({
|
||||
host = host.trim();
|
||||
port = port.trim();
|
||||
if(type != AuthBackendType.local || port != kDefaultBackendPort.toString()) {
|
||||
yield AuthBackendResult(AuthBackendResultType.starting);
|
||||
yield AuthBackendEvent(AuthBackendEventType.starting);
|
||||
}
|
||||
|
||||
if (host.isEmpty) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startMissingHostError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startMissingHostError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (port.isEmpty) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startMissingPortError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startMissingPortError);
|
||||
return;
|
||||
}
|
||||
|
||||
final portNumber = int.tryParse(port);
|
||||
if (portNumber == null) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startIllegalPortError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startIllegalPortError);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((type != AuthBackendType.local || port != kDefaultBackendPort.toString()) && !(await isAuthBackendPortFree())) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startFreeingPort);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startFreeingPort);
|
||||
final result = await freeAuthBackendPort();
|
||||
if(!result) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startFreePortError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startFreePortError);
|
||||
return;
|
||||
}
|
||||
|
||||
yield AuthBackendResult(AuthBackendResultType.startFreePortSuccess);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startFreePortSuccess);
|
||||
}
|
||||
|
||||
switch(type){
|
||||
case AuthBackendType.embedded:
|
||||
process = await _startEmbedded(detached, onError: onError);
|
||||
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(process: process));
|
||||
yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(process: process));
|
||||
break;
|
||||
case AuthBackendType.remote:
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingingRemote);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingingRemote);
|
||||
final uriResult = await _ping(host, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingError);
|
||||
return;
|
||||
}
|
||||
|
||||
server = await _startRemote(uriResult);
|
||||
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||
yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||
break;
|
||||
case AuthBackendType.local:
|
||||
if(portNumber != kDefaultBackendPort) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingingLocal);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingingLocal);
|
||||
final uriResult = await _ping(kDefaultBackendHost, portNumber);
|
||||
if(uriResult == null) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingError);
|
||||
return;
|
||||
}
|
||||
|
||||
server = await _startRemote(Uri.parse("http://$kDefaultBackendHost:$port"));
|
||||
yield AuthBackendResult(AuthBackendResultType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||
yield AuthBackendEvent(AuthBackendEventType.startedImplementation, implementation: AuthBackendImplementation(server: server));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingingLocal);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingingLocal);
|
||||
final uriResult = await _ping(kDefaultBackendHost, kDefaultBackendPort);
|
||||
if(uriResult == null) {
|
||||
yield AuthBackendResult(AuthBackendResultType.startPingError);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startPingError);
|
||||
process?.kill(ProcessSignal.sigterm);
|
||||
server?.close(force: true);
|
||||
return;
|
||||
}
|
||||
|
||||
yield AuthBackendResult(AuthBackendResultType.startSuccess);
|
||||
yield AuthBackendEvent(AuthBackendEventType.startSuccess);
|
||||
}catch(error, stackTrace) {
|
||||
yield AuthBackendResult(
|
||||
AuthBackendResultType.startError,
|
||||
yield AuthBackendEvent(
|
||||
AuthBackendEventType.startError,
|
||||
error: error,
|
||||
stackTrace: stackTrace
|
||||
);
|
||||
@@ -141,8 +141,8 @@ Future<Process> _startEmbedded(bool detached, {BackendErrorHandler? onError}) as
|
||||
|
||||
Future<HttpServer> _startRemote(Uri uri) async => await serve(proxyHandler(uri), kDefaultBackendHost, kDefaultBackendPort);
|
||||
|
||||
Stream<AuthBackendResult> stopAuthBackend({required AuthBackendType type, required AuthBackendImplementation? implementation}) async* {
|
||||
yield AuthBackendResult(AuthBackendResultType.stopping);
|
||||
Stream<AuthBackendEvent> stopAuthBackend({required AuthBackendType type, required AuthBackendImplementation? implementation}) async* {
|
||||
yield AuthBackendEvent(AuthBackendEventType.stopping);
|
||||
try{
|
||||
switch(type){
|
||||
case AuthBackendType.embedded:
|
||||
@@ -158,10 +158,10 @@ Stream<AuthBackendResult> stopAuthBackend({required AuthBackendType type, requir
|
||||
await implementation?.server?.close(force: true);
|
||||
break;
|
||||
}
|
||||
yield AuthBackendResult(AuthBackendResultType.stopSuccess);
|
||||
yield AuthBackendEvent(AuthBackendEventType.stopSuccess);
|
||||
}catch(error, stackTrace){
|
||||
yield AuthBackendResult(
|
||||
AuthBackendResultType.stopError,
|
||||
yield AuthBackendEvent(
|
||||
AuthBackendEventType.stopError,
|
||||
error: error,
|
||||
stackTrace: stackTrace
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
class AuthBackendResult {
|
||||
final AuthBackendResultType type;
|
||||
class AuthBackendEvent {
|
||||
final AuthBackendEventType type;
|
||||
final AuthBackendImplementation? implementation;
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
AuthBackendResult(this.type, {this.implementation, this.error, this.stackTrace});
|
||||
AuthBackendEvent(this.type, {this.implementation, this.error, this.stackTrace});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -14,14 +14,7 @@ class AuthBackendResult {
|
||||
}
|
||||
}
|
||||
|
||||
class AuthBackendImplementation {
|
||||
final Process? process;
|
||||
final HttpServer? server;
|
||||
|
||||
AuthBackendImplementation({this.process, this.server});
|
||||
}
|
||||
|
||||
enum AuthBackendResultType {
|
||||
enum AuthBackendEventType {
|
||||
starting,
|
||||
startMissingHostError,
|
||||
startMissingPortError,
|
||||
@@ -43,5 +36,12 @@ enum AuthBackendResultType {
|
||||
|
||||
bool get isError => name.endsWith("Error");
|
||||
|
||||
bool get isSuccess => this == AuthBackendResultType.startSuccess || this == AuthBackendResultType.stopSuccess;
|
||||
bool get isSuccess => this == AuthBackendEventType.startSuccess || this == AuthBackendEventType.stopSuccess;
|
||||
}
|
||||
|
||||
class AuthBackendImplementation {
|
||||
final Process? process;
|
||||
final HttpServer? server;
|
||||
|
||||
AuthBackendImplementation({this.process, this.server});
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter_acrylic/flutter_acrylic.dart';
|
||||
@@ -16,7 +15,7 @@ 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/message/error.dart';
|
||||
import 'package:reboot_launcher/src/pager/pager.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/pager.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/url_protocol.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
|
||||
@@ -8,9 +8,9 @@ import 'package:get_storage/get_storage.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
|
||||
typedef BackendInteractiveEventHandler = InfoBarEntry? Function(AuthBackendType, AuthBackendResult);
|
||||
typedef BackendInteractiveEventHandler = SnackBar? Function(AuthBackendType, AuthBackendEvent);
|
||||
|
||||
class BackendController extends GetxController {
|
||||
static const String storageName = "v3_backend_storage";
|
||||
@@ -27,7 +27,7 @@ class BackendController extends GetxController {
|
||||
late final RxBool detached;
|
||||
AuthBackendImplementation? implementation;
|
||||
StreamSubscription? _worker;
|
||||
InfoBarEntry? _interactiveEntry;
|
||||
SnackBar? _interactiveEntry;
|
||||
|
||||
BackendController() {
|
||||
_storage = appWithNoStorage ? null : GetStorage(storageName);
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:get_storage/get_storage.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/page/settings_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/settings/page.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@@ -75,7 +75,7 @@ class DllController extends GetxController {
|
||||
}
|
||||
|
||||
Future<bool> updateGameServerDll({bool force = false, bool silent = false}) async {
|
||||
InfoBarEntry? infoBarEntry;
|
||||
SnackBar? SnackBar;
|
||||
try {
|
||||
if(customGameServer.value) {
|
||||
status.value = UpdateStatus.success;
|
||||
@@ -94,7 +94,7 @@ class DllController extends GetxController {
|
||||
}
|
||||
|
||||
if(!silent) {
|
||||
infoBarEntry = showRebootInfoBar(
|
||||
SnackBar = SnackBar.open(
|
||||
translations.downloadingDll("reboot"),
|
||||
loading: true,
|
||||
duration: null
|
||||
@@ -111,19 +111,19 @@ class DllController extends GetxController {
|
||||
).then((values) => values.reduce((first, second) => first && second));
|
||||
if(!result) {
|
||||
status.value = UpdateStatus.error;
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, "reboot"),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
infoBarEntry?.close();
|
||||
SnackBar?.close();
|
||||
return false;
|
||||
}
|
||||
timestamp.value = DateTime.now().millisecondsSinceEpoch;
|
||||
status.value = UpdateStatus.success;
|
||||
infoBarEntry?.close();
|
||||
SnackBar?.close();
|
||||
if(!silent) {
|
||||
infoBarEntry = showRebootInfoBar(
|
||||
SnackBar = SnackBar.open(
|
||||
translations.downloadDllSuccess("reboot"),
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarShortDuration
|
||||
@@ -132,20 +132,20 @@ class DllController extends GetxController {
|
||||
_listenToFileEvents(GameDll.gameServer);
|
||||
return true;
|
||||
}catch(message) {
|
||||
infoBarEntry?.close();
|
||||
SnackBar?.close();
|
||||
var error = message.toString();
|
||||
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
||||
error = error.toLowerCase();
|
||||
status.value = UpdateStatus.error;
|
||||
final completer = Completer<bool>();
|
||||
infoBarEntry = showRebootInfoBar(
|
||||
SnackBar = SnackBar.open(
|
||||
translations.downloadDllError(error.toString(), "reboot.dll"),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error,
|
||||
onDismissed: () => completer.complete(false),
|
||||
action: Button(
|
||||
onPressed: () async {
|
||||
infoBarEntry?.close();
|
||||
SnackBar?.close();
|
||||
final result = updateGameServerDll(
|
||||
force: true,
|
||||
silent: silent
|
||||
@@ -216,7 +216,7 @@ class DllController extends GetxController {
|
||||
|
||||
Future<bool> download(GameDll dll, String filePath, {bool silent = false, bool force = false}) async {
|
||||
log("[DLL] Asking for $dll at $filePath(silent: $silent, force: $force)");
|
||||
InfoBarEntry? entry;
|
||||
SnackBar? entry;
|
||||
try {
|
||||
if (dll == GameDll.gameServer) {
|
||||
return await updateGameServerDll(silent: silent);
|
||||
@@ -232,7 +232,7 @@ class DllController extends GetxController {
|
||||
final fileNameWithoutExtension = basenameWithoutExtension(filePath);
|
||||
if(!silent) {
|
||||
log("[DLL] Showing dialog while downloading $dll...");
|
||||
entry = showRebootInfoBar(
|
||||
entry = SnackBar.open(
|
||||
translations.downloadingDll(fileNameWithoutExtension),
|
||||
loading: true,
|
||||
duration: null
|
||||
@@ -243,7 +243,7 @@ class DllController extends GetxController {
|
||||
final result = await downloadDependency(dll, filePath);
|
||||
if(!result) {
|
||||
entry?.close();
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, dll.name),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -254,7 +254,7 @@ class DllController extends GetxController {
|
||||
entry?.close();
|
||||
if(!silent) {
|
||||
log("[DLL] Showing success dialog for $dll");
|
||||
entry = await showRebootInfoBar(
|
||||
entry = await SnackBar.open(
|
||||
translations.downloadDllSuccess(fileNameWithoutExtension),
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarShortDuration
|
||||
@@ -271,7 +271,7 @@ class DllController extends GetxController {
|
||||
error = error.contains(": ") ? error.substring(error.indexOf(": ") + 2) : error;
|
||||
error = error.toLowerCase();
|
||||
final completer = Completer<bool>();
|
||||
await showRebootInfoBar(
|
||||
await SnackBar.open(
|
||||
translations.downloadDllError(error.toString(), dll.name),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error,
|
||||
@@ -317,7 +317,7 @@ class DllController extends GetxController {
|
||||
.instance
|
||||
.value
|
||||
?.kill();
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.downloadDllAntivirus(antiVirusName ?? defaultAntiVirusName, injectable.name),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:version/version.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
|
||||
Future<void> showResetDialog(Function() onConfirm) => showRebootDialog(
|
||||
builder: (context) => InfoDialog(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
|
||||
Future<void> showDllDeletedDialog() => showRebootDialog(
|
||||
builder: (context) => InfoDialog(
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/util/extensions.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/button/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:windows_taskbar/windows_taskbar.dart';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
|
||||
String? lastError;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/button/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
class ImportVersionDialog extends StatefulWidget {
|
||||
|
||||
@@ -5,16 +5,16 @@ import 'package:reboot_launcher/src/controller/backend_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/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/message/profile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/pager/pager.dart';
|
||||
import 'package:reboot_launcher/src/page/host_page.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/page/play_page.dart';
|
||||
import 'package:reboot_launcher/src/button/version_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/pager.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/host/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/play/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
||||
|
||||
void startOnboarding() {
|
||||
final gameController = Get.find<GameController>();
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
|
||||
Future<bool> showProfileForm(BuildContext context, TextEditingController username, TextEditingController password) async{
|
||||
final showPassword = RxBool(false);
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
|
||||
import 'info_bar.dart';
|
||||
|
||||
bool inDialog = false;
|
||||
|
||||
Future<T?> showRebootDialog<T extends Object?>({required WidgetBuilder builder, bool dismissWithEsc = true}) async {
|
||||
inDialog = true;
|
||||
pagesController.add(null);
|
||||
try {
|
||||
return await fluent.showDialog(
|
||||
context: appNavigatorKey.currentContext!,
|
||||
useRootNavigator: false,
|
||||
dismissWithEsc: dismissWithEsc,
|
||||
builder: builder
|
||||
);
|
||||
}finally {
|
||||
inDialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractDialog extends StatelessWidget {
|
||||
const AbstractDialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context);
|
||||
}
|
||||
|
||||
class GenericDialog extends AbstractDialog {
|
||||
final Widget header;
|
||||
final List<DialogButton> buttons;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const GenericDialog({Key? key, required this.header, required this.buttons, this.padding}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ContentDialog(
|
||||
style: ContentDialogThemeData(
|
||||
padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0)
|
||||
),
|
||||
content: header,
|
||||
actions: buttons
|
||||
);
|
||||
}
|
||||
|
||||
class FormDialog extends AbstractDialog {
|
||||
final Widget content;
|
||||
final List<DialogButton> buttons;
|
||||
|
||||
const FormDialog({Key? key, required this.content, required this.buttons}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final parsed = buttons.map((entry) => _createFormButton(entry, context)).toList();
|
||||
return GenericDialog(
|
||||
header: content,
|
||||
buttons: parsed
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
DialogButton _createFormButton(DialogButton entry, BuildContext context) {
|
||||
if (entry.type != ButtonType.primary) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
return DialogButton(
|
||||
text: entry.text,
|
||||
type: entry.type,
|
||||
onTap: () {
|
||||
if(!Form.of(context).validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.onTap?.call();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InfoDialog extends AbstractDialog {
|
||||
final String text;
|
||||
final List<DialogButton>? buttons;
|
||||
|
||||
const InfoDialog({required this.text, this.buttons, Key? key}) : super(key: key);
|
||||
|
||||
InfoDialog.ofOnly({required this.text, required DialogButton button, Key? key})
|
||||
: buttons = [button], super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GenericDialog(
|
||||
header: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(text, textAlign: TextAlign.center)
|
||||
),
|
||||
buttons: buttons ?? [_defaultCloseButton],
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0)
|
||||
);
|
||||
}
|
||||
|
||||
DialogButton get _defaultCloseButton =>DialogButton(
|
||||
text: translations.defaultDialogSecondaryAction,
|
||||
type: ButtonType.only
|
||||
);
|
||||
}
|
||||
|
||||
class ProgressDialog extends AbstractDialog {
|
||||
final String text;
|
||||
final Function()? onStop;
|
||||
final bool showButton;
|
||||
|
||||
const ProgressDialog({required this.text, this.onStop, this.showButton = true, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GenericDialog(
|
||||
header: InfoLabel(
|
||||
label: text,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
width: double.infinity,
|
||||
child: const ProgressBar()
|
||||
),
|
||||
),
|
||||
buttons: [
|
||||
if(showButton)
|
||||
DialogButton(
|
||||
text: translations.defaultDialogSecondaryAction,
|
||||
type: ButtonType.only,
|
||||
onTap: onStop
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FutureBuilderDialog extends AbstractDialog {
|
||||
final Future future;
|
||||
final String loadingMessage;
|
||||
final Widget successfulBody;
|
||||
final Widget unsuccessfulBody;
|
||||
final Function(Object) errorMessageBuilder;
|
||||
final Function()? onError;
|
||||
final bool closeAutomatically;
|
||||
|
||||
const FutureBuilderDialog(
|
||||
{Key? key,
|
||||
required this.future,
|
||||
required this.loadingMessage,
|
||||
required this.successfulBody,
|
||||
required this.unsuccessfulBody,
|
||||
required this.errorMessageBuilder,
|
||||
this.onError,
|
||||
this.closeAutomatically = false}) : super(key: key);
|
||||
|
||||
static Container ofMessage(String message) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, snapshot) => GenericDialog(
|
||||
header: _createBody(context, snapshot),
|
||||
buttons: [_createButton(context, snapshot)]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _createBody(BuildContext context, AsyncSnapshot snapshot){
|
||||
if (snapshot.hasError) {
|
||||
onError?.call();
|
||||
return ofMessage(errorMessageBuilder(snapshot.error!));
|
||||
}
|
||||
|
||||
if(snapshot.connectionState == ConnectionState.done && (snapshot.data == null || (snapshot.data is bool && !snapshot.data))){
|
||||
return unsuccessfulBody;
|
||||
}
|
||||
|
||||
if (!snapshot.hasData) {
|
||||
return _createLoadingBody();
|
||||
}
|
||||
|
||||
if(closeAutomatically){
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop(true));
|
||||
return _createLoadingBody();
|
||||
}
|
||||
|
||||
return successfulBody;
|
||||
}
|
||||
|
||||
InfoLabel _createLoadingBody() {
|
||||
return InfoLabel(
|
||||
label: loadingMessage,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
width: double.infinity,
|
||||
child: const ProgressBar()),
|
||||
);
|
||||
}
|
||||
|
||||
DialogButton _createButton(BuildContext context, AsyncSnapshot snapshot){
|
||||
return DialogButton(
|
||||
text: snapshot.hasData
|
||||
|| snapshot.hasError
|
||||
|| (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? translations.defaultDialogSecondaryAction : translations.stopLoadingDialogAction,
|
||||
type: ButtonType.only,
|
||||
onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorDialog extends AbstractDialog {
|
||||
final Object exception;
|
||||
final StackTrace? stackTrace;
|
||||
final Function(Object) errorMessageBuilder;
|
||||
|
||||
const ErrorDialog({Key? key, required this.exception, required this.errorMessageBuilder, this.stackTrace}) : super(key: key);
|
||||
|
||||
static DialogButton createCopyErrorButton({required Object error, required StackTrace? stackTrace, required Function() onClick, ButtonType type = ButtonType.primary}) => DialogButton(
|
||||
text: translations.copyErrorDialogTitle,
|
||||
type: type,
|
||||
onTap: () async {
|
||||
FlutterClipboard.controlC("$error\n$stackTrace");
|
||||
showRebootInfoBar(translations.copyErrorDialogSuccess);
|
||||
onClick();
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InfoDialog(
|
||||
text: errorMessageBuilder(exception),
|
||||
buttons: [
|
||||
DialogButton(
|
||||
type: stackTrace == null ? ButtonType.only : ButtonType.secondary
|
||||
),
|
||||
|
||||
if(stackTrace != null)
|
||||
createCopyErrorButton(
|
||||
error: exception,
|
||||
stackTrace: stackTrace,
|
||||
onClick: () => Navigator.pop(context)
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DialogButton extends StatefulWidget {
|
||||
final String? text;
|
||||
final Function()? onTap;
|
||||
final ButtonType type;
|
||||
final Color? color;
|
||||
|
||||
const DialogButton(
|
||||
{Key? key,
|
||||
this.text,
|
||||
this.onTap,
|
||||
this.color,
|
||||
required this.type})
|
||||
: assert(type != ButtonType.primary || onTap != null,
|
||||
"OnTap handler cannot be null for primary buttons"),
|
||||
assert(type != ButtonType.primary || text != null,
|
||||
"Text cannot be null for primary buttons"),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<DialogButton> createState() => _DialogButtonState();
|
||||
}
|
||||
|
||||
class _DialogButtonState extends State<DialogButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.type == ButtonType.only ? _onlyButton : _button;
|
||||
|
||||
SizedBox get _onlyButton => SizedBox(
|
||||
width: double.infinity,
|
||||
child: _button
|
||||
);
|
||||
|
||||
Widget get _button => widget.type == ButtonType.primary ? _primaryButton : _secondaryButton;
|
||||
|
||||
Widget get _primaryButton => Button(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor)
|
||||
),
|
||||
onPressed: widget.onTap!,
|
||||
child: Text(widget.text!),
|
||||
);
|
||||
|
||||
Widget get _secondaryButton => Button(
|
||||
style: widget.color != null ? ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(widget.color!)
|
||||
) : null,
|
||||
onPressed: widget.onTap ?? _onDefaultSecondaryActionTap,
|
||||
child: Text(widget.text ?? translations.defaultDialogSecondaryAction),
|
||||
);
|
||||
|
||||
void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null);
|
||||
}
|
||||
|
||||
enum ButtonType {
|
||||
primary,
|
||||
secondary,
|
||||
only
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
|
||||
const infoBarLongDuration = Duration(seconds: 4);
|
||||
const infoBarShortDuration = Duration(seconds: 2);
|
||||
const _height = 64.0;
|
||||
|
||||
InfoBarEntry showRebootInfoBar(String text, {
|
||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
bool loading = false,
|
||||
Duration? duration = infoBarShortDuration,
|
||||
void Function()? onDismissed,
|
||||
Widget? action
|
||||
}) {
|
||||
final overlay = _buildOverlay(text, action, loading, severity);
|
||||
final overlayEntry = InfoBarEntry(overlay: overlay, onDismissed: onDismissed);
|
||||
if(duration != null) {
|
||||
Future.delayed(duration)
|
||||
.then((_) => WidgetsBinding.instance.addPostFrameCallback((timeStamp) => overlayEntry.close()));
|
||||
}
|
||||
return overlayEntry;
|
||||
}
|
||||
|
||||
Widget _buildOverlay(text, Widget? action, bool loading, InfoBarSeverity severity) => ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: _height
|
||||
),
|
||||
child: Mica(
|
||||
elevation: 1,
|
||||
child: InfoBar(
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: text is Widget ? text : Text(text)
|
||||
),
|
||||
if(action != null)
|
||||
action
|
||||
],
|
||||
),
|
||||
isLong: false,
|
||||
isIconVisible: true,
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: loading ? const Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
bottom: 2.0,
|
||||
right: 6.0
|
||||
),
|
||||
child: ProgressBar(),
|
||||
) : const SizedBox()
|
||||
),
|
||||
severity: severity
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
class InfoBarEntry {
|
||||
final Widget overlay;
|
||||
final void Function()? onDismissed;
|
||||
|
||||
InfoBarEntry({required this.overlay, required this.onDismissed}) {
|
||||
final context = pageKey.currentContext;
|
||||
if(context != null) {
|
||||
infoBarAreaKey.currentState?.insertChild(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
bool close() {
|
||||
final result = infoBarAreaKey.currentState?.removeChild(overlay) ?? false;
|
||||
if(result) {
|
||||
onDismissed?.call();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
|
||||
class InfoBarArea extends StatefulWidget {
|
||||
const InfoBarArea({super.key});
|
||||
|
||||
@override
|
||||
State<InfoBarArea> createState() => InfoBarAreaState();
|
||||
}
|
||||
|
||||
class InfoBarAreaState extends State<InfoBarArea> {
|
||||
final Rx<List<Widget>> _children = Rx([]);
|
||||
|
||||
void insertChild(Widget child) {
|
||||
_children.value.add(child);
|
||||
_children.refresh();
|
||||
}
|
||||
|
||||
bool removeChild(Widget child) {
|
||||
final result = _children.value.remove(child);
|
||||
_children.refresh();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => StreamBuilder(
|
||||
stream: pagesController.stream,
|
||||
builder: (context, _) => Obx(() => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: hasPageButton ? 72.0 : 16.0
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _children.value.map((child) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12.0
|
||||
),
|
||||
child: child
|
||||
)).toList(growable: false)
|
||||
),
|
||||
))
|
||||
);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons;
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/button/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
|
||||
const double _kButtonDimensions = 30;
|
||||
const double _kButtonSpacing = 8;
|
||||
|
||||
SettingTile createFileSetting({
|
||||
required GlobalKey<TextFormBoxState> key,
|
||||
required String title,
|
||||
required String description,
|
||||
required TextEditingController controller,
|
||||
required void Function() onReset
|
||||
}) {
|
||||
final obx = RxnString();
|
||||
final selecting = RxBool(false);
|
||||
return SettingTile(
|
||||
icon: Icon(
|
||||
FluentIcons.document_24_regular
|
||||
),
|
||||
title: Text(title),
|
||||
subtitle: Text(description),
|
||||
contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions,
|
||||
content: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FileSelector(
|
||||
placeholder: translations.selectPathPlaceholder,
|
||||
windowTitle: translations.selectPathWindowTitle,
|
||||
controller: controller,
|
||||
validator: (text) {
|
||||
final result = _checkDll(text);
|
||||
obx.value = result;
|
||||
return result;
|
||||
},
|
||||
extension: "dll",
|
||||
folder: false,
|
||||
validatorMode: AutovalidateMode.always,
|
||||
allowNavigator: false,
|
||||
validatorKey: key
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _kButtonSpacing),
|
||||
Obx(() => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: obx.value == null ? 0.0 : 20.0
|
||||
),
|
||||
child: Tooltip(
|
||||
message: translations.selectFile,
|
||||
child: Button(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero)
|
||||
),
|
||||
onPressed: () => _onPressed(selecting, controller),
|
||||
child: SizedBox.square(
|
||||
dimension: _kButtonDimensions,
|
||||
child: Icon(
|
||||
fluentIcons.FluentIcons.open_folder_horizontal
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
)),
|
||||
const SizedBox(width: _kButtonSpacing),
|
||||
Obx(() => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: obx.value == null ? 0.0 : 20.0
|
||||
),
|
||||
child: Tooltip(
|
||||
message: translations.reset,
|
||||
child: Button(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero)
|
||||
),
|
||||
onPressed: onReset,
|
||||
child: SizedBox.square(
|
||||
dimension: _kButtonDimensions,
|
||||
child: Icon(
|
||||
FluentIcons.arrow_reset_24_regular
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void _onPressed(RxBool selecting, TextEditingController controller) {
|
||||
if(selecting.value){
|
||||
return;
|
||||
}
|
||||
|
||||
selecting.value = true;
|
||||
compute(openFilePicker, "dll")
|
||||
.then((value) => _updateText(controller, value))
|
||||
.then((_) => selecting.value = false);
|
||||
}
|
||||
|
||||
void _updateText(TextEditingController controller, String? value) {
|
||||
final text = value ?? controller.text;
|
||||
controller.text = text;
|
||||
controller.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
|
||||
String? _checkDll(String? text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return translations.invalidDllPath;
|
||||
}
|
||||
|
||||
final file = File(text);
|
||||
try {
|
||||
file.readAsBytesSync();
|
||||
}catch(_) {
|
||||
return translations.dllDoesNotExist;
|
||||
}
|
||||
|
||||
if (!text.endsWith(".dll")) {
|
||||
return translations.invalidDllExtension;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
|
||||
class InfoTile extends StatelessWidget {
|
||||
final Key? expanderKey;
|
||||
final Text title;
|
||||
final Text content;
|
||||
|
||||
const InfoTile({
|
||||
this.expanderKey,
|
||||
required this.title,
|
||||
required this.content
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 4.0
|
||||
),
|
||||
child: Expander(
|
||||
key: expanderKey,
|
||||
header: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
FluentIcons.info_24_regular
|
||||
),
|
||||
const SizedBox(width: 16.0),
|
||||
title
|
||||
],
|
||||
),
|
||||
content: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -53,7 +53,9 @@ String _sanitize(String value) {
|
||||
}
|
||||
|
||||
List<String> _getArguments(List<String>? arguments) {
|
||||
if (arguments == null) return ['%s'];
|
||||
if (arguments == null) {
|
||||
return ['%s'];
|
||||
}
|
||||
|
||||
if (arguments.isEmpty && !arguments.any((e) => e.contains('%s'))) {
|
||||
throw ArgumentError('arguments must contain at least 1 instance of "%s"');
|
||||
|
||||
20
gui/lib/src/widget/dialog/dialog.dart
Normal file
20
gui/lib/src/widget/dialog/dialog.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/sections.dart';
|
||||
|
||||
abstract class Dialog extends StatelessWidget {
|
||||
static Future<T?> open<T extends Object?>({
|
||||
required WidgetBuilder builder,
|
||||
bool dismissWithEsc = true
|
||||
}) => fluent.showDialog(
|
||||
context: appNavigatorKey.currentContext!,
|
||||
useRootNavigator: false,
|
||||
dismissWithEsc: dismissWithEsc,
|
||||
builder: builder
|
||||
);
|
||||
|
||||
const Dialog({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context);
|
||||
}
|
||||
58
gui/lib/src/widget/dialog/dialog_button.dart
Normal file
58
gui/lib/src/widget/dialog/dialog_button.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
class DialogButton extends StatefulWidget {
|
||||
final String? text;
|
||||
final Function()? onTap;
|
||||
final DialogButtonType type;
|
||||
final Color? color;
|
||||
|
||||
const DialogButton({
|
||||
Key? key,
|
||||
this.text,
|
||||
this.onTap,
|
||||
this.color,
|
||||
required this.type
|
||||
}): assert(type != DialogButtonType.primary || onTap != null, "OnTap handler cannot be null for primary buttons"),
|
||||
assert(type != DialogButtonType.primary || text != null, "Text cannot be null for primary buttons"),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<DialogButton> createState() => _DialogButtonState();
|
||||
}
|
||||
|
||||
class _DialogButtonState extends State<DialogButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.type == DialogButtonType.only ? _onlyButton : _button;
|
||||
|
||||
SizedBox get _onlyButton => SizedBox(
|
||||
width: double.infinity,
|
||||
child: _button
|
||||
);
|
||||
|
||||
Widget get _button => widget.type == DialogButtonType.primary ? _primaryButton : _secondaryButton;
|
||||
|
||||
Widget get _primaryButton => Button(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(FluentTheme.of(context).accentColor)
|
||||
),
|
||||
onPressed: widget.onTap!,
|
||||
child: Text(widget.text!),
|
||||
);
|
||||
|
||||
Widget get _secondaryButton => Button(
|
||||
style: widget.color != null ? ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(widget.color!)
|
||||
) : null,
|
||||
onPressed: widget.onTap ?? _onDefaultSecondaryActionTap,
|
||||
child: Text(widget.text ?? translations.defaultDialogSecondaryAction),
|
||||
);
|
||||
|
||||
void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null);
|
||||
}
|
||||
|
||||
enum DialogButtonType {
|
||||
primary,
|
||||
secondary,
|
||||
only
|
||||
}
|
||||
39
gui/lib/src/widget/dialog/error_dialog.dart
Normal file
39
gui/lib/src/widget/dialog/error_dialog.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
import 'info_dialog.dart';
|
||||
|
||||
class ErrorDialog extends Dialog {
|
||||
final Object exception;
|
||||
final StackTrace? stackTrace;
|
||||
final Function(Object) errorMessageBuilder;
|
||||
|
||||
const ErrorDialog({Key? key, required this.exception, required this.errorMessageBuilder, this.stackTrace}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InfoDialog(
|
||||
text: errorMessageBuilder(exception),
|
||||
buttons: [
|
||||
DialogButton(
|
||||
type: stackTrace == null ? DialogButtonType.only : DialogButtonType.secondary
|
||||
),
|
||||
|
||||
if(stackTrace != null)
|
||||
DialogButton(
|
||||
text: translations.copyErrorDialogTitle,
|
||||
type: DialogButtonType.primary,
|
||||
onTap: () async {
|
||||
FlutterClipboard.controlC("$exception\n$stackTrace");
|
||||
SnackBar.open(translations.copyErrorDialogSuccess);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
40
gui/lib/src/widget/dialog/form_dialog.dart
Normal file
40
gui/lib/src/widget/dialog/form_dialog.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
import 'generic_dialog.dart';
|
||||
|
||||
class FormDialog extends Dialog {
|
||||
final Widget content;
|
||||
final List<DialogButton> buttons;
|
||||
|
||||
const FormDialog({Key? key, required this.content, required this.buttons}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Form(
|
||||
child: Builder(
|
||||
builder: (context) => GenericDialog(
|
||||
header: content,
|
||||
buttons: buttons.map((entry) => _createFormButton(entry, context)).toList()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
DialogButton _createFormButton(DialogButton entry, BuildContext context) {
|
||||
if (entry.type != DialogButtonType.primary) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
return DialogButton(
|
||||
text: entry.text,
|
||||
type: entry.type,
|
||||
onTap: () {
|
||||
if(!Form.of(context).validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
entry.onTap?.call();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
88
gui/lib/src/widget/dialog/future_dialog.dart
Normal file
88
gui/lib/src/widget/dialog/future_dialog.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
import 'generic_dialog.dart';
|
||||
|
||||
class FutureDialog extends Dialog {
|
||||
final Future future;
|
||||
final String loadingMessage;
|
||||
final Widget successfulBody;
|
||||
final Widget unsuccessfulBody;
|
||||
final Function(Object) errorMessageBuilder;
|
||||
final Function()? onError;
|
||||
final bool closeAutomatically;
|
||||
|
||||
const FutureDialog(
|
||||
{Key? key,
|
||||
required this.future,
|
||||
required this.loadingMessage,
|
||||
required this.successfulBody,
|
||||
required this.unsuccessfulBody,
|
||||
required this.errorMessageBuilder,
|
||||
this.onError,
|
||||
this.closeAutomatically = false}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, snapshot) => GenericDialog(
|
||||
header: _createBody(context, snapshot),
|
||||
buttons: [_createButton(context, snapshot)]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _createBody(BuildContext context, AsyncSnapshot snapshot){
|
||||
if (snapshot.hasError) {
|
||||
onError?.call();
|
||||
return _buildData(errorMessageBuilder(snapshot.error!));
|
||||
}
|
||||
|
||||
if(snapshot.connectionState == ConnectionState.done
|
||||
&& (snapshot.data == null || (snapshot.data is bool && !snapshot.data))){
|
||||
return unsuccessfulBody;
|
||||
}
|
||||
|
||||
if (!snapshot.hasData) {
|
||||
return _loadingBody;
|
||||
}
|
||||
|
||||
if(closeAutomatically){
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => Navigator.of(context).pop(true));
|
||||
return _loadingBody;
|
||||
}
|
||||
|
||||
return successfulBody;
|
||||
}
|
||||
|
||||
Widget _buildData(String message) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _loadingBody => InfoLabel(
|
||||
label: loadingMessage,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
width: double.infinity,
|
||||
child: const ProgressBar()),
|
||||
);
|
||||
|
||||
DialogButton _createButton(BuildContext context, AsyncSnapshot snapshot)=> DialogButton(
|
||||
text: snapshot.hasData
|
||||
|| snapshot.hasError
|
||||
|| (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? translations.defaultDialogSecondaryAction : translations.stopLoadingDialogAction,
|
||||
type: DialogButtonType.only,
|
||||
onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData)
|
||||
);
|
||||
}
|
||||
21
gui/lib/src/widget/dialog/generic_dialog.dart
Normal file
21
gui/lib/src/widget/dialog/generic_dialog.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
|
||||
class GenericDialog extends Dialog {
|
||||
final Widget header;
|
||||
final List<DialogButton> buttons;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
const GenericDialog({Key? key, required this.header, required this.buttons, this.padding}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ContentDialog(
|
||||
style: ContentDialogThemeData(
|
||||
padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0)
|
||||
),
|
||||
content: header,
|
||||
actions: buttons
|
||||
);
|
||||
}
|
||||
33
gui/lib/src/widget/dialog/info_dialog.dart
Normal file
33
gui/lib/src/widget/dialog/info_dialog.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
import 'generic_dialog.dart';
|
||||
|
||||
class InfoDialog extends Dialog {
|
||||
final String text;
|
||||
final List<DialogButton>? buttons;
|
||||
|
||||
const InfoDialog({required this.text, this.buttons, Key? key}) : super(key: key);
|
||||
|
||||
InfoDialog.ofOnly({required this.text, required DialogButton button, Key? key})
|
||||
: buttons = [button], super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GenericDialog(
|
||||
header: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Text(text, textAlign: TextAlign.center)
|
||||
),
|
||||
buttons: buttons ?? [_defaultCloseButton],
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0)
|
||||
);
|
||||
}
|
||||
|
||||
DialogButton get _defaultCloseButton =>DialogButton(
|
||||
text: translations.defaultDialogSecondaryAction,
|
||||
type: DialogButtonType.only
|
||||
);
|
||||
}
|
||||
36
gui/lib/src/widget/dialog/progress_dialog.dart
Normal file
36
gui/lib/src/widget/dialog/progress_dialog.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'dialog.dart';
|
||||
import 'dialog_button.dart';
|
||||
import 'generic_dialog.dart';
|
||||
|
||||
class ProgressDialog extends Dialog {
|
||||
final String text;
|
||||
final Function()? onStop;
|
||||
final bool showButton;
|
||||
|
||||
const ProgressDialog({required this.text, this.onStop, this.showButton = true, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GenericDialog(
|
||||
header: InfoLabel(
|
||||
label: text,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
width: double.infinity,
|
||||
child: const ProgressBar()
|
||||
),
|
||||
),
|
||||
buttons: [
|
||||
if(showButton)
|
||||
DialogButton(
|
||||
text: translations.defaultDialogSecondaryAction,
|
||||
type: DialogButtonType.only,
|
||||
onTap: onStop
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ class FileSelector extends StatefulWidget {
|
||||
final bool folder;
|
||||
final void Function(String)? onSelected;
|
||||
|
||||
const FileSelector(
|
||||
{required this.placeholder,
|
||||
const FileSelector({
|
||||
required this.placeholder,
|
||||
required this.windowTitle,
|
||||
required this.controller,
|
||||
required this.folder,
|
||||
@@ -29,8 +29,7 @@ class FileSelector extends StatefulWidget {
|
||||
this.extension,
|
||||
this.validatorMode,
|
||||
this.onSelected,
|
||||
Key? key})
|
||||
: assert(folder || extension != null, "Missing extension for file selector"),
|
||||
Key? key}) : assert(folder || extension != null, "Missing extension for file selector"),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/message/onboard.dart';
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
InfoBarEntry? onBackendResult(AuthBackendType type, AuthBackendResult event) {
|
||||
SnackBar? onBackendResult(AuthBackendType type, AuthBackendEvent event) {
|
||||
switch (event.type) {
|
||||
case AuthBackendResultType.starting:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.starting:
|
||||
return SnackBar.open(
|
||||
translations.startingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case AuthBackendResultType.startSuccess:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startSuccess:
|
||||
return SnackBar.open(
|
||||
type == AuthBackendType.local
|
||||
? translations.checkedServer
|
||||
: translations.startedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
case AuthBackendResultType.startError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startError:
|
||||
return SnackBar.open(
|
||||
type == AuthBackendType.local
|
||||
? translations.localServerError(event.error ?? translations.unknownError)
|
||||
: translations.startServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case AuthBackendResultType.stopping:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.stopping:
|
||||
return SnackBar.open(
|
||||
translations.stoppingServer,
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case AuthBackendResultType.stopSuccess:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.stopSuccess:
|
||||
return SnackBar.open(
|
||||
translations.stoppedServer,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
case AuthBackendResultType.stopError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.stopError:
|
||||
return SnackBar.open(
|
||||
translations.stopServerError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case AuthBackendResultType.startMissingHostError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startMissingHostError:
|
||||
return SnackBar.open(
|
||||
translations.missingHostNameError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case AuthBackendResultType.startMissingPortError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startMissingPortError:
|
||||
return SnackBar.open(
|
||||
translations.missingPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case AuthBackendResultType.startIllegalPortError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startIllegalPortError:
|
||||
return SnackBar.open(
|
||||
translations.illegalPortError,
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case AuthBackendResultType.startFreeingPort:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startFreeingPort:
|
||||
return SnackBar.open(
|
||||
translations.freeingPort,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case AuthBackendResultType.startFreePortSuccess:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startFreePortSuccess:
|
||||
return SnackBar.open(
|
||||
translations.freedPort,
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarShortDuration
|
||||
);
|
||||
case AuthBackendResultType.startFreePortError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startFreePortError:
|
||||
return SnackBar.open(
|
||||
translations.freePortError(event.error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
case AuthBackendResultType.startPingingRemote:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startPingingRemote:
|
||||
return SnackBar.open(
|
||||
translations.pingingServer(AuthBackendType.remote.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case AuthBackendResultType.startPingingLocal:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startPingingLocal:
|
||||
return SnackBar.open(
|
||||
translations.pingingServer(type.name),
|
||||
severity: InfoBarSeverity.info,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
case AuthBackendResultType.startPingError:
|
||||
return showRebootInfoBar(
|
||||
case AuthBackendEventType.startPingError:
|
||||
return SnackBar.open(
|
||||
translations.pingError(type.name),
|
||||
severity: InfoBarSeverity.error
|
||||
);
|
||||
case AuthBackendResultType.startedImplementation:
|
||||
case AuthBackendEventType.startedImplementation:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void onBackendError(Object error) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.backendErrorMessage,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -5,22 +5,22 @@ import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/keyboard.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/message/data.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/button/backend_start_button.dart';
|
||||
import 'package:reboot_launcher/src/button/server_type_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/state_toggle.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/type_selector.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> backendTypeOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> backendGameServerAddressOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> backendUnrealEngineOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> backendDetachedOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> backendTypeOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> backendGameServerAddressOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> backendUnrealEngineOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> backendDetachedOverlayTargetKey = GlobalKey();
|
||||
|
||||
class BackendPage extends AbstractPage {
|
||||
const BackendPage({Key? key}) : super(key: key);
|
||||
@@ -44,12 +44,12 @@ class BackendPage extends AbstractPage {
|
||||
class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
final BackendController _backendController = Get.find<BackendController>();
|
||||
|
||||
InfoBarEntry? _infoBarEntry;
|
||||
SnackBar? _SnackBar;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
ServicesBinding.instance.keyboard.addHandler((keyEvent) {
|
||||
if(_infoBarEntry == null) {
|
||||
if(_SnackBar == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
_backendController.consoleKey.value = keyEvent.physicalKey;
|
||||
}
|
||||
|
||||
_infoBarEntry?.close();
|
||||
_infoBarEntry = null;
|
||||
_SnackBar?.close();
|
||||
_SnackBar = null;
|
||||
return true;
|
||||
});
|
||||
super.initState();
|
||||
@@ -87,7 +87,7 @@ class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
),
|
||||
title: Text(translations.matchmakerConfigurationAddressName),
|
||||
subtitle: Text(translations.matchmakerConfigurationAddressDescription),
|
||||
content: OverlayTarget(
|
||||
content: TutorialOverlayTarget(
|
||||
key: backendGameServerAddressOverlayTargetKey,
|
||||
child: TextFormBox(
|
||||
placeholder: translations.matchmakerConfigurationAddressName,
|
||||
@@ -158,7 +158,7 @@ class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
const SizedBox(
|
||||
width: 16.0
|
||||
),
|
||||
OverlayTarget(
|
||||
TutorialOverlayTarget(
|
||||
key: backendDetachedOverlayTargetKey,
|
||||
child: ToggleSwitch(
|
||||
checked: _backendController.detached(),
|
||||
@@ -182,11 +182,11 @@ class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
title: Text(translations.settingsClientConsoleKeyName),
|
||||
subtitle: Text(translations.settingsClientConsoleKeyDescription),
|
||||
contentWidth: null,
|
||||
content: OverlayTarget(
|
||||
content: TutorialOverlayTarget(
|
||||
key: backendUnrealEngineOverlayTargetKey,
|
||||
child: Button(
|
||||
onPressed: () {
|
||||
_infoBarEntry = showRebootInfoBar(
|
||||
_SnackBar = SnackBar.open(
|
||||
translations.clickKey,
|
||||
loading: true,
|
||||
duration: null
|
||||
@@ -234,7 +234,7 @@ class _BackendPageState extends AbstractPageState<BackendPage> {
|
||||
),
|
||||
title: Text(translations.backendTypeName),
|
||||
subtitle: Text(translations.backendTypeDescription),
|
||||
content: ServerTypeSelector(
|
||||
content: BackendTypeSelector(
|
||||
overlayKey: backendTypeOverlayTargetKey
|
||||
)
|
||||
);
|
||||
@@ -6,11 +6,9 @@ import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_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/message/backend.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/message.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../messenger/info_bar.dart';
|
||||
|
||||
class BackendButton extends StatefulWidget {
|
||||
const BackendButton({Key? key}) : super(key: key);
|
||||
@@ -50,18 +48,26 @@ class _BackendButtonState extends State<BackendButton> {
|
||||
alignment: Alignment.center,
|
||||
child: StreamBuilder(
|
||||
stream: _textController.stream,
|
||||
builder: (context, snapshot) => Obx(() => Text(_buttonText))
|
||||
builder: (context, snapshot) => Obx(() => Text(_text))
|
||||
),
|
||||
),
|
||||
onPressed: () => _backendController.toggle(
|
||||
eventHandler: (type, event) {
|
||||
eventHandler: _onEvent,
|
||||
errorHandler: _onError
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
SnackBar? _onEvent(AuthBackendType type, AuthBackendEvent event) {
|
||||
_backendController.started.value = event.type.isStart && !event.type.isError;
|
||||
if(event.type == AuthBackendResultType.startedImplementation) {
|
||||
if(event.type == AuthBackendEventType.startedImplementation) {
|
||||
_backendController.implementation = event.implementation;
|
||||
}
|
||||
return onBackendResult(type, event);
|
||||
},
|
||||
errorHandler: (error) {
|
||||
}
|
||||
|
||||
void _onError(String error) {
|
||||
if(_backendController.started.value) {
|
||||
_backendController.stop();
|
||||
_gameController.instance.value?.kill();
|
||||
@@ -69,21 +75,15 @@ class _BackendButtonState extends State<BackendButton> {
|
||||
onBackendError(error);
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
String get _buttonText {
|
||||
if(_backendController.type.value == AuthBackendType.local && _backendController.port.text.trim() == kDefaultBackendPort.toString()){
|
||||
String get _text {
|
||||
if(_backendController.type.value == AuthBackendType.local
|
||||
&& _backendController.port.text.trim() == kDefaultBackendPort.toString()){
|
||||
return translations.checkServer;
|
||||
}
|
||||
|
||||
if(_backendController.started.value){
|
||||
}else if(_backendController.started.value){
|
||||
return translations.stopServer;
|
||||
}
|
||||
|
||||
}else {
|
||||
return translations.startServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,35 +3,34 @@ import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/backend_controller.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
|
||||
class ServerTypeSelector extends StatefulWidget {
|
||||
class BackendTypeSelector extends StatefulWidget {
|
||||
final Key overlayKey;
|
||||
const ServerTypeSelector({required this.overlayKey});
|
||||
const BackendTypeSelector({required this.overlayKey});
|
||||
|
||||
@override
|
||||
State<ServerTypeSelector> createState() => _ServerTypeSelectorState();
|
||||
State<BackendTypeSelector> createState() => _BackendTypeSelectorState();
|
||||
}
|
||||
|
||||
class _ServerTypeSelectorState extends State<ServerTypeSelector> {
|
||||
class _BackendTypeSelectorState extends State<BackendTypeSelector> {
|
||||
late final BackendController _backendController = Get.find<BackendController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() => OverlayTarget(
|
||||
return Obx(() => TutorialOverlayTarget(
|
||||
key: widget.overlayKey,
|
||||
child: DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(_backendController.type.value.label),
|
||||
items: AuthBackendType.values
|
||||
.map((type) => _createItem(type))
|
||||
.toList()
|
||||
items: _items
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<MenuFlyoutItem> get _items => AuthBackendType.values
|
||||
.map((type) => _createItem(type))
|
||||
.toList();
|
||||
|
||||
MenuFlyoutItem _createItem(AuthBackendType type) => MenuFlyoutItem(
|
||||
text: Text(type.label),
|
||||
onPressed: () async {
|
||||
@@ -42,9 +41,9 @@ class _ServerTypeSelectorState extends State<ServerTypeSelector> {
|
||||
}
|
||||
|
||||
extension _ServerTypeExtension on AuthBackendType {
|
||||
String get label {
|
||||
return this == AuthBackendType.embedded ? translations.embedded
|
||||
: this == AuthBackendType.remote ? translations.remote
|
||||
: translations.local;
|
||||
}
|
||||
String get label => switch(this) {
|
||||
AuthBackendType.embedded => translations.embedded,
|
||||
AuthBackendType.remote => translations.remote,
|
||||
AuthBackendType.local => translations.local
|
||||
};
|
||||
}
|
||||
13
gui/lib/src/widget/page/browser/filter.dart
Normal file
13
gui/lib/src/widget/page/browser/filter.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
enum ServerBrowserFilter {
|
||||
all,
|
||||
accessible,
|
||||
playable;
|
||||
|
||||
String get translatedName => switch(this) {
|
||||
ServerBrowserFilter.all => translations.all,
|
||||
ServerBrowserFilter.accessible => translations.accessible,
|
||||
ServerBrowserFilter.playable => translations.playable
|
||||
};
|
||||
}
|
||||
15
gui/lib/src/widget/page/browser/order.dart
Normal file
15
gui/lib/src/widget/page/browser/order.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
|
||||
enum ServerBrowserOrder {
|
||||
timeAscending,
|
||||
timeDescending,
|
||||
nameAscending,
|
||||
nameDescending;
|
||||
|
||||
String get translatedName => switch(this) {
|
||||
ServerBrowserOrder.timeAscending => translations.timeAscending,
|
||||
ServerBrowserOrder.timeDescending => translations.timeDescending,
|
||||
ServerBrowserOrder.nameAscending => translations.nameAscending,
|
||||
ServerBrowserOrder.nameDescending => translations.nameDescending
|
||||
};
|
||||
}
|
||||
@@ -11,16 +11,20 @@ import 'package:reboot_launcher/src/controller/backend_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/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/dialog_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/form_dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/browser/filter.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/browser/order.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/sections.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/cryptography.dart';
|
||||
import 'package:reboot_launcher/src/util/extensions.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
|
||||
class BrowsePage extends AbstractPage {
|
||||
const BrowsePage({Key? key}) : super(key: key);
|
||||
@@ -49,8 +53,8 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
final TextEditingController _filterController = TextEditingController();
|
||||
final StreamController<String> _filterControllerStream = StreamController.broadcast();
|
||||
|
||||
final Rx<_Filter> _filter = Rx(_Filter.all);
|
||||
final Rx<_Sort> _sort = Rx(_Sort.timeDescending);
|
||||
final Rx<ServerBrowserFilter> _filter = Rx(ServerBrowserFilter.all);
|
||||
final Rx<ServerBrowserOrder> _sort = Rx(ServerBrowserOrder.timeDescending);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -76,7 +80,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
if(server != null) {
|
||||
_joinServer(_hostingController.uuid, server);
|
||||
}else {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.noServerFound,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -124,11 +128,11 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_buildFilter(context),
|
||||
_buildFilterSection(context),
|
||||
const SizedBox(
|
||||
width: 16.0
|
||||
),
|
||||
_buildSort(context),
|
||||
_buildOrderSection(context),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
@@ -142,7 +146,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
}
|
||||
);
|
||||
|
||||
Widget _buildSort(BuildContext context) => Row(
|
||||
Widget _buildOrderSection(BuildContext context) => Row(
|
||||
children: [
|
||||
Icon(
|
||||
fluentIcons.FluentIcons.arrow_sort_24_regular,
|
||||
@@ -159,24 +163,27 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
Obx(() => SizedBox(
|
||||
width: 230,
|
||||
child: DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(
|
||||
_sort.value.translatedName,
|
||||
textAlign: TextAlign.start
|
||||
),
|
||||
title: const Spacer(),
|
||||
items: _Sort.values.map((entry) => MenuFlyoutItem(
|
||||
text: Text(entry.translatedName),
|
||||
onPressed: () => _sort.value = entry
|
||||
)).toList()
|
||||
items: _orders
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
|
||||
Row _buildFilter(BuildContext context) {
|
||||
return Row(
|
||||
List<MenuFlyoutItem> get _orders => ServerBrowserOrder.values
|
||||
.map(_buildOrder)
|
||||
.toList();
|
||||
|
||||
MenuFlyoutItem _buildOrder(ServerBrowserOrder entry) => MenuFlyoutItem(
|
||||
text: Text(entry.translatedName),
|
||||
onPressed: () => _sort.value = entry
|
||||
);
|
||||
|
||||
Widget _buildFilterSection(BuildContext context) => Row(
|
||||
children: [
|
||||
Icon(
|
||||
fluentIcons.FluentIcons.filter_24_regular,
|
||||
@@ -193,45 +200,48 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
Obx(() => SizedBox(
|
||||
width: 125,
|
||||
child: DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(
|
||||
_filter.value.translatedName,
|
||||
textAlign: TextAlign.start
|
||||
),
|
||||
title: const Spacer(),
|
||||
items: _Filter.values.map((entry) => MenuFlyoutItem(
|
||||
text: Text(entry.translatedName),
|
||||
onPressed: () => _filter.value = entry
|
||||
)).toList()
|
||||
items: _filters
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<MenuFlyoutItem> get _filters => ServerBrowserFilter.values
|
||||
.map((entry) => _buildFilter(entry))
|
||||
.toList();
|
||||
|
||||
MenuFlyoutItem _buildFilter(ServerBrowserFilter entry) => MenuFlyoutItem(
|
||||
text: Text(entry.translatedName),
|
||||
onPressed: () => _filter.value = entry
|
||||
);
|
||||
|
||||
Widget _buildPopulatedListBody(List<ServerBrowserEntry>? items) => Obx(() {
|
||||
final filter = _filter.value;
|
||||
final sorted = items?.where((element) {
|
||||
switch(filter) {
|
||||
case _Filter.all:
|
||||
case ServerBrowserFilter.all:
|
||||
return true;
|
||||
case _Filter.accessible:
|
||||
case ServerBrowserFilter.accessible:
|
||||
return element.password.isNotEmpty;
|
||||
case _Filter.playable:
|
||||
case ServerBrowserFilter.playable:
|
||||
return _gameController.getVersionByGame(element.version) != null;
|
||||
}
|
||||
}).toList();
|
||||
final sort = _sort.value;
|
||||
sorted?.sort((first, second) {
|
||||
switch(sort) {
|
||||
case _Sort.timeAscending:
|
||||
case ServerBrowserOrder.timeAscending:
|
||||
return first.timestamp.compareTo(second.timestamp);
|
||||
case _Sort.timeDescending:
|
||||
case ServerBrowserOrder.timeDescending:
|
||||
return second.timestamp.compareTo(first.timestamp);
|
||||
case _Sort.nameAscending:
|
||||
case ServerBrowserOrder.nameAscending:
|
||||
return first.name.compareTo(second.name);
|
||||
case _Sort.nameDescending:
|
||||
case ServerBrowserOrder.nameDescending:
|
||||
return second.name.compareTo(first.name);
|
||||
}
|
||||
});
|
||||
@@ -369,7 +379,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
|
||||
Future<void> _joinServer(String uuid, ServerBrowserEntry server) async {
|
||||
if(!kDebugMode && uuid == server.id) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.joinSelfServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -379,7 +389,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
|
||||
final version = _gameController.getVersionByGame(server.version.toString());
|
||||
if(version == null) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.cannotJoinServerVersion(server.version.toString()),
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -407,7 +417,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
}
|
||||
|
||||
if(!checkPassword(confirmPassword, hashedPassword)) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.wrongServerPassword,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -425,7 +435,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
}
|
||||
|
||||
Future<bool> _isServerValid(String name, String address) async {
|
||||
final loadingBar = showRebootInfoBar(
|
||||
final loadingBar = SnackBar.open(
|
||||
translations.joiningServer(name),
|
||||
duration: infoBarLongDuration,
|
||||
loading: true,
|
||||
@@ -438,7 +448,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
return true;
|
||||
}
|
||||
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.offlineServer,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.error
|
||||
@@ -450,7 +460,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
final confirmPasswordController = TextEditingController();
|
||||
final showPassword = RxBool(false);
|
||||
final showPasswordTrailing = RxBool(false);
|
||||
return await showRebootDialog<String?>(
|
||||
return await Dialog.open<String?>(
|
||||
builder: (context) => FormDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -486,12 +496,12 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
buttons: [
|
||||
DialogButton(
|
||||
text: translations.serverPasswordCancel,
|
||||
type: ButtonType.secondary
|
||||
type: DialogButtonType.secondary
|
||||
),
|
||||
|
||||
DialogButton(
|
||||
text: translations.serverPasswordConfirm,
|
||||
type: ButtonType.primary,
|
||||
type: DialogButtonType.primary,
|
||||
onTap: () => Navigator.of(context).pop(confirmPasswordController.text)
|
||||
)
|
||||
]
|
||||
@@ -507,7 +517,7 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
FlutterClipboard.controlC(decryptedIp);
|
||||
}
|
||||
Get.find<GameController>().selectedVersion.value = version;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => showRebootInfoBar(
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => SnackBar.open(
|
||||
embedded ? translations.joinedServer(author) : translations.copiedIp,
|
||||
duration: infoBarLongDuration,
|
||||
severity: InfoBarSeverity.success
|
||||
@@ -520,40 +530,3 @@ class _BrowsePageState extends AbstractPageState<BrowsePage> {
|
||||
@override
|
||||
List<Widget> get settings => [];
|
||||
}
|
||||
|
||||
enum _Filter {
|
||||
all,
|
||||
accessible,
|
||||
playable;
|
||||
|
||||
String get translatedName {
|
||||
switch(this) {
|
||||
case _Filter.all:
|
||||
return translations.all;
|
||||
case _Filter.accessible:
|
||||
return translations.accessible;
|
||||
case _Filter.playable:
|
||||
return translations.playable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _Sort {
|
||||
timeAscending,
|
||||
timeDescending,
|
||||
nameAscending,
|
||||
nameDescending;
|
||||
|
||||
String get translatedName {
|
||||
switch(this) {
|
||||
case _Sort.timeAscending:
|
||||
return translations.timeAscending;
|
||||
case _Sort.timeDescending:
|
||||
return translations.timeDescending;
|
||||
case _Sort.nameAscending:
|
||||
return translations.nameAscending;
|
||||
case _Sort.nameDescending:
|
||||
return translations.nameDescending;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,22 @@ import 'package:reboot_launcher/main.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_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/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/message/data.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/button/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/button/version_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/play/state_toggle.dart';
|
||||
import 'package:reboot_launcher/src/widget/version_selector.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> hostVersionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostInfoOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostInfoNameOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostInfoDescriptionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostInfoPasswordOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<OverlayTargetState> hostShareOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostVersionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostInfoOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostInfoNameOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostInfoDescriptionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostInfoPasswordOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> hostShareOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<SettingTileState> hostInfoTileKey = GlobalKey();
|
||||
|
||||
class HostPage extends AbstractPage {
|
||||
@@ -100,7 +100,7 @@ class _HostingPageState extends AbstractPageState<HostPage> {
|
||||
),
|
||||
title: Text(translations.hostGameServerNameName),
|
||||
subtitle: Text(translations.hostGameServerNameDescription),
|
||||
content: OverlayTarget(
|
||||
content: TutorialOverlayTarget(
|
||||
key: hostInfoNameOverlayTargetKey,
|
||||
child: TextFormBox(
|
||||
placeholder: translations.hostGameServerNameName,
|
||||
@@ -116,7 +116,7 @@ class _HostingPageState extends AbstractPageState<HostPage> {
|
||||
),
|
||||
title: Text(translations.hostGameServerDescriptionName),
|
||||
subtitle: Text(translations.hostGameServerDescriptionDescription),
|
||||
content: OverlayTarget(
|
||||
content: TutorialOverlayTarget(
|
||||
key: hostInfoDescriptionOverlayTargetKey,
|
||||
child: TextFormBox(
|
||||
placeholder: translations.hostGameServerDescriptionName,
|
||||
@@ -132,7 +132,7 @@ class _HostingPageState extends AbstractPageState<HostPage> {
|
||||
),
|
||||
title: Text(translations.hostGameServerPasswordName),
|
||||
subtitle: Text(translations.hostGameServerPasswordDescription),
|
||||
content: Obx(() => OverlayTarget(
|
||||
content: Obx(() => TutorialOverlayTarget(
|
||||
key: hostInfoPasswordOverlayTargetKey,
|
||||
child: TextFormBox(
|
||||
placeholder: translations.hostGameServerPasswordName,
|
||||
@@ -275,7 +275,7 @@ class _HostingPageState extends AbstractPageState<HostPage> {
|
||||
subtitle: Text(translations.hostShareIpDescription),
|
||||
content: Button(
|
||||
onPressed: () async {
|
||||
InfoBarEntry? entry;
|
||||
SnackBar? entry;
|
||||
try {
|
||||
entry = _showCopyingIp();
|
||||
final ip = await Ipify.ipv4();
|
||||
@@ -317,29 +317,29 @@ class _HostingPageState extends AbstractPageState<HostPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void _showCopiedLink() => showRebootInfoBar(
|
||||
void _showCopiedLink() => SnackBar.open(
|
||||
translations.hostShareLinkMessageSuccess,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
|
||||
InfoBarEntry _showCopyingIp() => showRebootInfoBar(
|
||||
SnackBar _showCopyingIp() => SnackBar.open(
|
||||
translations.hostShareIpMessageLoading,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
|
||||
void _showCopiedIp() => showRebootInfoBar(
|
||||
void _showCopiedIp() => SnackBar.open(
|
||||
translations.hostShareIpMessageSuccess,
|
||||
severity: InfoBarSeverity.success
|
||||
);
|
||||
|
||||
void _showCannotCopyIp(Object error) => showRebootInfoBar(
|
||||
void _showCannotCopyIp(Object error) => SnackBar.open(
|
||||
translations.hostShareIpMessageError(error.toString()),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
);
|
||||
|
||||
void _showCannotUpdateGameServer(Object error) => showRebootInfoBar(
|
||||
void _showCannotUpdateGameServer(Object error) => SnackBar.open(
|
||||
translations.cannotUpdateGameServer(error.toString()),
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarLongDuration
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/message/onboard.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class InfoPage extends AbstractPage {
|
||||
@@ -13,22 +13,21 @@ 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/pager/page_suggestion.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_suggestion.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/profile_tile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/widget/profile_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
import 'package:reboot_launcher/src/util/updater.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar_area.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar_area.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> profileOverlayKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> profileOverlayKey = GlobalKey();
|
||||
const double _kDefaultPadding = 12.0;
|
||||
|
||||
class RebootPager extends StatefulWidget {
|
||||
@@ -92,7 +91,7 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
}
|
||||
|
||||
_backendController.gameServerAddress.text = kDefaultGameServerHost;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => showRebootInfoBar(
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => SnackBar.open(
|
||||
translations.serverNoLongerAvailableUnnamed,
|
||||
severity: InfoBarSeverity.warning,
|
||||
duration: infoBarLongDuration
|
||||
@@ -106,8 +105,8 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
void _checkUpdates() {
|
||||
checkLauncherUpdate(
|
||||
onUpdate: (latestVersion) {
|
||||
late InfoBarEntry infoBar;
|
||||
infoBar = showRebootInfoBar(
|
||||
late SnackBar infoBar;
|
||||
infoBar = SnackBar.open(
|
||||
translations.updateAvailable(latestVersion.toString()),
|
||||
duration: null,
|
||||
severity: InfoBarSeverity.warning,
|
||||
@@ -312,7 +311,7 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
fit: StackFit.loose,
|
||||
children: [
|
||||
_buildBodyContent(),
|
||||
InfoBarArea(
|
||||
SnackBarArea(
|
||||
key: infoBarAreaKey
|
||||
)
|
||||
],
|
||||
@@ -414,10 +413,6 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
return TextSpan(
|
||||
text: name,
|
||||
recognizer: last ? null : (TapGestureRecognizer()..onTap = () {
|
||||
if(inDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pops = length - 1 - index;
|
||||
while(pops-- > 0) {
|
||||
Navigator.of(pageKey.currentContext!).pop();
|
||||
@@ -439,7 +434,7 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
children: [
|
||||
Obx(() {
|
||||
pageIndex.value;
|
||||
return ProfileWidget(
|
||||
return ProfileTile(
|
||||
overlayKey: profileOverlayKey
|
||||
);
|
||||
}),
|
||||
@@ -470,7 +465,7 @@ class _RebootPagerState extends State<RebootPager> with WindowListener, Automati
|
||||
|
||||
Widget _buildNavigationItem(AbstractPage page) {
|
||||
final index = page.type.index;
|
||||
return OverlayTarget(
|
||||
return TutorialOverlayTarget(
|
||||
key: getOverlayTargetKeyByPage(index),
|
||||
child: HoverButton(
|
||||
onPressed: () {
|
||||
@@ -3,16 +3,16 @@ import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/message/data.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/button/game_start_button.dart';
|
||||
import 'package:reboot_launcher/src/button/version_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/play/state_toggle.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/version_selector.dart';
|
||||
|
||||
final GlobalKey<OverlayTargetState> gameVersionOverlayTargetKey = GlobalKey();
|
||||
final GlobalKey<TutorialOverlayTargetState> gameVersionOverlayTargetKey = GlobalKey();
|
||||
|
||||
class PlayPage extends AbstractPage {
|
||||
const PlayPage({Key? key}) : super(key: key);
|
||||
@@ -14,11 +14,13 @@ 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/message/backend.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/dialog_button.dart';
|
||||
import 'package:reboot_launcher/src/widget/dialog/info_dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/message.dart';
|
||||
import 'package:reboot_launcher/src/util/matchmaker.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:version/version.dart';
|
||||
@@ -43,8 +45,8 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
final DllController _dllController = Get.find<DllController>();
|
||||
final ServerBrowserController _serverBrowserController = Get.find<ServerBrowserController>();
|
||||
|
||||
InfoBarEntry? _gameClientInfoBar;
|
||||
InfoBarEntry? _gameServerInfoBar;
|
||||
SnackBar? _gameClientInfoBar;
|
||||
SnackBar? _gameServerInfoBar;
|
||||
CancelableOperation? _operation;
|
||||
Completer? _pingOperation;
|
||||
|
||||
@@ -106,7 +108,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
final backendResult = _backendController.started() || await _backendController.toggle(
|
||||
eventHandler: (type, event) {
|
||||
_backendController.started.value = event.type.isStart && !event.type.isError;
|
||||
if(event.type == AuthBackendResultType.startedImplementation) {
|
||||
if(event.type == AuthBackendEventType.startedImplementation) {
|
||||
_backendController.implementation = event.implementation;
|
||||
}
|
||||
return onBackendResult(type, event);
|
||||
@@ -211,16 +213,16 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await showRebootDialog<bool>(
|
||||
final result = await Dialog.open<bool>(
|
||||
builder: (context) => InfoDialog(
|
||||
text: translations.automaticGameServerDialogContent,
|
||||
buttons: [
|
||||
DialogButton(
|
||||
type: ButtonType.secondary,
|
||||
type: DialogButtonType.secondary,
|
||||
text: translations.automaticGameServerDialogIgnore
|
||||
),
|
||||
DialogButton(
|
||||
type: ButtonType.primary,
|
||||
type: DialogButtonType.primary,
|
||||
text: translations.automaticGameServerDialogStart,
|
||||
onTap: () => Navigator.of(context).pop(true),
|
||||
),
|
||||
@@ -469,7 +471,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
void _onGameClientInjected() {
|
||||
_gameClientInfoBar?.close();
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.gameClientStarted,
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarLongDuration
|
||||
@@ -488,7 +490,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
final started = await _checkLocalGameServer(gameServerPort);
|
||||
if(!started) {
|
||||
if (_hostingController.instance.value?.killed != true) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.gameServerStartWarning,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration
|
||||
@@ -500,7 +502,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
final accessible = await _checkPublicGameServer(gameServerPort);
|
||||
if (!accessible) {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.gameServerStartLocalWarning,
|
||||
severity: InfoBarSeverity.warning,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -514,7 +516,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
final serverBrowserEntry = await _hostingController.createServerBrowserEntry();
|
||||
await _serverBrowserController.addServer(serverBrowserEntry);
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.gameServerStarted,
|
||||
severity: InfoBarSeverity.success,
|
||||
duration: infoBarLongDuration
|
||||
@@ -526,7 +528,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
Future<bool> _checkLocalGameServer(String gameServerPort) async {
|
||||
try {
|
||||
_gameServerInfoBar = showRebootInfoBar(
|
||||
_gameServerInfoBar = SnackBar.open(
|
||||
translations.waitingForGameServer,
|
||||
loading: true,
|
||||
duration: null
|
||||
@@ -549,7 +551,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
|
||||
Future<bool> _checkPublicGameServer(String gameServerPort) async {
|
||||
try {
|
||||
_gameServerInfoBar = showRebootInfoBar(
|
||||
_gameServerInfoBar = SnackBar.open(
|
||||
translations.checkingGameServer,
|
||||
loading: true,
|
||||
duration: null
|
||||
@@ -668,21 +670,21 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
case _StopReason.normal:
|
||||
break;
|
||||
case _StopReason.missingVersionError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.missingVersionError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.missingExecutableError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.missingExecutableError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.multipleExecutablesError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.multipleExecutablesError(error ?? translations.unknown),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -691,7 +693,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
case _StopReason.exitCode:
|
||||
if(instance != null && !instance.launched) {
|
||||
final injectedDlls = instance.injectedDlls;
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -700,7 +702,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
break;
|
||||
case _StopReason.corruptedVersionError:
|
||||
final injectedDlls = instance?.injectedDlls ?? [];
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.corruptedVersionError(injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -711,14 +713,14 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
);
|
||||
break;
|
||||
case _StopReason.corruptedDllError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.corruptedDllError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.missingCustomDllError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.missingCustomDllError(error!),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -727,7 +729,7 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
case _StopReason.tokenError:
|
||||
_backendController.stop();
|
||||
final injectedDlls = instance?.injectedDlls;
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.tokenError(injectedDlls == null || injectedDlls.isEmpty ? translations.none : injectedDlls.map((element) => element.name).join(", ")),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -738,21 +740,21 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
);
|
||||
break;
|
||||
case _StopReason.crash:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.fortniteCrashError(host ? translations.gameServer : translations.client),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.unknownError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.unknownFortniteError(error ?? translations.unknownError),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
);
|
||||
break;
|
||||
case _StopReason.gameServerPortError:
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.gameServerPortEqualsBackendPort(kDefaultBackendPort),
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
@@ -829,14 +831,14 @@ class _LaunchButtonState extends State<LaunchButton> {
|
||||
return null;
|
||||
}
|
||||
|
||||
InfoBarEntry _showLaunchingGameServerWidget() => _gameServerInfoBar = showRebootInfoBar(
|
||||
SnackBar _showLaunchingGameServerWidget() => _gameServerInfoBar = SnackBar.open(
|
||||
translations.launchingGameServer,
|
||||
loading: true,
|
||||
duration: null
|
||||
);
|
||||
|
||||
InfoBarEntry _showLaunchingGameClientWidget(GameVersion version, bool headless, bool linkedHosting) {
|
||||
return _gameClientInfoBar = showRebootInfoBar(
|
||||
SnackBar _showLaunchingGameClientWidget(GameVersion version, bool headless, bool linkedHosting) {
|
||||
return _gameClientInfoBar = SnackBar.open(
|
||||
linkedHosting ? translations.launchingGameClientAndServer : translations.launchingGameClientOnly,
|
||||
loading: true,
|
||||
duration: null,
|
||||
142
gui/lib/src/widget/page/settings/file_tile.dart
Normal file
142
gui/lib/src/widget/page/settings/file_tile.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluentIcons show FluentIcons;
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:reboot_launcher/src/util/os.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/widget/file_selector.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/setting_tile.dart';
|
||||
|
||||
class FileSetting extends StatefulWidget {
|
||||
final GlobalKey<TextFormBoxState> validatorKey;
|
||||
final String title;
|
||||
final String description;
|
||||
final TextEditingController controller;
|
||||
final VoidCallback onReset;
|
||||
final String extension;
|
||||
final bool folder;
|
||||
|
||||
const FileSetting({
|
||||
Key? key,
|
||||
required this.validatorKey,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.controller,
|
||||
required this.onReset,
|
||||
this.extension = 'dll',
|
||||
this.folder = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<FileSetting> createState() => _FileSettingState();
|
||||
}
|
||||
|
||||
class _FileSettingState extends State<FileSetting> {
|
||||
static const double _kButtonDimensions = 30;
|
||||
static const double _kButtonSpacing = 8;
|
||||
|
||||
String? _validationMessage;
|
||||
bool _selecting = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingTile(
|
||||
icon: const Icon(FluentIcons.document_24_regular),
|
||||
title: Text(widget.title),
|
||||
subtitle: Text(widget.description),
|
||||
contentWidth: SettingTile.kDefaultContentWidth + _kButtonDimensions,
|
||||
content: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FileSelector(
|
||||
placeholder: translations.selectPathPlaceholder,
|
||||
windowTitle: translations.selectPathWindowTitle,
|
||||
controller: widget.controller,
|
||||
validator: (text) {
|
||||
final result = _checkDll(text);
|
||||
setState(() => _validationMessage = result);
|
||||
return result;
|
||||
},
|
||||
extension: widget.extension,
|
||||
folder: widget.folder,
|
||||
validatorMode: AutovalidateMode.always,
|
||||
allowNavigator: false,
|
||||
validatorKey: widget.validatorKey,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _kButtonSpacing),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: _validationMessage == null ? 0.0 : 20.0),
|
||||
child: Tooltip(
|
||||
message: translations.selectFile,
|
||||
child: Button(
|
||||
style: ButtonStyle(padding: WidgetStateProperty.all(EdgeInsets.zero)),
|
||||
onPressed: _selecting ? null : _onSelectPressed,
|
||||
child: SizedBox.square(
|
||||
dimension: _kButtonDimensions,
|
||||
child: const Icon(fluentIcons.FluentIcons.open_folder_horizontal),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: _kButtonSpacing),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: _validationMessage == null ? 0.0 : 20.0),
|
||||
child: Tooltip(
|
||||
message: translations.reset,
|
||||
child: Button(
|
||||
style: ButtonStyle(padding: WidgetStateProperty.all(EdgeInsets.zero)),
|
||||
onPressed: widget.onReset,
|
||||
child: SizedBox.square(
|
||||
dimension: _kButtonDimensions,
|
||||
child: const Icon(FluentIcons.arrow_reset_24_regular),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSelectPressed() async {
|
||||
if (_selecting) return;
|
||||
setState(() => _selecting = true);
|
||||
|
||||
try {
|
||||
final picked = await compute(openFilePicker, widget.extension);
|
||||
_updateText(widget.controller, picked);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _selecting = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateText(TextEditingController controller, String? value) {
|
||||
final text = value ?? controller.text;
|
||||
controller.text = text;
|
||||
controller.selection = TextSelection.collapsed(offset: text.length);
|
||||
}
|
||||
|
||||
String? _checkDll(String? text) {
|
||||
if (text == null || text.isEmpty) {
|
||||
return translations.invalidDllPath;
|
||||
}
|
||||
|
||||
final file = File(text);
|
||||
try {
|
||||
file.readAsBytesSync();
|
||||
} catch (_) {
|
||||
return translations.dllDoesNotExist;
|
||||
}
|
||||
|
||||
if (!text.endsWith('.dll')) {
|
||||
return translations.invalidDllExtension;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,11 @@ import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/l10n/reboot_localizations.dart';
|
||||
import 'package:reboot_launcher/src/controller/dll_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/settings_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/file_setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/settings/file_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final GlobalKey<TextFormBoxState> settingsConsoleDllInputKey = GlobalKey();
|
||||
@@ -63,8 +62,8 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
title: Text(translations.settingsClientName),
|
||||
subtitle: Text(translations.settingsClientDescription),
|
||||
children: [
|
||||
createFileSetting(
|
||||
key: settingsConsoleDllInputKey,
|
||||
FileSetting(
|
||||
validatorKey: settingsConsoleDllInputKey,
|
||||
title: translations.settingsClientConsoleName,
|
||||
description: translations.settingsClientConsoleDescription,
|
||||
controller: _dllController.unrealEngineConsoleDll,
|
||||
@@ -73,10 +72,10 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
_dllController.unrealEngineConsoleDll.text = path;
|
||||
await _dllController.download(GameDll.console, path, force: true);
|
||||
settingsConsoleDllInputKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
),
|
||||
createFileSetting(
|
||||
key: settingsAuthDllInputKey,
|
||||
FileSetting(
|
||||
validatorKey: settingsAuthDllInputKey,
|
||||
title: translations.settingsClientAuthName,
|
||||
description: translations.settingsClientAuthDescription,
|
||||
controller: _dllController.backendDll,
|
||||
@@ -85,10 +84,10 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
_dllController.backendDll.text = path;
|
||||
await _dllController.download(GameDll.auth, path, force: true);
|
||||
settingsAuthDllInputKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
),
|
||||
createFileSetting(
|
||||
key: settingsMemoryDllInputKey,
|
||||
FileSetting(
|
||||
validatorKey: settingsMemoryDllInputKey,
|
||||
title: translations.settingsClientMemoryName,
|
||||
description: translations.settingsClientMemoryDescription,
|
||||
controller: _dllController.memoryLeakDll,
|
||||
@@ -97,7 +96,7 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
_dllController.memoryLeakDll.text = path;
|
||||
await _dllController.download(GameDll.memoryLeak, path, force: true);
|
||||
settingsAuthDllInputKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
),
|
||||
_gameServer
|
||||
],
|
||||
@@ -124,8 +123,6 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
subtitle: Text(translations.settingsServerTypeDescription),
|
||||
contentWidth: SettingTile.kDefaultContentWidth + 30,
|
||||
content: Obx(() => DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(_dllController.customGameServer.value ? translations.settingsServerTypeCustomName : translations.settingsServerTypeEmbeddedName),
|
||||
items: {
|
||||
false: translations.settingsServerTypeEmbeddedName,
|
||||
@@ -200,8 +197,8 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
)
|
||||
);
|
||||
}else {
|
||||
return createFileSetting(
|
||||
key: settingsGameServerDllInputKey,
|
||||
return FileSetting(
|
||||
validatorKey: settingsGameServerDllInputKey,
|
||||
title: translations.settingsOldServerFileName,
|
||||
description: translations.settingsServerFileDescription,
|
||||
controller: _dllController.customGameServerDll,
|
||||
@@ -210,7 +207,7 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
_dllController.customGameServerDll.text = path;
|
||||
await _dllController.download(GameDll.gameServer, path);
|
||||
settingsGameServerDllInputKey.currentState?.validate();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -295,8 +292,6 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
title: Text(translations.settingsUtilsLanguageName),
|
||||
subtitle: Text(translations.settingsUtilsLanguageDescription),
|
||||
content: Obx(() => DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(_getLocaleName(_settingsController.language.value)),
|
||||
items: AppLocalizations.supportedLocales.map((locale) => MenuFlyoutItem(
|
||||
text: Text(_getLocaleName(locale.languageCode)),
|
||||
@@ -321,8 +316,6 @@ class _SettingsPageState extends AbstractPageState<SettingsPage> {
|
||||
title: Text(translations.settingsUtilsThemeName),
|
||||
subtitle: Text(translations.settingsUtilsThemeDescription),
|
||||
content: Obx(() => DropDownButton(
|
||||
onOpen: () => inDialog = true,
|
||||
onClose: () => inDialog = false,
|
||||
leading: Text(_settingsController.themeMode.value.title),
|
||||
items: ThemeMode.values.map((themeMode) => MenuFlyoutItem(
|
||||
text: Text(themeMode.title),
|
||||
@@ -3,25 +3,25 @@ import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/message/profile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
|
||||
class ProfileWidget extends StatefulWidget {
|
||||
final GlobalKey<OverlayTargetState> overlayKey;
|
||||
const ProfileWidget({required this.overlayKey});
|
||||
class ProfileTile extends StatefulWidget {
|
||||
final GlobalKey<TutorialOverlayTargetState> overlayKey;
|
||||
const ProfileTile({required this.overlayKey});
|
||||
|
||||
@override
|
||||
State<ProfileWidget> createState() => _ProfileWidgetState();
|
||||
State<ProfileTile> createState() => _ProfileTileState();
|
||||
}
|
||||
|
||||
class _ProfileWidgetState extends State<ProfileWidget> {
|
||||
class _ProfileTileState extends State<ProfileTile> {
|
||||
final GameController _gameController = Get.find<GameController>();
|
||||
final HostingController _hostingController = Get.find<HostingController>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => OverlayTarget(
|
||||
Widget build(BuildContext context) => TutorialOverlayTarget(
|
||||
key: widget.overlayKey,
|
||||
child: HoverButton(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
@@ -3,16 +3,16 @@ import 'dart:collection';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:reboot_launcher/src/pager/page_type.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/backend_page.dart';
|
||||
import 'package:reboot_launcher/src/page/browser_page.dart';
|
||||
import 'package:reboot_launcher/src/page/host_page.dart';
|
||||
import 'package:reboot_launcher/src/page/info_page.dart';
|
||||
import 'package:reboot_launcher/src/pager/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/page/play_page.dart';
|
||||
import 'package:reboot_launcher/src/page/settings_page.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar_area.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/host/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/info/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/settings/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar_area.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/page_type.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/backend/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/browser/page.dart';
|
||||
import 'package:reboot_launcher/src/widget/page/abstract_page.dart';
|
||||
import 'package:reboot_launcher/src/widget/section/play/page.dart';
|
||||
|
||||
final StreamController<void> pagesController = StreamController.broadcast();
|
||||
bool hitBack = false;
|
||||
@@ -26,7 +26,7 @@ final List<AbstractPage> pages = [
|
||||
const SettingsPage()
|
||||
];
|
||||
|
||||
final List<GlobalKey<OverlayTargetState>> _flyoutPageControllers = List.generate(pages.length, (_) => GlobalKey());
|
||||
final List<GlobalKey<TutorialOverlayTargetState>> _flyoutPageControllers = List.generate(pages.length, (_) => GlobalKey());
|
||||
|
||||
final RxInt pageIndex = RxInt(PageType.play.index);
|
||||
|
||||
@@ -36,7 +36,7 @@ final GlobalKey<NavigatorState> appNavigatorKey = GlobalKey();
|
||||
|
||||
final GlobalKey<OverlayState> appOverlayKey = GlobalKey();
|
||||
|
||||
final GlobalKey<InfoBarAreaState> infoBarAreaKey = GlobalKey();
|
||||
final GlobalKey<SnackBarAreaState> infoBarAreaKey = GlobalKey();
|
||||
|
||||
GlobalKey get pageKey => getPageKeyByIndex(pageIndex.value);
|
||||
|
||||
@@ -82,6 +82,6 @@ void addSubPageToCurrent(String pageName) {
|
||||
pagesController.add(null);
|
||||
}
|
||||
|
||||
GlobalKey<OverlayTargetState> getOverlayTargetKeyByPage(int pageIndex) => _flyoutPageControllers[pageIndex];
|
||||
GlobalKey<TutorialOverlayTargetState> getOverlayTargetKeyByPage(int pageIndex) => _flyoutPageControllers[pageIndex];
|
||||
|
||||
GlobalKey<OverlayTargetState> get pageOverlayTargetKey => _flyoutPageControllers[pageIndex.value];
|
||||
GlobalKey<TutorialOverlayTargetState> get pageOverlayTargetKey => _flyoutPageControllers[pageIndex.value];
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
|
||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:skeletons/skeletons.dart';
|
||||
|
||||
class SettingTile extends StatefulWidget {
|
||||
@@ -78,7 +78,7 @@ class SettingTileState extends State<SettingTile> {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if(widget.overlayKey != null)
|
||||
OverlayTarget(
|
||||
TutorialOverlayTarget(
|
||||
key: widget.overlayKey,
|
||||
child: isSkeleton ? _skeletonIcon : icon,
|
||||
)
|
||||
81
gui/lib/src/widget/snackbar/snackbar.dart
Normal file
81
gui/lib/src/widget/snackbar/snackbar.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
|
||||
const infoBarLongDuration = Duration(seconds: 4);
|
||||
const infoBarShortDuration = Duration(seconds: 2);
|
||||
const _height = 64.0;
|
||||
|
||||
class SnackBar {
|
||||
static SnackBar open(String text, {
|
||||
InfoBarSeverity severity = InfoBarSeverity.info,
|
||||
bool loading = false,
|
||||
Duration? duration = infoBarShortDuration,
|
||||
void Function()? onDismissed,
|
||||
Widget? action
|
||||
}) {
|
||||
final overlayEntry = SnackBar._internal(
|
||||
overlay: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: _height
|
||||
),
|
||||
child: Mica(
|
||||
elevation: 1,
|
||||
child: InfoBar(
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(text)
|
||||
),
|
||||
if(action != null)
|
||||
action
|
||||
],
|
||||
),
|
||||
isLong: false,
|
||||
isIconVisible: true,
|
||||
content: SizedBox(
|
||||
width: double.infinity,
|
||||
child: loading ? const Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
bottom: 2.0,
|
||||
right: 6.0
|
||||
),
|
||||
child: ProgressBar(),
|
||||
) : const SizedBox()
|
||||
),
|
||||
severity: severity
|
||||
)
|
||||
),
|
||||
),
|
||||
onDismissed: onDismissed
|
||||
);
|
||||
if(duration != null) {
|
||||
Future.delayed(duration)
|
||||
.then((_) => WidgetsBinding.instance.addPostFrameCallback((timeStamp) => overlayEntry.close()));
|
||||
}
|
||||
return overlayEntry;
|
||||
}
|
||||
|
||||
final Widget overlay;
|
||||
final void Function()? onDismissed;
|
||||
|
||||
SnackBar._internal({
|
||||
required this.overlay,
|
||||
required this.onDismissed
|
||||
}) {
|
||||
final context = pageKey.currentContext;
|
||||
if(context != null) {
|
||||
infoBarAreaKey.currentState?.insertChild(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
bool close() {
|
||||
final result = infoBarAreaKey.currentState?.removeChild(overlay) ?? false;
|
||||
if(result) {
|
||||
onDismissed?.call();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
53
gui/lib/src/widget/snackbar/snackbar_area.dart
Normal file
53
gui/lib/src/widget/snackbar/snackbar_area.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
|
||||
class SnackBarArea extends StatefulWidget {
|
||||
const SnackBarArea({super.key});
|
||||
|
||||
@override
|
||||
State<SnackBarArea> createState() => SnackBarAreaState();
|
||||
}
|
||||
|
||||
class SnackBarAreaState extends State<SnackBarArea> {
|
||||
final List<Widget> _children = [];
|
||||
|
||||
void insertChild(Widget child) {
|
||||
setState(() {
|
||||
_children.add(child);
|
||||
});
|
||||
}
|
||||
|
||||
bool removeChild(Widget child) {
|
||||
var result = false;
|
||||
setState(() {
|
||||
result = _children.remove(child);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: pagesController.stream,
|
||||
builder: (context, _) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: hasPageButton ? 72.0 : 16.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _snackbars,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> get _snackbars => _children
|
||||
.map((child) => _buildSnackbar(child))
|
||||
.toList(growable: false);
|
||||
|
||||
Widget _buildSnackbar(Widget child) => Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:reboot_launcher/src/page/pages.dart';
|
||||
import 'package:reboot_launcher/src/widget/sections.dart';
|
||||
|
||||
import 'tutorial_overlay_attach_mode.dart';
|
||||
|
||||
typedef WidgetBuilder = Widget Function(BuildContext, void Function());
|
||||
|
||||
class OverlayTarget extends StatefulWidget {
|
||||
class TutorialOverlayTarget extends StatefulWidget {
|
||||
final Widget child;
|
||||
const OverlayTarget({super.key, required this.child});
|
||||
const TutorialOverlayTarget({super.key, required this.child});
|
||||
|
||||
@override
|
||||
State<OverlayTarget> createState() => OverlayTargetState();
|
||||
State<TutorialOverlayTarget> createState() => TutorialOverlayTargetState();
|
||||
|
||||
OverlayTargetState of(BuildContext context) => context.findAncestorStateOfType<OverlayTargetState>()!;
|
||||
TutorialOverlayTargetState of(BuildContext context) => context.findAncestorStateOfType<TutorialOverlayTargetState>()!;
|
||||
}
|
||||
|
||||
class OverlayTargetState extends State<OverlayTarget> {
|
||||
class TutorialOverlayTargetState extends State<TutorialOverlayTarget> {
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
|
||||
@@ -23,7 +25,7 @@ class OverlayTargetState extends State<OverlayTarget> {
|
||||
required WidgetBuilder actionBuilder,
|
||||
Offset offset = Offset.zero,
|
||||
bool ignoreTargetPointers = true,
|
||||
AttachMode attachMode = AttachMode.start
|
||||
TutorialOverlayAttachMode attachMode = TutorialOverlayAttachMode.start
|
||||
}) {
|
||||
final renderBox = context.findRenderObject() as RenderBox;
|
||||
final position = renderBox.localToGlobal(Offset.zero);
|
||||
@@ -40,10 +42,10 @@ class OverlayTargetState extends State<OverlayTarget> {
|
||||
)
|
||||
),
|
||||
Positioned(
|
||||
left: position.dx - (attachMode != AttachMode.start ? renderBox.size.width : 0) + offset.dx,
|
||||
left: position.dx - (attachMode != TutorialOverlayAttachMode.start ? renderBox.size.width : 0) + offset.dx,
|
||||
top: position.dy + (renderBox.size.height / 2) + offset.dy,
|
||||
child: CustomPaint(
|
||||
painter: _CallOutShape(color, attachMode != AttachMode.start),
|
||||
painter: _CallOutShape(color, attachMode != TutorialOverlayAttachMode.start),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
@@ -65,12 +67,6 @@ class OverlayTargetState extends State<OverlayTarget> {
|
||||
}
|
||||
}
|
||||
|
||||
enum AttachMode {
|
||||
start,
|
||||
middle,
|
||||
end;
|
||||
}
|
||||
|
||||
// Harder than one would think
|
||||
class _CallOutShape extends CustomPainter {
|
||||
final Color color;
|
||||
@@ -0,0 +1,5 @@
|
||||
enum TutorialOverlayAttachMode {
|
||||
start,
|
||||
middle,
|
||||
end;
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import 'package:get/get.dart';
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:reboot_launcher/src/controller/game_controller.dart';
|
||||
import 'package:reboot_launcher/src/util/translations.dart';
|
||||
import 'package:reboot_launcher/src/tile/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/messenger/dialog.dart';
|
||||
import 'package:reboot_launcher/src/messenger/info_bar.dart';
|
||||
import 'package:reboot_launcher/src/messenger/overlay.dart';
|
||||
import 'package:reboot_launcher/src/widget/setting_tile.dart';
|
||||
import 'package:reboot_launcher/src/widget/overlay/dialog.dart';
|
||||
import 'package:reboot_launcher/src/widget/snackbar/snackbar.dart';
|
||||
import 'package:reboot_launcher/src/widget/tutorial/tutorial_overlay.dart';
|
||||
import 'package:reboot_launcher/src/message/download_version.dart';
|
||||
import 'package:reboot_launcher/src/message/import_version.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -19,7 +19,7 @@ class VersionSelector extends StatefulWidget {
|
||||
const VersionSelector({Key? key}) : super(key: key);
|
||||
|
||||
static SettingTile buildTile({
|
||||
required GlobalKey<OverlayTargetState> key
|
||||
required GlobalKey<TutorialOverlayTargetState> key
|
||||
}) => SettingTile(
|
||||
icon: Icon(
|
||||
FluentIcons.play_24_regular
|
||||
@@ -221,7 +221,7 @@ class _VersionSelectorState extends State<VersionSelector> {
|
||||
}
|
||||
|
||||
bool _onExplorerError() {
|
||||
showRebootInfoBar(
|
||||
SnackBar.open(
|
||||
translations.missingVersionError,
|
||||
severity: InfoBarSeverity.error,
|
||||
duration: infoBarLongDuration,
|
||||
Reference in New Issue
Block a user