From 877c5a827cc5f83008e745ebbbb2534afd953689 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sun, 10 Oct 2021 00:36:59 +0800 Subject: [PATCH] feat(multiplayer): join request should be cancelled when user operation time out. --- .../org/jackhuang/hmcl/ui/Controllers.java | 8 +- .../hmcl/ui/construct/MessageDialogPane.java | 117 ++++++++++++------ .../hmcl/ui/multiplayer/MultiplayerPage.java | 6 +- .../java/org/jackhuang/hmcl/util/Lang.java | 8 +- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 20a2e614b..9e604ef17 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -227,7 +227,7 @@ public final class Controllers { } public static void dialog(String text, String title, MessageType type, Runnable ok) { - dialog(MessageDialogPane.ok(text, title, type, ok)); + dialog(new MessageDialogPane.Builder(text, title, type).ok(ok).build()); } public static void confirm(String text, String title, Runnable yes, Runnable no) { @@ -235,15 +235,15 @@ public final class Controllers { } public static void confirm(String text, String title, MessageType type, Runnable yes, Runnable no) { - dialog(MessageDialogPane.yesOrNo(text, title, type, yes, no)); + dialog(new MessageDialogPane.Builder(text, title, type).yesOrNo(yes, no).build()); } public static void confirmAction(String text, String title, MessageType type, ButtonBase actionButton) { - dialog(MessageDialogPane.actionOrCancel(text, title, type, actionButton, null)); + dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, null).build()); } public static void confirmAction(String text, String title, MessageType type, ButtonBase actionButton, Runnable cancel) { - dialog(MessageDialogPane.actionOrCancel(text, title, type, actionButton, cancel)); + dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build()); } public static CompletableFuture prompt(String title, FutureCallback onResult) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 72be8aae6..f0ee57074 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.controls.JFXButton; - import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.ButtonBase; @@ -28,12 +27,16 @@ import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.util.Lang; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class MessageDialogPane extends StackPane { @@ -105,53 +108,89 @@ public final class MessageDialogPane extends StackPane { cancelButton = btn; } - public static MessageDialogPane ok(String text, String title, MessageType type, Runnable ok) { - MessageDialogPane dialog = new MessageDialogPane(text, title, type); - - JFXButton btnOk = new JFXButton(i18n("button.ok")); - btnOk.getStyleClass().add("dialog-accept"); - if (ok != null) { - btnOk.setOnAction(e -> ok.run()); - } - dialog.addButton(btnOk); - dialog.setCancelButton(btnOk); - - return dialog; + public ButtonBase getCancelButton() { + return cancelButton; } - public static MessageDialogPane yesOrNo(String text, String title, MessageType type, Runnable yes, Runnable no) { - MessageDialogPane dialog = new MessageDialogPane(text, title, type); + public static class Builder { + private final MessageDialogPane dialog; - JFXButton btnYes = new JFXButton(i18n("button.yes")); - btnYes.getStyleClass().add("dialog-accept"); - if (yes != null) { - btnYes.setOnAction(e -> yes.run()); + public Builder(String text, String title, MessageType type) { + this.dialog = new MessageDialogPane(text, title, type); } - dialog.addButton(btnYes); - JFXButton btnNo = new JFXButton(i18n("button.no")); - btnNo.getStyleClass().add("dialog-cancel"); - if (no != null) { - btnNo.setOnAction(e -> no.run()); + public Builder ok(Runnable ok) { + JFXButton btnOk = new JFXButton(i18n("button.ok")); + btnOk.getStyleClass().add("dialog-accept"); + if (ok != null) { + btnOk.setOnAction(e -> ok.run()); + } + dialog.addButton(btnOk); + dialog.setCancelButton(btnOk); + return this; } - dialog.addButton(btnNo); - dialog.setCancelButton(btnNo); - return dialog; - } + public Builder yesOrNo(Runnable yes, Runnable no) { + JFXButton btnYes = new JFXButton(i18n("button.yes")); + btnYes.getStyleClass().add("dialog-accept"); + if (yes != null) { + btnYes.setOnAction(e -> yes.run()); + } + dialog.addButton(btnYes); - public static MessageDialogPane actionOrCancel(String text, String title, MessageType type, ButtonBase actionButton, Runnable cancel) { - MessageDialogPane dialog = new MessageDialogPane(text, title, type); - dialog.addButton(actionButton); - - JFXButton btnCancel = new JFXButton(i18n("button.cancel")); - btnCancel.getStyleClass().add("dialog-cancel"); - if (cancel != null) { - btnCancel.setOnAction(e -> cancel.run()); + JFXButton btnNo = new JFXButton(i18n("button.no")); + btnNo.getStyleClass().add("dialog-cancel"); + if (no != null) { + btnNo.setOnAction(e -> no.run()); + } + dialog.addButton(btnNo); + dialog.setCancelButton(btnNo); + return this; } - dialog.addButton(btnCancel); - dialog.setCancelButton(btnCancel); - return dialog; + public Builder actionOrCancel(ButtonBase actionButton, Runnable cancel) { + dialog.addButton(actionButton); + + JFXButton btnCancel = new JFXButton(i18n("button.cancel")); + btnCancel.getStyleClass().add("dialog-cancel"); + if (cancel != null) { + btnCancel.setOnAction(e -> cancel.run()); + } + dialog.addButton(btnCancel); + dialog.setCancelButton(btnCancel); + + return this; + } + + public Builder cancelOnTimeout(long timeoutMs) { + if (dialog.getCancelButton() == null) { + throw new IllegalStateException("Call ok/yesOrNo/actionOrCancel before calling cancelOnTimeout"); + } + + ButtonBase cancelButton = dialog.getCancelButton(); + String originalText = cancelButton.getText(); + + Timer timer = Lang.getTimer(); + timer.scheduleAtFixedRate(new TimerTask() { + long timeout = timeoutMs; + + @Override + public void run() { + if (timeout < 0) { + cancel(); + return; + } + timeout -= 1000; + long currentTimeout = timeout; + runInFX(() -> cancelButton.setText(originalText + " (" + (currentTimeout / 1000) + ")")); + } + }, 1000, 1000); + + return this; + } + + public MessageDialogPane build() { + return dialog; + } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java index e69f720ce..f09d6d3de 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -215,8 +215,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getServer().getMotd(), gamePort, result.isAllowAllJoinRequests()); session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> { runInFX(() -> { - Controllers.confirm(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO, - resolveClient, () -> rejectClient.accept("")); + Controllers.dialog(new MessageDialogPane.Builder(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO) + .yesOrNo(resolveClient, () -> rejectClient.accept("")) + .cancelOnTimeout(30 * 1000) + .build()); }); }); session.getServer().onClientAdded().register(event -> { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index c898acf2c..4ee4a5275 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -359,17 +359,21 @@ public final class Lang { private static Timer timer; - public static synchronized TimerTask setTimeout(Runnable runnable, long delayMs) { + public static synchronized Timer getTimer() { if (timer == null) { timer = new Timer(); } + return timer; + } + + public static synchronized TimerTask setTimeout(Runnable runnable, long delayMs) { TimerTask task = new TimerTask() { @Override public void run() { runnable.run(); } }; - timer.schedule(task, delayMs); + getTimer().schedule(task, delayMs); return task; }