/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.account.content.fiataccounts;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.components.paymentmethods.*;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.gui.util.validation.*;
import io.bitsquare.locale.BSResources;
import io.bitsquare.payment.ClearXchangeAccount;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.payment.PaymentAccountFactory;
import io.bitsquare.payment.PaymentMethod;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.TextAlignment;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javax.inject.Inject;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static io.bitsquare.gui.util.FormBuilder.*;
@FxmlView
public class FiatAccountsView extends ActivatableViewAndModel<GridPane, FiatAccountsViewModel> {
private ListView<PaymentAccount> paymentAccountsListView;
private ComboBox<PaymentMethod> paymentMethodComboBox;
private final IBANValidator ibanValidator;
private final BICValidator bicValidator;
private final InputValidator inputValidator;
private final OKPayValidator okPayValidator;
private final AliPayValidator aliPayValidator;
private final PerfectMoneyValidator perfectMoneyValidator;
private final SwishValidator swishValidator;
private final ClearXchangeValidator clearXchangeValidator;
private final ChaseQuickPayValidator chaseQuickPayValidator;
private final InteracETransferValidator interacETransferValidator;
private final USPostalMoneyOrderValidator usPostalMoneyOrderValidator;
private BSFormatter formatter;
private PaymentMethodForm paymentMethodForm;
private TitledGroupBg accountTitledGroupBg;
private Button addAccountButton, saveNewAccountButton, exportButton, importButton;
private int gridRow = 0;
private ChangeListener<PaymentAccount> paymentAccountChangeListener;
@Inject
public FiatAccountsView(FiatAccountsViewModel model,
IBANValidator ibanValidator,
BICValidator bicValidator,
InputValidator inputValidator,
OKPayValidator okPayValidator,
AliPayValidator aliPayValidator,
PerfectMoneyValidator perfectMoneyValidator,
SwishValidator swishValidator,
ClearXchangeValidator clearXchangeValidator,
ChaseQuickPayValidator chaseQuickPayValidator,
InteracETransferValidator interacETransferValidator,
USPostalMoneyOrderValidator usPostalMoneyOrderValidator,
BSFormatter formatter) {
super(model);
this.ibanValidator = ibanValidator;
this.bicValidator = bicValidator;
this.inputValidator = inputValidator;
this.okPayValidator = okPayValidator;
this.aliPayValidator = aliPayValidator;
this.perfectMoneyValidator = perfectMoneyValidator;
this.swishValidator = swishValidator;
this.clearXchangeValidator = clearXchangeValidator;
this.chaseQuickPayValidator = chaseQuickPayValidator;
this.interacETransferValidator = interacETransferValidator;
this.usPostalMoneyOrderValidator = usPostalMoneyOrderValidator;
this.formatter = formatter;
}
@Override
public void initialize() {
buildForm();
paymentAccountChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null)
onSelectAccount(newValue);
};
Label placeholder = new Label("There are no accounts set up yet");
placeholder.setWrapText(true);
paymentAccountsListView.setPlaceholder(placeholder);
}
@Override
protected void activate() {
paymentAccountsListView.setItems(model.getPaymentAccounts());
paymentAccountsListView.getSelectionModel().selectedItemProperty().addListener(paymentAccountChangeListener);
addAccountButton.setOnAction(event -> addNewAccount());
exportButton.setOnAction(event -> model.dataModel.exportAccounts());
importButton.setOnAction(event -> model.dataModel.importAccounts());
}
@Override
protected void deactivate() {
paymentAccountsListView.getSelectionModel().selectedItemProperty().removeListener(paymentAccountChangeListener);
addAccountButton.setOnAction(null);
exportButton.setOnAction(null);
importButton.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
private void onSaveNewAccount(PaymentAccount paymentAccount) {
if (paymentAccount instanceof ClearXchangeAccount) {
final String cxc = BSResources.get(paymentAccount.getPaymentMethod().getId());
new Popup().information("Please be sure that you fulfill the requirements for the usage of " +
cxc + ".\n\n" +
"1. You need to have your " + cxc + " account verified at their platform " +
"before starting a trade or creating an offer.\n\n" +
"2. You need to have a bank account at one of the following member banks:\n" +
" ● Bank of America\n" +
" ● Capital One P2P Payments\n" +
" ● Chase QuickPay\n" +
" ● FirstBank Person to Person Transfers\n" +
" ● Frost Send Money\n" +
" ● U.S. Bank Send Money\n" +
" ● Wells Fargo SurePay\n\n" +
"Please use " + cxc + " only if you fulfill those requirements, " +
"otherwise it is very likely that the " + cxc + " transfer fails and the trade ends up in a dispute.\n" +
"If you have not fulfilled the above requirements you would lose your security deposit in such a case.")
.width(900)
.closeButtonText("Cancel")
.actionButtonText("I confirm")
.onAction(() -> doSaveNewAccount(paymentAccount))
.show();
} else {
doSaveNewAccount(paymentAccount);
}
}
private void doSaveNewAccount(PaymentAccount paymentAccount) {
if (!model.getPaymentAccounts().stream().filter(e -> {
if (e.getAccountName() != null)
return e.getAccountName().equals(paymentAccount.getAccountName());
else
return false;
}).findAny().isPresent()) {
model.onSaveNewAccount(paymentAccount);
removeNewAccountForm();
} else {
new Popup().warning("That account name is already used in a saved account.\n" +
"Please use another name.").show();
}
}
private void onCancelNewAccount() {
removeNewAccountForm();
}
private void onDeleteAccount(PaymentAccount paymentAccount) {
new Popup().warning("Do you really want to delete the selected account?")
.actionButtonText("Yes")
.onAction(() -> {
boolean isPaymentAccountUsed = model.onDeleteAccount(paymentAccount);
if (!isPaymentAccountUsed)
removeSelectAccountForm();
else
UserThread.runAfter(() -> {
new Popup().warning("You cannot delete that account because it is used in an " +
"open offer or in a trade.").show();
}, 100, TimeUnit.MILLISECONDS);
})
.closeButtonText("Cancel")
.show();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Base form
///////////////////////////////////////////////////////////////////////////////////////////
private void buildForm() {
addTitledGroupBg(root, gridRow, 1, "Manage accounts");
Tuple2<Label, ListView> tuple = addLabelListView(root, gridRow, "Your national currency\naccounts:", Layout.FIRST_ROW_DISTANCE);
GridPane.setValignment(tuple.first, VPos.TOP);
tuple.first.setTextAlignment(TextAlignment.RIGHT);
paymentAccountsListView = tuple.second;
paymentAccountsListView.setPrefHeight(2 * Layout.LIST_ROW_HEIGHT + 14);
paymentAccountsListView.setCellFactory(new Callback<ListView<PaymentAccount>, ListCell<PaymentAccount>>() {
@Override
public ListCell<PaymentAccount> call(ListView<PaymentAccount> list) {
return new ListCell<PaymentAccount>() {
final Label label = new Label();
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new Button("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
{
label.setLayoutY(5);
removeButton.setId("icon-button");
AnchorPane.setRightAnchor(removeButton, 0d);
}
@Override
public void updateItem(final PaymentAccount item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
label.setText(item.getAccountName());
removeButton.setOnAction(e -> onDeleteAccount(item));
setGraphic(pane);
} else {
setGraphic(null);
}
}
};
}
});
Tuple3<Button, Button, Button> tuple3 = add3ButtonsAfterGroup(root, ++gridRow, "Add new account", "Export Accounts", "Import Accounts");
addAccountButton = tuple3.first;
exportButton = tuple3.second;
importButton = tuple3.third;
}
// Add new account form
private void addNewAccount() {
paymentAccountsListView.getSelectionModel().clearSelection();
removeAccountRows();
addAccountButton.setDisable(true);
accountTitledGroupBg = addTitledGroupBg(root, ++gridRow, 1, "Create new account", Layout.GROUP_DISTANCE);
paymentMethodComboBox = addLabelComboBox(root, gridRow, "Payment method:", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
paymentMethodComboBox.setPromptText("Select payment method");
paymentMethodComboBox.setVisibleRowCount(15);
paymentMethodComboBox.setPrefWidth(250);
List<PaymentMethod> list = PaymentMethod.ALL_VALUES.stream()
.filter(paymentMethod -> !paymentMethod.getId().equals(PaymentMethod.BLOCK_CHAINS_ID))
.collect(Collectors.toList());
paymentMethodComboBox.setItems(FXCollections.observableArrayList(list));
paymentMethodComboBox.setConverter(new StringConverter<PaymentMethod>() {
@Override
public String toString(PaymentMethod paymentMethod) {
return paymentMethod != null ? BSResources.get(paymentMethod.getId()) : "";
}
@Override
public PaymentMethod fromString(String s) {
return null;
}
});
paymentMethodComboBox.setOnAction(e -> {
if (paymentMethodForm != null) {
FormBuilder.removeRowsFromGridPane(root, 3, paymentMethodForm.getGridRow() + 1);
GridPane.setRowSpan(accountTitledGroupBg, paymentMethodForm.getRowSpan() + 1);
}
gridRow = 2;
paymentMethodForm = getPaymentMethodForm(paymentMethodComboBox.getSelectionModel().getSelectedItem());
if (paymentMethodForm != null) {
paymentMethodForm.addFormForAddAccount();
gridRow = paymentMethodForm.getGridRow();
Tuple2<Button, Button> tuple2 = add2ButtonsAfterGroup(root, ++gridRow, "Save new account", "Cancel");
saveNewAccountButton = tuple2.first;
saveNewAccountButton.setOnAction(event -> onSaveNewAccount(paymentMethodForm.getPaymentAccount()));
saveNewAccountButton.disableProperty().bind(paymentMethodForm.allInputsValidProperty().not());
Button cancelButton = tuple2.second;
cancelButton.setOnAction(event -> onCancelNewAccount());
GridPane.setRowSpan(accountTitledGroupBg, paymentMethodForm.getRowSpan() + 1);
}
});
}
// Select account form
private void onSelectAccount(PaymentAccount paymentAccount) {
removeAccountRows();
addAccountButton.setDisable(false);
accountTitledGroupBg = addTitledGroupBg(root, ++gridRow, 1, "Selected account", Layout.GROUP_DISTANCE);
paymentMethodForm = getPaymentMethodForm(paymentAccount);
if (paymentMethodForm != null) {
paymentMethodForm.addFormForDisplayAccount();
gridRow = paymentMethodForm.getGridRow();
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(root, ++gridRow, "Delete account", "Cancel");
Button deleteAccountButton = tuple.first;
deleteAccountButton.setOnAction(event -> onDeleteAccount(paymentMethodForm.getPaymentAccount()));
Button cancelButton = tuple.second;
cancelButton.setOnAction(event -> removeSelectAccountForm());
GridPane.setRowSpan(accountTitledGroupBg, paymentMethodForm.getRowSpan());
model.onSelectAccount(paymentAccount);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private PaymentMethodForm getPaymentMethodForm(PaymentAccount paymentAccount) {
return getPaymentMethodForm(paymentAccount.getPaymentMethod(), paymentAccount);
}
private PaymentMethodForm getPaymentMethodForm(PaymentMethod paymentMethod) {
return getPaymentMethodForm(paymentMethod, PaymentAccountFactory.getPaymentAccount(paymentMethod));
}
private PaymentMethodForm getPaymentMethodForm(PaymentMethod paymentMethod, PaymentAccount paymentAccount) {
switch (paymentMethod.getId()) {
case PaymentMethod.OK_PAY_ID:
return new OKPayForm(paymentAccount, okPayValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.PERFECT_MONEY_ID:
return new PerfectMoneyForm(paymentAccount, perfectMoneyValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.SEPA_ID:
return new SepaForm(paymentAccount, ibanValidator, bicValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.FASTER_PAYMENTS_ID:
return new FasterPaymentsForm(paymentAccount, inputValidator, root, gridRow, formatter);
case PaymentMethod.NATIONAL_BANK_ID:
return new NationalBankForm(paymentAccount, inputValidator, root, gridRow, formatter, () -> onCancelNewAccount());
case PaymentMethod.SAME_BANK_ID:
return new SameBankForm(paymentAccount, inputValidator, root, gridRow, formatter, () -> onCancelNewAccount());
case PaymentMethod.SPECIFIC_BANKS_ID:
return new SpecificBankForm(paymentAccount, inputValidator, root, gridRow, formatter, () -> onCancelNewAccount());
case PaymentMethod.ALI_PAY_ID:
return new AliPayForm(paymentAccount, aliPayValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.SWISH_ID:
return new SwishForm(paymentAccount, swishValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.CLEAR_X_CHANGE_ID:
return new ClearXchangeForm(paymentAccount, clearXchangeValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.CHASE_QUICK_PAY_ID:
return new ChaseQuickPayForm(paymentAccount, chaseQuickPayValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.INTERAC_E_TRANSFER_ID:
return new InteracETransferForm(paymentAccount, interacETransferValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.US_POSTAL_MONEY_ORDER_ID:
return new USPostalMoneyOrderForm(paymentAccount, usPostalMoneyOrderValidator, inputValidator, root, gridRow, formatter);
case PaymentMethod.CASH_DEPOSIT_ID:
return new CashDepositForm(paymentAccount, inputValidator, root, gridRow, formatter);
default:
log.error("Not supported PaymentMethod: " + paymentMethod);
return null;
}
}
private void removeNewAccountForm() {
saveNewAccountButton.disableProperty().unbind();
removeAccountRows();
addAccountButton.setDisable(false);
}
private void removeSelectAccountForm() {
FormBuilder.removeRowsFromGridPane(root, 2, gridRow);
gridRow = 1;
addAccountButton.setDisable(false);
paymentAccountsListView.getSelectionModel().clearSelection();
}
private void removeAccountRows() {
FormBuilder.removeRowsFromGridPane(root, 2, gridRow);
gridRow = 1;
}
}