feat(multiplayer): join request should be cancelled when user operation time out.

This commit is contained in:
huanghongxun
2021-10-10 00:36:59 +08:00
parent eedcff785e
commit 877c5a827c
4 changed files with 92 additions and 47 deletions

View File

@@ -227,7 +227,7 @@ public final class Controllers {
} }
public static void dialog(String text, String title, MessageType type, Runnable ok) { 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) { 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) { 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) { 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) { 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<String> prompt(String title, FutureCallback<String> onResult) { public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult) {

View File

@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.construct; package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ButtonBase; import javafx.scene.control.ButtonBase;
@@ -28,12 +27,16 @@ import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.util.Lang;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Locale; 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.onEscPressed;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class MessageDialogPane extends StackPane { public final class MessageDialogPane extends StackPane {
@@ -105,53 +108,89 @@ public final class MessageDialogPane extends StackPane {
cancelButton = btn; cancelButton = btn;
} }
public static MessageDialogPane ok(String text, String title, MessageType type, Runnable ok) { public ButtonBase getCancelButton() {
MessageDialogPane dialog = new MessageDialogPane(text, title, type); return cancelButton;
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 static MessageDialogPane yesOrNo(String text, String title, MessageType type, Runnable yes, Runnable no) { public static class Builder {
MessageDialogPane dialog = new MessageDialogPane(text, title, type); private final MessageDialogPane dialog;
JFXButton btnYes = new JFXButton(i18n("button.yes")); public Builder(String text, String title, MessageType type) {
btnYes.getStyleClass().add("dialog-accept"); this.dialog = new MessageDialogPane(text, title, type);
if (yes != null) {
btnYes.setOnAction(e -> yes.run());
} }
dialog.addButton(btnYes);
JFXButton btnNo = new JFXButton(i18n("button.no")); public Builder ok(Runnable ok) {
btnNo.getStyleClass().add("dialog-cancel"); JFXButton btnOk = new JFXButton(i18n("button.ok"));
if (no != null) { btnOk.getStyleClass().add("dialog-accept");
btnNo.setOnAction(e -> no.run()); 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) { JFXButton btnNo = new JFXButton(i18n("button.no"));
MessageDialogPane dialog = new MessageDialogPane(text, title, type); btnNo.getStyleClass().add("dialog-cancel");
dialog.addButton(actionButton); if (no != null) {
btnNo.setOnAction(e -> no.run());
JFXButton btnCancel = new JFXButton(i18n("button.cancel")); }
btnCancel.getStyleClass().add("dialog-cancel"); dialog.addButton(btnNo);
if (cancel != null) { dialog.setCancelButton(btnNo);
btnCancel.setOnAction(e -> cancel.run()); 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;
}
} }
} }

View File

@@ -215,8 +215,10 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getServer().getMotd(), gamePort, result.isAllowAllJoinRequests()); MultiplayerManager.CatoSession session = MultiplayerManager.createSession(globalConfig().getMultiplayerToken(), result.getServer().getMotd(), gamePort, result.isAllowAllJoinRequests());
session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> { session.getServer().setOnClientAdding((client, resolveClient, rejectClient) -> {
runInFX(() -> { runInFX(() -> {
Controllers.confirm(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO, Controllers.dialog(new MessageDialogPane.Builder(i18n("multiplayer.session.create.join.prompt", client.getUsername()), i18n("multiplayer.session.create.join"), MessageDialogPane.MessageType.INFO)
resolveClient, () -> rejectClient.accept("")); .yesOrNo(resolveClient, () -> rejectClient.accept(""))
.cancelOnTimeout(30 * 1000)
.build());
}); });
}); });
session.getServer().onClientAdded().register(event -> { session.getServer().onClientAdded().register(event -> {

View File

@@ -359,17 +359,21 @@ public final class Lang {
private static Timer timer; private static Timer timer;
public static synchronized TimerTask setTimeout(Runnable runnable, long delayMs) { public static synchronized Timer getTimer() {
if (timer == null) { if (timer == null) {
timer = new Timer(); timer = new Timer();
} }
return timer;
}
public static synchronized TimerTask setTimeout(Runnable runnable, long delayMs) {
TimerTask task = new TimerTask() { TimerTask task = new TimerTask() {
@Override @Override
public void run() { public void run() {
runnable.run(); runnable.run();
} }
}; };
timer.schedule(task, delayMs); getTimer().schedule(task, delayMs);
return task; return task;
} }