diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 2cd40af11..607de7bcf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -23,6 +23,7 @@ import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; @@ -30,8 +31,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.NetworkUtils; @@ -41,8 +40,10 @@ import java.io.IOException; import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; import static org.jackhuang.hmcl.util.Lang.mapOf; +import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; /** @@ -58,7 +59,7 @@ public final class Accounts { public static final Map> ACCOUNT_FACTORY = mapOf( pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE), pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)), - pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector)) + pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector, Accounts::getOrCreateAuthlibInjectorServer)) ); private static final Map AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>(); @@ -104,7 +105,23 @@ public final class Accounts { } } - public static TaskResult getAuthlibInjectorServerNameAsync(AuthlibInjectorAccount account) { - return Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(account.getServerBaseURL())); + private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) { + return Settings.SETTINGS.authlibInjectorServers.stream() + .filter(server -> url.equals(server.getUrl())) + .findFirst() + .orElseGet(() -> { + // this usually happens when migrating data from an older version + String name; + try { + name = Accounts.getAuthlibInjectorServerName(url); + LOG.info("Migrated authlib injector server [" + url + "], name=[" + name + "]"); + } catch (Exception e) { + name = url; + LOG.log(Level.WARNING, "Failed to migrate authlib injector server [" + url + "]", e); + } + AuthlibInjectorServer server = new AuthlibInjectorServer(url, name); + Settings.SETTINGS.authlibInjectorServers.add(server); + return server; + }); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 744815f7a..d295404d3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -17,11 +17,12 @@ */ package org.jackhuang.hmcl.setting; -import java.util.HashSet; import java.util.Map; import java.util.TreeMap; import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; + import com.google.gson.annotations.SerializedName; import javafx.beans.property.BooleanProperty; @@ -35,7 +36,6 @@ import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; -import javafx.collections.ObservableSet; public final class Config implements Cloneable { @@ -99,8 +99,7 @@ public final class Config implements Cloneable { @SerializedName("logLines") public final IntegerProperty logLines = new SimpleIntegerProperty(100); - @SerializedName("authlibInjectorServerURLs") - public final ObservableSet authlibInjectorServerURLs = FXCollections.observableSet(new HashSet<>()); + public final ObservableList authlibInjectorServers = FXCollections.observableArrayList(); @Override public Config clone() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java index eb22f9607..5582bb567 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java @@ -20,14 +20,18 @@ package org.jackhuang.hmcl.setting; import org.jackhuang.hmcl.Launcher; public final class Profiles { + + public static final String DEFAULT_PROFILE = "Default"; + public static final String HOME_PROFILE = "Home"; + private Profiles() { } public static String getProfileDisplayName(Profile profile) { switch (profile.getName()) { - case Settings.DEFAULT_PROFILE: + case Profiles.DEFAULT_PROFILE: return Launcher.i18n("profile.default"); - case Settings.HOME_PROFILE: + case Profiles.HOME_PROFILE: return Launcher.i18n("profile.home"); default: return profile.getName(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index 713ad792e..6003265ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.setting; import com.google.gson.Gson; import com.google.gson.GsonBuilder; + import javafx.beans.property.ObjectProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; @@ -35,6 +36,7 @@ import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.event.*; import org.jackhuang.hmcl.task.Schedulers; @@ -51,7 +53,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; +import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; import static org.jackhuang.hmcl.util.Lang.tryCast; +import static org.jackhuang.hmcl.util.Logging.LOG; public class Settings { public static final Gson GSON = new GsonBuilder() @@ -65,15 +70,12 @@ public class Settings { .setPrettyPrinting() .create(); - public static final String DEFAULT_PROFILE = "Default"; - public static final String HOME_PROFILE = "Home"; - public static final String SETTINGS_FILE_NAME = "hmcl.json"; public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile(); - public static final Settings INSTANCE = new Settings(); + public static final Config SETTINGS = initSettings(); - private final Config SETTINGS = initSettings(); + public static final Settings INSTANCE = new Settings(); private final Map accounts = new ConcurrentHashMap<>(); @@ -84,7 +86,7 @@ public class Settings { Map settings = iterator.next(); AccountFactory factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse("")); if (factory == null) { - // unrecognized account type, so remove it. + LOG.warning("Unrecognized account type, removing: " + settings); iterator.remove(); continue; } @@ -93,7 +95,7 @@ public class Settings { try { account = factory.fromStorage(settings, getProxy()); } catch (Exception e) { - // storage is malformed, delete. + LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e); iterator.remove(); continue; } @@ -101,7 +103,8 @@ public class Settings { accounts.put(Accounts.getAccountId(account), account); } - checkAuthlibInjectorAccounts(); + SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts)); + checkProfileMap(); save(); @@ -115,7 +118,7 @@ public class Settings { Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save))); } - private Config initSettings() { + private static Config initSettings() { Config c = new Config(); if (SETTINGS_FILE.exists()) try { @@ -305,30 +308,17 @@ public class Settings { * AUTHLIB INJECTORS * ****************************************/ - public Set getAuthlibInjectorServerURLs() { - return SETTINGS.authlibInjectorServerURLs; - } - - public void removeAuthlibInjectorServerURL(String serverURL) { - SETTINGS.authlibInjectorServerURLs.remove(serverURL); - - checkAuthlibInjectorAccounts(); - save(); - } - - public void addAuthlibInjectorServerURL(String serverURL) { - SETTINGS.authlibInjectorServerURLs.add(serverURL); - save(); - } - - private void checkAuthlibInjectorAccounts() { - for (Account account : getAccounts()) { - if (account instanceof AuthlibInjectorAccount) { - AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account; - if (!SETTINGS.authlibInjectorServerURLs.contains(injectorAccount.getServerBaseURL())) - deleteAccount(account); - } - } + /** + * After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed. + * This method performs a check and removes the dangling accounts. + * Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost. + */ + private void removeDanglingAuthlibInjectorAccounts() { + accounts.values().stream() + .filter(AuthlibInjectorAccount.class::isInstance) + .filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer())) + .collect(toList()) + .forEach(this::deleteAccount); } /**************************************** @@ -561,8 +551,8 @@ public class Settings { private void checkProfileMap() { if (getProfileMap().isEmpty()) { - getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE)); - getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY)); + getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE)); + getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY)); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java index 18d5a96c0..af9cedf5e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java @@ -34,7 +34,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.game.AccountHelper; -import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; @@ -80,8 +79,7 @@ public class AccountPage extends StackPane implements DecoratorPage { FXUtils.setLimitWidth(this, 300); if (account instanceof AuthlibInjectorAccount) { - Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account) - .subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName"))); + lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName()); FXUtils.setLimitHeight(this, 182); } else { componentList.removeChildren(paneServer); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java index 91e711129..7fc653ed2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java @@ -33,6 +33,7 @@ import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; @@ -46,16 +47,17 @@ import org.jackhuang.hmcl.ui.animation.TransitionHandler; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.IconedItem; import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Logging; -import java.util.Collection; +import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory; +import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; + import java.util.List; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class AddAccountPane extends StackPane { @@ -64,7 +66,7 @@ public class AddAccountPane extends StackPane { @FXML private Label lblCreationWarning; @FXML private Label lblPassword; @FXML private JFXComboBox cboType; - @FXML private JFXComboBox cboServers; + @FXML private JFXComboBox cboServers; @FXML private Label lblInjectorServer; @FXML private Hyperlink linkManageInjectorServers; @FXML private JFXDialogLayout layout; @@ -82,7 +84,13 @@ public class AddAccountPane extends StackPane { transitionHandler = new TransitionHandler(acceptPane); acceptPane.getChildren().setAll(btnAccept); - loadServers(); + cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl()))); + cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName)); + cboServers.setItems(Settings.INSTANCE.SETTINGS.authlibInjectorServers); + + // workaround: otherwise the combox will be black + if (!cboServers.getItems().isEmpty()) + cboServers.getSelectionModel().select(0); cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector")); cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> { @@ -95,10 +103,6 @@ public class AddAccountPane extends StackPane { }); cboType.getSelectionModel().select(0); - // These two lines can eliminate black, don't know why. - cboServers.getItems().setAll(new TwoLineListItem("", "")); - cboServers.getSelectionModel().select(0); - txtPassword.setOnAction(e -> onCreationAccept()); txtUsername.setOnAction(e -> onCreationAccept()); txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@"))); @@ -111,24 +115,6 @@ public class AddAccountPane extends StackPane { btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate())); } - private void loadServers() { - Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() - .flatMap(serverURL -> { - try { - return Stream.of(new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL)); - } catch (Exception e) { - Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e); - return Stream.empty(); - } - }) - .collect(Collectors.toList())) - .subscribe(Task.of(Schedulers.javafx(), variables -> { - cboServers.getItems().setAll(variables.>get("list")); - if (!cboServers.getItems().isEmpty()) - cboServers.getSelectionModel().select(0); - })); - } - private void showSpinner() { transitionHandler.setContent(spinnerAccept, ContainerAnimations.FADE.getAnimationProducer()); } @@ -139,34 +125,51 @@ public class AddAccountPane extends StackPane { @FXML private void onCreationAccept() { - int type = cboType.getSelectionModel().getSelectedIndex(); String username = txtUsername.getText(); String password = txtPassword.getText(); - String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null); + Object addtionalData; + + int type = cboType.getSelectionModel().getSelectedIndex(); + AccountFactory factory; + switch (type) { + case 0: + factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); + addtionalData = null; + break; + case 1: + factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); + addtionalData = null; + break; + case 2: + factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); + Optional server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()); + if (server.isPresent()) { + addtionalData = server.get(); + } else { + lblCreationWarning.setText(Launcher.i18n("account.failed.no_selected_server")); + return; + } + break; + default: + throw new Error(); + } + showSpinner(); lblCreationWarning.setText(""); - Task.ofResult("create_account", () -> { - AccountFactory factory; - switch (type) { - case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break; - case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break; - case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break; - default: throw new Error(); - } - return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy()); - }).finalized(Schedulers.javafx(), variables -> { - Settings.INSTANCE.addAccount(variables.get("create_account")); - hideSpinner(); - finalization.accept(this); - }, exception -> { - if (exception instanceof NoSelectedCharacterException) { - finalization.accept(this); - } else { - lblCreationWarning.setText(accountException(exception)); - } - hideSpinner(); - }).start(); + Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy())) + .finalized(Schedulers.javafx(), variables -> { + Settings.INSTANCE.addAccount(variables.get("create_account")); + hideSpinner(); + finalization.accept(this); + }, exception -> { + if (exception instanceof NoSelectedCharacterException) { + finalization.accept(this); + } else { + lblCreationWarning.setText(accountException(exception)); + } + hideSpinner(); + }).start(); } @FXML @@ -224,13 +227,13 @@ public class AddAccountPane extends StackPane { image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy()); } catch (Exception e) { Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e); - image = FXUtils.DEFAULT_ICON; + image = null; } ImageView portraitView = new ImageView(); portraitView.setSmooth(false); - if (image == FXUtils.DEFAULT_ICON) - portraitView.setImage(FXUtils.DEFAULT_ICON); - else { + if (image == null) { + portraitView.setImage(Constants.DEFAULT_ICON.get()); + } else { portraitView.setImage(image); portraitView.setViewport(AccountHelper.getViewport(4)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java index 09df2e5fb..f5c874dfc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java @@ -23,19 +23,19 @@ import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.setting.Theme; import java.util.function.Consumer; public final class AuthlibInjectorServerItem extends BorderPane { - private final AuthlibInjectorServerInfo info; + private final AuthlibInjectorServer server; private final Label lblServerName = new Label(); private final Label lblServerIp = new Label(); - public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer deleteCallback) { - this.info = info; + public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer deleteCallback) { + this.server = server; lblServerName.setStyle("-fx-font-size: 15;"); lblServerIp.setStyle("-fx-font-size: 10;"); @@ -54,11 +54,11 @@ public final class AuthlibInjectorServerItem extends BorderPane { setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); JFXDepthManager.setDepth(this, 1); - lblServerName.setText(info.getServerName()); - lblServerIp.setText(info.getServerIp()); + lblServerName.setText(server.getName()); + lblServerIp.setText(server.getUrl()); } - public AuthlibInjectorServerInfo getInfo() { - return info; + public AuthlibInjectorServer getServer() { + return server; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java index dd5e20352..05d2309d4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java @@ -4,13 +4,12 @@ import com.jfoenix.controls.*; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Launcher; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Schedulers; @@ -18,35 +17,32 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionHandler; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; -import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.NetworkUtils; -import java.util.Collection; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import static java.util.stream.Collectors.toList; public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("account.injector.server")); @FXML private ScrollPane scrollPane; @FXML private StackPane addServerContainer; - @FXML private Label lblServerIp; + @FXML private Label lblServerUrl; @FXML private Label lblServerName; @FXML private Label lblCreationWarning; @FXML private Label lblServerWarning; @FXML private VBox listPane; - @FXML private JFXTextField txtServerIp; + @FXML private JFXTextField txtServerUrl; @FXML private JFXDialogLayout addServerPane; @FXML private JFXDialogLayout confirmServerPane; @FXML private JFXDialog dialog; @FXML private StackPane contentPane; - @FXML private JFXSpinner spinner; @FXML private JFXProgressBar progressBar; @FXML private JFXButton btnAddNext; private final TransitionHandler transitionHandler; + private AuthlibInjectorServer serverBeingAdded; + { FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml"); FXUtils.smoothScrolling(scrollPane); @@ -55,49 +51,33 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa getChildren().remove(dialog); dialog.setDialogContainer(this); - txtServerIp.textProperty().addListener((a, b, newValue) -> - btnAddNext.setDisable(!txtServerIp.validate())); + txtServerUrl.textProperty().addListener((a, b, newValue) -> + btnAddNext.setDisable(!txtServerUrl.validate())); - loading(); + reload(); } private void removeServer(AuthlibInjectorServerItem item) { - Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp()); - loading(); + Settings.INSTANCE.SETTINGS.authlibInjectorServers.remove(item.getServer()); + reload(); } - private void loading() { - getChildren().remove(contentPane); - spinner.setVisible(true); - - Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() - .flatMap(serverURL -> { - try { - return Stream.of(new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer)); - } catch (Exception e) { - Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e); - return Stream.empty(); - } - }) - .collect(Collectors.toList())) - .subscribe(Task.of(Schedulers.javafx(), variables -> { - listPane.getChildren().setAll(variables.>get("list")); - loadingCompleted(); - })); - } - - private void loadingCompleted() { - getChildren().add(contentPane); - spinner.setVisible(false); - - if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty()) + private void reload() { + listPane.getChildren().setAll( + Settings.INSTANCE.SETTINGS.authlibInjectorServers.stream() + .map(server -> new AuthlibInjectorServerItem(server, this::removeServer)) + .collect(toList())); + if (Settings.INSTANCE.SETTINGS.authlibInjectorServers.isEmpty()) { onAdd(); + } } @FXML private void onAdd() { transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); - txtServerIp.setText(""); + txtServerUrl.setText(""); + txtServerUrl.resetValidation(); + lblCreationWarning.setText(""); addServerPane.setDisable(false); progressBar.setVisible(false); dialog.show(); @@ -110,26 +90,28 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa @FXML private void onAddNext() { - String serverIp = txtServerIp.getText(); + String url = fixInputUrl(txtServerUrl.getText()); + progressBar.setVisible(true); addServerPane.setDisable(true); - Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(serverIp)) - .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { - progressBar.setVisible(false); - addServerPane.setDisable(false); + Task.of(() -> { + serverBeingAdded = new AuthlibInjectorServer(url, Accounts.getAuthlibInjectorServerName(url)); + }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + progressBar.setVisible(false); + addServerPane.setDisable(false); - if (isDependentsSucceeded) { - lblServerName.setText(variables.get("serverName")); - lblServerIp.setText(txtServerIp.getText()); + if (isDependentsSucceeded) { + lblServerName.setText(serverBeingAdded.getName()); + lblServerUrl.setText(serverBeingAdded.getUrl()); - lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverIp).getProtocol())); - - transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); - } else - lblCreationWarning.setText(variables.get("lastException").getLocalizedMessage()); - }).start(); + lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol())); + transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); + } else { + lblCreationWarning.setText(variables.get("lastException").getLocalizedMessage()); + } + }).start(); } @@ -140,11 +122,10 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa @FXML private void onAddFinish() { - String ip = txtServerIp.getText(); - if (!ip.endsWith("/")) - ip += "/"; - Settings.INSTANCE.addAuthlibInjectorServerURL(ip); - loading(); + if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) { + Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded); + } + reload(); dialog.close(); } @@ -160,4 +141,11 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa public void setTitle(String title) { this.title.set(title); } + + private String fixInputUrl(String url) { + if (!url.endsWith("/")) { + url += "/"; + } + return url; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index ef3659ab4..27a2e9aa0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -23,6 +23,7 @@ import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.property.Property; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -30,18 +31,22 @@ import javafx.beans.value.WeakChangeListener; import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.scene.Node; +import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.ScrollBar; import javafx.scene.control.ScrollPane; import javafx.scene.control.Tooltip; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; import javafx.scene.layout.Region; import javafx.scene.shape.Rectangle; +import javafx.util.Callback; import javafx.util.Duration; +import javafx.util.StringConverter; + import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.util.*; import java.io.File; @@ -52,6 +57,7 @@ import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.logging.Level; @@ -68,6 +74,10 @@ public final class FXUtils { } } + public static InvalidationListener onInvalidating(Runnable action) { + return arg -> action.run(); + } + public static void onChange(ObservableValue value, Consumer consumer) { value.addListener((a, b, c) -> consumer.accept(c)); } @@ -402,7 +412,32 @@ public final class FXUtils { } } - public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png"); + public static StringConverter stringConverter(Function func) { + return new StringConverter() { + + @Override + public String toString(T object) { + return object == null ? "" : func.apply(object); + } + + @Override + public T fromString(String string) { + throw new UnsupportedOperationException(); + } + }; + } + + public static Callback, ListCell> jfxListCellFactory(Function graphicBuilder) { + return view -> new JFXListCell() { + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (!empty) { + setGraphic(graphicBuilder.apply(item)); + } + } + }; + } public static final Interpolator SINE = new Interpolator() { @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java index 749d59aef..8ed51b8ad 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java @@ -186,9 +186,9 @@ public final class LeftPaneController { } else item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4)); - if (account instanceof AuthlibInjectorAccount) - Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account) - .subscribe(Schedulers.javafx(), variables -> FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(variables.get("serverName")))); + if (account instanceof AuthlibInjectorAccount) { + FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName())); + } if (selectedAccount == account) ripplerContainer.setSelected(true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java index 1a70f0853..162d30e5c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java @@ -264,7 +264,7 @@ public final class VersionSettingsController { if (iconFile.exists()) iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath())); else - iconPickerItem.setImage(FXUtils.DEFAULT_ICON); + iconPickerItem.setImage(Constants.DEFAULT_ICON.get()); FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32); } } diff --git a/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml b/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml index e37c03188..e5b4715bd 100644 --- a/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml +++ b/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml @@ -8,7 +8,6 @@ - @@ -32,7 +31,7 @@