Compare commits

...

11 Commits

34 changed files with 1156 additions and 410 deletions

View File

@ -4,6 +4,8 @@
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.

View File

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

View File

@ -2,19 +2,18 @@ package me.teridax.jcash;
import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.Loader;
import me.teridax.jcash.gui.account.AccountController;
import me.teridax.jcash.gui.login.LoginController;
import me.teridax.jcash.gui.MainFrame;
import me.teridax.jcash.gui.Utils;
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 {
@ -24,29 +23,56 @@ public final class Main {
private static Main instance;
/**
* Primary window of this program
* Primary class for controlling GUI of this application
*/
private final JFrame window;
private final MainFrame window;
private Main() {
// 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());
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);
}
}
public static void main(String[] args) {
initializeSystemLogger(Level.FINE);
Locales.autodetectDefaultLocale();
setPlatformDependingTheme();
loadExtraFont();
Locales.autodetectDefaultLocale();
// create main instance and show the login screen
instance();
getInstance().showLoginScreen();
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());
}
}
/**
@ -109,53 +135,16 @@ public final class Main {
Main.instance = new Main();
}
/**
* 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);
}
});
public JFrame getWindow() {
return this.window.getWindow();
}
/**
* Logs the user out of the database, hiding the main window.
* 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.
*/
public void logout() {
window.setContentPane(new JLabel(translate("you're logged out")));
window.setVisible(false);
}
public JFrame getWindow() {
return this.window;
this.window.logout();
}
}

View File

@ -4,6 +4,7 @@ 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;

View File

@ -5,6 +5,8 @@ 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.
@ -111,7 +113,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, getClass().getSimpleName());
return String.format("%s (%s)", iban, translate(getClass().getSimpleName()));
}
/**

View File

@ -6,6 +6,8 @@ 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.*;
@ -23,6 +25,13 @@ 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
@ -76,7 +85,7 @@ public final class BankingManagementSystem {
LOGGER.fine("parsing banking management system from file: " + Objects.toString(file, "null"));
try {
var bms = new BankingManagementSystem();
var content = Files.readString(file);
var content = getSource(file);
// read line by line
// and skip the first line
@ -114,15 +123,33 @@ 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.
*

View File

@ -2,6 +2,7 @@ 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;
@ -17,6 +18,35 @@ 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
@ -117,7 +147,7 @@ public class StringDecoder {
var trimmed = name.trim();
var pattern = Pattern.compile("[^\\s]+", Pattern.CASE_INSENSITIVE);
var pattern = Pattern.compile("[^\\d]+", Pattern.CASE_INSENSITIVE);
var matcher = pattern.matcher(trimmed);
if (matcher.find()) {
return matcher.group();

View File

@ -7,14 +7,28 @@ 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));
@ -25,4 +39,12 @@ 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");
}
}

View File

@ -0,0 +1,21 @@
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

@ -0,0 +1,128 @@
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,12 +1,25 @@
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>&lt;html>&lt;h1>$string&lt;/h1>&lt;/html></pre>
*/
public static String addHeading(String title) {
return String.format("<html><title>%s</title></html>", title);
return String.format("<html><h1>%s</h1></html>", title);
}
/**
@ -27,6 +40,9 @@ 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;
@ -58,4 +74,13 @@ 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);
}
}

View File

@ -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.DepositDialog;
import me.teridax.jcash.gui.takeoff.TakeoffDialog;
import me.teridax.jcash.gui.transfer.TransferDialog;
import me.teridax.jcash.gui.deposit.DepositController;
import me.teridax.jcash.gui.takeoff.TakeoffController;
import me.teridax.jcash.gui.transfer.TransferController;
/**
* Controller for controlling the gui of an account.
@ -18,35 +18,68 @@ public class AccountController {
*/
private final AccountView view;
private final Profile profile;
private Profile profile;
public AccountController(Profile profile, BankingManagementSystem bms) {
this.profile = profile;
public AccountController() {
this.view = new AccountView();
this.view.setProfile(profile);
this.data = new AccountData(bms);
createListeners(profile);
this.data = new AccountData();
}
private void createListeners(Profile profile) {
/**
* 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.view.setProfile(profile);
this.data.setBms(bms);
this.createListeners();
}
/**
* Create listeners for GUI components
*/
private void createListeners() {
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());
}
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() {
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);

View File

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

View File

@ -1,6 +1,7 @@
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;
@ -8,6 +9,8 @@ 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;
@ -37,38 +40,19 @@ 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) {
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.updateAccountVariables(profile);
this.accountSelection.removeAllItems();
for (var otherAccount : profile.getAccounts()) {
this.accountSelection.addItem(otherAccount.getDescription());
}
this.accountSelection.setSelectedItem(account.getDescription());
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());
}
private void createLayout() {
@ -156,4 +140,40 @@ 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());
}
}
}

