fixed banking dialogs input validation

This commit is contained in:
Sven Vogel 2023-07-22 13:58:52 +02:00
parent 8f17a6aa49
commit f1f743d233
20 changed files with 545 additions and 246 deletions

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

@ -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,17 @@ public class StringDecoder {
return NumberFormat.getInstance(Locales.getDefaultLocale());
}
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;
}
/**
* 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

View File

@ -0,0 +1,18 @@
package me.teridax.jcash.gui;
import java.text.ParseException;
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,17 @@
package me.teridax.jcash.gui;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.NumberFormatter;
public class NumberDocumentFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, string, attr);
}
private String filterString(String text) {
return "";
}
}

View File

@ -1,12 +1,17 @@
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 {
public static String addHeading(String title) {
return String.format("<html><title>%s</title></html>", title);
return String.format("<html><h1>%s</h1></html>", title);
}
/**
@ -58,4 +63,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.
@ -31,14 +31,25 @@ public class AccountController {
private void createListeners(Profile profile) {
this.view.getAccountSelection().addActionListener(e -> changeAccount());
this.view.getLogout().addActionListener(e -> logout());
this.view.getDeposit().addActionListener(e -> depositMoney());
this.view.getTakeoff().addActionListener(e -> takeoffMoney());
this.view.getTransfer().addActionListener(e -> transferMoney());
}
this.view.getDeposit().addActionListener(e -> new DepositDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
private void depositMoney() {
new DepositController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
}
this.view.getTakeoff().addActionListener(e -> new TakeoffDialog(profile.getPrimaryAccount(), () -> this.view.setProfile(profile)));
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)));
private void takeoffMoney() {
new TakeoffController(profile.getPrimaryAccount());
this.view.updateAccountVariables(profile);
}
private void logout() {

View File

@ -38,37 +38,14 @@ public class AccountView extends JPanel {
}
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());
this.accountSelection.setSelectedItem(profile.getPrimaryAccount().getDescription());
}
private void createLayout() {
@ -156,4 +133,31 @@ public class AccountView extends JPanel {
public JButton getTakeoff() {
return takeoff;
}
public void updateAccountVariables(Profile profile) {
Logging.LOGGER.finer("Updating 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());
}
}
}

View File

@ -0,0 +1,71 @@
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;
public class DepositController {
private final DepositView view;
private final Account account;
private final DepositData data;
public DepositController(Account account) {
this.account = account;
this.data = new DepositData(account.getBalance());
this.view = new DepositView(this.data.getMaxValue());
this.view.getDeposit().addActionListener(e -> depositMoney());
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.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();
}
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,14 @@
package me.teridax.jcash.gui.deposit;
public class DepositData {
private double maxValue;
public DepositData(double maxValue) {
this.maxValue = maxValue;
}
public double getMaxValue() {
return maxValue;
}
}

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,10 +3,10 @@ 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;
@ -16,10 +16,12 @@ public class DepositView {
private JDialog dialog;
private JButton cancel;
private JButton deposit;
private JLabel enteredValue;
private JLabel balanceAfterDeposit;
private JFormattedTextField value;
public DepositView() {
createComponents();
public DepositView(double maxValue) {
createComponents(maxValue);
layoutComponents();
}
@ -42,71 +44,97 @@ 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("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("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() {
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 +146,9 @@ public class DepositView {
public void dispose() {
this.dialog.dispose();
}
public void setCommittedValue(double amount, double after) {
enteredValue.setText(StringDecoder.getNumberFormat().format(amount));
balanceAfterDeposit.setText(StringDecoder.getNumberFormat().format(after));
}
}

View File

@ -2,6 +2,7 @@ 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;
@ -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("no 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("no PIN entered");
return;
}
@ -68,8 +67,7 @@ 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);
}
}

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;
@ -27,12 +28,26 @@ 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() {

View File

@ -0,0 +1,67 @@
package me.teridax.jcash.gui.takeoff;
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;
public class TakeoffController {
private final Account account;
private final TakeoffView view;
private final TakeoffData data;
public TakeoffController(Account account) {
this.account = account;
this.data = new TakeoffData(account.getBalance());
this.view = new TakeoffView(this.data.getMaxValue());
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();
}
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,14 @@
package me.teridax.jcash.gui.takeoff;
public class TakeoffData {
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,15 +1,14 @@
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;
public class TakeoffView {
@ -17,10 +16,12 @@ 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();
}
@ -41,42 +42,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("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("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 +104,35 @@ public class TakeoffView {
dialog.getContentPane().add(buttonPanel, c);
}
private void createComponents() {
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;
}
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);
}
}
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 +141,10 @@ public class TakeoffView {
return takeoff;
}
public JFormattedTextField getValue() {
return value;
}
public void dispose() {
this.dialog.dispose();
}

View File

@ -0,0 +1,72 @@
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 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;
this.view = new TransferView(account.getBalance());
this.transferData = new TransferData(bms);
this.view.getTransfer().addActionListener(e -> transfer());
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.getTransfer().setEnabled(true);
} catch (InvalidInputException | ParseException ex) {
view.setCommittedValue(0, balance);
view.getTransfer().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();
}
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

@ -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,12 +3,12 @@ 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;
public class TransferView {
@ -20,8 +20,11 @@ public class TransferView {
private JFormattedTextField blz;
private JFormattedTextField value;
public TransferView() {
createComponents();
private JLabel balanceAfterTransfer;
private JLabel enteredValue;
public TransferView(double maxValue) {
createComponents(maxValue);
layoutComponents();
}
@ -42,97 +45,123 @@ public class TransferView {
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("entered value:", 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("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.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;
}
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);
}
}
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;
}