way improved account login dialog

This commit is contained in:
Bixilon 2020-12-22 18:27:31 +01:00
parent d8ce48f102
commit 569b9f4ba4
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
13 changed files with 281 additions and 99 deletions

View File

@ -198,6 +198,7 @@ public final class Minosoft {
config.saveToFile();
return;
}
if (account.needsRefresh()) {
MojangAccount.RefreshStates refreshState = account.refreshToken();
if (refreshState == MojangAccount.RefreshStates.ERROR) {
account.delete();
@ -205,6 +206,7 @@ public final class Minosoft {
selectedAccount = null;
return;
}
}
config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getUserId());
if (Launcher.getMainWindow() != null) {
Launcher.getMainWindow().selectAccount(selectedAccount);

View File

@ -80,6 +80,7 @@ public enum Strings {
LOGIN_DIALOG_TITLE,
LOGIN_DIALOG_HEADER,
LOGIN_ACCOUNT_ALREADY_PRESENT,
MINOSOFT_STILL_STARTING_TITLE,
MINOSOFT_STILL_STARTING_HEADER,

View File

@ -13,27 +13,19 @@
package de.bixilon.minosoft.gui.main;
import com.jfoenix.controls.*;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import de.bixilon.minosoft.util.mojang.api.MojangAccountAuthenticationAttempt;
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Window;
import javafx.stage.Modality;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
@ -55,72 +47,11 @@ public class AccountWindow implements Initializable {
@FXML
public void addAccount() {
JFXAlert<?> dialog = new JFXAlert<>();
dialog.setTitle(LocaleManager.translate(Strings.LOGIN_DIALOG_TITLE));
GUITools.initializePane(dialog.getDialogPane());
JFXDialogLayout layout = new JFXDialogLayout();
layout.setHeading(new Label(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER)));
JFXButton loginButton = new JFXButton(LocaleManager.translate(Strings.BUTTON_LOGIN));
layout.setActions(loginButton);
GridPane gridPane = new GridPane();
gridPane.setHgap(15);
gridPane.setVgap(15);
JFXTextField emailField = new JFXTextField();
emailField.setPromptText(LocaleManager.translate(Strings.EMAIL));
JFXPasswordField passwordField = new JFXPasswordField();
passwordField.setPromptText(LocaleManager.translate(Strings.PASSWORD));
gridPane.add(new Label(LocaleManager.translate(Strings.EMAIL) + ":"), 0, 0);
gridPane.add(emailField, 1, 0);
gridPane.add(new Label(LocaleManager.translate(Strings.PASSWORD) + ":"), 0, 1);
gridPane.add(passwordField, 1, 1);
emailField.textProperty().addListener((observable, oldValue, newValue) -> loginButton.setDisable(newValue.trim().isEmpty()));
loginButton.setDisable(true);
layout.setBody(gridPane);
dialog.setContent(layout);
Platform.runLater(emailField::requestFocus);
loginButton.addEventFilter(ActionEvent.ACTION, event -> {
MojangAccountAuthenticationAttempt attempt = MojangAuthentication.login(emailField.getText(), passwordField.getText());
if (attempt.succeeded()) {
// login okay
MojangAccount account = attempt.getAccount();
Minosoft.getConfig().putMojangAccount(account);
account.saveToConfig();
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
Log.info(String.format("Added and saved account (playerName=%s, email=%s, uuid=%s)", account.getPlayerName(), account.getMojangUserName(), account.getUUID()));
dialog.close();
return;
try {
GUITools.showPane("/layout/dialogs/login_mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_DIALOG_TITLE));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
event.consume();
Label error = new Label();
error.setStyle("-fx-text-fill: red");
error.setText(attempt.getError());
gridPane.add(new Label(LocaleManager.translate(Strings.ERROR)), 0, 2);
gridPane.add(error, 1, 2);
// ToDo resize window
});
Window window = dialog.getDialogPane().getScene().getWindow();
window.setOnCloseRequest(windowEvent -> window.hide());
dialog.getDialogPane().setOnKeyReleased(keyEvent -> {
if (keyEvent.getCode() != KeyCode.ENTER) {
return;
}
if (emailField.getText().trim().isEmpty()) {
return;
}
loginButton.fire();
});
dialog.showAndWait();
}
}

View File

@ -19,12 +19,16 @@ import de.bixilon.minosoft.data.mappings.versions.Versions;
import de.bixilon.minosoft.logging.LogLevels;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
@ -82,4 +86,25 @@ public class GUITools {
initializeScene(pane.getScene());
return pane;
}
public static <T> T showPane(String fxmlPath, Modality modality, String title) throws IOException {
FXMLLoader loader = new FXMLLoader(GUITools.class.getResource(fxmlPath));
Parent root = loader.load();
Stage stage = new Stage();
stage.initModality(modality);
double width = 600;
double height = 400;
if (root instanceof Pane pane) {
width = pane.getPrefWidth();
height = pane.getPrefHeight();
}
Scene scene = new Scene(root, width, height);
stage.setScene(scene);
stage.setTitle(title);
initializeScene(scene);
stage.show();
return loader.getController();
}
}

View File

@ -37,8 +37,6 @@ import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@ -48,7 +46,6 @@ import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
@ -418,15 +415,8 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
public void manageSessions() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/layout/sessions.fxml"));
Parent parent = loader.load();
((SessionsWindow) loader.getController()).setServer(this.server);
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle(LocaleManager.translate(Strings.SESSIONS_DIALOG_TITLE, this.server.getName()));
stage.setScene(new Scene(parent));
GUITools.initializeScene(stage.getScene());
stage.show();
SessionsWindow sessionsWindow = GUITools.showPane("/layout/dialogs/login_mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.SESSIONS_DIALOG_TITLE, this.server.getName()));
sessionsWindow.setServer(this.server);
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -0,0 +1,144 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.main.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXTextField;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.gui.main.AccountListCell;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import de.bixilon.minosoft.util.mojang.api.MojangAccountAuthenticationAttempt;
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
public class MojangLoginController implements Initializable {
public HBox hBox;
public Label header;
public Label emailLabel;
public JFXTextField email;
public Label passwordLabel;
public JFXPasswordField password;
public JFXButton loginButton;
public Label errorMessage;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// translate
this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER));
this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL));
this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD));
this.loginButton.setText(LocaleManager.translate(Strings.BUTTON_LOGIN));
this.email.textProperty().addListener(this::checkData);
this.password.textProperty().addListener(this::checkData);
this.hBox.setOnKeyReleased(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (this.loginButton.isDisable()) {
return;
}
this.loginButton.fire();
return;
}
if (keyEvent.getCode() == KeyCode.ESCAPE) {
close();
}
});
}
public void login(ActionEvent event) {
event.consume();
this.email.setDisable(true);
this.password.setDisable(true);
this.loginButton.setDisable(true);
this.errorMessage.setVisible(false);
new Thread(() -> { // ToDo: recycle thread
MojangAccountAuthenticationAttempt attempt = MojangAuthentication.login(this.email.getText(), this.password.getText());
if (attempt.succeeded()) {
// login okay
final MojangAccount account = attempt.getAccount();
if (Minosoft.getConfig().getAccountList().containsValue(account)) {
// account already present
// replace access token
MojangAccount existingAccount = Minosoft.getConfig().getAccountList().get(account.getUserId());
existingAccount.setAccessToken(attempt.getAccount().getAccessToken());
existingAccount.saveToConfig();
Platform.runLater(() -> {
this.errorMessage.setText(LocaleManager.translate(Strings.LOGIN_ACCOUNT_ALREADY_PRESENT));
this.errorMessage.setVisible(true);
this.email.setDisable(false);
this.password.setDisable(false);
this.loginButton.setDisable(true);
});
return;
}
Minosoft.getConfig().putMojangAccount(account);
account.saveToConfig();
Log.info(String.format("Added and saved account (playerName=%s, email=%s, uuid=%s)", account.getPlayerName(), account.getMojangUserName(), account.getUUID()));
Platform.runLater(() -> {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
close();
});
if (Minosoft.getSelectedAccount() == null) {
// select account
Minosoft.selectAccount(account);
}
return;
}
Platform.runLater(() -> {
this.errorMessage.setText(attempt.getError());
this.errorMessage.setVisible(true);
this.email.setDisable(false);
this.password.setDisable(false);
this.loginButton.setDisable(true);
});
}, "AccountLoginThread").start();
}
private void checkData(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
if (newValue.isEmpty()) {
this.loginButton.setDisable(true);
return;
}
this.loginButton.setDisable(this.email.getText().isBlank() || this.password.getText().isBlank());
}
public void close() {
getStage().close();
}
public Stage getStage() {
return ((Stage) this.hBox.getScene().getWindow());
}
}

