diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/FirstRunAccountDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/FirstRunAccountDialog.java new file mode 100644 index 0000000000..700f371ac6 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/FirstRunAccountDialog.java @@ -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(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java index 95b3aeb5ea..82f5c21e1d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java @@ -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 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) { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 394aefad29..df25926b4c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -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 diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index 1183659a44..e3f0351091 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -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=啟戲 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties index 741c830f17..bc10bf1a87 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties @@ -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=开始游戏 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties index e1f50043cd..060a6f6c52 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties @@ -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=開始遊戲