Compare commits
No commits in common. "main" and "JCash_1_5_0_RC_1" have entirely different histories.
main
...
JCash_1_5_
|
@ -4,8 +4,6 @@
|
|||
|
||||
Draft program for the Java class of semester 2. The goal was to simulate basic cash machine that can read customer data from a `.csv` file and let the user view the data with crude forms of authentication.
|
||||
|
||||
> This project was graded with 100,0 out of 100,0 points
|
||||
|
||||
## Overview
|
||||
|
||||
The program can read `.csv` file from disk and allows the user login to an account specified.
|
||||
|
|
|
@ -7,8 +7,6 @@ import java.time.LocalDateTime;
|
|||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.logging.*;
|
||||
|
||||
import static java.nio.file.Path.of;
|
||||
|
||||
/**
|
||||
* Utility class for providing a global logger for the entire application instance at runtime
|
||||
*/
|
||||
|
@ -47,7 +45,7 @@ public final class Logging {
|
|||
* @param level the level to set the handler to
|
||||
*/
|
||||
private static void createConsoleLogger(Level level) {
|
||||
ConsoleHandler ch = new ConsoleHandler();
|
||||
var ch = new ConsoleHandler();
|
||||
ch.setLevel(level);
|
||||
LOGGER.addHandler(ch);
|
||||
}
|
||||
|
@ -62,15 +60,15 @@ public final class Logging {
|
|||
*/
|
||||
private static void createFileLogger(Level level) {
|
||||
// setup log file name
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
|
||||
String logFileName = LOG_FOLDER_NAME + dateTime + ".log";
|
||||
var now = LocalDateTime.now();
|
||||
var dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
|
||||
var logFileName = LOG_FOLDER_NAME + dateTime + ".log";
|
||||
|
||||
// setup the folder for the logs
|
||||
initializeLogFolder();
|
||||
|
||||
try {
|
||||
FileHandler fh = new FileHandler(logFileName);
|
||||
var fh = new FileHandler(logFileName);
|
||||
fh.setLevel(level);
|
||||
LOGGER.addHandler(fh);
|
||||
} catch (Exception e) {
|
||||
|
@ -83,7 +81,7 @@ public final class Logging {
|
|||
* If the folder does not exist, the function will create a new folder.
|
||||
*/
|
||||
private static void initializeLogFolder() {
|
||||
Path folderPath = of(LOG_FOLDER_NAME);
|
||||
var folderPath = Path.of(LOG_FOLDER_NAME);
|
||||
|
||||
if (Files.isDirectory(folderPath))
|
||||
return;
|
||||
|
|
|
@ -2,18 +2,19 @@ package me.teridax.jcash;
|
|||
|
||||
import me.teridax.jcash.gui.IconProvider;
|
||||
import me.teridax.jcash.gui.Loader;
|
||||
import me.teridax.jcash.gui.MainFrame;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
import me.teridax.jcash.gui.account.AccountController;
|
||||
import me.teridax.jcash.gui.login.LoginController;
|
||||
import me.teridax.jcash.lang.Locales;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.Logging.LOGGER;
|
||||
import static me.teridax.jcash.Logging.initializeSystemLogger;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
public final class Main {
|
||||
|
||||
|
@ -23,56 +24,29 @@ public final class Main {
|
|||
private static Main instance;
|
||||
|
||||
/**
|
||||
* Primary class for controlling GUI of this application
|
||||
* Primary window of this program
|
||||
*/
|
||||
private final MainFrame window;
|
||||
private final JFrame window;
|
||||
|
||||
private Main() {
|
||||
this.window = new MainFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user a dialog to select a file to load the database from.
|
||||
* If a valid database has been read a login screen will be shown.
|
||||
* If no file was selected or the database was invalid the application will close.
|
||||
*/
|
||||
public void loadDatabase() {
|
||||
try {
|
||||
var bms = Loader.load();
|
||||
this.window.setBms(bms);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Failed to load database: " + e.getMessage());
|
||||
Utils.error("Failed to load database");
|
||||
System.exit(1);
|
||||
}
|
||||
// create main window and set defaults
|
||||
this.window = new JFrame();
|
||||
this.window.setTitle(translate("Cashmachine"));
|
||||
this.window.setLocationByPlatform(true);
|
||||
this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
this.window.setIconImage(IconProvider.getWindowIcon());
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
initializeSystemLogger(Level.FINE);
|
||||
|
||||
setPlatformDependingTheme();
|
||||
|
||||
loadExtraFont();
|
||||
|
||||
Locales.autodetectDefaultLocale();
|
||||
|
||||
setPlatformDependingTheme();
|
||||
|
||||
// create main instance and show the login screen
|
||||
instance();
|
||||
getInstance().loadDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the extra font file used on the login button
|
||||
*/
|
||||
private static void loadExtraFont() {
|
||||
try {
|
||||
var font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(IconProvider.class.getResourceAsStream("res/Circus.ttf")));
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
ge.registerFont(font);
|
||||
} catch (IOException | FontFormatException | NullPointerException e) {
|
||||
LOGGER.warning("Could not load font file: " + e.getMessage());
|
||||
}
|
||||
getInstance().showLoginScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,16 +109,53 @@ public final class Main {
|
|||
Main.instance = new Main();
|
||||
}
|
||||
|
||||
public JFrame getWindow() {
|
||||
return this.window.getWindow();
|
||||
/**
|
||||
* Shows the open dialog for selecting a database file. After selection, it then proceeds to prompt login.
|
||||
* Afterward the selected account can be managed.
|
||||
* This method is non-blocking and all work described is performed asynchronously on the AWT Event dispatcher.
|
||||
*/
|
||||
public void showLoginScreen() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
LOGGER.finer("showing login screen");
|
||||
|
||||
try {
|
||||
// select db file
|
||||
var path = Loader.load();
|
||||
// read database and login
|
||||
var login = new LoginController(path);
|
||||
|
||||
// when we have logged in set the account viewer as window content
|
||||
login.addAccountSelectionListener(account -> {
|
||||
LOGGER.finer("account selected: " + Objects.toString(account, "null"));
|
||||
|
||||
var profileCont = new AccountController(account, login.getData().getBms());
|
||||
this.window.setContentPane(profileCont.getView());
|
||||
this.window.revalidate();
|
||||
this.window.repaint();
|
||||
});
|
||||
|
||||
// we are not logged in yet, so show the login prompt on the main window
|
||||
this.window.setContentPane(login.getView());
|
||||
this.window.setSize(800, 600);
|
||||
this.window.setVisible(true);
|
||||
|
||||
} catch (IllegalStateException e) {
|
||||
LOGGER.fine("Unable to show login mask: " + e.getMessage());
|
||||
showMessageDialog(null, e.getMessage(), translate("Closing JCash"), ERROR_MESSAGE);
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out of the currently open account.
|
||||
* This will show the login mask and clear the password field or the previous
|
||||
* login attempt.
|
||||
* Logs the user out of the database, hiding the main window.
|
||||
*/
|
||||
public void logout() {
|
||||
this.window.logout();
|
||||
window.setContentPane(new JLabel(translate("you're logged out")));
|
||||
window.setVisible(false);
|
||||
}
|
||||
|
||||
public JFrame getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@ import me.teridax.jcash.Logging;
|
|||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.banking.accounts.Owner;
|
||||
import me.teridax.jcash.banking.management.Profile;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
|
@ -5,8 +5,6 @@ import me.teridax.jcash.decode.StringDecoder;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* Base class for bank accounts.
|
||||
* Stores the iban, pin and balance.
|
||||
|
@ -113,7 +111,7 @@ public abstract class Account {
|
|||
* @return a basic description of the account in form a string
|
||||
*/
|
||||
public String getDescription() {
|
||||
return String.format("%s (%s)", iban, translate(getClass().getSimpleName()));
|
||||
return String.format("%s (%s)", iban, getClass().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,8 +6,6 @@ import me.teridax.jcash.banking.accounts.Owner;
|
|||
import me.teridax.jcash.decode.StringDecoder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
@ -25,13 +23,6 @@ public final class BankingManagementSystem {
|
|||
* Separator used to separate columns of CSV files
|
||||
*/
|
||||
private static final String SEPARATOR = ";";
|
||||
/**
|
||||
* Charsets to try when decoding the source file
|
||||
*/
|
||||
private static final Charset[] ENCODINGS = {
|
||||
StandardCharsets.UTF_8,
|
||||
Charset.forName("windows-1252")
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of banks
|
||||
|
@ -85,7 +76,7 @@ public final class BankingManagementSystem {
|
|||
LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null"));
|
||||
try {
|
||||
var bms = new BankingManagementSystem();
|
||||
var content = getSource(file);
|
||||
var content = Files.readString(file);
|
||||
|
||||
// read line by line
|
||||
// and skip the first line
|
||||
|
@ -123,33 +114,15 @@ public final class BankingManagementSystem {
|
|||
|
||||
return bms;
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Could not read file: " + file + " due to: " + e.getMessage());
|
||||
throw new IllegalArgumentException("Could not read file " + file, e);
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage());
|
||||
throw new IllegalArgumentException("Could not parse file " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read the entire file into a string.
|
||||
* This method tires out all encodings in {@link #ENCODINGS}
|
||||
* @param file the file to read
|
||||
* @throws IllegalArgumentException if the file cannot be read
|
||||
* @return the content of the file
|
||||
*/
|
||||
private static String getSource(Path file) throws IllegalArgumentException {
|
||||
Exception lastException = null;
|
||||
for (var encoding : ENCODINGS) {
|
||||
try {
|
||||
return Files.readString(file, encoding);
|
||||
} catch (IOException e) {
|
||||
LOGGER.severe("Could not read file: " + file + " due to: " + e.getMessage());
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
assert lastException != null;
|
||||
throw new IllegalArgumentException("Invalid encoding, or IO exception: " + lastException.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a bank with the given blz.
|
||||
*
|
||||
|
|
|
@ -2,7 +2,6 @@ package me.teridax.jcash.decode;
|
|||
|
||||
import me.teridax.jcash.lang.Locales;
|
||||
|
||||
import javax.swing.text.NumberFormatter;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Objects;
|
||||
|
@ -18,35 +17,6 @@ public class StringDecoder {
|
|||
return NumberFormat.getInstance(Locales.getDefaultLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a NumberFormatter for parsing double values in the appropriate locale.
|
||||
* @return the number formatter
|
||||
*/
|
||||
public static NumberFormatter getNumberFormatter(double maxValue) {
|
||||
var formatter = new NumberFormatter();
|
||||
formatter.setValueClass(Double.class);
|
||||
formatter.setMinimum(0d);
|
||||
formatter.setMaximum(maxValue);
|
||||
formatter.setAllowsInvalid(true);
|
||||
formatter.setCommitsOnValidEdit(true);
|
||||
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a NumberFormatter for parsing integer values in the appropriate locale.
|
||||
* @return the number formatter
|
||||
*/
|
||||
public static NumberFormatter getIntegerNumberFormatter() {
|
||||
var formatter = new NumberFormatter();
|
||||
formatter.setValueClass(Integer.class);
|
||||
formatter.setMinimum(0d);
|
||||
formatter.setAllowsInvalid(true);
|
||||
formatter.setCommitsOnValidEdit(true);
|
||||
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to convert the given string into a double value representing a percentage.
|
||||
* The output value will be in the range [0, 100]. Strings formatted without a percentage
|
||||
|
@ -147,7 +117,7 @@ public class StringDecoder {
|
|||
|
||||
var trimmed = name.trim();
|
||||
|
||||
var pattern = Pattern.compile("[^\\d]+", Pattern.CASE_INSENSITIVE);
|
||||
var pattern = Pattern.compile("[^\\s]+", Pattern.CASE_INSENSITIVE);
|
||||
var matcher = pattern.matcher(trimmed);
|
||||
if (matcher.find()) {
|
||||
return matcher.group();
|
||||
|
|
|
@ -7,28 +7,14 @@ import java.util.Objects;
|
|||
|
||||
import static me.teridax.jcash.Logging.LOGGER;
|
||||
|
||||
/**
|
||||
* Static class for providing the capabilities to load images from file.
|
||||
*/
|
||||
public class IconProvider {
|
||||
|
||||
private static final Image DEFAULT_IMAGE = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
/**
|
||||
* Fetches the windows icon.
|
||||
* @return the windows icon
|
||||
*/
|
||||
public static Image getWindowIcon() {
|
||||
return loadIcon("res/register.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specified image from disk.
|
||||
* If the file cannot be made into an image because its corrupted or the file cannot be read,
|
||||
* the default image is returned {@link #DEFAULT_IMAGE}
|
||||
* @param path the path to the image
|
||||
* @return the specified image or {@link #DEFAULT_IMAGE}
|
||||
*/
|
||||
private static Image loadIcon(String path) {
|
||||
try {
|
||||
var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path));
|
||||
|
@ -39,12 +25,4 @@ public class IconProvider {
|
|||
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the background image used for the login screen
|
||||
* @return login screen background image
|
||||
*/
|
||||
public static Image getBackground() {
|
||||
return loadIcon("res/background.png");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package me.teridax.jcash.gui;
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown when some user input is invalid
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class InvalidInputException extends IllegalStateException {
|
||||
|
||||
public InvalidInputException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InvalidInputException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidInputException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package me.teridax.jcash.gui;
|
||||
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
import me.teridax.jcash.gui.account.AccountController;
|
||||
import me.teridax.jcash.gui.login.LoginController;
|
||||
import me.teridax.jcash.lang.Locales;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import static me.teridax.jcash.Logging.LOGGER;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
public class MainFrame {
|
||||
|
||||
/**
|
||||
* Constant used to identify the login screen on the cardlayout
|
||||
*/
|
||||
private static final String LOGIN_SCREEN_STRING_IDENT = "LoginScreen";
|
||||
/**
|
||||
* Constant used to identify the profile screen on the cardlayout
|
||||
*/
|
||||
private static final String PROFILE_SCREEN_STRING_IDENT = "ProfileScreen";
|
||||
/**
|
||||
* Version of this application
|
||||
*/
|
||||
private static final String VERSION = "v2.1.0";
|
||||
|
||||
/**
|
||||
* Primary window of this program
|
||||
*/
|
||||
private final JFrame window;
|
||||
/**
|
||||
* Primary layout of this application
|
||||
*/
|
||||
private final CardLayout layout;
|
||||
|
||||
/**
|
||||
* Database containing every bank, account and owner available
|
||||
*/
|
||||
private BankingManagementSystem bms;
|
||||
|
||||
private LoginController loginMask;
|
||||
private AccountController accountController;
|
||||
|
||||
public MainFrame() {
|
||||
// create main window and set defaults
|
||||
this.window = new JFrame();
|
||||
this.window.setTitle(translate("Cashmachine") + getInfoString());
|
||||
this.window.setLocationByPlatform(true);
|
||||
this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
this.window.setIconImage(IconProvider.getWindowIcon());
|
||||
|
||||
this.layout = new CardLayout();
|
||||
this.window.getContentPane().setLayout(this.layout);
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns a general information string about this application
|
||||
* @return the locale and the current version as string
|
||||
*/
|
||||
private String getInfoString() {
|
||||
return " locale: [" + Locales.getDefaultLocale().toString() + "] " + VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the GUI components of login screen and profile view
|
||||
*/
|
||||
private void initialize() {
|
||||
// create the login mask
|
||||
|
||||
this.loginMask = new LoginController();
|
||||
|
||||
// when we have logged in set the account viewer as window content
|
||||
this.loginMask.addAccountSelectionListener(account -> {
|
||||
LOGGER.finer("account selected: " + Objects.toString(account, "null"));
|
||||
accountController.setProfile(account, bms);
|
||||
layout.show(window.getContentPane(), PROFILE_SCREEN_STRING_IDENT);
|
||||
});
|
||||
|
||||
this.window.getContentPane().add(loginMask.getView(), LOGIN_SCREEN_STRING_IDENT);
|
||||
|
||||
// create the account viewer
|
||||
|
||||
this.accountController = new AccountController();
|
||||
this.window.getContentPane().add(accountController.getView(), PROFILE_SCREEN_STRING_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the BMS of this application to use for the GUI.
|
||||
* This method will show the login screen to the user
|
||||
* @param bms the BMS to use for the GUI
|
||||
*/
|
||||
public void setBms(BankingManagementSystem bms) {
|
||||
this.bms = bms;
|
||||
this.loginMask.setBankingManagementSystem(bms);
|
||||
this.showLoginScreen();
|
||||
this.window.pack();
|
||||
this.window.setResizable(false);
|
||||
this.window.setLocationRelativeTo(null);
|
||||
this.window.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the open dialog for selecting a database file. After selection, it then proceeds to prompt login.
|
||||
* Afterward the selected account can be managed.
|
||||
* This method is non-blocking and all work described is performed asynchronously on the AWT Event dispatcher.
|
||||
*/
|
||||
private void showLoginScreen() {
|
||||
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out of the database, hiding the main window.
|
||||
*/
|
||||
public void logout() {
|
||||
this.loginMask.logout();
|
||||
this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
|
||||
}
|
||||
|
||||
public JFrame getWindow() {
|
||||
return this.window;
|
||||
}
|
||||
}
|
|
@ -1,25 +1,12 @@
|
|||
package me.teridax.jcash.gui;
|
||||
|
||||
import me.teridax.jcash.Main;
|
||||
import me.teridax.jcash.lang.Locales;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.text.Format;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* Formats the string so that it will be displayed as a Heading 1 element by JLabels.
|
||||
* This embeds the given string into two html tags.
|
||||
* Note that eny html entities in the string will be formatted as valid HTML entities.
|
||||
* Meaning they won't show up in as plain text.
|
||||
* @param title the title to format.
|
||||
* @return the given string embedded into <pre><html><h1>$string</h1></html></pre>
|
||||
*/
|
||||
public static String addHeading(String title) {
|
||||
return String.format("<html><h1>%s</h1></html>", title);
|
||||
return String.format("<html><title>%s</title></html>", title);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,9 +27,6 @@ public class Utils {
|
|||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
target.add(new JLabel(name, SwingConstants.RIGHT), constraints);
|
||||
|
||||
if (comp == null)
|
||||
return;
|
||||
|
||||
constraints.gridx = 2;
|
||||
constraints.gridy = row;
|
||||
constraints.weightx = 1;
|
||||
|
@ -74,13 +58,4 @@ public class Utils {
|
|||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
target.add(comp, constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an error message dialog. This function will block the calling thread until the error message
|
||||
* disposed.
|
||||
* @param message the message to show to the user
|
||||
*/
|
||||
public static void error(String message) {
|
||||
JOptionPane.showMessageDialog(Main.getInstance().getWindow(), message, "Error occurred", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import me.teridax.jcash.Logging;
|
|||
import me.teridax.jcash.Main;
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
import me.teridax.jcash.banking.management.Profile;
|
||||
import me.teridax.jcash.gui.deposit.DepositController;
|
||||
import me.teridax.jcash.gui.takeoff.TakeoffController;
|
||||
import me.teridax.jcash.gui.transfer.TransferController;
|
||||
import me.teridax.jcash.gui.deposit.DepositDialog;
|
||||
import me.teridax.jcash.gui.takeoff.TakeoffDialog;
|
||||
import me.teridax.jcash.gui.transfer.TransferDialog;
|
||||
|
||||
/**
|
||||
* Controller for controlling the gui of an account.
|
||||
|
@ -18,68 +18,35 @@ public class AccountController {
|
|||
*/
|
||||
private final AccountView view;
|
||||
|
||||
private Profile profile;
|
||||
private final Profile profile;
|
||||
|
||||
public AccountController() {
|
||||
this.view = new AccountView();
|
||||
this.data = new AccountData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the profile and BMS used to manage banking.
|
||||
* @param profile the profile used to manage the account
|
||||
* @param bms the BMS used access other banking accounts
|
||||
*/
|
||||
public void setProfile(Profile profile, BankingManagementSystem bms) {
|
||||
public AccountController(Profile profile, BankingManagementSystem bms) {
|
||||
this.profile = profile;
|
||||
this.view = new AccountView();
|
||||
this.view.setProfile(profile);
|
||||
this.data.setBms(bms);
|
||||
this.createListeners();
|
||||
this.data = new AccountData(bms);
|
||||
|
||||
createListeners(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create listeners for GUI components
|
||||
*/
|
||||
private void createListeners() {
|
||||
private void createListeners(Profile profile) {
|
||||
this.view.getAccountSelection().addActionListener(e -> changeAccount());
|
||||
|
||||
this.view.getLogout().addActionListener(e -> logout());
|
||||
this.view.getDeposit().addActionListener(e -> depositMoney());
|
||||
this.view.getTakeoff().addActionListener(e -> takeoffMoney());
|
||||
this.view.getTransfer().addActionListener(e -> transferMoney());
|
||||
}
|
||||
|
||||
/**
|
||||
* Open dialog to deposit money
|
||||
*/
|
||||
private void depositMoney() {
|
||||
new DepositController(profile.getPrimaryAccount());
|
||||
this.view.updateAccountVariables(profile);
|
||||
}
|
||||
this.view.getDeposit().addActionListener(e -> new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
|
||||
|
||||
/**
|
||||
* Open dialog to transfer money
|
||||
*/
|
||||
private void transferMoney() {
|
||||
new TransferController(profile.getPrimaryAccount(), data.getBms());
|
||||
this.view.updateAccountVariables(profile);
|
||||
}
|
||||
this.view.getTakeoff().addActionListener(e -> new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
|
||||
|
||||
/**
|
||||
* Open dialog to take off money
|
||||
*/
|
||||
private void takeoffMoney() {
|
||||
new TakeoffController(profile.getPrimaryAccount());
|
||||
this.view.updateAccountVariables(profile);
|
||||
this.view.getTransfer().addActionListener(e -> new TransferDialog(profile.getPrimaryAccount(), data.getBms(), () -> this.view.setProfile(profile)));
|
||||
}
|
||||
|
||||
private void logout() {
|
||||
Logging.LOGGER.fine("Logging out of account");
|
||||
Main.getInstance().logout();
|
||||
Main.getInstance().showLoginScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the selected account.
|
||||
*/
|
||||
private void changeAccount() {
|
||||
var description = ((String) this.view.getAccountSelection().getSelectedItem());
|
||||
Logging.LOGGER.fine("Changing primary account selected: " + description);
|
||||
|
|
|
@ -2,18 +2,15 @@ package me.teridax.jcash.gui.account;
|
|||
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
|
||||
/**
|
||||
* Data storage class for account management
|
||||
*/
|
||||
public class AccountData {
|
||||
|
||||
private BankingManagementSystem bms;
|
||||
private final BankingManagementSystem bms;
|
||||
|
||||
public AccountData(BankingManagementSystem bms) {
|
||||
this.bms = bms;
|
||||
}
|
||||
|
||||
public BankingManagementSystem getBms() {
|
||||
return bms;
|
||||
}
|
||||
|
||||
public void setBms(BankingManagementSystem bms) {
|
||||
this.bms = bms;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package me.teridax.jcash.gui.account;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.banking.accounts.CurrentAccount;
|
||||
import me.teridax.jcash.banking.accounts.SavingsAccount;
|
||||
import me.teridax.jcash.banking.management.Profile;
|
||||
|
@ -9,8 +8,6 @@ import me.teridax.jcash.decode.StringDecoder;
|
|||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import static javax.swing.SwingConstants.RIGHT;
|
||||
import static me.teridax.jcash.gui.Utils.addGridBagRow;
|
||||
|
@ -40,19 +37,38 @@ public class AccountView extends JPanel {
|
|||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* The profile to manage via the GUI.
|
||||
* @param profile the profile to manage
|
||||
*/
|
||||
public void setProfile(Profile profile) {
|
||||
this.updateAccountVariables(profile);
|
||||
Logging.LOGGER.finer("Changing profile of account view");
|
||||
var bank = profile.getBank();
|
||||
var account = profile.getPrimaryAccount();
|
||||
var owner = profile.getOwner();
|
||||
|
||||
this.blz.setText(bank.getBlz());
|
||||
this.bankName.setText(bank.getName());
|
||||
|
||||
this.iban.setText(String.valueOf(account.getIban()));
|
||||
this.name.setText(owner.getName() + " " + owner.getFamilyName());
|
||||
this.address.setText(owner.getStreet() + " " + owner.getCity());
|
||||
|
||||
this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + " €");
|
||||
|
||||
this.type.setText(translate(account.getClass().getSimpleName()));
|
||||
if (account instanceof CurrentAccount) {
|
||||
this.typeSpecialLabel.setText(translate("Overdraft"));
|
||||
this.typeSpecialProperty.setText(StringDecoder.getNumberFormat().format(((CurrentAccount) account).getOverdraft()) + " €");
|
||||
} else if (account instanceof SavingsAccount) {
|
||||
this.typeSpecialLabel.setText(translate("Interest rate"));
|
||||
this.typeSpecialProperty.setText(((SavingsAccount) account).getInterest() + " %");
|
||||
} else {
|
||||
Logging.LOGGER.severe("Type of new primary account cannot be determined: " + account.getClass().getName());
|
||||
}
|
||||
|
||||
this.accountSelection.removeAllItems();
|
||||
|
||||
var accounts = profile.getAccounts();
|
||||
Arrays.stream(accounts).sorted(Comparator.comparingInt(Account::getIban)).forEach(a -> this.accountSelection.addItem(a.getDescription()));
|
||||
|
||||
this.accountSelection.setSelectedItem(profile.getPrimaryAccount().getDescription());
|
||||
for (var otherAccount : profile.getAccounts()) {
|
||||
this.accountSelection.addItem(otherAccount.getDescription());
|
||||
}
|
||||
this.accountSelection.setSelectedItem(account.getDescription());
|
||||
}
|
||||
|
||||
private void createLayout() {
|
||||
|
@ -140,40 +156,4 @@ public class AccountView extends JPanel {
|
|||
public JButton getTakeoff() {
|
||||
return takeoff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the accessible class fields of the primary account
|
||||
* into the text fields. Also updates the combo box for
|
||||
* all associated accounts.
|
||||
* @param profile the profile to update from
|
||||
*/
|
||||
public void updateAccountVariables(Profile profile) {
|
||||
Logging.LOGGER.finer("Updating account view");
|
||||
// temporarily extract data
|
||||
var bank = profile.getBank();
|
||||
var account = profile.getPrimaryAccount();
|
||||
var owner = profile.getOwner();
|
||||
|
||||
this.blz.setText(bank.getBlz());
|
||||
this.bankName.setText(bank.getName());
|
||||
|
||||
this.iban.setText(String.valueOf(account.getIban()));
|
||||
this.name.setText(owner.getName() + " " + owner.getFamilyName());
|
||||
this.address.setText(owner.getStreet() + " " + owner.getCity());
|
||||
|
||||
this.balance.setText(StringDecoder.getNumberFormat().format(account.getBalance()) + " €");
|
||||
|
||||
// update account type specific fields
|
||||
|
||||
this.type.setText(translate(account.getClass().getSimpleName()));
|
||||
if (account instanceof CurrentAccount) {
|
||||
this.typeSpecialLabel.setText(translate("Overdraft"));
|
||||
this.typeSpecialProperty.setText(StringDecoder.getNumberFormat().format(((CurrentAccount) account).getOverdraft()) + " €");
|
||||
} else if (account instanceof SavingsAccount) {
|
||||
this.typeSpecialLabel.setText(translate("Interest rate"));
|
||||
this.typeSpecialProperty.setText(((SavingsAccount) account).getInterest() + " %");
|
||||
} else {
|
||||
Logging.LOGGER.severe("Type of new primary account cannot be determined: " + account.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
package me.teridax.jcash.gui.deposit;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Class for controlling the deposit operation via a dialog.
|
||||
*/
|
||||
public class DepositController {
|
||||
|
||||
private final DepositView view;
|
||||
/**
|
||||
* Account to deposit money to.
|
||||
*/
|
||||
private final Account account;
|
||||
|
||||
public DepositController(Account account) {
|
||||
this.account = account;
|
||||
|
||||
this.view = new DepositView(account.getBalance());
|
||||
|
||||
this.view.getDeposit().addActionListener(e -> depositMoney());
|
||||
this.view.getCancel().addActionListener(e -> view.dispose());
|
||||
this.view.getValue().getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
/**
|
||||
* Validate the amount to deposit and update display
|
||||
* variables.
|
||||
*/
|
||||
private void validateInputState() {
|
||||
var balance = account.getBalance();
|
||||
try {
|
||||
view.getValue().commitEdit();
|
||||
var amount = view.getAmount();
|
||||
view.setCommittedValue(amount, balance + amount);
|
||||
view.getDeposit().setEnabled(true);
|
||||
} catch (InvalidInputException | ParseException ex) {
|
||||
view.setCommittedValue(0, balance);
|
||||
view.getDeposit().setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
});
|
||||
this.view.showDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deposit the last valid value to the account.
|
||||
* This method may display error dialogs when no money can be deposited.
|
||||
*/
|
||||
private void depositMoney() {
|
||||
try {
|
||||
var amount = view.getAmount();
|
||||
Logging.LOGGER.fine("Depositing money of account: " + account.getIban() + " amount: " + amount);
|
||||
account.deposit(amount);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logging.LOGGER.severe("Cannot deposit money of account: " + account.getIban() + " because: " + ex.getMessage());
|
||||
Utils.error(ex.getMessage());
|
||||
} catch (InvalidInputException ex) {
|
||||
Utils.error(ex.getMessage());
|
||||
}
|
||||
view.dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package me.teridax.jcash.gui.deposit;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
|
||||
public class DepositDialog {
|
||||
|
||||
private final DepositView view;
|
||||
private final Account account;
|
||||
private final Runnable onDeposit;
|
||||
|
||||
public DepositDialog(Account account, Runnable onDeposit) {
|
||||
this.account = account;
|
||||
this.onDeposit = onDeposit;
|
||||
|
||||
this.view = new DepositView();
|
||||
|
||||
this.view.getDeposit().addActionListener(e -> depositMoney());
|
||||
this.view.getCancel().addActionListener(e -> view.dispose());
|
||||
this.view.showDialog();
|
||||
}
|
||||
|
||||
private void depositMoney() {
|
||||
var amount = view.getAmount();
|
||||
|
||||
Logging.LOGGER.fine("Depositing money of account: " + account.getIban() + " amount: " + amount);
|
||||
|
||||
try {
|
||||
account.deposit(amount);
|
||||
onDeposit.run();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logging.LOGGER.severe("Cannot deposit money of account: " + account.getIban() + " because: " + ex.getMessage());
|
||||
} finally {
|
||||
view.dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,41 +3,23 @@ package me.teridax.jcash.gui.deposit;
|
|||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.decode.StringDecoder;
|
||||
import me.teridax.jcash.gui.IconProvider;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* View class for displaying a dialog prompting the user to
|
||||
* enter a valid amount to deposit at their account
|
||||
*/
|
||||
public class DepositView {
|
||||
|
||||
/**
|
||||
* Window to use
|
||||
*/
|
||||
private JDialog dialog;
|
||||
private JButton cancel;
|
||||
/**
|
||||
* Button for applying the deposit operation
|
||||
*/
|
||||
private JButton deposit;
|
||||
/**
|
||||
* Displays the validated value to deposit
|
||||
*/
|
||||
private JLabel enteredValue;
|
||||
/**
|
||||
* Displays the account balance after the deposit operation
|
||||
*/
|
||||
private JLabel balanceAfterDeposit;
|
||||
private JFormattedTextField value;
|
||||
|
||||
public DepositView(double maxValue) {
|
||||
createComponents(maxValue);
|
||||
public DepositView() {
|
||||
createComponents();
|
||||
layoutComponents();
|
||||
}
|
||||
|
||||
|
@ -60,103 +42,71 @@ public class DepositView {
|
|||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.weightx = 1;
|
||||
c.weighty = 1;
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.CENTER;
|
||||
c.insets = new Insets(4, 4, 4, 4);
|
||||
dialog.getContentPane().add(new JLabel(translate("Deposit money")), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0;
|
||||
c.insets = new Insets(6, 6, 6, 6);
|
||||
dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(value, c);
|
||||
|
||||
c.gridx = 2;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LINE_START;
|
||||
c.weightx = 0;
|
||||
dialog.getContentPane().add(new JLabel("€"), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(new JLabel(translate("Value to deposit:"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(enteredValue, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(new JLabel(translate("Balance after deposit:"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(balanceAfterDeposit, c);
|
||||
|
||||
var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
|
||||
buttonPanel.add(cancel);
|
||||
buttonPanel.add(deposit);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 3;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.insets = new Insets(10, 10, 10, 10);
|
||||
dialog.getContentPane().add(buttonPanel, c);
|
||||
}
|
||||
|
||||
private void createComponents(double maxValue) {
|
||||
private void createComponents() {
|
||||
this.dialog = new JDialog();
|
||||
|
||||
this.cancel = new JButton(translate("Cancel"));
|
||||
this.deposit = new JButton(translate("Deposit"));
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(Double.MAX_VALUE));
|
||||
this.enteredValue = new JLabel();
|
||||
this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
|
||||
|
||||
this.deposit.setEnabled(false);
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
|
||||
|
||||
this.dialog.setContentPane(new JPanel(new GridBagLayout()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of money that should be deposited
|
||||
* This value derives from the input of the user.
|
||||
* @return the value to deposit
|
||||
* @throws InvalidInputException if the user entered something invalid
|
||||
*/
|
||||
public double getAmount() throws InvalidInputException {
|
||||
public double getAmount() {
|
||||
if (value.getText().isBlank())
|
||||
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
|
||||
return 0;
|
||||
|
||||
try {
|
||||
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
|
||||
return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
|
||||
} catch (ParseException e) {
|
||||
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
|
||||
throw new InvalidInputException(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JFormattedTextField getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public JButton getCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
@ -168,14 +118,4 @@ public class DepositView {
|
|||
public void dispose() {
|
||||
this.dialog.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the supplied amount to the preview GUI fields.
|
||||
* @param amount the value to display for value to deposit
|
||||
* @param after the value to display for balance after deposit
|
||||
*/
|
||||
public void setCommittedValue(double amount, double after) {
|
||||
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
|
||||
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@ package me.teridax.jcash.gui.login;
|
|||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Optional;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
public class LoginController {
|
||||
|
||||
private final LoginView view;
|
||||
|
@ -14,17 +17,13 @@ public class LoginController {
|
|||
|
||||
private AccountSelectionListener listener;
|
||||
|
||||
public LoginController() {
|
||||
public LoginController(BankingManagementSystem bms) {
|
||||
this.view = new LoginView();
|
||||
this.data = new LoginData();
|
||||
this.data = new LoginData(bms);
|
||||
|
||||
addActionListeners();
|
||||
}
|
||||
|
||||
public void setBankingManagementSystem(BankingManagementSystem bms) {
|
||||
this.data.setBms(bms);
|
||||
}
|
||||
|
||||
private void addActionListeners() {
|
||||
this.view.getLogin().addActionListener(this::login);
|
||||
}
|
||||
|
@ -53,13 +52,15 @@ public class LoginController {
|
|||
var blz = this.view.getBlz().getText();
|
||||
var iban = this.getIban();
|
||||
if (iban.isEmpty()) {
|
||||
Utils.error("invalid IBAN entered");
|
||||
Logging.LOGGER.severe("IBAN is invalid: " + iban);
|
||||
showMessageDialog(null, translate("Invalid IBAN"), translate("Faulty login attempt"), ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
var pin = this.getPin();
|
||||
if (pin.isEmpty()) {
|
||||
Utils.error("invalid PIN entered");
|
||||
Logging.LOGGER.severe("PIN is invalid: " + pin);
|
||||
showMessageDialog(null, translate("Invalid pin"), translate("Faulty login attempt"), ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -67,8 +68,9 @@ public class LoginController {
|
|||
if (account.isPresent()) {
|
||||
this.listener.onAccountSelected(account.get());
|
||||
} else {
|
||||
Logging.LOGGER.warning("invalid login credentials: " + iban + " / " + pin);
|
||||
}
|
||||
Logging.LOGGER.severe("invalid login credentials: " + iban + " / " + pin);
|
||||
showMessageDialog(null, translate("Invalid login credentials"), translate("Faulty login attempt"), ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAccountSelectionListener(AccountSelectionListener listener) {
|
||||
|
@ -82,8 +84,4 @@ public class LoginController {
|
|||
public LoginData getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
this.view.getPin().setText("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package me.teridax.jcash.gui.login;
|
|||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
import me.teridax.jcash.banking.management.Profile;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -12,7 +11,11 @@ import java.util.Optional;
|
|||
*/
|
||||
public class LoginData {
|
||||
|
||||
private BankingManagementSystem bms;
|
||||
private final BankingManagementSystem bms;
|
||||
|
||||
public LoginData(BankingManagementSystem bms) {
|
||||
this.bms = bms;
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticate the specified account with the provided pin.
|
||||
|
@ -24,29 +27,15 @@ public class LoginData {
|
|||
*/
|
||||
public Optional<Profile> authenticateAccount(String blz, int iban, int pin) {
|
||||
Logging.LOGGER.info("Authenticating account " + iban);
|
||||
|
||||
var optionalBank = bms.getBank(blz);
|
||||
if (optionalBank.isEmpty()) {
|
||||
Utils.error("Unknown BLZ: " + blz);
|
||||
if (optionalBank.isEmpty())
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var profile = optionalBank.get().getAccount(iban);
|
||||
if (profile.isEmpty()) {
|
||||
Utils.error("Unknown account: " + iban);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var account = profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
|
||||
if (account.isEmpty()) {
|
||||
Utils.error("PIN does not match");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return account;
|
||||
return profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
|
||||
}
|
||||
|
||||
public void setBms(BankingManagementSystem bms) {
|
||||
this.bms = bms;
|
||||
public BankingManagementSystem getBms() {
|
||||
return bms;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package me.teridax.jcash.gui.login;
|
||||
|
||||
import me.teridax.jcash.gui.IconProvider;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
import java.awt.*;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import static java.awt.BorderLayout.NORTH;
|
||||
import static javax.swing.SwingConstants.CENTER;
|
||||
import static me.teridax.jcash.gui.Utils.addGridBagRow;
|
||||
import static me.teridax.jcash.gui.Utils.addHeading;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* GUI class for login into an account
|
||||
*/
|
||||
public class LoginView extends JPanel {
|
||||
|
||||
/**
|
||||
|
@ -20,10 +18,6 @@ public class LoginView extends JPanel {
|
|||
* N = log10(2^32-1) = 9,632959861146281
|
||||
*/
|
||||
private static final int MAX_PIN_DECIMAL_DIGITS = 9;
|
||||
/**
|
||||
* Number of pixels the banner image should be in width
|
||||
*/
|
||||
public static final int BANNER_WIDTH = 400;
|
||||
|
||||
private JFormattedTextField blz;
|
||||
private JFormattedTextField iban;
|
||||
|
@ -36,49 +30,37 @@ public class LoginView extends JPanel {
|
|||
}
|
||||
|
||||
private void layoutComponents() {
|
||||
var content = new JLabel();
|
||||
content.setIcon(new ImageIcon(IconProvider.getBackground()));
|
||||
content.setLayout(new BorderLayout());
|
||||
var content = new JPanel(new GridBagLayout());
|
||||
|
||||
var loginPane = new JPanel(new GridBagLayout());
|
||||
loginPane.setOpaque(true);
|
||||
content.add(loginPane, BorderLayout.CENTER);
|
||||
content.add(Box.createHorizontalStrut(BANNER_WIDTH), BorderLayout.WEST);
|
||||
|
||||
this.setLayout(new BorderLayout(32, 32));
|
||||
this.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
this.setLayout(new BorderLayout(16, 16));
|
||||
this.add(new JScrollPane(content), BorderLayout.CENTER);
|
||||
this.add(new JLabel(addHeading(translate("Cashmachine")), CENTER), NORTH);
|
||||
|
||||
var constraints = new GridBagConstraints();
|
||||
|
||||
constraints.gridwidth = 4;
|
||||
constraints.insets = new Insets(12, 12, 12, 12);
|
||||
|
||||
addGridBagRow(constraints, loginPane, new JLabel(addHeading(translate("Cashmachine"))), 0, "");
|
||||
addGridBagRow(constraints, loginPane, blz, 1, translate("BLZ"));
|
||||
addGridBagRow(constraints, loginPane, iban, 2, translate("IBAN"));
|
||||
addGridBagRow(constraints, loginPane, pin, 3, translate("PIN"));
|
||||
addGridBagRow(constraints, content, blz, 1, translate("BLZ"));
|
||||
addGridBagRow(constraints, content, iban, 2, translate("IBAN"));
|
||||
addGridBagRow(constraints, content, pin, 3, translate("PIN"));
|
||||
|
||||
constraints.gridy = 4;
|
||||
constraints.anchor = GridBagConstraints.PAGE_END;
|
||||
constraints.weightx = 0;
|
||||
constraints.fill = GridBagConstraints.NONE;
|
||||
constraints.insets = new Insets(0, 0, 0, 12);
|
||||
loginPane.add(login, constraints);
|
||||
constraints.insets = new Insets(12, 12, 12, 12);
|
||||
content.add(login, constraints);
|
||||
}
|
||||
|
||||
private void createComponents() {
|
||||
this.blz = new JFormattedTextField();
|
||||
this.iban = new JFormattedTextField();
|
||||
this.iban = new JFormattedTextField(getNumberFormat());
|
||||
this.pin = new JPasswordField();
|
||||
this.login = new JButton(translate("Login"));
|
||||
|
||||
// customize login button
|
||||
// this may not work with every swing look and feel
|
||||
this.login.setFont(new Font("Circus", Font.PLAIN, 28));
|
||||
this.login.setBackground(Color.CYAN);
|
||||
|
||||
restrictPasswordToDigits();
|
||||
restrictIbanInput();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,22 +81,17 @@ public class LoginView extends JPanel {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a document filter onto {@link #iban} that filters out everything that is not a digit.
|
||||
* The filter also restricts the amount of digits that can be entered to {@link #MAX_PIN_DECIMAL_DIGITS}
|
||||
*/
|
||||
private void restrictIbanInput() {
|
||||
((AbstractDocument) this.iban.getDocument()).setDocumentFilter(new DocumentFilter() {
|
||||
@Override
|
||||
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
|
||||
throws BadLocationException {
|
||||
String newText = fb.getDocument().getText(0, fb.getDocument().getLength()) + text;
|
||||
private NumberFormatter getNumberFormat() {
|
||||
var format = NumberFormat.getIntegerInstance();
|
||||
format.setGroupingUsed(false);
|
||||
|
||||
if (newText.matches(String.format("\\d{1,%s}", MAX_PIN_DECIMAL_DIGITS))) {
|
||||
super.replace(fb, offset, length, text, attrs);
|
||||
}
|
||||
}
|
||||
});
|
||||
var formatter = new NumberFormatter(format);
|
||||
formatter.setValueClass(Integer.class);
|
||||
formatter.setMinimum(0);
|
||||
formatter.setMaximum(Integer.MAX_VALUE);
|
||||
formatter.setAllowsInvalid(false);
|
||||
|
||||
return formatter;
|
||||
}
|
||||
|
||||
public JTextField getBlz() {
|
||||
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB |
|
@ -1,4 +1,2 @@
|
|||
https://pixabay.com/vectors/register-cash-register-modern-23666/
|
||||
![register](https://cdn.pixabay.com/photo/2012/04/01/17/34/register-23666_960_720.png)
|
||||
Font file
|
||||
https://www.dafont.com/de/circus.font?text=Login
|
||||
![register](https://cdn.pixabay.com/photo/2012/04/01/17/34/register-23666_960_720.png)
|
|
@ -1,86 +0,0 @@
|
|||
package me.teridax.jcash.gui.takeoff;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.banking.accounts.CurrentAccount;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Controller class for handling bank account take off.
|
||||
*/
|
||||
public class TakeoffController {
|
||||
|
||||
/**
|
||||
* Account to take off
|
||||
*/
|
||||
private final Account account;
|
||||
/**
|
||||
* GUI object
|
||||
*/
|
||||
private final TakeoffView view;
|
||||
|
||||
public TakeoffController(Account account) {
|
||||
this.account = account;
|
||||
|
||||
// add overdraft on top of the maximum amount
|
||||
// a user is allowed to take off
|
||||
var overdraft = 0.0;
|
||||
if (account instanceof CurrentAccount) {
|
||||
overdraft += ((CurrentAccount) account).getOverdraft();
|
||||
}
|
||||
|
||||
TakeoffData data = new TakeoffData(account.getBalance());
|
||||
this.view = new TakeoffView(data.getMaxValue() + overdraft);
|
||||
|
||||
this.view.getTakeoff().addActionListener(e -> takeOff());
|
||||
this.view.getCancel().addActionListener(e -> view.dispose());
|
||||
this.view.getValue().getDocument().addDocumentListener(new DocumentListener() {
|
||||
private void validateInputState() {
|
||||
var balance = account.getBalance();
|
||||
try {
|
||||
view.getValue().commitEdit();
|
||||
var amount = view.getAmount();
|
||||
view.setCommittedValue(amount, balance - amount);
|
||||
view.getTakeoff().setEnabled(true);
|
||||
} catch (InvalidInputException | ParseException ex) {
|
||||
view.setCommittedValue(0, balance);
|
||||
view.getTakeoff().setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
});
|
||||
this.view.showDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to take off some money from an account.
|
||||
*/
|
||||
private void takeOff() {
|
||||
try {
|
||||
account.takeoff(view.getAmount());
|
||||
} catch (IllegalArgumentException | InvalidInputException ex) {
|
||||
Logging.LOGGER.severe("Could not take off money: " + ex.getMessage());
|
||||
Utils.error("Reason: " + ex.getMessage());
|
||||
}
|
||||
view.dispose();
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package me.teridax.jcash.gui.takeoff;
|
||||
|
||||
/**
|
||||
* Data class for taking off value from a certain account
|
||||
*/
|
||||
public class TakeoffData {
|
||||
|
||||
/**
|
||||
* Maximum value a user is allowed to take off
|
||||
*/
|
||||
private final double maxValue;
|
||||
|
||||
public TakeoffData(double maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
public double getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package me.teridax.jcash.gui.takeoff;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.Main;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
public class TakeoffDialog {
|
||||
|
||||
private final Account account;
|
||||
private final Runnable onTakeoff;
|
||||
private final TakeoffView view;
|
||||
|
||||
public TakeoffDialog(Account account, Runnable onTakeoff) {
|
||||
this.account = account;
|
||||
this.onTakeoff = onTakeoff;
|
||||
|
||||
this.view = new TakeoffView();
|
||||
|
||||
this.view.getTakeoff().addActionListener(e -> takeOff());
|
||||
this.view.getCancel().addActionListener(e -> view.dispose());
|
||||
this.view.showDialog();
|
||||
}
|
||||
|
||||
private void takeOff() {
|
||||
try {
|
||||
account.takeoff(view.getAmount());
|
||||
onTakeoff.run();
|
||||
view.dispose();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logging.LOGGER.severe("Could not take off money: " + ex.getMessage());
|
||||
showMessageDialog(Main.getInstance().getWindow(), "Reason: " + ex.getMessage(), translate("Could not take off money"), ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +1,29 @@
|
|||
package me.teridax.jcash.gui.takeoff;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.decode.StringDecoder;
|
||||
import me.teridax.jcash.gui.IconProvider;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* Dialog for taking off money of an account.
|
||||
*/
|
||||
public class TakeoffView {
|
||||
|
||||
private JDialog dialog;
|
||||
private JButton cancel;
|
||||
private JButton takeoff;
|
||||
private JLabel enteredValue;
|
||||
private JLabel balanceAfterDeposit;
|
||||
private JFormattedTextField value;
|
||||
|
||||
public TakeoffView(double maxValue) {
|
||||
createComponents(maxValue);
|
||||
public TakeoffView() {
|
||||
createComponents();
|
||||
layoutComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes this dialog visible.
|
||||
*/
|
||||
public void showDialog() {
|
||||
dialog.setIconImage(IconProvider.getWindowIcon());
|
||||
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
|
||||
|
@ -48,61 +41,42 @@ public class TakeoffView {
|
|||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.weightx = 1;
|
||||
c.weighty = 1;
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.CENTER;
|
||||
c.insets = new Insets(4, 4, 4, 4);
|
||||
dialog.getContentPane().add(new JLabel(translate("Takeoff money")), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0;
|
||||
c.insets = new Insets(6,6,6,6);
|
||||
dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(value, c);
|
||||
|
||||
c.gridx = 2;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LINE_START;
|
||||
c.weightx = 0;
|
||||
dialog.getContentPane().add(new JLabel("€"), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(new JLabel(translate("Value to takeoff:"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(enteredValue, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(new JLabel(translate("Balance after takeoff:"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
dialog.getContentPane().add(balanceAfterDeposit, c);
|
||||
|
||||
var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
|
||||
buttonPanel.add(cancel);
|
||||
buttonPanel.add(takeoff);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 3;
|
||||
c.gridy = 2;
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
|
@ -110,50 +84,29 @@ public class TakeoffView {
|
|||
dialog.getContentPane().add(buttonPanel, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* The createComponents function creates the components of the dialog.
|
||||
* @param maxValue Set the maximum value of the jformattedtextfield
|
||||
*/
|
||||
private void createComponents(double maxValue) {
|
||||
private void createComponents() {
|
||||
this.dialog = new JDialog();
|
||||
|
||||
this.cancel = new JButton(translate("Cancel"));
|
||||
this.takeoff = new JButton(translate("Takeoff"));
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue));
|
||||
this.enteredValue = new JLabel();
|
||||
this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
|
||||
|
||||
this.dialog.setContentPane(new JPanel(new GridBagLayout()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The getAmount function is used to get the amount of currency that has been entered into the text field.
|
||||
* @return A double value, which is the parsed amount from the text field
|
||||
*/
|
||||
public double getAmount() throws InvalidInputException {
|
||||
if (value.getText().isBlank())
|
||||
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
|
||||
public double getAmount() {
|
||||
if (value.getText().isBlank()) {
|
||||
showMessageDialog(null, translate("Invalid amount"), translate("Currency must not be blank"), ERROR_MESSAGE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
|
||||
return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
|
||||
} catch (ParseException e) {
|
||||
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
|
||||
throw new InvalidInputException(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The setCommittedValue function sets the text of the enteredValue and balanceAfterDeposit TextFields to
|
||||
* a String representation of amount and after, respectively.
|
||||
* @param amount Set the text of enteredvalue
|
||||
* @param after Set the balance after deposit
|
||||
*/
|
||||
public void setCommittedValue(double amount, double after) {
|
||||
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
|
||||
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));
|
||||
}
|
||||
|
||||
public JButton getCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
@ -162,10 +115,6 @@ public class TakeoffView {
|
|||
return takeoff;
|
||||
}
|
||||
|
||||
public JFormattedTextField getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
this.dialog.dispose();
|
||||
}
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
package me.teridax.jcash.gui.transfer;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.Main;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.banking.accounts.CurrentAccount;
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
import me.teridax.jcash.gui.Utils;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Dialog class for transferring some value from one account to another
|
||||
*/
|
||||
public class TransferController {
|
||||
|
||||
private final Account account;
|
||||
private final TransferData transferData;
|
||||
private final TransferView view;
|
||||
|
||||
public TransferController(Account account, BankingManagementSystem bms) {
|
||||
this.account = account;
|
||||
|
||||
var overdraft = 0.0;
|
||||
if (account instanceof CurrentAccount) {
|
||||
overdraft += ((CurrentAccount) account).getOverdraft();
|
||||
}
|
||||
|
||||
this.view = new TransferView(account.getBalance() + overdraft);
|
||||
this.transferData = new TransferData(bms);
|
||||
this.view.getTransfer().addActionListener(e -> transfer());
|
||||
this.view.getCancel().addActionListener(e -> view.dispose());
|
||||
|
||||
// validates the users input
|
||||
var validator = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent documentEvent) {
|
||||
validateInputState();
|
||||
}
|
||||
};
|
||||
|
||||
this.view.getValue().getDocument().addDocumentListener(validator);
|
||||
this.view.getIbanTextField().getDocument().addDocumentListener(validator);
|
||||
this.view.getBlzTextField().getDocument().addDocumentListener(validator);
|
||||
|
||||
this.view.showDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the target bank account is valid.
|
||||
* @return true if the target bank account is valid false otherwise
|
||||
*/
|
||||
private boolean validateTargetAccount() {
|
||||
if (transferData.validateBLZ(this.view.getBlz())) {
|
||||
return transferData.validateIBAN(this.view.getBlz(), view.getIban());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the entered value to transfer is valid.
|
||||
* This method will also commit the valid value back to the text field.
|
||||
* @return true if the value to transfer is valid, false otherwise
|
||||
*/
|
||||
private boolean validateTransferValue() {
|
||||
var balance = account.getBalance();
|
||||
try {
|
||||
view.getValue().commitEdit();
|
||||
var amount = view.getAmount();
|
||||
view.setCommittedValue(amount, balance - amount);
|
||||
return true;
|
||||
} catch (InvalidInputException | ParseException ex) {
|
||||
view.setCommittedValue(0, balance);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateInputState() {
|
||||
var valid = validateTargetAccount() && validateTransferValue();
|
||||
view.getTransfer().setEnabled(valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to transfer the balance from one account to another
|
||||
* This method will close the dialog.
|
||||
*/
|
||||
private void transfer() {
|
||||
try {
|
||||
var amount = view.getAmount();
|
||||
this.account.takeoff(amount);
|
||||
this.transferData.transferValue(amount, view.getBlz(), view.getIban());
|
||||
} catch (IllegalArgumentException | InvalidInputException ex) {
|
||||
Logging.LOGGER.severe("Could not transfer: " + ex.getMessage());
|
||||
Utils.error("Reason: " + ex.getMessage());
|
||||
}
|
||||
this.view.dispose();
|
||||
}
|
||||
}
|
|
@ -46,32 +46,4 @@ public class TransferData {
|
|||
|
||||
account.get().getPrimaryAccount().deposit(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given BLZ. If no bank with the given BLZ can be found
|
||||
* this method returns false. Otherwise, true is returned.
|
||||
* @param blz the BLZ to validate
|
||||
* @return true if the BLZ is valid and false otherwise
|
||||
*/
|
||||
public boolean validateBLZ(String blz) {
|
||||
return bms.getBank(blz).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given IBAN for the given BLZ. This method assumes the BLZ to be valid.
|
||||
* If this is not the case, this function will throw an exception.
|
||||
* Returns true if an account with the given IBAN was found for bank of the given BLZ.
|
||||
* @param blz bank to search in
|
||||
* @param ibanString account to search for
|
||||
* @return true if the account was found false otherwise
|
||||
*/
|
||||
public boolean validateIBAN(String blz, String ibanString) {
|
||||
var bank = bms.getBank(blz);
|
||||
try {
|
||||
var iban = StringDecoder.decodeUniqueIdentificationNumber(ibanString);
|
||||
return bank.map(value -> value.getAccount(iban).isPresent()).orElse(false);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package me.teridax.jcash.gui.transfer;
|
||||
|
||||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.Main;
|
||||
import me.teridax.jcash.banking.accounts.Account;
|
||||
import me.teridax.jcash.banking.management.BankingManagementSystem;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* Dialog class for transferring some value from one account to another
|
||||
*/
|
||||
public class TransferDialog {
|
||||
|
||||
private final Account account;
|
||||
private final Runnable onDeposit;
|
||||
private final TransferData transferData;
|
||||
private final TransferView transferView;
|
||||
|
||||
public TransferDialog(Account account, BankingManagementSystem bms, Runnable onDeposit) {
|
||||
this.account = account;
|
||||
this.onDeposit = onDeposit;
|
||||
|
||||
this.transferView = new TransferView();
|
||||
this.transferData = new TransferData(bms);
|
||||
this.transferView.getTransfer().addActionListener(e -> transfer());
|
||||
this.transferView.getCancel().addActionListener(e -> transferView.dispose());
|
||||
this.transferView.showDialog();
|
||||
}
|
||||
|
||||
private void transfer() {
|
||||
try {
|
||||
var amount = transferView.getAmount();
|
||||
|
||||
this.account.takeoff(amount);
|
||||
this.transferData.transferValue(amount, transferView.getBlz(), transferView.getIban());
|
||||
this.onDeposit.run();
|
||||
this.transferView.dispose();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logging.LOGGER.severe("Could not transfer: " + ex.getMessage());
|
||||
showMessageDialog(Main.getInstance().getWindow(), translate("Invalid account"), translate("Could not transfer"), ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,32 +3,14 @@ package me.teridax.jcash.gui.transfer;
|
|||
import me.teridax.jcash.Logging;
|
||||
import me.teridax.jcash.decode.StringDecoder;
|
||||
import me.teridax.jcash.gui.IconProvider;
|
||||
import me.teridax.jcash.gui.InvalidInputException;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.text.ParseException;
|
||||
|
||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||
import static javax.swing.JOptionPane.showMessageDialog;
|
||||
import static me.teridax.jcash.lang.Translator.translate;
|
||||
|
||||
/**
|
||||
* JDialog for displaying the GUI for a transfer dialog
|
||||
* with the following crude layout:
|
||||
* <pre>
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ ┌─────────────┐ ┌────────────────┐ │
|
||||
│ BLZ │ │ IBAN │ │ │
|
||||
│ └─────────────┘ └────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────┐ │
|
||||
│ VALUE │ │ │
|
||||
│ └─────────────┘ │
|
||||
│ ┌───────┐ ┌────────┐ │
|
||||
│ │Cancel │ │Transfer│ │
|
||||
│ └───────┘ └────────┘ │
|
||||
└───────────────────────────────────────────────┘
|
||||
* </pre>
|
||||
*/
|
||||
public class TransferView {
|
||||
|
||||
private JDialog dialog;
|
||||
|
@ -37,17 +19,12 @@ public class TransferView {
|
|||
private JFormattedTextField iban;
|
||||
private JFormattedTextField blz;
|
||||
private JFormattedTextField value;
|
||||
private JLabel balanceAfterTransfer;
|
||||
private JLabel enteredValue;
|
||||
|
||||
public TransferView(double maxValue) {
|
||||
createComponents(maxValue);
|
||||
public TransferView() {
|
||||
createComponents();
|
||||
layoutComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes this dialog visible to the user
|
||||
*/
|
||||
public void showDialog() {
|
||||
dialog.setIconImage(IconProvider.getWindowIcon());
|
||||
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
|
||||
|
@ -60,142 +37,102 @@ public class TransferView {
|
|||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout all components of this dialog.
|
||||
*/
|
||||
private void layoutComponents() {
|
||||
var c = new GridBagConstraints();
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.weightx = 1;
|
||||
c.weighty = 1;
|
||||
c.gridwidth = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.CENTER;
|
||||
c.insets = new Insets(4, 4, 4, 4);
|
||||
dialog.getContentPane().add(new JLabel(translate("Transfer money")), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0;
|
||||
c.insets = new Insets(6,6,6,6);
|
||||
dialog.getContentPane().add(new JLabel(translate("BLZ"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(blz, c);
|
||||
|
||||
c.gridx = 2;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0;
|
||||
dialog.getContentPane().add(new JLabel(translate("IBAN"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 3;
|
||||
c.gridy = 0;
|
||||
c.gridy = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 1;
|
||||
dialog.getContentPane().add(iban, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 1;
|
||||
c.gridy = 2;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0;
|
||||
dialog.getContentPane().add(new JLabel(translate("Betrag"), SwingConstants.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 1;
|
||||
c.gridy = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(value, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(new JLabel(translate("Value to transfer:"), JLabel.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 2;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(enteredValue, c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(new JLabel(translate("Balance after transfer:"), JLabel.RIGHT), c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = 3;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.weightx = 0.5;
|
||||
dialog.getContentPane().add(balanceAfterTransfer, c);
|
||||
|
||||
var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
|
||||
buttonPanel.add(cancel);
|
||||
buttonPanel.add(transfer);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 4;
|
||||
c.gridy = 3;
|
||||
c.gridwidth = 4;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.anchor = GridBagConstraints.LAST_LINE_END;
|
||||
c.insets = new Insets(10, 10, 10, 10);
|
||||
dialog.getContentPane().add(buttonPanel, c);
|
||||
}
|
||||
|
||||
private void createComponents(double maxValue) {
|
||||
private void createComponents() {
|
||||
this.dialog = new JDialog();
|
||||
|
||||
this.cancel = new JButton(translate("Cancel"));
|
||||
this.transfer = new JButton(translate("Transfer"));
|
||||
this.transfer.setEnabled(false);
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue));
|
||||
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
|
||||
this.iban = new JFormattedTextField();
|
||||
this.blz = new JFormattedTextField();
|
||||
this.enteredValue = new JLabel();
|
||||
this.balanceAfterTransfer = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
|
||||
|
||||
this.dialog.setContentPane(new JPanel(new GridBagLayout()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entered amount parsed into a double value.
|
||||
* @return the amount parsed into a double
|
||||
* @throws InvalidInputException if the text in {@link #value} is not a valid double value.
|
||||
*/
|
||||
public double getAmount() throws InvalidInputException {
|
||||
if (value.getText().isBlank())
|
||||
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
|
||||
public double getAmount() {
|
||||
if (value.getText().isBlank()) {
|
||||
Logging.LOGGER.severe("Amount is empty");
|
||||
showMessageDialog(null, translate("invalid amount"), translate("currency must not be blank"), ERROR_MESSAGE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
|
||||
} catch (ParseException e) {
|
||||
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
|
||||
throw new InvalidInputException(e);
|
||||
return StringDecoder.decodeCurrency(value.getText());
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logging.LOGGER.severe("Invalid amount: " + value.getText());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values to display in the dialog labels as overview information.
|
||||
* @param amount the amount to transfer
|
||||
* @param after balance after the transfer
|
||||
*/
|
||||
public void setCommittedValue(double amount, double after) {
|
||||
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
|
||||
balanceAfterTransfer.setText(StringDecoder.getNumberFormat().format(after));
|
||||
}
|
||||
|
||||
public JFormattedTextField getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public JButton getCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
@ -212,14 +149,6 @@ public class TransferView {
|
|||
return blz.getText();
|
||||
}
|
||||
|
||||
public JFormattedTextField getIbanTextField() {
|
||||
return iban;
|
||||
}
|
||||
|
||||
public JFormattedTextField getBlzTextField() {
|
||||
return blz;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
this.dialog.dispose();
|
||||
}
|
||||
|
|
|
@ -10,9 +10,6 @@ import java.util.Locale;
|
|||
@SuppressWarnings("unused")
|
||||
public class Locales {
|
||||
|
||||
/**
|
||||
* Default locale initialized to the fallback locale used by this application.
|
||||
*/
|
||||
private static Locale defaultLocale = new Locale("en", "EN");
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Static translator class.
|
||||
* This is a very simple translator able to translate base tokens from english into a variety of
|
||||
* configured languages.
|
||||
*/
|
||||
public final class Translator {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,54 +1,49 @@
|
|||
en_EN,de_DE,es_ES,fr_FR,zh_Hans
|
||||
Bank,Bank,Banco,Banque,银行
|
||||
BLZ,BLZ,BLZ,BLZ,BLZ
|
||||
PIN,PIN,PIN,PIN,密码
|
||||
Balance,Kontostand,Saldo,Solde,余额
|
||||
BLZ,BLZ,BLZ,CODE BANCAIRE,分类代码
|
||||
PIN,PIN,PIN,CODE PIN,密码
|
||||
Balance,Kontostand,Saldo,Solde du compte,账户余额
|
||||
Account type,Kontoart,Tipo de cuenta,Type de compte,账户类型
|
||||
Interest,Zinsen,Interés,Intérêts,利息
|
||||
Overdraft,Überziehungskredit,Descubierto,Découvert,透支
|
||||
Interest,Zins,Interés,Intérêt,利息
|
||||
Overdraft,Überziehungsbetrag,Importe del descubierto,Montant du découvert,透支金额
|
||||
Customer number,Kundennummer,Número de cliente,Numéro de client,客户编号
|
||||
Name,Name,Nombre,Nom du client,姓名
|
||||
Name,Name,Nombre,Nom et prénom,姓名
|
||||
Name,Name,Nombre,Nom,客户姓名
|
||||
Name,Vorname,Nombre,Prénom,姓名
|
||||
Street,Straße,Calle,Rue,街道
|
||||
PLZ,PLZ,PLZ,PLZ,PLZ
|
||||
City,Ort,Ciudad,Ville,城市
|
||||
Password,Kennwort,Contraseña,Mot de passe,密码
|
||||
Login,Anmeldung,Inicio de sesión,Connexion,登录
|
||||
PLZ,PLZ,PLZ,NPA,邮政编码
|
||||
City,Ort,Ubicación,Ville,城市
|
||||
Password,Passwort,contraseña,Mot de passe,密码
|
||||
Login,Anmelden,Inicio de sesión,S'inscrire,登录
|
||||
CurrentAccount,Girokonto,Cuenta corriente,Compte courant,活期账户
|
||||
SavingsAccount,Sparkonto,CuentaAhorro,Compte d'épargne,储蓄账户
|
||||
SavingsAccount,Sparkonto,Cuenta de ahorro,Compte d'épargne,储蓄账户
|
||||
Address,Adresse,Dirección,Adresse,地址
|
||||
Logout,Abmelden,Cerrar sesión,Déconnexion,注销
|
||||
Transfer,Überweisung,Transferir,Virement,转账
|
||||
Deposit,Einzahlen,Ingresar,Dépôt,存款
|
||||
Take off,Abheben,Retirar,Enlever,取款
|
||||
Value,Wert,Valor,Valeur,价值
|
||||
Logout,Abmelden,desconectarse,Se désinscrire,退出登录
|
||||
Transfer,Überweisen,transferencia,Virement bancaire,转账
|
||||
Deposit,Einzahlen,depósito,Dépôt,存款
|
||||
Take off,Abheben,despegar,Retrait,取款
|
||||
Value,Betrag,Importe,Montant,金额
|
||||
Cancel,Abbrechen,Cancelar,Annuler,取消
|
||||
Load database,Datenbank laden,Cargar base de datos,Charger la base de données,加载数据库
|
||||
Invalid account,Ungültiges Konto,Cuenta no válida,Compte non valide,无效账户
|
||||
Could not transfer,Konnte nicht übertragen werden,No se ha podido transferir,Impossible de transférer,无法转账
|
||||
Transfer,Überweisung,Transferencia,Transférer,转账
|
||||
invalid amount,Ungültiger Betrag,Importe no válido,montant non valide,无效金额
|
||||
currency must not be blank,Währung darf nicht leer sein,La divisa no debe estar en blanco,la devise ne doit pas être vide,货币不得为空
|
||||
Transfer money,Geld überweisen,Transferencia de dinero,Transférer de l'argent,转账金额
|
||||
Could not take off money,Konnte Geld nicht abheben,No se ha podido retirar el dinero,Impossible de retirer de l'argent,无法取款
|
||||
Load database,Datenbank auswählen,Seleccionar base de datos,Sélectionner la base de données,选择数据库
|
||||
Invalid account,Ungültiges Benutzerkonto,Cuenta de usuario no válida,Compte utilisateur non valide,用户账户无效
|
||||
Could not transfer,Überweiung fehlgeschlagen,Transferencia fallida,Échec du transfert,转账失败
|
||||
Transfer,Überweisen,Transferencia,Transfert,转帐
|
||||
invalid amount,Ungültiger Betrag,Importe no válido,Montant non valide,金额无效
|
||||
currency must not be blank,Betrag darf nicht leer sein,El importe no debe estar vacío,Le montant ne doit pas être vide,金额不能为空
|
||||
Transfer money,Geld überweisen,Transferencia de dinero,Transférer de l'argent,汇款
|
||||
Could not take off money,Geld konnte nicht abgehoben werden,No se ha podido retirar dinero,L'argent n'a pas pu être retiré,无法取款
|
||||
Takeoff money,Geld abheben,Retirar dinero,Retirer de l'argent,取款
|
||||
Takeoff,Abheben,Despegue,Décoller,起飞
|
||||
Currency must not be blank,Die Währung darf nicht leer sein,La divisa no debe estar en blanco,La monnaie ne doit pas être en blanc,货币不得为空
|
||||
Cashmachine,Geldautomat,Cajero,Cashmachine,提款机
|
||||
Invalid IBAN,Ungültige IBAN,IBAN no válido,IBAN non valide,IBAN 无效
|
||||
Faulty login attempt,Fehlerhafter Anmeldeversuch,Intento de acceso erróneo,Tentative de connexion erronée,尝试登录失败
|
||||
Invalid PIN,Ungültige PIN,PIN no válido,PIN invalide,密码无效
|
||||
Invalid login credentials,Ungültige Anmeldedaten,Credenciales de inicio de sesión no válidas,Identifiants de connexion invalides,登录凭证无效
|
||||
Deposit money,Geld einzahlen,Depositar dinero,Dépôt d'argent,存款
|
||||
Interest rate,Zinssatz,Tipo de interés,Taux d'intérêt,利率
|
||||
Name/Family-name,Name/Familienname,Nombre y apellidos,Nom/Prénom de famille,姓名
|
||||
Address,Anschrift,Dirección,Adresse,地址
|
||||
Takeoff,Abheben,Retirar,Retrait,取款
|
||||
Currency must not be blank,Betrag darf nicht leer sein,El importe no debe estar vacío,Le montant ne doit pas être vide,金额不能为空
|
||||
Cashmachine,Bankautomat,CAJERO,Distributeur automatique de billets,ATM
|
||||
Invalid IBAN,Ungültige IBAN,IBAN no válido,IBAN non valide,无效的IBAN
|
||||
Faulty login attempt,Ungültiger Authentifizierungsverzuch,Solicitud de autenticación no válida,Demande d'authentification non valide,验证请求无效
|
||||
Invalid PIN,Ungültiger PIN,PIN no válido,Code PIN non valide,密码无效
|
||||
Invalid login credentials,Ungültiges Passwort oder Nutzername,Contraseña o nombre de usuario no válidos,Mot de passe ou nom d'utilisateur non valide,密码或用户名无效
|
||||
Deposit money,Geld einzahlen,Depositar dinero,Dépôt d'argent,存款金额
|
||||
Interest rate,Zinsbetrag,Importe de los intereses,Montant des intérêts,利息金额
|
||||
Name/Family-name,Vorname/Name,Nombre y apellidos,Prénom/nom,名/姓
|
||||
Address,Adresse,Dirección,Adresse,地址
|
||||
Account,Konto,Cuenta,Compte,账户
|
||||
Closing JCash,JCash wird geschlossen,Cerrar JCash,Clôture de JCash,关闭 JCash
|
||||
you're logged out,Sie sind abgemeldet,has cerrado sesión,Vous êtes déconnecté,您已退出登录
|
||||
Comma separated value spreadsheet,Kommagetrennte Werte-Tabelle,Hoja de cálculo de valores separados por comas,Tableur de valeurs séparées par des virgules,逗号分隔值电子表格
|
||||
Value to transfer:,Zu übertragender Wert:,Valor a transferir:,Valeur à transférer :,要转账的金额:
|
||||
Balance after transfer:,Kontostand nach Überweisung:,Saldo después de la transferencia:,Solde après transfert :,转账后的余额
|
||||
Balance after takeoff:,Kontostand nach dem Abheben:,Saldo después de retirar:,Solde après décollage :,取出后的余额
|
||||
Value to deposit:,Einzuzahlender Wert:,Valor a ingresar:,Valeur à déposer :,存款价值
|
||||
Value to takeoff:,Auszuhalender Wert:,Valor a despegar:,Valeur à l'enlèvement :,起飞价值
|
||||
Closing JCash,JCash wird geschlossen,JCash está cerrado,JCash est fermé,JCash 已关闭
|
||||
you're logged out,Du bist abgemeldet,Ha cerrado la sesión,Tu es déconnecté,您已注销
|
||||
Comma separated value spreadsheet,Tabelle mit kommagetrennten Werten,Planilla de valores separados por comas,Feuille de calcul à valeurs séparées par des virgules,逗号分隔值电子表格
|
|
Loading…
Reference in New Issue