This commit is contained in:
Alessandro Autiero
2024-05-20 17:24:00 +02:00
parent 7c2caed16c
commit 9f5590d41c
562 changed files with 3303 additions and 156787 deletions

View File

@@ -1,17 +1,27 @@
import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/translations.dart';
import 'dialog_button.dart';
Future<T?> showAppDialog<T extends Object?>({required WidgetBuilder builder}) => fluent.showDialog(
context: appKey.currentContext!,
useRootNavigator: false,
builder: builder
);
bool inDialog = false;
Future<T?> showAppDialog<T extends Object?>({required WidgetBuilder builder}) async {
inDialog = true;
pagesController.add(null);
try {
return await fluent.showDialog(
context: appKey.currentContext!,
useRootNavigator: false,
builder: builder
);
}finally {
inDialog = false;
}
}
abstract class AbstractDialog extends StatelessWidget {
const AbstractDialog({Key? key}) : super(key: key);

View File

@@ -1,39 +1,38 @@
import 'dart:collection';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:sync/semaphore.dart';
const infoBarLongDuration = Duration(seconds: 4);
const infoBarShortDuration = Duration(seconds: 2);
Semaphore _semaphore = Semaphore();
HashMap<int, OverlayEntry?> _overlays = HashMap();
HashMap<int, _OverlayEntry> _overlays = HashMap();
void restoreMessage(int pageIndex, int lastIndex) {
removeMessageByPage(lastIndex);
var overlay = _overlays[pageIndex];
if(overlay == null) {
final entry = _overlays[pageIndex];
if(entry == null) {
return;
}
Overlay.of(pageKey.currentContext!).insert(overlay);
Overlay.of(pageKey.currentContext!).insert(entry.overlay);
}
OverlayEntry showInfoBar(dynamic text,
{RebootPageType? pageType,
InfoBarSeverity severity = InfoBarSeverity.info,
{InfoBarSeverity severity = InfoBarSeverity.info,
bool loading = false,
Duration? duration = snackbarShortDuration,
Duration? duration = infoBarShortDuration,
void Function()? onDismissed,
Widget? action}) {
try {
_semaphore.acquire();
var index = pageType?.index ?? pageIndex.value;
removeMessageByPage(index);
var overlay = OverlayEntry(
removeMessageByPage(pageIndex.value);
final overlay = OverlayEntry(
builder: (context) => Padding(
padding: EdgeInsets.only(
right: 12.0,
left: 12.0,
bottom: pagesWithButtonIndexes.contains(index) ? 72.0 : 16.0
bottom: hasPageButton ? 72.0 : 16.0
),
child: Align(
alignment: AlignmentDirectional.bottomCenter,
@@ -71,19 +70,22 @@ OverlayEntry showInfoBar(dynamic text,
),
)
);
if(index == pageIndex.value) {
Overlay.of(pageKey.currentContext!).insert(overlay);
}
_overlays[index] = overlay;
Overlay.of(pageKey.currentContext!).insert(overlay);
_overlays[pageIndex.value] = _OverlayEntry(
overlay: overlay,
onDismissed: onDismissed
);
if(duration != null) {
Future.delayed(duration).then((_) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if(_overlays[index] == overlay) {
final currentOverlay = _overlays[pageIndex.value];
if(currentOverlay == overlay) {
if(overlay.mounted) {
overlay.remove();
}
_overlays[index] = null;
_overlays.remove(pageIndex.value);
currentOverlay?.onDismissed?.call();
}
});
});
@@ -95,20 +97,23 @@ OverlayEntry showInfoBar(dynamic text,
}
void removeMessageByPage(int index) {
var lastOverlay = _overlays[index];
final lastOverlay = _overlays[index];
if(lastOverlay != null) {
removeMessageByOverlay(lastOverlay);
_overlays[index] = null;
try {
lastOverlay.overlay.remove();
}catch(_) {
// Do not use .isMounted
// This is intended behaviour
}finally {
_overlays.remove(index);
lastOverlay.onDismissed?.call();
}
}
}
void removeMessageByOverlay(OverlayEntry? overlay) {
try {
if(overlay != null) {
overlay.remove();
}
}catch(_) {
// Do not use .isMounted
// This is intended behaviour
}
class _OverlayEntry {
final OverlayEntry overlay;
final void Function()? onDismissed;
_OverlayEntry({required this.overlay, required this.onDismissed});
}

View File

@@ -0,0 +1,24 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/util/translations.dart';
Future<void> showDllDeletedDialog(Function() onConfirm) => showAppDialog(
builder: (context) => InfoDialog(
text: translations.dllDeletedTitle,
buttons: [
DialogButton(
type: ButtonType.secondary,
text: translations.dllDeletedSecondaryAction,
),
DialogButton(
type: ButtonType.secondary,
text: translations.dllDeletedPrimaryAction,
onTap: () {
Navigator.pop(context);
onConfirm();
},
),
],
)
);

View File

@@ -2,8 +2,8 @@ import 'dart:async';
import 'package:clipboard/clipboard.dart';
import 'package:dart_ipify/dart_ipify.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:fluent_ui/fluent_ui.dart' hide FluentIcons;
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:get/get.dart';
import 'package:reboot_common/common.dart';
import 'package:reboot_launcher/src/controller/hosting_controller.dart';
@@ -12,27 +12,22 @@ import 'package:reboot_launcher/src/controller/server_controller.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog.dart';
import 'package:reboot_launcher/src/dialog/abstract/dialog_button.dart';
import 'package:reboot_launcher/src/dialog/abstract/info_bar.dart';
import 'package:reboot_launcher/src/page/abstract/page_type.dart';
import 'package:reboot_launcher/src/page/pages.dart';
import 'package:reboot_launcher/src/util/cryptography.dart';
import 'package:reboot_launcher/src/util/matchmaker.dart';
import 'package:reboot_launcher/src/util/translations.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:sync/semaphore.dart';
extension ServerControllerDialog on ServerController {
Future<bool> toggleInteractive(RebootPageType caller, [bool showSuccessMessage = true]) async {
Future<bool> toggleInteractive([bool showSuccessMessage = true]) async {
var stream = toggle();
return await _handleStream(caller, stream, showSuccessMessage);
}
Future<bool> _handleStream(RebootPageType caller, Stream<ServerResult> stream, bool showSuccessMessage) async {
var completer = Completer<bool>();
worker = stream.listen((event) {
switch (event.type) {
case ServerResultType.starting:
showInfoBar(
translations.startingServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -41,8 +36,7 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.startSuccess:
if(showSuccessMessage) {
showInfoBar(
translations.startedServer(controllerName),
pageType: caller,
type.value == ServerType.local ? translations.checkedServer(controllerName) : translations.startedServer(controllerName),
severity: InfoBarSeverity.success
);
}
@@ -50,17 +44,14 @@ extension ServerControllerDialog on ServerController {
break;
case ServerResultType.startError:
showInfoBar(
translations.startServerError(
event.error ?? translations.unknownError, controllerName),
pageType: caller,
type.value == ServerType.local ? translations.localServerError(event.error ?? translations.unknownError, controllerName) : translations.startServerError(event.error ?? translations.unknownError, controllerName),
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
duration: infoBarLongDuration
);
break;
case ServerResultType.stopping:
showInfoBar(
translations.stoppingServer,
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -70,7 +61,6 @@ extension ServerControllerDialog on ServerController {
if(showSuccessMessage) {
showInfoBar(
translations.stoppedServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.success
);
}
@@ -80,36 +70,31 @@ extension ServerControllerDialog on ServerController {
showInfoBar(
translations.stopServerError(
event.error ?? translations.unknownError, controllerName),
pageType: caller,
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
duration: infoBarLongDuration
);
break;
case ServerResultType.missingHostError:
showInfoBar(
translations.missingHostNameError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.missingPortError:
showInfoBar(
translations.missingPortError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.illegalPortError:
showInfoBar(
translations.illegalPortError(controllerName),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
case ServerResultType.freeingPort:
showInfoBar(
translations.freeingPort(defaultPort),
pageType: caller,
loading: true,
duration: null
);
@@ -117,24 +102,21 @@ extension ServerControllerDialog on ServerController {
case ServerResultType.freePortSuccess:
showInfoBar(
translations.freedPort(defaultPort),
pageType: caller,
severity: InfoBarSeverity.success,
duration: snackbarShortDuration
duration: infoBarShortDuration
);
break;
case ServerResultType.freePortError:
showInfoBar(
translations.freePortError(event.error ?? translations.unknownError, controllerName),
pageType: caller,
severity: InfoBarSeverity.error,
duration: snackbarLongDuration
duration: infoBarLongDuration
);
break;
case ServerResultType.pingingRemote:
if(started.value) {
showInfoBar(
translations.pingingRemoteServer(controllerName),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
@@ -142,20 +124,16 @@ extension ServerControllerDialog on ServerController {
}
break;
case ServerResultType.pingingLocal:
if(started.value) {
showInfoBar(
translations.pingingLocalServer(controllerName, type().name),
pageType: caller,
severity: InfoBarSeverity.info,
loading: true,
duration: null
);
}
showInfoBar(
translations.pingingLocalServer(controllerName, type().name),
severity: InfoBarSeverity.info,
loading: true,
duration: null
);
break;
case ServerResultType.pingError:
showInfoBar(
translations.pingError(controllerName, type().name),
pageType: caller,
severity: InfoBarSeverity.error
);
break;
@@ -166,19 +144,12 @@ extension ServerControllerDialog on ServerController {
}
});
var result = await completer.future;
if(result && type() == ServerType.embedded) {
watchProcess(embeddedServerPid!).then((value) {
if(started()) {
started.value = false;
}
});
}
return result;
return await completer.future;
}
}
final Semaphore _publishingSemaphore = Semaphore();
extension MatchmakerControllerExtension on MatchmakerController {
void joinLocalHost() {
gameServerAddress.text = kDefaultGameServerHost;
@@ -190,7 +161,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
if(uuid == id) {
showInfoBar(
translations.joinSelfServer,
duration: snackbarLongDuration,
duration: infoBarLongDuration,
severity: InfoBarSeverity.error
);
return;
@@ -219,7 +190,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
if(!checkPassword(confirmPassword, hashedPassword)) {
showInfoBar(
translations.wrongServerPassword,
duration: snackbarLongDuration,
duration: infoBarLongDuration,
severity: InfoBarSeverity.error
);
return;
@@ -242,16 +213,16 @@ extension MatchmakerControllerExtension on MatchmakerController {
showInfoBar(
translations.offlineServer,
duration: snackbarLongDuration,
duration: infoBarLongDuration,
severity: InfoBarSeverity.error
);
return false;
}
Future<String?> _askForPassword() async {
var confirmPasswordController = TextEditingController();
var showPassword = RxBool(false);
var showPasswordTrailing = RxBool(false);
final confirmPasswordController = TextEditingController();
final showPassword = RxBool(false);
final showPasswordTrailing = RxBool(false);
return await showAppDialog<String?>(
builder: (context) => FormDialog(
content: Column(
@@ -270,15 +241,14 @@ extension MatchmakerControllerExtension on MatchmakerController {
autofocus: true,
autocorrect: false,
onChanged: (text) => showPasswordTrailing.value = text.isNotEmpty,
suffix: Button(
onPressed: () => showPasswordTrailing.value = !showPasswordTrailing.value,
suffix: !showPasswordTrailing.value ? null : Button(
onPressed: () => showPassword.value = !showPassword.value,
style: ButtonStyle(
shape: ButtonState.all(const CircleBorder()),
backgroundColor: ButtonState.all(Colors.transparent)
),
child: Icon(
showPassword.value ? Icons.visibility_off : Icons.visibility,
color: showPassword.value ? null : Colors.transparent
showPassword.value ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular
),
)
))
@@ -312,7 +282,7 @@ extension MatchmakerControllerExtension on MatchmakerController {
}
WidgetsBinding.instance.addPostFrameCallback((_) => showInfoBar(
embedded ? translations.joinedServer(author) : translations.copiedIp,
duration: snackbarLongDuration,
duration: infoBarLongDuration,
severity: InfoBarSeverity.success
));
}
@@ -320,40 +290,62 @@ extension MatchmakerControllerExtension on MatchmakerController {
extension HostingControllerExtension on HostingController {
Future<void> publishServer(String author, String version) async {
var passwordText = password.text;
var hasPassword = passwordText.isNotEmpty;
var ip = await Ipify.ipv4();
if(hasPassword) {
ip = aes256Encrypt(ip, passwordText);
}
try {
_publishingSemaphore.acquire();
if(published.value) {
return;
}
var supabase = Supabase.instance.client;
var hosts = supabase.from('hosts');
var payload = {
'name': name.text,
'description': description.text,
'author': author,
'ip': ip,
'version': version,
'password': hasPassword ? hashPassword(passwordText) : null,
'timestamp': DateTime.now().toIso8601String(),
'discoverable': discoverable.value
};
if(published()) {
await hosts.update(payload).eq("id", uuid);
}else {
payload["id"] = uuid;
await hosts.insert(payload);
}
final passwordText = password.text;
final hasPassword = passwordText.isNotEmpty;
var ip = await Ipify.ipv4();
if(hasPassword) {
ip = aes256Encrypt(ip, passwordText);
}
published.value = true;
var supabase = Supabase.instance.client;
var hosts = supabase.from("hosting");
var payload = {
'name': name.text,
'description': description.text,
'author': author,
'ip': ip,
'version': version,
'password': hasPassword ? hashPassword(passwordText) : null,
'timestamp': DateTime.now().toIso8601String(),
'discoverable': discoverable.value
};
if(published()) {
await hosts.update(payload).eq("id", uuid);
}else {
payload["id"] = uuid;
await hosts.insert(payload);
}
published.value = true;
}catch(error) {
published.value = false;
}finally {
_publishingSemaphore.release();
}
}
Future<void> discardServer() async {
var supabase = Supabase.instance.client;
await supabase.from('hosts')
.delete()
.match({'id': uuid});
published.value = false;
try {
_publishingSemaphore.acquire();
if(!published.value) {
return;
}
final supabase = Supabase.instance.client;
await supabase.from("hosting")
.delete()
.match({'id': uuid});
published.value = false;
}catch(_) {
published.value = true;
}finally {
_publishingSemaphore.release();
}
}
}