View File

@ -0,0 +1,84 @@
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

@ -1,37 +0,0 @@
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,23 +3,41 @@ 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() {
createComponents();
public DepositView(double maxValue) {
createComponents(maxValue);
layoutComponents();
}
@ -42,71 +60,103 @@ 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 = 1;
c.gridy = 0;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5;
dialog.getContentPane().add(value, c);
c.gridx = 2;
c.gridy = 1;
c.gridy = 0;
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 = 2;
c.gridy = 3;
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() {
private void createComponents(double maxValue) {
this.dialog = new JDialog();
this.cancel = new JButton(translate("Cancel"));
this.deposit = new JButton(translate("Deposit"));
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
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.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())
return 0;
throw new InvalidInputException("currency value is blank or has been invalid whilst entered");
try {
return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
} catch (ParseException e) {
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
throw new RuntimeException(e);
throw new InvalidInputException(e);
}
}
public JFormattedTextField getValue() {
return value;
}
public JButton getCancel() {
return cancel;
}
@ -118,4 +168,14 @@ 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));
}
}

View File

@ -2,14 +2,11 @@ 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;
@ -17,13 +14,17 @@ public class LoginController {
private AccountSelectionListener listener;
public LoginController(BankingManagementSystem bms) {
public LoginController() {
this.view = new LoginView();
this.data = new LoginData(bms);
this.data = new LoginData();
addActionListeners();
}
public void setBankingManagementSystem(BankingManagementSystem bms) {
this.data.setBms(bms);
}
private void addActionListeners() {
this.view.getLogin().addActionListener(this::login);
}
@ -52,15 +53,13 @@ public class LoginController {
var blz = this.view.getBlz().getText();
var iban = this.getIban();
if (iban.isEmpty()) {
Logging.LOGGER.severe("IBAN is invalid: " + iban);
showMessageDialog(null, translate("Invalid IBAN"), translate("Faulty login attempt"), ERROR_MESSAGE);
Utils.error("invalid IBAN entered");
return;
}
var pin = this.getPin();
if (pin.isEmpty()) {
Logging.LOGGER.severe("PIN is invalid: " + pin);
showMessageDialog(null, translate("Invalid pin"), translate("Faulty login attempt"), ERROR_MESSAGE);
Utils.error("invalid PIN entered");
return;
}
@ -68,9 +67,8 @@ public class LoginController {
if (account.isPresent()) {
this.listener.onAccountSelected(account.get());
} else {
Logging.LOGGER.severe("invalid login credentials: " + iban + " / " + pin);
showMessageDialog(null, translate("Invalid login credentials"), translate("Faulty login attempt"), ERROR_MESSAGE);
}
Logging.LOGGER.warning("invalid login credentials: " + iban + " / " + pin);
}
}
public void addAccountSelectionListener(AccountSelectionListener listener) {
@ -84,4 +82,8 @@ public class LoginController {
public LoginData getData() {
return data;
}
public void logout() {
this.view.getPin().setText("");
}
}

View File

@ -3,6 +3,7 @@ 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;
@ -11,11 +12,7 @@ import java.util.Optional;
*/
public class LoginData {
private final BankingManagementSystem bms;
public LoginData(BankingManagementSystem bms) {
this.bms = bms;
}
private BankingManagementSystem bms;
/**
* authenticate the specified account with the provided pin.
@ -27,15 +24,29 @@ 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())
if (optionalBank.isEmpty()) {
Utils.error("Unknown BLZ: " + blz);
return Optional.empty();
}
var profile = optionalBank.get().getAccount(iban);
return profile.filter(value -> value.getPrimaryAccount().getPin() == pin);
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;
}
public BankingManagementSystem getBms() {
return bms;
public void setBms(BankingManagementSystem bms) {
this.bms = bms;
}
}

View File

@ -1,16 +1,18 @@
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 {
/**
@ -18,6 +20,10 @@ 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;
@ -30,37 +36,49 @@ public class LoginView extends JPanel {
}
private void layoutComponents() {
var content = new JPanel(new GridBagLayout());
var content = new JLabel();
content.setIcon(new ImageIcon(IconProvider.getBackground()));
content.setLayout(new BorderLayout());
this.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
this.setLayout(new BorderLayout(16, 16));
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.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, content, blz, 1, translate("BLZ"));
addGridBagRow(constraints, content, iban, 2, translate("IBAN"));
addGridBagRow(constraints, content, pin, 3, translate("PIN"));
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"));
constraints.gridy = 4;
constraints.anchor = GridBagConstraints.PAGE_END;
constraints.weightx = 0;
constraints.fill = GridBagConstraints.NONE;
constraints.insets = new Insets(12, 12, 12, 12);
content.add(login, constraints);
constraints.insets = new Insets(0, 0, 0, 12);
loginPane.add(login, constraints);
}
private void createComponents() {
this.blz = new JFormattedTextField();
this.iban = new JFormattedTextField(getNumberFormat());
this.iban = new JFormattedTextField();
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();
}
/**
@ -81,17 +99,22 @@ public class LoginView extends JPanel {
});
}
private NumberFormatter getNumberFormat() {
var format = NumberFormat.getIntegerInstance();
format.setGroupingUsed(false);
/**
* 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;
var formatter = new NumberFormatter(format);
formatter.setValueClass(Integer.class);
formatter.setMinimum(0);
formatter.setMaximum(Integer.MAX_VALUE);
formatter.setAllowsInvalid(false);
return formatter;
if (newText.matches(String.format("\\d{1,%s}", MAX_PIN_DECIMAL_DIGITS))) {
super.replace(fb, offset, length, text, attrs);
}
}
});
}
public JTextField getBlz() {

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -1,2 +1,4 @@
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

@ -0,0 +1,86 @@
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

@ -0,0 +1,20 @@
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

@ -1,38 +0,0 @@
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,29 +1,36 @@
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() {
createComponents();
public TakeoffView(double maxValue) {
createComponents(maxValue);
layoutComponents();
}
/**
* Makes this dialog visible.
*/
public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -41,42 +48,61 @@ 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 = 1;
c.gridy = 0;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5;
dialog.getContentPane().add(value, c);
c.gridx = 2;
c.gridy = 1;
c.gridy = 0;
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 = 2;
c.gridy = 3;
c.gridwidth = 3;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END;
@ -84,29 +110,50 @@ public class TakeoffView {
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.cancel = new JButton(translate("Cancel"));
this.takeoff = new JButton(translate("Takeoff"));
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue));
this.enteredValue = new JLabel();
this.balanceAfterDeposit = new JLabel(StringDecoder.getNumberFormat().format(maxValue));
this.dialog.setContentPane(new JPanel(new GridBagLayout()));
}
public double getAmount() {
if (value.getText().isBlank()) {
showMessageDialog(null, translate("Invalid amount"), translate("Currency must not be blank"), ERROR_MESSAGE);
return 0;
}
/**
* 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");
try {
return NumberFormat.getNumberInstance().parse(value.getText()).doubleValue();
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
} catch (ParseException e) {
throw new RuntimeException(e);
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
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() {
return cancel;
}
@ -115,6 +162,10 @@ public class TakeoffView {
return takeoff;
}
public JFormattedTextField getValue() {
return value;
}
public void dispose() {
this.dialog.dispose();
}

View File

@ -0,0 +1,111 @@
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,4 +46,32 @@ 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;
}
}
}

View File

@ -1,46 +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.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,14 +3,32 @@ 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;
@ -19,12 +37,17 @@ public class TransferView {
private JFormattedTextField iban;
private JFormattedTextField blz;
private JFormattedTextField value;
private JLabel balanceAfterTransfer;
private JLabel enteredValue;
public TransferView() {
createComponents();
public TransferView(double maxValue) {
createComponents(maxValue);
layoutComponents();
}
/**
* Makes this dialog visible to the user
*/
public void showDialog() {
dialog.setIconImage(IconProvider.getWindowIcon());
dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
@ -37,102 +60,142 @@ 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 = 1;
c.gridy = 0;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 0.5;
dialog.getContentPane().add(blz, c);
c.gridx = 2;
c.gridy = 1;
c.gridy = 0;
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 = 1;
c.gridy = 0;
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.LAST_LINE_END;
c.weightx = 1;
dialog.getContentPane().add(iban, c);
c.gridx = 0;
c.gridy = 2;
c.gridy = 1;
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 = 2;
c.gridy = 1;
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 = 3;
c.gridy = 4;
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() {
private void createComponents(double maxValue) {
this.dialog = new JDialog();
this.cancel = new JButton(translate("Cancel"));
this.transfer = new JButton(translate("Transfer"));
this.value = new JFormattedTextField(StringDecoder.getNumberFormat());
this.transfer.setEnabled(false);
this.value = new JFormattedTextField(StringDecoder.getNumberFormatter(maxValue));
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()));
}
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;
}
/**
* 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");
try {
return StringDecoder.decodeCurrency(value.getText());
} catch (IllegalArgumentException e) {
Logging.LOGGER.severe("Invalid amount: " + value.getText());
throw new RuntimeException(e);
return StringDecoder.getNumberFormat().parse(value.getText()).doubleValue();
} catch (ParseException e) {
Logging.LOGGER.severe("Amount text field contains invalid value: " + value);
throw new InvalidInputException(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;
}
@ -149,6 +212,14 @@ public class TransferView {
return blz.getText();
}
public JFormattedTextField getIbanTextField() {
return iban;
}
public JFormattedTextField getBlzTextField() {
return blz;
}
public void dispose() {
this.dialog.dispose();
}

View File

@ -10,6 +10,9 @@ 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");
/**

View File

@ -6,6 +6,11 @@ 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 {
/**

View File

@ -1,49 +1,54 @@
en_EN,de_DE,es_ES,fr_FR,zh_Hans
Bank,Bank,Banco,Banque,银行
BLZ,BLZ,BLZ,CODE BANCAIRE,分类代码
PIN,PIN,PIN,CODE PIN,密码
Balance,Kontostand,Saldo,Solde du compte,账户余额
BLZ,BLZ,BLZ,BLZ,BLZ
PIN,PIN,PIN,PIN,密码
Balance,Kontostand,Saldo,Solde,余额
Account type,Kontoart,Tipo de cuenta,Type de compte,账户类型
Interest,Zins,Interés,Intérêt,利息
Overdraft,Überziehungsbetrag,Importe del descubierto,Montant du découvert,透支金额
Interest,Zinsen,Interés,Intérêts,利息
Overdraft,Überziehungskredit,Descubierto,Découvert,透支
Customer number,Kundennummer,Número de cliente,Numéro de client,客户编号
Name,Name,Nombre,Nom,客户姓名
Name,Vorname,Nombre,Prénom,姓名
Name,Name,Nombre,Nom du client,姓名
Name,Name,Nombre,Nom et prénom,姓名
Street,Straße,Calle,Rue,街道
PLZ,PLZ,PLZ,NPA,邮政编码
City,Ort,Ubicación,Ville,城市
Password,Passwort,contraseña,Mot de passe,密码
Login,Anmelden,Inicio de sesión,S'inscrire,登录
PLZ,PLZ,PLZ,PLZ,PLZ
City,Ort,Ciudad,Ville,城市
Password,Kennwort,Contraseña,Mot de passe,密码
Login,Anmeldung,Inicio de sesión,Connexion,登录
CurrentAccount,Girokonto,Cuenta corriente,Compte courant,活期账户
SavingsAccount,Sparkonto,Cuenta de ahorro,Compte d'épargne,储蓄账户
SavingsAccount,Sparkonto,CuentaAhorro,Compte d'épargne,储蓄账户
Address,Adresse,Dirección,Adresse,地址
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,金额
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,价值
Cancel,Abbrechen,Cancelar,Annuler,取消
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é,无法取款
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,无法取款
Takeoff money,Geld abheben,Retirar dinero,Retirer de l'argent,取款
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,地址
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,地址
Account,Konto,Cuenta,Compte,账户
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,逗号分隔值电子表格
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 :,起飞价值

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