Compare commits

..

No commits in common. "main" and "JCash_1_5_0_RC_1" have entirely different histories.

34 changed files with 409 additions and 1155 deletions

View File

@ -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. 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 ## Overview
The program can read `.csv` file from disk and allows the user login to an account specified. The program can read `.csv` file from disk and allows the user login to an account specified.

View File

@ -7,8 +7,6 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.logging.*; 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 * 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 * @param level the level to set the handler to
*/ */
private static void createConsoleLogger(Level level) { private static void createConsoleLogger(Level level) {
ConsoleHandler ch = new ConsoleHandler(); var ch = new ConsoleHandler();
ch.setLevel(level); ch.setLevel(level);
LOGGER.addHandler(ch); LOGGER.addHandler(ch);
} }
@ -62,15 +60,15 @@ public final class Logging {
*/ */
private static void createFileLogger(Level level) { private static void createFileLogger(Level level) {
// setup log file name // setup log file name
LocalDateTime now = LocalDateTime.now(); var now = LocalDateTime.now();
String dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now); var dateTime = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT).format(now);
String logFileName = LOG_FOLDER_NAME + dateTime + ".log"; var logFileName = LOG_FOLDER_NAME + dateTime + ".log";
// setup the folder for the logs // setup the folder for the logs
initializeLogFolder(); initializeLogFolder();
try { try {
FileHandler fh = new FileHandler(logFileName); var fh = new FileHandler(logFileName);
fh.setLevel(level); fh.setLevel(level);
LOGGER.addHandler(fh); LOGGER.addHandler(fh);
} catch (Exception e) { } catch (Exception e) {
@ -83,7 +81,7 @@ public final class Logging {
* If the folder does not exist, the function will create a new folder. * If the folder does not exist, the function will create a new folder.
*/ */
private static void initializeLogFolder() { private static void initializeLogFolder() {
Path folderPath = of(LOG_FOLDER_NAME); var folderPath = Path.of(LOG_FOLDER_NAME);
if (Files.isDirectory(folderPath)) if (Files.isDirectory(folderPath))
return; return;

View File

@ -2,18 +2,19 @@ package me.teridax.jcash;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.Loader; import me.teridax.jcash.gui.Loader;
import me.teridax.jcash.gui.MainFrame; import me.teridax.jcash.gui.account.AccountController;
import me.teridax.jcash.gui.Utils; import me.teridax.jcash.gui.login.LoginController;
import me.teridax.jcash.lang.Locales; import me.teridax.jcash.lang.Locales;
import javax.swing.*; import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.logging.Level; 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.LOGGER;
import static me.teridax.jcash.Logging.initializeSystemLogger; import static me.teridax.jcash.Logging.initializeSystemLogger;
import static me.teridax.jcash.lang.Translator.translate;
public final class Main { public final class Main {
@ -23,56 +24,29 @@ public final class Main {
private static Main instance; 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() { private Main() {
this.window = new MainFrame(); // create main window and set defaults
} this.window = new JFrame();
this.window.setTitle(translate("Cashmachine"));
/** this.window.setLocationByPlatform(true);
* Prompts the user a dialog to select a file to load the database from. this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
* If a valid database has been read a login screen will be shown. this.window.setIconImage(IconProvider.getWindowIcon());
* 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);
}
} }
public static void main(String[] args) { public static void main(String[] args) {
initializeSystemLogger(Level.FINE); initializeSystemLogger(Level.FINE);
setPlatformDependingTheme();
loadExtraFont();
Locales.autodetectDefaultLocale(); Locales.autodetectDefaultLocale();
setPlatformDependingTheme();
// create main instance and show the login screen // create main instance and show the login screen
instance(); instance();
getInstance().loadDatabase(); getInstance().showLoginScreen();
}
/**
* 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());
}
} }
/** /**
@ -135,16 +109,53 @@ public final class Main {
Main.instance = new 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. * Logs the user out of the database, hiding the main window.
* This will show the login mask and clear the password field or the previous
* login attempt.
*/ */
public void logout() { public void logout() {
this.window.logout(); window.setContentPane(new JLabel(translate("you're logged out")));
window.setVisible(false);
}
public JFrame getWindow() {
return this.window;
} }
} }

View File

@ -4,7 +4,6 @@ import me.teridax.jcash.Logging;
import me.teridax.jcash.banking.accounts.Account; import me.teridax.jcash.banking.accounts.Account;
import me.teridax.jcash.banking.accounts.Owner; import me.teridax.jcash.banking.accounts.Owner;
import me.teridax.jcash.banking.management.Profile; import me.teridax.jcash.banking.management.Profile;
import me.teridax.jcash.gui.Utils;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View File

@ -5,8 +5,6 @@ import me.teridax.jcash.decode.StringDecoder;
import java.util.Objects; import java.util.Objects;
import static me.teridax.jcash.lang.Translator.translate;
/** /**
* Base class for bank accounts. * Base class for bank accounts.
* Stores the iban, pin and balance. * 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 * @return a basic description of the account in form a string
*/ */
public String getDescription() { public String getDescription() {
return String.format("%s (%s)", iban, translate(getClass().getSimpleName())); return String.format("%s (%s)", iban, getClass().getSimpleName());
} }
/** /**

View File

@ -6,8 +6,6 @@ import me.teridax.jcash.banking.accounts.Owner;
import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.decode.StringDecoder;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
@ -25,13 +23,6 @@ public final class BankingManagementSystem {
* Separator used to separate columns of CSV files * Separator used to separate columns of CSV files
*/ */
private static final String SEPARATOR = ";"; 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 * A set of banks
@ -85,7 +76,7 @@ public final class BankingManagementSystem {
LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null")); LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null"));
try { try {
var bms = new BankingManagementSystem(); var bms = new BankingManagementSystem();
var content = getSource(file); var content = Files.readString(file);
// read line by line // read line by line
// and skip the first line // and skip the first line
@ -123,33 +114,15 @@ public final class BankingManagementSystem {
return bms; 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) { } catch (IllegalArgumentException | NullPointerException e) {
LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage()); LOGGER.severe("Could not parse file: " + file + " due to: " + e.getMessage());
throw new IllegalArgumentException("Could not parse file " + file, e); 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. * Return a bank with the given blz.
* *

View File

@ -2,7 +2,6 @@ package me.teridax.jcash.decode;
import me.teridax.jcash.lang.Locales; import me.teridax.jcash.lang.Locales;
import javax.swing.text.NumberFormatter;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.Objects; import java.util.Objects;
@ -18,35 +17,6 @@ public class StringDecoder {
return NumberFormat.getInstance(Locales.getDefaultLocale()); 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. * 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 * 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 trimmed = name.trim();
var pattern = Pattern.compile("[^\\d]+", Pattern.CASE_INSENSITIVE); var pattern = Pattern.compile("[^\\s]+", Pattern.CASE_INSENSITIVE);
var matcher = pattern.matcher(trimmed); var matcher = pattern.matcher(trimmed);
if (matcher.find()) { if (matcher.find()) {
return matcher.group(); return matcher.group();

View File

@ -7,28 +7,14 @@ import java.util.Objects;
import static me.teridax.jcash.Logging.LOGGER; import static me.teridax.jcash.Logging.LOGGER;
/**
* Static class for providing the capabilities to load images from file.
*/
public class IconProvider { public class IconProvider {
private static final Image DEFAULT_IMAGE = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB); 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() { public static Image getWindowIcon() {
return loadIcon("res/register.png"); 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) { private static Image loadIcon(String path) {
try { try {
var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path)); var is = Objects.requireNonNull(IconProvider.class.getResourceAsStream(path));
@ -39,12 +25,4 @@ public class IconProvider {
return DEFAULT_IMAGE; 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");
}
} }

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -1,25 +1,12 @@
package me.teridax.jcash.gui; package me.teridax.jcash.gui;
import me.teridax.jcash.Main;
import me.teridax.jcash.lang.Locales;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.text.Format;
import java.text.NumberFormat;
public class Utils { 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>&lt;html>&lt;h1>$string&lt;/h1>&lt;/html></pre>
*/
public static String addHeading(String title) { 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; constraints.fill = GridBagConstraints.HORIZONTAL;
target.add(new JLabel(name, SwingConstants.RIGHT), constraints); target.add(new JLabel(name, SwingConstants.RIGHT), constraints);
if (comp == null)
return;
constraints.gridx = 2; constraints.gridx = 2;
constraints.gridy = row; constraints.gridy = row;
constraints.weightx = 1; constraints.weightx = 1;
@ -74,13 +58,4 @@ public class Utils {
constraints.fill = GridBagConstraints.HORIZONTAL; constraints.fill = GridBagConstraints.HORIZONTAL;
target.add(comp, constraints); 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);
}
} }

View File

@ -4,9 +4,9 @@ import me.teridax.jcash.Logging;
import me.teridax.jcash.Main; import me.teridax.jcash.Main;
import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.banking.management.Profile; import me.teridax.jcash.banking.management.Profile;
import me.teridax.jcash.gui.deposit.DepositController; import me.teridax.jcash.gui.deposit.DepositDialog;
import me.teridax.jcash.gui.takeoff.TakeoffController; import me.teridax.jcash.gui.takeoff.TakeoffDialog;
import me.teridax.jcash.gui.transfer.TransferController; import me.teridax.jcash.gui.transfer.TransferDialog;
/** /**
* Controller for controlling the gui of an account. * Controller for controlling the gui of an account.
@ -18,68 +18,35 @@ public class AccountController {
*/ */
private final AccountView view; private final AccountView view;
private Profile profile; private final Profile profile;
public AccountController() { public AccountController(Profile profile, BankingManagementSystem bms) {
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) {
this.profile = profile; this.profile = profile;
this.view = new AccountView();
this.view.setProfile(profile); this.view.setProfile(profile);
this.data.setBms(bms); this.data = new AccountData(bms);
this.createListeners();
createListeners(profile);
} }
/** private void createListeners(Profile profile) {
* Create listeners for GUI components
*/
private void createListeners() {
this.view.getAccountSelection().addActionListener(e -> changeAccount()); this.view.getAccountSelection().addActionListener(e -> changeAccount());
this.view.getLogout().addActionListener(e -> logout()); this.view.getLogout().addActionListener(e -> logout());
this.view.getDeposit().addActionListener(e -> depositMoney());
this.view.getTakeoff().addActionListener(e -> takeoffMoney());
this.view.getTransfer().addActionListener(e -> transferMoney());
}
/** this.view.getDeposit().addActionListener(e -> new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
* Open dialog to deposit money
*/
private void depositMoney() {
new DepositController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
}
/** this.view.getTakeoff().addActionListener(e -> new TakeoffDialog(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.getTransfer().addActionListener(e -> new TransferDialog(profile.getPrimaryAccount(), data.getBms(), () -> this.view.setProfile(profile)));
* Open dialog to take off money
*/
private void takeoffMoney() {
new TakeoffController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
} }
private void logout() { private void logout() {
Logging.LOGGER.fine("Logging out of account"); Logging.LOGGER.fine("Logging out of account");
Main.getInstance().logout(); Main.getInstance().logout();
Main.getInstance().showLoginScreen();
} }
/**
* Change the selected account.
*/
private void changeAccount() { private void changeAccount() {
var description = ((String) this.view.getAccountSelection().getSelectedItem()); var description = ((String) this.view.getAccountSelection().getSelectedItem());
Logging.LOGGER.fine("Changing primary account selected: " + description); Logging.LOGGER.fine("Changing primary account selected: " + description);

View File

@ -2,18 +2,15 @@ package me.teridax.jcash.gui.account;
import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.BankingManagementSystem;
/**
* Data storage class for account management
*/
public class AccountData { public class AccountData {
private BankingManagementSystem bms; private final BankingManagementSystem bms;
public AccountData(BankingManagementSystem bms) {
this.bms = bms;
}
public BankingManagementSystem getBms() { public BankingManagementSystem getBms() {
return bms; return bms;
} }
public void setBms(BankingManagementSystem bms) {
this.bms = bms;
}
} }

View File

@ -1,7 +1,6 @@
package me.teridax.jcash.gui.account; package me.teridax.jcash.gui.account;
import me.teridax.jcash.Logging; 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.CurrentAccount;
import me.teridax.jcash.banking.accounts.SavingsAccount; import me.teridax.jcash.banking.accounts.SavingsAccount;
import me.teridax.jcash.banking.management.Profile; import me.teridax.jcash.banking.management.Profile;
@ -9,8 +8,6 @@ import me.teridax.jcash.decode.StringDecoder;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.Arrays;
import java.util.Comparator;
import static javax.swing.SwingConstants.RIGHT; import static javax.swing.SwingConstants.RIGHT;
import static me.teridax.jcash.gui.Utils.addGridBagRow; import static me.teridax.jcash.gui.Utils.addGridBagRow;
@ -40,19 +37,38 @@ public class AccountView extends JPanel {
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); 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) { 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(); this.accountSelection.removeAllItems();
var accounts = profile.getAccounts(); for (var otherAccount : profile.getAccounts()) {
Arrays.stream(accounts).sorted(Comparator.comparingInt(Account::getIban)).forEach(a -> this.accountSelection.addItem(a.getDescription())); this.accountSelection.addItem(otherAccount.getDescription());
}
this.accountSelection.setSelectedItem(profile.getPrimaryAccount().getDescription()); this.accountSelection.setSelectedItem(account.getDescription());
} }
private void createLayout() { private void createLayout() {
@ -140,40 +156,4 @@ public class AccountView extends JPanel {
public JButton getTakeoff() { public JButton getTakeoff() {
return takeoff; 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());
}
}
} }

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -3,41 +3,23 @@ package me.teridax.jcash.gui.deposit;
import me.teridax.jcash.Logging; import me.teridax.jcash.Logging;
import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.InvalidInputException;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import static me.teridax.jcash.lang.Translator.translate; 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 { public class DepositView {
/**
* Window to use
*/
private JDialog dialog; private JDialog dialog;
private JButton cancel; private JButton cancel;
/**
* Button for applying the deposit operation
*/
private JButton deposit; 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; private JFormattedTextField value;
public DepositView(double maxValue) { public DepositView() {
createComponents(maxValue); createComponents();
layoutComponents(); layoutComponents();
} }
@ -60,103 +42,71 @@ public class DepositView {
c.gridx = 0; c.gridx = 0;
c.gridy = 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.gridwidth = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0; c.weightx = 0;
c.insets = new Insets(6, 6, 6, 6);
dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c);
c.gridx = 1; c.gridx = 1;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5; c.weightx = 0.5;
dialog.getContentPane().add(value, c); dialog.getContentPane().add(value, c);
c.gridx = 2; c.gridx = 2;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LINE_START; c.anchor = GridBagConstraints.LINE_START;
c.weightx = 0; c.weightx = 0;
dialog.getContentPane().add(new JLabel(""), c); 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)); var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
buttonPanel.add(cancel); buttonPanel.add(cancel);
buttonPanel.add(deposit); buttonPanel.add(deposit);
c.gridx = 0; c.gridx = 0;
c.gridy = 3; c.gridy = 2;
c.gridwidth = 3; c.gridwidth = 3;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.insets = new Insets(10, 10, 10, 10);
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
private void createComponents(double maxValue) { private void createComponents() {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton(translate("Cancel")); this.cancel = new JButton(translate("Cancel"));
this.deposit = new JButton(translate("Deposit")); this.deposit = new JButton(translate("Deposit"));
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(Double.MAX_VALUE)); this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
this.enteredValue = new JLabel();
this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
this.deposit.setEnabled(false);
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/** public double getAmount() {
* 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 {
if (value.getText().isBlank()) if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered"); return 0;
try { try {
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
} catch (ParseException e) { } catch (ParseException e) {
Logging.LOGGER.severe("Amount text field contains invalid value: " + value); 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() { public JButton getCancel() {
return cancel; return cancel;
} }
@ -168,14 +118,4 @@ public class DepositView {
public void dispose() { public void dispose() {
this.dialog.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));
}
} }

View File

@ -2,11 +2,14 @@ package me.teridax.jcash.gui.login;
import me.teridax.jcash.Logging; import me.teridax.jcash.Logging;
import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.gui.Utils;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.Optional; 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 { public class LoginController {
private final LoginView view; private final LoginView view;
@ -14,17 +17,13 @@ public class LoginController {
private AccountSelectionListener listener; private AccountSelectionListener listener;
public LoginController() { public LoginController(BankingManagementSystem bms) {
this.view = new LoginView(); this.view = new LoginView();
this.data = new LoginData(); this.data = new LoginData(bms);
addActionListeners(); addActionListeners();
} }
public void setBankingManagementSystem(BankingManagementSystem bms) {
this.data.setBms(bms);
}
private void addActionListeners() { private void addActionListeners() {
this.view.getLogin().addActionListener(this::login); this.view.getLogin().addActionListener(this::login);
} }
@ -53,13 +52,15 @@ public class LoginController {
var blz = this.view.getBlz().getText(); var blz = this.view.getBlz().getText();
var iban = this.getIban(); var iban = this.getIban();
if (iban.isEmpty()) { 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; return;
} }
var pin = this.getPin(); var pin = this.getPin();
if (pin.isEmpty()) { 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; return;
} }
@ -67,8 +68,9 @@ public class LoginController {
if (account.isPresent()) { if (account.isPresent()) {
this.listener.onAccountSelected(account.get()); this.listener.onAccountSelected(account.get());
} else { } 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) { public void addAccountSelectionListener(AccountSelectionListener listener) {
@ -82,8 +84,4 @@ public class LoginController {
public LoginData getData() { public LoginData getData() {
return data; return data;
} }
public void logout() {
this.view.getPin().setText("");
}
} }

View File

@ -3,7 +3,6 @@ package me.teridax.jcash.gui.login;
import me.teridax.jcash.Logging; import me.teridax.jcash.Logging;
import me.teridax.jcash.banking.management.BankingManagementSystem; import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.banking.management.Profile; import me.teridax.jcash.banking.management.Profile;
import me.teridax.jcash.gui.Utils;
import java.util.Optional; import java.util.Optional;
@ -12,7 +11,11 @@ import java.util.Optional;
*/ */
public class LoginData { 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. * 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) { public Optional<Profile> authenticateAccount(String blz, int iban, int pin) {
Logging.LOGGER.info("Authenticating account " + iban); Logging.LOGGER.info("Authenticating account " + iban);
var optionalBank = bms.getBank(blz); var optionalBank = bms.getBank(blz);
if (optionalBank.isEmpty()) { if (optionalBank.isEmpty())
Utils.error("Unknown BLZ: " + blz);
return Optional.empty(); return Optional.empty();
}
var profile = optionalBank.get().getAccount(iban); var profile = optionalBank.get().getAccount(iban);
if (profile.isEmpty()) { return profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
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;
} }
public void setBms(BankingManagementSystem bms) { public BankingManagementSystem getBms() {
this.bms = bms; return bms;
} }
} }

View File

@ -1,18 +1,16 @@
package me.teridax.jcash.gui.login; package me.teridax.jcash.gui.login;
import me.teridax.jcash.gui.IconProvider;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.*; import javax.swing.text.*;
import java.awt.*; 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.addGridBagRow;
import static me.teridax.jcash.gui.Utils.addHeading; import static me.teridax.jcash.gui.Utils.addHeading;
import static me.teridax.jcash.lang.Translator.translate; import static me.teridax.jcash.lang.Translator.translate;
/**
* GUI class for login into an account
*/
public class LoginView extends JPanel { public class LoginView extends JPanel {
/** /**
@ -20,10 +18,6 @@ public class LoginView extends JPanel {
* N = log10(2^32-1) = 9,632959861146281 * N = log10(2^32-1) = 9,632959861146281
*/ */
private static final int MAX_PIN_DECIMAL_DIGITS = 9; 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 blz;
private JFormattedTextField iban; private JFormattedTextField iban;
@ -36,49 +30,37 @@ public class LoginView extends JPanel {
} }
private void layoutComponents() { private void layoutComponents() {
var content = new JLabel(); var content = new JPanel(new GridBagLayout());
content.setIcon(new ImageIcon(IconProvider.getBackground()));
content.setLayout(new BorderLayout());
var loginPane = new JPanel(new GridBagLayout()); this.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
loginPane.setOpaque(true); this.setLayout(new BorderLayout(16, 16));
content.add(loginPane, BorderLayout.CENTER);
content.add(Box.createHorizontalStrut(BANNER_WIDTH), BorderLayout.WEST);
this.setLayout(new BorderLayout(32, 32));
this.add(new JScrollPane(content), BorderLayout.CENTER); this.add(new JScrollPane(content), BorderLayout.CENTER);
this.add(new JLabel(addHeading(translate("Cashmachine")), CENTER), NORTH);
var constraints = new GridBagConstraints(); var constraints = new GridBagConstraints();
constraints.gridwidth = 4; constraints.gridwidth = 4;
constraints.insets = new Insets(12, 12, 12, 12); constraints.insets = new Insets(12, 12, 12, 12);
addGridBagRow(constraints, loginPane, new JLabel(addHeading(translate("Cashmachine"))), 0, ""); addGridBagRow(constraints, content, blz, 1, translate("BLZ"));
addGridBagRow(constraints, loginPane, blz, 1, translate("BLZ")); addGridBagRow(constraints, content, iban, 2, translate("IBAN"));
addGridBagRow(constraints, loginPane, iban, 2, translate("IBAN")); addGridBagRow(constraints, content, pin, 3, translate("PIN"));
addGridBagRow(constraints, loginPane, pin, 3, translate("PIN"));
constraints.gridy = 4; constraints.gridy = 4;
constraints.anchor = GridBagConstraints.PAGE_END; constraints.anchor = GridBagConstraints.PAGE_END;
constraints.weightx = 0; constraints.weightx = 0;
constraints.fill = GridBagConstraints.NONE; constraints.fill = GridBagConstraints.NONE;
constraints.insets = new Insets(0, 0, 0, 12); constraints.insets = new Insets(12, 12, 12, 12);
loginPane.add(login, constraints); content.add(login, constraints);
} }
private void createComponents() { private void createComponents() {
this.blz = new JFormattedTextField(); this.blz = new JFormattedTextField();
this.iban = new JFormattedTextField(); this.iban = new JFormattedTextField(getNumberFormat());
this.pin = new JPasswordField(); this.pin = new JPasswordField();
this.login = new JButton(translate("Login")); 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(); restrictPasswordToDigits();
restrictIbanInput();
} }
/** /**
@ -99,22 +81,17 @@ public class LoginView extends JPanel {
}); });
} }
/** private NumberFormatter getNumberFormat() {
* Adds a document filter onto {@link #iban} that filters out everything that is not a digit. var format = NumberFormat.getIntegerInstance();
* The filter also restricts the amount of digits that can be entered to {@link #MAX_PIN_DECIMAL_DIGITS} format.setGroupingUsed(false);
*/
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;
if (newText.matches(String.format("\\d{1,%s}", MAX_PIN_DECIMAL_DIGITS))) { var formatter = new NumberFormatter(format);
super.replace(fb, offset, length, text, attrs); formatter.setValueClass(Integer.class);
} formatter.setMinimum(0);
} formatter.setMaximum(Integer.MAX_VALUE);
}); formatter.setAllowsInvalid(false);
return formatter;
} }
public JTextField getBlz() { public JTextField getBlz() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -1,4 +1,2 @@
https://pixabay.com/vectors/register-cash-register-modern-23666/ 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) ![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

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -1,36 +1,29 @@
package me.teridax.jcash.gui.takeoff; package me.teridax.jcash.gui.takeoff;
import me.teridax.jcash.Logging;
import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.InvalidInputException;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.text.NumberFormat;
import java.text.ParseException; 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; import static me.teridax.jcash.lang.Translator.translate;
/**
* Dialog for taking off money of an account.
*/
public class TakeoffView { public class TakeoffView {
private JDialog dialog; private JDialog dialog;
private JButton cancel; private JButton cancel;
private JButton takeoff; private JButton takeoff;
private JLabel enteredValue;
private JLabel balanceAfterDeposit;
private JFormattedTextField value; private JFormattedTextField value;
public TakeoffView(double maxValue) { public TakeoffView() {
createComponents(maxValue); createComponents();
layoutComponents(); layoutComponents();
} }
/**
* Makes this dialog visible.
*/
public void showDialog() { public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon()); dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -48,61 +41,42 @@ public class TakeoffView {
c.gridx = 0; c.gridx = 0;
c.gridy = 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.gridwidth = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0; c.weightx = 0;
c.insets = new Insets(6,6,6,6);
dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c);
c.gridx = 1; c.gridx = 1;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5; c.weightx = 0.5;
dialog.getContentPane().add(value, c); dialog.getContentPane().add(value, c);
c.gridx = 2; c.gridx = 2;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LINE_START; c.anchor = GridBagConstraints.LINE_START;
c.weightx = 0; c.weightx = 0;
dialog.getContentPane().add(new JLabel(""), c); 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)); var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
buttonPanel.add(cancel); buttonPanel.add(cancel);
buttonPanel.add(takeoff); buttonPanel.add(takeoff);
c.gridx = 0; c.gridx = 0;
c.gridy = 3; c.gridy = 2;
c.gridwidth = 3; c.gridwidth = 3;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
@ -110,50 +84,29 @@ public class TakeoffView {
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
/** private void createComponents() {
* The createComponents function creates the components of the dialog.
* @param maxValue Set the maximum value of the jformattedtextfield
*/
private void createComponents(double maxValue) {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton(translate("Cancel")); this.cancel = new JButton(translate("Cancel"));
this.takeoff = new JButton(translate("Takeoff")); this.takeoff = new JButton(translate("Takeoff"));
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue)); this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
this.enteredValue = new JLabel();
this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
this.dialog.setContentPane(new JPanel(new GridBagLayout())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/** public double getAmount() {
* The getAmount function is used to get the amount of currency that has been entered into the text field. if (value.getText().isBlank()) {
* @return A double value, which is the parsed amount from the text field showMessageDialog(null, translate("Invalid amount"), translate("Currency must not be blank"), ERROR_MESSAGE);
*/ return 0;
public double getAmount() throws InvalidInputException { }
if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
try { try {
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
} catch (ParseException e) { } catch (ParseException e) {
Logging.LOGGER.severe("Amount text field contains invalid value: " + value); throw new RuntimeException(e);
throw new InvalidInputException(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() { public JButton getCancel() {
return cancel; return cancel;
} }
@ -162,10 +115,6 @@ public class TakeoffView {
return takeoff; return takeoff;
} }
public JFormattedTextField getValue() {
return value;
}
public void dispose() { public void dispose() {
this.dialog.dispose(); this.dialog.dispose();
} }

View File

@ -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();
}
}

View File

@ -46,32 +46,4 @@ public class TransferData {
account.get().getPrimaryAccount().deposit(amount); 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;
}
}
} }

View File

@ -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);
}
}
}

View File

@ -3,32 +3,14 @@ package me.teridax.jcash.gui.transfer;
import me.teridax.jcash.Logging; import me.teridax.jcash.Logging;
import me.teridax.jcash.decode.StringDecoder; import me.teridax.jcash.decode.StringDecoder;
import me.teridax.jcash.gui.IconProvider; import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.InvalidInputException;
import javax.swing.*; import javax.swing.*;
import java.awt.*; 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; 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 { public class TransferView {
private JDialog dialog; private JDialog dialog;
@ -37,17 +19,12 @@ public class TransferView {
private JFormattedTextField iban; private JFormattedTextField iban;
private JFormattedTextField blz; private JFormattedTextField blz;
private JFormattedTextField value; private JFormattedTextField value;
private JLabel balanceAfterTransfer;
private JLabel enteredValue;
public TransferView(double maxValue) { public TransferView() {
createComponents(maxValue); createComponents();
layoutComponents(); layoutComponents();
} }
/**
* Makes this dialog visible to the user
*/
public void showDialog() { public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon()); dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -60,142 +37,102 @@ public class TransferView {
dialog.setVisible(true); dialog.setVisible(true);
} }
/**
* Layout all components of this dialog.
*/
private void layoutComponents() { private void layoutComponents() {
var c = new GridBagConstraints(); var c = new GridBagConstraints();
c.gridx = 0; c.gridx = 0;
c.gridy = 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.gridwidth = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0; c.weightx = 0;
c.insets = new Insets(6,6,6,6);
dialog.getContentPane().add(new JLabel(translate("BLZ"), SwingConstants.RIGHT), c); dialog.getContentPane().add(new JLabel(translate("BLZ"), SwingConstants.RIGHT), c);
c.gridx = 1; c.gridx = 1;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5; c.weightx = 0.5;
dialog.getContentPane().add(blz, c); dialog.getContentPane().add(blz, c);
c.gridx = 2; c.gridx = 2;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0; c.weightx = 0;
dialog.getContentPane().add(new JLabel(translate("IBAN"), SwingConstants.RIGHT), c); dialog.getContentPane().add(new JLabel(translate("IBAN"), SwingConstants.RIGHT), c);
c.gridx = 3; c.gridx = 3;
c.gridy = 0; c.gridy = 1;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 1; c.weightx = 1;
dialog.getContentPane().add(iban, c); dialog.getContentPane().add(iban, c);
c.gridx = 0; c.gridx = 0;
c.gridy = 1; c.gridy = 2;
c.fill = GridBagConstraints.NONE; c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0; c.weightx = 0;
dialog.getContentPane().add(new JLabel(translate("Betrag"), SwingConstants.RIGHT), c); dialog.getContentPane().add(new JLabel(translate("Betrag"), SwingConstants.RIGHT), c);
c.gridx = 1; c.gridx = 1;
c.gridy = 1; c.gridy = 2;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5; c.weightx = 0.5;
dialog.getContentPane().add(value, c); 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)); var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
buttonPanel.add(cancel); buttonPanel.add(cancel);
buttonPanel.add(transfer); buttonPanel.add(transfer);
c.gridx = 0; c.gridx = 0;
c.gridy = 4; c.gridy = 3;
c.gridwidth = 4; c.gridwidth = 4;
c.fill = GridBagConstraints.HORIZONTAL; c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END; c.anchor = GridBagConstraints.LAST_LINE_END;
c.insets = new Insets(10, 10, 10, 10);
dialog.getContentPane().add(buttonPanel, c); dialog.getContentPane().add(buttonPanel, c);
} }
private void createComponents(double maxValue) { private void createComponents() {
this.dialog = new JDialog(); this.dialog = new JDialog();
this.cancel = new JButton(translate("Cancel")); this.cancel = new JButton(translate("Cancel"));
this.transfer = new JButton(translate("Transfer")); this.transfer = new JButton(translate("Transfer"));
this.transfer.setEnabled(false); this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue));
this.iban = new JFormattedTextField(); this.iban = new JFormattedTextField();
this.blz = 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())); this.dialog.setContentPane(new JPanel(new GridBagLayout()));
} }
/** public double getAmount() {
* Returns the entered amount parsed into a double value. if (value.getText().isBlank()) {
* @return the amount parsed into a double Logging.LOGGER.severe("Amount is empty");
* @throws InvalidInputException if the text in {@link #value} is not a valid double value. showMessageDialog(null, translate("invalid amount"), translate("currency must not be blank"), ERROR_MESSAGE);
*/ return 0;
public double getAmount() throws InvalidInputException { }
if (value.getText().isBlank())
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
try { try {
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue(); return StringDecoder.decodeCurrency(value.getText());
} catch (ParseException e) { } catch (IllegalArgumentException e) {
Logging.LOGGER.severe("Amount text field contains invalid value: " + value); Logging.LOGGER.severe("Invalid amount: " + value.getText());
throw new InvalidInputException(e); 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() { public JButton getCancel() {
return cancel; return cancel;
} }
@ -212,14 +149,6 @@ public class TransferView {
return blz.getText(); return blz.getText();
} }
public JFormattedTextField getIbanTextField() {
return iban;
}
public JFormattedTextField getBlzTextField() {
return blz;
}
public void dispose() { public void dispose() {
this.dialog.dispose(); this.dialog.dispose();
} }

View File

@ -10,9 +10,6 @@ import java.util.Locale;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class Locales { public class Locales {
/**
* Default locale initialized to the fallback locale used by this application.
*/
private static Locale defaultLocale = new Locale("en", "EN"); private static Locale defaultLocale = new Locale("en", "EN");
/** /**

View File

@ -6,11 +6,6 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; 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 { public final class Translator {
/** /**

View File

@ -1,54 +1,49 @@
en_EN,de_DE,es_ES,fr_FR,zh_Hans en_EN,de_DE,es_ES,fr_FR,zh_Hans
Bank,Bank,Banco,Banque,银行 Bank,Bank,Banco,Banque,银行
BLZ,BLZ,BLZ,BLZ,BLZ BLZ,BLZ,BLZ,CODE BANCAIRE,分类代码
PIN,PIN,PIN,PIN,密码 PIN,PIN,PIN,CODE PIN,密码
Balance,Kontostand,Saldo,Solde,余额 Balance,Kontostand,Saldo,Solde du compte,账户余额
Account type,Kontoart,Tipo de cuenta,Type de compte,账户类型 Account type,Kontoart,Tipo de cuenta,Type de compte,账户类型
Interest,Zinsen,Interés,Intérêts,利息 Interest,Zins,Interés,Intérêt,利息
Overdraft,Überziehungskredit,Descubierto,Découvert,透支 Overdraft,Überziehungsbetrag,Importe del descubierto,Montant du découvert,透支金额
Customer number,Kundennummer,Número de cliente,Numéro de client,客户编号 Customer number,Kundennummer,Número de cliente,Numéro de client,客户编号
Name,Name,Nombre,Nom du client,姓名 Name,Name,Nombre,Nom,客户姓名
Name,Name,Nombre,Nom et prénom,姓名 Name,Vorname,Nombre,Prénom,姓名
Street,Straße,Calle,Rue,街道 Street,Straße,Calle,Rue,街道
PLZ,PLZ,PLZ,PLZ,PLZ PLZ,PLZ,PLZ,NPA,邮政编码
City,Ort,Ciudad,Ville,城市 City,Ort,Ubicación,Ville,城市
Password,Kennwort,Contraseña,Mot de passe,密码 Password,Passwort,contraseña,Mot de passe,密码
Login,Anmeldung,Inicio de sesión,Connexion,登录 Login,Anmelden,Inicio de sesión,S'inscrire,登录
CurrentAccount,Girokonto,Cuenta corriente,Compte courant,活期账户 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,地址 Address,Adresse,Dirección,Adresse,地址
Logout,Abmelden,Cerrar sesión,Déconnexion,注销 Logout,Abmelden,desconectarse,Se désinscrire,退出登录
Transfer,Überweisung,Transferir,Virement,转账 Transfer,Überweisen,transferencia,Virement bancaire,转账
Deposit,Einzahlen,Ingresar,Dépôt,存款 Deposit,Einzahlen,depósito,Dépôt,存款
Take off,Abheben,Retirar,Enlever,取款 Take off,Abheben,despegar,Retrait,取款
Value,Wert,Valor,Valeur,价值 Value,Betrag,Importe,Montant,金额
Cancel,Abbrechen,Cancelar,Annuler,取消 Cancel,Abbrechen,Cancelar,Annuler,取消
Load database,Datenbank laden,Cargar base de datos,Charger la base de données,加载数据库 Load database,Datenbank auswählen,Seleccionar base de datos,Sélectionner la base de données,选择数据库
Invalid account,Ungültiges Konto,Cuenta no válida,Compte non valide,无效账户 Invalid account,Ungültiges Benutzerkonto,Cuenta de usuario no válida,Compte utilisateur non valide,用户账户无效
Could not transfer,Konnte nicht übertragen werden,No se ha podido transferir,Impossible de transférer,无法转账 Could not transfer,Überweiung fehlgeschlagen,Transferencia fallida,Échec du transfert,转账失败
Transfer,Überweisung,Transferencia,Transférer,转账 Transfer,Überweisen,Transferencia,Transfert,转帐
invalid amount,Ungültiger Betrag,Importe no válido,montant non valide,无效金额 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,货币不得为空 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,转账金额 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,无法取款 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 money,Geld abheben,Retirar dinero,Retirer de l'argent,取款
Takeoff,Abheben,Despegue,Décoller,起飞 Takeoff,Abheben,Retirar,Retrait,取款
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,货币不得为空 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,Geldautomat,Cajero,Cashmachine,提款机 Cashmachine,Bankautomat,CAJERO,Distributeur automatique de billets,ATM
Invalid IBAN,Ungültige IBAN,IBAN no válido,IBAN non valide,IBAN 无效 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,尝试登录失败 Faulty login attempt,Ungültiger Authentifizierungsverzuch,Solicitud de autenticación no válida,Demande d'authentification non valide,验证请求无效
Invalid PIN,Ungültige PIN,PIN no válido,PIN invalide,密码无效 Invalid PIN,Ungültiger PIN,PIN no válido,Code PIN non valide,密码无效
Invalid login credentials,Ungültige Anmeldedaten,Credenciales de inicio de sesión no válidas,Identifiants de connexion invalides,登录凭证无效 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,存款 Deposit money,Geld einzahlen,Depositar dinero,Dépôt d'argent,存款金额
Interest rate,Zinssatz,Tipo de interés,Taux d'intérêt,利率 Interest rate,Zinsbetrag,Importe de los intereses,Montant des intérêts,利息金额
Name/Family-name,Name/Familienname,Nombre y apellidos,Nom/Prénom de famille,姓名 Name/Family-name,Vorname/Name,Nombre y apellidos,Prénom/nom,名/姓
Address,Anschrift,Dirección,Adresse,地址 Address,Adresse,Dirección,Adresse,地址
Account,Konto,Cuenta,Compte,账户 Account,Konto,Cuenta,Compte,账户
Closing JCash,JCash wird geschlossen,Cerrar JCash,Clôture de JCash,关闭 JCash Closing JCash,JCash wird geschlossen,JCash está cerrado,JCash est fermé,JCash 已关闭
you're logged out,Sie sind abgemeldet,has cerrado sesión,Vous êtes déconnecté,您已退出登录 you're logged out,Du bist abgemeldet,Ha cerrado la sesión,Tu es 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,逗号分隔值电子表格 Comma separated value spreadsheet,Tabelle mit kommagetrennten Werten,Planilla de valores separados por comas,Feuille de calcul à 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 :,起飞价值
1 en_EN de_DE es_ES fr_FR zh_Hans
2 Bank Bank Banco Banque 银行
3 BLZ BLZ BLZ BLZ CODE BANCAIRE BLZ 分类代码
4 PIN PIN PIN PIN CODE PIN 密码
5 Balance Kontostand Saldo Solde Solde du compte 余额 账户余额
6 Account type Kontoart Tipo de cuenta Type de compte 账户类型
7 Interest Zinsen Zins Interés Intérêts Intérêt 利息
8 Overdraft Überziehungskredit Überziehungsbetrag Descubierto Importe del descubierto Découvert Montant du découvert 透支 透支金额
9 Customer number Kundennummer Número de cliente Numéro de client 客户编号
10 Name Name Nombre Nom du client Nom 姓名 客户姓名
11 Name Name Vorname Nombre Nom et prénom Prénom 姓名
12 Street Straße Calle Rue 街道
13 PLZ PLZ PLZ PLZ NPA PLZ 邮政编码
14 City Ort Ciudad Ubicación Ville 城市
15 Password Kennwort Passwort Contraseña contraseña Mot de passe 密码
16 Login Anmeldung Anmelden Inicio de sesión Connexion S'inscrire 登录
17 CurrentAccount Girokonto Cuenta corriente Compte courant 活期账户
18 SavingsAccount Sparkonto CuentaAhorro Cuenta de ahorro Compte d'épargne 储蓄账户
19 Address Adresse Dirección Adresse 地址
20 Logout Abmelden Cerrar sesión desconectarse Déconnexion Se désinscrire 注销 退出登录
21 Transfer Überweisung Überweisen Transferir transferencia Virement Virement bancaire 转账
22 Deposit Einzahlen Ingresar depósito Dépôt 存款
23 Take off Abheben Retirar despegar Enlever Retrait 取款
24 Value Wert Betrag Valor Importe Valeur Montant 价值 金额
25 Cancel Abbrechen Cancelar Annuler 取消
26 Load database Datenbank laden Datenbank auswählen Cargar base de datos Seleccionar base de datos Charger la base de données Sélectionner la base de données 加载数据库 选择数据库
27 Invalid account Ungültiges Konto Ungültiges Benutzerkonto Cuenta no válida Cuenta de usuario no válida Compte non valide Compte utilisateur non valide 无效账户 用户账户无效
28 Could not transfer Konnte nicht übertragen werden Überweiung fehlgeschlagen No se ha podido transferir Transferencia fallida Impossible de transférer Échec du transfert 无法转账 转账失败
29 Transfer Überweisung Überweisen Transferencia Transférer Transfert 转账 转帐
30 invalid amount Ungültiger Betrag Importe no válido montant non valide Montant non valide 无效金额 金额无效
31 currency must not be blank Währung darf nicht leer sein Betrag darf nicht leer sein La divisa no debe estar en blanco El importe no debe estar vacío la devise ne doit pas être vide Le montant ne doit pas être vide 货币不得为空 金额不能为空
32 Transfer money Geld überweisen Transferencia de dinero Transférer de l'argent 转账金额 汇款
33 Could not take off money Konnte Geld nicht abheben Geld konnte nicht abgehoben werden No se ha podido retirar el dinero No se ha podido retirar dinero Impossible de retirer de l'argent L'argent n'a pas pu être retiré 无法取款
34 Takeoff money Geld abheben Retirar dinero Retirer de l'argent 取款
35 Takeoff Abheben Despegue Retirar Décoller Retrait 起飞 取款
36 Currency must not be blank Die Währung darf nicht leer sein Betrag darf nicht leer sein La divisa no debe estar en blanco El importe no debe estar vacío La monnaie ne doit pas être en blanc Le montant ne doit pas être vide 货币不得为空 金额不能为空
37 Cashmachine Geldautomat Bankautomat Cajero CAJERO Cashmachine Distributeur automatique de billets 提款机 ATM
38 Invalid IBAN Ungültige IBAN IBAN no válido IBAN non valide IBAN 无效 无效的IBAN
39 Faulty login attempt Fehlerhafter Anmeldeversuch Ungültiger Authentifizierungsverzuch Intento de acceso erróneo Solicitud de autenticación no válida Tentative de connexion erronée Demande d'authentification non valide 尝试登录失败 验证请求无效
40 Invalid PIN Ungültige PIN Ungültiger PIN PIN no válido PIN invalide Code PIN non valide 密码无效
41 Invalid login credentials Ungültige Anmeldedaten Ungültiges Passwort oder Nutzername Credenciales de inicio de sesión no válidas Contraseña o nombre de usuario no válidos Identifiants de connexion invalides Mot de passe ou nom d'utilisateur non valide 登录凭证无效 密码或用户名无效
42 Deposit money Geld einzahlen Depositar dinero Dépôt d'argent 存款 存款金额
43 Interest rate Zinssatz Zinsbetrag Tipo de interés Importe de los intereses Taux d'intérêt Montant des intérêts 利率 利息金额
44 Name/Family-name Name/Familienname Vorname/Name Nombre y apellidos Nom/Prénom de famille Prénom/nom 姓名 名/姓
45 Address Anschrift Adresse Dirección Adresse 地址
46 Account Konto Cuenta Compte 账户
47 Closing JCash JCash wird geschlossen Cerrar JCash JCash está cerrado Clôture de JCash JCash est fermé 关闭 JCash JCash 已关闭
48 you're logged out Sie sind abgemeldet Du bist abgemeldet has cerrado sesión Ha cerrado la sesión Vous êtes déconnecté Tu es déconnecté 您已退出登录 您已注销
49 Comma separated value spreadsheet Kommagetrennte Werte-Tabelle Tabelle mit kommagetrennten Werten Hoja de cálculo de valores separados por comas Planilla de valores separados por comas Tableur de valeurs séparées par des virgules Feuille de calcul à 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 : 起飞价值