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 34a680130..43b79e803 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -34,15 +34,20 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.authlibinjector.SimpleAuthlibInjectorArtifactProvider; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccountFactory; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftService; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.account.MicrosoftAccountLoginStage; import java.io.IOException; import java.nio.file.Paths; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; @@ -51,6 +56,7 @@ import static java.util.stream.Collectors.toList; import static javafx.collections.FXCollections.observableArrayList; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; +import static org.jackhuang.hmcl.util.Lang.immutableListOf; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; @@ -78,6 +84,8 @@ public final class Accounts { public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE; public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG; public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer); + public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(MicrosoftAccountLoginStage.INSTANCE)); + public static final List> FACTORIES = immutableListOf(FACTORY_OFFLINE, FACTORY_MOJANG, FACTORY_AUTHLIB_INJECTOR, FACTORY_MICROSOFT); // ==== login type / account factory mapping ==== private static final Map> type2factory = new HashMap<>(); @@ -86,6 +94,7 @@ public final class Accounts { type2factory.put("offline", FACTORY_OFFLINE); type2factory.put("yggdrasil", FACTORY_MOJANG); type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR); + type2factory.put("microsoft", FACTORY_MICROSOFT); type2factory.forEach((type, factory) -> factory2type.put(factory, type)); } @@ -108,6 +117,8 @@ public final class Accounts { return FACTORY_AUTHLIB_INJECTOR; else if (account instanceof YggdrasilAccount) return FACTORY_MOJANG; + else if (account instanceof MicrosoftAccount) + return FACTORY_MICROSOFT; else throw new IllegalArgumentException("Failed to determine account type: " + account); } @@ -309,7 +320,8 @@ public final class Accounts { private static Map, String> unlocalizedLoginTypeNames = mapOf( pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"), pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"), - pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector")); + pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"), + pair(Accounts.FACTORY_MICROSOFT, "account.methods.microsoft")); public static String getLocalizedLoginTypeName(AccountFactory factory) { return i18n(Optional.ofNullable(unlocalizedLoginTypeNames.get(factory)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 8407b8128..564294168 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -31,6 +31,7 @@ import org.jackhuang.hmcl.setting.EnumCommonDirectory; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage; +import org.jackhuang.hmcl.ui.account.MicrosoftAccountLoginStage; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.construct.InputDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; @@ -107,6 +108,7 @@ public final class Controllers { Logging.LOG.info("Start initializing application"); Controllers.stage = stage; + MicrosoftAccountLoginStage.INSTANCE.initOwner(stage); stage.setHeight(config().getHeight()); stageHeight.bind(stage.heightProperty()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java index ba4878869..957a692df 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java @@ -17,7 +17,11 @@ */ package org.jackhuang.hmcl.ui; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.concurrent.Worker; import javafx.scene.Scene; +import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; @@ -25,12 +29,19 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.newImage; public class WebStage extends Stage { - private final WebView webView = new WebView(); + protected final WebView webView = new WebView(); + protected final WebEngine webEngine = webView.getEngine(); public WebStage() { - setScene(new Scene(webView, 800, 480)); + this(800, 480); + } + + public WebStage(int width, int height) { + setScene(new Scene(webView, width, height)); getScene().getStylesheets().addAll(config().getTheme().getStylesheets()); getIcons().add(newImage("/assets/img/icon.png")); + webView.setContextMenuEnabled(false); + titleProperty().bind(webEngine.titleProperty()); } public WebView getWebView() { 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 8917c27a6..e0a03614b 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 @@ -73,7 +73,9 @@ public class AccountListItem extends RadioButton { if (account instanceof OfflineAccount) { title.bind(characterName); } else { - title.bind(Bindings.concat(account.getUsername(), " - ", characterName)); + title.bind( + account.getUsername().isEmpty() ? characterName : + Bindings.concat(account.getUsername(), " - ", characterName)); } image.bind(TexturesLoader.fxAvatarBinding(account, 32)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java index 0175791d9..44242c45b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java @@ -68,6 +68,7 @@ public class AddAccountPane extends StackPane { @FXML private JFXPasswordField txtPassword; @FXML private Label lblCreationWarning; @FXML private Label lblPassword; + @FXML private Label lblUsername; @FXML private JFXComboBox> cboType; @FXML private JFXComboBox cboServers; @FXML private Label lblInjectorServer; @@ -88,7 +89,7 @@ public class AddAccountPane extends StackPane { cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer)); selectDefaultServer(); - cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_MOJANG, Accounts.FACTORY_AUTHLIB_INJECTOR); + cboType.getItems().setAll(Accounts.FACTORIES); cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName)); // try selecting the preferred login type cboType.getSelectionModel().select( @@ -108,7 +109,9 @@ public class AddAccountPane extends StackPane { // remember the last used login type loginType.addListener((observable, oldValue, newValue) -> config().setPreferredLoginType(Accounts.getLoginType(newValue))); - txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE)); + txtUsername.visibleProperty().bind(Bindings.createBooleanBinding(() -> loginType.get().getLoginType().requiresUsername, loginType)); + lblUsername.visibleProperty().bind(txtUsername.visibleProperty()); + txtPassword.visibleProperty().bind(Bindings.createBooleanBinding(() -> loginType.get().getLoginType().requiresPassword, loginType)); lblPassword.visibleProperty().bind(txtPassword.visibleProperty()); cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR)); @@ -118,7 +121,7 @@ public class AddAccountPane extends StackPane { btnAccept.disableProperty().bind(Bindings.createBooleanBinding( () -> !( // consider the opposite situation: input is valid - txtUsername.validate() && + (!txtUsername.isVisible() || txtUsername.validate()) && // invisible means the field is not needed, neither should it be validated (!txtPassword.isVisible() || txtPassword.validate()) && (!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginStage.java new file mode 100644 index 000000000..ad3df1b85 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/MicrosoftAccountLoginStage.java @@ -0,0 +1,49 @@ +package org.jackhuang.hmcl.ui.account; + +import javafx.application.Platform; +import javafx.stage.Modality; +import org.jackhuang.hmcl.auth.microsoft.MicrosoftService; +import org.jackhuang.hmcl.ui.WebStage; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class MicrosoftAccountLoginStage extends WebStage implements MicrosoftService.WebViewCallback { + public static final MicrosoftAccountLoginStage INSTANCE = new MicrosoftAccountLoginStage(); + + CompletableFuture future; + Predicate urlTester; + + public MicrosoftAccountLoginStage() { + super(600, 600); + initModality(Modality.APPLICATION_MODAL); + + webEngine.locationProperty().addListener((observable, oldValue, newValue) -> { + if (urlTester != null && urlTester.test(newValue)) { + future.complete(newValue); + hide(); + } + }); + + showingProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue) { + if (future != null) { + future.completeExceptionally(new InterruptedException()); + } + future = null; + urlTester = null; + } + }); + } + + @Override + public CompletableFuture show(MicrosoftService service, Predicate urlTester, String initialURL) { + Platform.runLater(() -> { + webEngine.load(initialURL); + show(); + }); + this.future = new CompletableFuture<>(); + this.urlTester = urlTester; + return future; + } +} diff --git a/HMCL/src/main/resources/assets/fxml/account-add.fxml b/HMCL/src/main/resources/assets/fxml/account-add.fxml index ccc5290b3..c274631a3 100644 --- a/HMCL/src/main/resources/assets/fxml/account-add.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-add.fxml @@ -45,7 +45,7 @@ -