feat: lightweight first-run dialog for picking a game name or signing in

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
layue13andClaude Sonnet 5.5 committed 2026-11-08 14:22:04 +08:00
1 parent b3f649c026
commit 06ef27f0e0
6 files changed
+118 -1

No files matched your search

@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jetbrains.annotations.NotNullByDefault;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/// A small dialog shown when the player has no account yet.
///
/// The player either types a game name, which creates an offline account, or chooses to sign in
/// with a Microsoft account instead. Authentication itself is left to the server.
@NotNullByDefault
public final class FirstRunAccountDialog extends JFXDialogLayout implements DialogAware {
/// Names accepted by the game: letters, digits and underscores, at most 16 characters.
private static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]{1,16}$");
private final JFXTextField nameField = new JFXTextField();
private final Label hint = new Label();
private final JFXButton acceptButton = new JFXButton(i18n("first_run.accept"));
/// Creates the dialog with its name field and the two actions.
public FirstRunAccountDialog() {
Label heading = new Label(i18n("first_run.title"));
setHeading(heading);
Label description = new Label(i18n("first_run.description"));
description.setWrapText(true);
nameField.setPromptText(i18n("first_run.name"));
hint.setStyle("-fx-text-fill: -monet-error;");
hint.setWrapText(true);
VBox body = new VBox(13, description, nameField, hint);
body.setPrefWidth(377);
setBody(body);
JFXButton microsoftButton = new JFXButton(i18n("first_run.microsoft"));
microsoftButton.getStyleClass().add("dialog-cancel");
microsoftButton.setOnAction(e -> {
fireEvent(new DialogCloseEvent());
Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT));
});
acceptButton.getStyleClass().add("dialog-accept");
acceptButton.setOnAction(e -> createAccount());
nameField.setOnAction(e -> acceptButton.fire());
setActions(microsoftButton, acceptButton);
}
/// Validates the typed name and creates and selects the offline account.
private void createAccount() {
String name = nameField.getText() == null ? "" : nameField.getText().trim();
if (!NAME_PATTERN.matcher(name).matches()) {
hint.setText(i18n("first_run.invalid"));
return;
}
hint.setText("");
acceptButton.setDisable(true);
Task.supplyAsync(() -> Accounts.FACTORY_OFFLINE.create(
new CreateAccountPane.DialogCharacterSelector(), name, null, null,
new OfflineAccountFactory.AdditionalData(null, null)))
.whenComplete(Schedulers.javafx(), account -> {
if (!Accounts.getAccounts().contains(account)) {
Accounts.getAccounts().add(account);
}
Accounts.setSelectedAccount(account);
fireEvent(new DialogCloseEvent());
}, exception -> {
acceptButton.setDisable(false);
hint.setText(Accounts.localizeErrorMessage(exception));
}).start();
}
@Override
public void onDialogShown() {
nameField.requestFocus();
}
}
@@ -18,6 +18,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.account.CreateAccountPane;
import org.jackhuang.hmcl.ui.account.FirstRunAccountDialog;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
@@ -363,7 +364,7 @@ public final class Instances {
private static void ensureSelectedAccount(Consumer<Account> action) {
Account account = Accounts.getSelectedAccount();
if (account == null) {
CreateAccountPane dialog = new CreateAccountPane();
FirstRunAccountDialog dialog = new FirstRunAccountDialog();
dialog.addEventHandler(DialogCloseEvent.CLOSE, e -> {
Account newAccount = Accounts.getSelectedAccount();
if (newAccount == null) {
@@ -800,6 +800,12 @@ home.not_installed=Not installed
launcher.name=Server Launcher
account.add=Add account
account.open_manage=Manage accounts
first_run.title=Pick a game name
first_run.description=Your name is used when you join the server. You can sign in with a Microsoft account instead.
first_run.name=Game name
first_run.accept=Start playing
first_run.microsoft=Sign in with Microsoft
first_run.invalid=Use 1 to 16 letters, digits or underscores.
server.join=Join Server
instance.launch_and_enter_world=Play World
instance.launch.empty=Start Game
@@ -620,6 +620,12 @@ home.not_installed=未裝
launcher.name=服啟
account.add=增帳
account.open_manage=理帳
first_run.title=署名
first_run.description=入服用此名。可改登微軟之帳。
first_run.name=名
first_run.accept=始
first_run.microsoft=登正版
first_run.invalid=一至十六字,限英文數字底線。
server.join=入服
# instance.launch_and_enter_world=
instance.launch.empty=啟戲
@@ -623,6 +623,12 @@ home.not_installed=尚未安装
launcher.name=服务器启动器
account.add=添加账号
account.open_manage=管理账号
first_run.title=起个游戏名
first_run.description=进服时会用这个名字。想用正版账号的话,可以改为登录微软账号。
first_run.name=游戏名
first_run.accept=开始游戏
first_run.microsoft=登录正版账号
first_run.invalid=只能用 1 到 16 个英文字母、数字或下划线。
server.join=进入服务器
instance.launch_and_enter_world=进入世界
instance.launch.empty=开始游戏
@@ -617,6 +617,12 @@ home.not_installed=尚未安裝
launcher.name=伺服器啟動器
account.add=新增帳號
account.open_manage=管理帳號
first_run.title=取個遊戲名
first_run.description=進服時會使用這個名字。想用正版帳號的話,可以改為登入微軟帳號。
first_run.name=遊戲名
first_run.accept=開始遊戲
first_run.microsoft=登入正版帳號
first_run.invalid=只能使用 1 到 16 個英文字母、數字或底線。
server.join=進入伺服器
instance.launch_and_enter_world=進入世界
instance.launch.empty=開始遊戲