From 4bee9b1b7211cd716241d78a222a0d49a0e86cd9 Mon Sep 17 00:00:00 2001 From: Ciilu <109708109+Ciilu@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:00:09 +0800 Subject: [PATCH 1/2] update --- .../hmcl/ui/account/AccountListItem.java | 27 ++++++++++++++----- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 65bbabb6a8..6ea974ba0f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -34,29 +34,32 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; +import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; 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.DialogController; +import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.skin.InvalidSkinException; -import org.jackhuang.hmcl.util.skin.NormalizedSkin; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicReference; import static java.util.Collections.emptySet; import static javafx.beans.binding.Bindings.createBooleanBinding; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class AccountListItem extends RadioButton { @@ -153,6 +156,20 @@ public Task uploadSkin() { return null; } + AtomicReference model = new AtomicReference<>(); + MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("account.skin.model.select"), i18n("account.skin.upload"), MessageDialogPane.MessageType.QUESTION); + builder.addAction(i18n("account.skin.model.default"), () -> model.set(TextureModel.WIDE)); + builder.addAction(i18n("account.skin.model.slim"), () -> model.set(TextureModel.SLIM)); + builder.addCancel(null); + + Controllers.dialog(builder.build()); + + TextureModel selectedModel = model.get(); + + if (selectedModel == null) { + return null; + } + return refreshAsync() .thenRunAsync(() -> { Image skinImg; @@ -164,10 +181,8 @@ public Task uploadSkin() { if (skinImg.isError()) { throw new InvalidSkinException("Failed to read skin image", skinImg.getException()); } - NormalizedSkin skin = new NormalizedSkin(skinImg); - String model = skin.isSlim() ? "slim" : ""; - LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]"); - account.uploadSkin(skin.isSlim(), selectedFile); + LOG.info("Uploading skin [" + selectedFile + "], model [" + selectedModel.modelName + "]"); + account.uploadSkin(Objects.equals(TextureModel.SLIM, selectedModel), selectedFile); }) .thenComposeAsync(refreshAsync()) .whenComplete(Schedulers.javafx(), e -> { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index aea95ae01f..4d5190f9c6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -152,6 +152,7 @@ account.skin.file=Skin File account.skin.model=Model account.skin.model.default=Classic account.skin.model.slim=Slim +account.skin.model.select=The model of the skin you chose is? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin account.skin.type.csl_api.location=Address diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index a4e00d7c17..c3d3c398f1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -155,6 +155,7 @@ account.skin.file=外觀圖片檔案 account.skin.model=模型 account.skin.model.default=寬型 account.skin.model.slim=纖細 +account.skin.model.select=你選取的外觀模型是? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin 伺服器 account.skin.type.csl_api.location=伺服器位址 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 0907753754..c41b2d50ca 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -163,6 +163,7 @@ account.skin.file=皮肤图片文件 account.skin.model=模型 account.skin.model.default=宽型 account.skin.model.slim=纤细 +account.skin.model.select=你选择的皮肤模型是? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin 服务器 account.skin.type.csl_api.location=服务器地址 From 76d70c3a8547783ce47f755035c993b41df1ee47 Mon Sep 17 00:00:00 2001 From: Ciilu <109708109+Ciilu@users.noreply.github.com> Date: Fri, 2 Jan 2026 22:06:09 +0800 Subject: [PATCH 2/2] update --- .../hmcl/ui/account/AccountListItem.java | 96 +++++++++++-------- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 6ea974ba0f..35fc756f34 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -41,10 +41,12 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.skin.InvalidSkinException; +import org.jackhuang.hmcl.util.skin.NormalizedSkin; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -54,7 +56,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CompletableFuture; import static java.util.Collections.emptySet; import static javafx.beans.binding.Bindings.createBooleanBinding; @@ -76,9 +78,7 @@ public AccountListItem(Account account) { String portableSuffix = account.isPortable() ? ", " + i18n("account.portable") : ""; if (account instanceof AuthlibInjectorAccount) { AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer(); - subtitle.bind(Bindings.concat( - loginTypeName, ", ", i18n("account.injector.server"), ": ", - Bindings.createStringBinding(server::getName, server), portableSuffix)); + subtitle.bind(Bindings.concat(loginTypeName, ", ", i18n("account.injector.server"), ": ", Bindings.createStringBinding(server::getName, server), portableSuffix)); } else { subtitle.set(loginTypeName + portableSuffix); } @@ -87,9 +87,7 @@ public AccountListItem(Account account) { if (account instanceof OfflineAccount) { title.bind(characterName); } else { - title.bind( - account.getUsername().isEmpty() ? characterName : - Bindings.concat(account.getUsername(), " - ", characterName)); + title.bind(account.getUsername().isEmpty() ? characterName : Bindings.concat(account.getUsername(), " - ", characterName)); } } @@ -123,9 +121,7 @@ public ObservableBooleanValue canUploadSkin() { if (account instanceof AuthlibInjectorAccount aiAccount) { ObjectBinding> profile = aiAccount.getYggdrasilService().getProfileRepository().binding(aiAccount.getUUID()); return createBooleanBinding(() -> { - Set uploadableTextures = profile.get() - .map(AuthlibInjectorAccount::getUploadableTextures) - .orElse(emptySet()); + Set uploadableTextures = profile.get().map(AuthlibInjectorAccount::getUploadableTextures).orElse(emptySet()); return uploadableTextures.contains(TextureType.SKIN); }, profile); } else if (account instanceof OfflineAccount || account.canUploadSkin()) { @@ -156,40 +152,60 @@ public Task uploadSkin() { return null; } - AtomicReference model = new AtomicReference<>(); - MessageDialogPane.Builder builder = new MessageDialogPane.Builder(i18n("account.skin.model.select"), i18n("account.skin.upload"), MessageDialogPane.MessageType.QUESTION); - builder.addAction(i18n("account.skin.model.default"), () -> model.set(TextureModel.WIDE)); - builder.addAction(i18n("account.skin.model.slim"), () -> model.set(TextureModel.SLIM)); - builder.addCancel(null); + return refreshAsync().thenSupplyAsync(() -> { + Image skinImg; + try (var input = Files.newInputStream(selectedFile)) { + skinImg = new Image(input); + } catch (IOException e) { + throw new InvalidSkinException("Failed to read skin image", e); + } + if (skinImg.isError()) { + throw new InvalidSkinException("Failed to read skin image", skinImg.getException()); + } - Controllers.dialog(builder.build()); + return new NormalizedSkin(skinImg); + }).thenApplyAsync(Schedulers.javafx(), (normalizedSkin) -> { + boolean isSlim = normalizedSkin.isSlim(); + + CompletableFuture modelFuture = new CompletableFuture<>(); + + FXUtils.runInFX(() -> { + MessageDialogPane.Builder builder = new MessageDialogPane.Builder( + i18n("account.skin.model.select"), + i18n("account.skin.upload"), + MessageDialogPane.MessageType.QUESTION + ); + + if (isSlim) { + builder.addAction(i18n("account.skin.model.slim") + " " + i18n("account.skin.model.auto"), + () -> modelFuture.complete(TextureModel.SLIM)); + builder.addAction(i18n("account.skin.model.default"), + () -> modelFuture.complete(TextureModel.WIDE)); + } else { + builder.addAction(i18n("account.skin.model.default") + " " + i18n("account.skin.model.auto"), + () -> modelFuture.complete(TextureModel.WIDE)); + builder.addAction(i18n("account.skin.model.slim"), + () -> modelFuture.complete(TextureModel.SLIM)); + } - TextureModel selectedModel = model.get(); + builder.addCancel(() -> modelFuture.complete(null)); + Controllers.dialog(builder.build()); + }); - if (selectedModel == null) { + return modelFuture; + }).thenApplyAsync((future) -> { + if (future == null) { + return null; + } + TextureModel model = future.get(); + LOG.info("Uploading skin [" + selectedFile + "], model [" + model.modelName + "]"); + account.uploadSkin(Objects.equals(TextureModel.SLIM, model), selectedFile); return null; - } - - return refreshAsync() - .thenRunAsync(() -> { - Image skinImg; - try (var input = Files.newInputStream(selectedFile)) { - skinImg = new Image(input); - } catch (IOException e) { - throw new InvalidSkinException("Failed to read skin image", e); - } - if (skinImg.isError()) { - throw new InvalidSkinException("Failed to read skin image", skinImg.getException()); - } - LOG.info("Uploading skin [" + selectedFile + "], model [" + selectedModel.modelName + "]"); - account.uploadSkin(Objects.equals(TextureModel.SLIM, selectedModel), selectedFile); - }) - .thenComposeAsync(refreshAsync()) - .whenComplete(Schedulers.javafx(), e -> { - if (e != null) { - Controllers.dialog(Accounts.localizeErrorMessage(e), i18n("account.skin.upload.failed"), MessageType.ERROR); - } - }); + }).thenComposeAsync(refreshAsync()).whenComplete(Schedulers.javafx(), e -> { + if (e != null) { + Controllers.dialog(Accounts.localizeErrorMessage(e), i18n("account.skin.upload.failed"), MessageType.ERROR); + } + }); } public void remove() { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 122c8e5a90..01b61569ed 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -153,6 +153,7 @@ account.skin.file=Skin File account.skin.model=Model account.skin.model.default=Classic account.skin.model.slim=Slim +account.skin.model.auto=(Auto) account.skin.model.select=The model of the skin you chose is? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 473ac84ce5..042814867b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -156,6 +156,7 @@ account.skin.file=外觀圖片檔案 account.skin.model=模型 account.skin.model.default=寬型 account.skin.model.slim=纖細 +account.skin.model.auto=(自動識別) account.skin.model.select=你選取的外觀模型是? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin 伺服器 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 25a0cbc827..bb213a165a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -164,6 +164,7 @@ account.skin.file=皮肤图片文件 account.skin.model=模型 account.skin.model.default=宽型 account.skin.model.slim=纤细 +account.skin.model.auto=(自动识别) account.skin.model.select=你选择的皮肤模型是? account.skin.type.alex=Alex account.skin.type.csl_api=Blessing Skin 服务器