import 'dart:async'; import 'package:clipboard/clipboard.dart'; 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_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/messenger/abstract/dialog.dart'; import 'package:reboot_launcher/src/messenger/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'; final List _infoBars = []; extension ServerControllerDialog on BackendController { void cancelInteractive() { worker?.cancel(); // Do not await or it will hang _infoBars.forEach((infoBar) => infoBar.close()); _infoBars.clear(); } Future toggleInteractive() async { cancelInteractive(); final stream = toggle(); final completer = Completer(); InfoBarEntry? entry; worker = stream.listen((event) { entry?.close(); entry = _handeEvent(event); if(event.type.isError) { completer.complete(false); }else if(event.type.isSuccess) { completer.complete(true); } }); return await completer.future; } InfoBarEntry _handeEvent(ServerResult event) { log("[BACKEND] Handling event: $event"); switch (event.type) { case ServerResultType.starting: return _showRebootInfoBar( translations.startingServer, severity: InfoBarSeverity.info, loading: true, duration: null ); case ServerResultType.startSuccess: final embeddedProcessPid = this.embeddedProcessPid; if(embeddedProcessPid != null) { watchProcess(embeddedProcessPid).then((_) { if(started.value) { started.value = false; _showRebootInfoBar( translations.backendProcessError, severity: InfoBarSeverity.error ); } }); } return _showRebootInfoBar( type.value == ServerType.local ? translations.checkedServer : translations.startedServer, severity: InfoBarSeverity.success ); case ServerResultType.startError: print(event.stackTrace); return _showRebootInfoBar( type.value == ServerType.local ? translations.localServerError(event.error ?? translations.unknownError) : translations.startServerError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); case ServerResultType.stopping: return _showRebootInfoBar( translations.stoppingServer, severity: InfoBarSeverity.info, loading: true, duration: null ); case ServerResultType.stopSuccess: return _showRebootInfoBar( translations.stoppedServer, severity: InfoBarSeverity.success ); case ServerResultType.stopError: return _showRebootInfoBar( translations.stopServerError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); case ServerResultType.missingHostError: return _showRebootInfoBar( translations.missingHostNameError, severity: InfoBarSeverity.error ); case ServerResultType.missingPortError: return _showRebootInfoBar( translations.missingPortError, severity: InfoBarSeverity.error ); case ServerResultType.illegalPortError: return _showRebootInfoBar( translations.illegalPortError, severity: InfoBarSeverity.error ); case ServerResultType.freeingPort: return _showRebootInfoBar( translations.freeingPort, loading: true, duration: null ); case ServerResultType.freePortSuccess: return _showRebootInfoBar( translations.freedPort, severity: InfoBarSeverity.success, duration: infoBarShortDuration ); case ServerResultType.freePortError: return _showRebootInfoBar( translations.freePortError(event.error ?? translations.unknownError), severity: InfoBarSeverity.error, duration: infoBarLongDuration ); case ServerResultType.pingingRemote: return _showRebootInfoBar( translations.pingingServer(ServerType.remote.name), severity: InfoBarSeverity.info, loading: true, duration: null ); case ServerResultType.pingingLocal: return _showRebootInfoBar( translations.pingingServer(type.value.name), severity: InfoBarSeverity.info, loading: true, duration: null ); case ServerResultType.pingError: return _showRebootInfoBar( translations.pingError(type.value.name), severity: InfoBarSeverity.error ); } } Future joinServerInteractive(String uuid, FortniteServer server) async { if(!kDebugMode && uuid == server.id) { _showRebootInfoBar( translations.joinSelfServer, duration: infoBarLongDuration, severity: InfoBarSeverity.error ); return; } final gameController = Get.find(); final version = gameController.getVersionByName(server.version.toString()); if(version == null) { _showRebootInfoBar( translations.cannotJoinServerVersion(server.version.toString()), duration: infoBarLongDuration, severity: InfoBarSeverity.error ); return; } final hashedPassword = server.password; final hasPassword = hashedPassword != null; final embedded = type.value == ServerType.embedded; final author = server.author; final encryptedIp = server.ip; if(!hasPassword) { final valid = await _isServerValid(encryptedIp); if(!valid) { return; } _onSuccess(gameController, embedded, encryptedIp, author, version); return; } final confirmPassword = await _askForPassword(); if(confirmPassword == null) { return; } if(!checkPassword(confirmPassword, hashedPassword)) { _showRebootInfoBar( translations.wrongServerPassword, duration: infoBarLongDuration, severity: InfoBarSeverity.error ); return; } final decryptedIp = aes256Decrypt(encryptedIp, confirmPassword); final valid = await _isServerValid(decryptedIp); if(!valid) { return; } _onSuccess(gameController, embedded, decryptedIp, author, version); } Future _isServerValid(String address) async { final result = await pingGameServer(address); if(result) { return true; } _showRebootInfoBar( translations.offlineServer, duration: infoBarLongDuration, severity: InfoBarSeverity.error ); return false; } Future _askForPassword() async { final confirmPasswordController = TextEditingController(); final showPassword = RxBool(false); final showPasswordTrailing = RxBool(false); return await showRebootDialog( builder: (context) => FormDialog( content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoLabel( label: translations.serverPassword, child: Obx(() => TextFormBox( placeholder: translations.serverPasswordPlaceholder, controller: confirmPasswordController, autovalidateMode: AutovalidateMode.always, obscureText: !showPassword.value, enableSuggestions: false, autofocus: true, autocorrect: false, onChanged: (text) => showPasswordTrailing.value = text.isNotEmpty, 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 ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular ), ) )) ), const SizedBox(height: 8.0) ], ), buttons: [ DialogButton( text: translations.serverPasswordCancel, type: ButtonType.secondary ), DialogButton( text: translations.serverPasswordConfirm, type: ButtonType.primary, onTap: () => Navigator.of(context).pop(confirmPasswordController.text) ) ] ) ); } void _onSuccess(GameController controller, bool embedded, String decryptedIp, String author, FortniteVersion version) { if(embedded) { gameServerAddress.text = decryptedIp; pageIndex.value = RebootPageType.play.index; }else { FlutterClipboard.controlC(decryptedIp); } controller.selectedVersion = version; WidgetsBinding.instance.addPostFrameCallback((_) => _showRebootInfoBar( embedded ? translations.joinedServer(author) : translations.copiedIp, duration: infoBarLongDuration, severity: InfoBarSeverity.success )); } InfoBarEntry _showRebootInfoBar(dynamic text, { InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = infoBarShortDuration, void Function()? onDismissed, Widget? action }) { final result = showRebootInfoBar( text, severity: severity, loading: loading, duration: duration, onDismissed: onDismissed, action: action ); _infoBars.add(result); return result; } }