View File

@ -26,6 +26,7 @@ public class MojangAccount {
final String mojangUserName;
String accessToken;
RefreshStates lastRefreshStatus;
private boolean needsRefresh = true;
public MojangAccount(String username, JsonObject json) {
this.accessToken = json.get("accessToken").getAsString();
@ -137,6 +138,15 @@ public class MojangAccount {
return account.getUserId().equals(getUserId());
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
this.needsRefresh = false;
}
public boolean needsRefresh() {
return this.needsRefresh;
}
public enum RefreshStates {
SUCCESSFUL,
ERROR, // account not valid anymore

View File

@ -56,6 +56,7 @@ public final class MojangAuthentication {
return new MojangAccountAuthenticationAttempt(new MojangAccount(username, jsonResponse));
}
public static void joinServer(MojangAccount account, String serverId) {
if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) {
return;

View File

@ -54,8 +54,9 @@
"SETTINGS_GENERAL": "Allgemein",
"SETTINGS_GENERAL_LOG_LEVEL": "Log Level",
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Anmelden - Minosoft",
"LOGIN_DIALOG_HEADER": "Bitte gib deine Minecraft Zugangsdaten ein dich anzumelden",
"LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account existiert bereits!",
"ERROR": "Fehler",
"MINOSOFT_STILL_STARTING_TITLE": "Bitte warten",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...",

View File

@ -55,8 +55,9 @@
"SETTINGS_GENERAL": "General",
"SETTINGS_GENERAL_LOG_LEVEL": "Log level",
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Login - Minosoft",
"LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Please login to your mojang account",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account is already present!",
"ERROR": "Error",
"MINOSOFT_STILL_STARTING_TITLE": "Please wait",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXPasswordField?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.MojangLoginController">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<GridPane HBox.hgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
</rowConstraints>
<Label fx:id="header" text="Please login to your mojang account" textAlignment="CENTER" GridPane.halignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
<font>
<Font size="23.0"/>
</font>
</Label>
<GridPane GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="NEVER"/>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
</rowConstraints>
<Label fx:id="emailLabel" text="EMAIL">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</Label>
<JFXTextField fx:id="email" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</JFXTextField>
<Label fx:id="passwordLabel" text="PASSWORD" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</Label>
<JFXPasswordField fx:id="password" minWidth="-Infinity" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</JFXPasswordField>
</GridPane>
<JFXButton fx:id="loginButton" disable="true" maxHeight="Infinity" maxWidth="Infinity" mnemonicParsing="false" text="LOGIN" GridPane.rowIndex="3" onAction="#login">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
<font>
<Font name="System Bold" size="31.0"/>
</font>
</JFXButton>
<Label fx:id="errorMessage" styleClass="error" text="Error" textAlignment="CENTER" textFill="RED" visible="false" wrapText="true" GridPane.halignment="CENTER" GridPane.rowIndex="2"/>
</GridPane>
</HBox>

View File

@ -37,7 +37,7 @@
</Menu>
</MenuBar>
<AnchorPane VBox.vgrow="ALWAYS" styleClass="anchor-pane">
<ScrollPane fitToHeight="true" fitToWidth="true" layoutX="14.0" layoutY="14.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="700.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<ScrollPane fitToHeight="true" fitToWidth="true" layoutX="14.0" layoutY="14.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<BorderPane fx:id="serversPane"/>
</ScrollPane>
</AnchorPane>

View File

@ -201,3 +201,7 @@
.obfuscated {
-fx-font-family: "Hack";
}
.error {
-fx-text-fill: red;
}