/*
* 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.altcoinaccounts;
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.CryptoCurrencyForm;
import io.bitsquare.gui.components.paymentmethods.PaymentMethodForm;
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.CryptoCurrency;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.payment.PaymentAccountFactory;
import io.bitsquare.payment.PaymentMethod;
import javafx.beans.value.ChangeListener;
import javafx.geometry.VPos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.util.Callback;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
@FxmlView
public class AltCoinAccountsView extends ActivatableViewAndModel<GridPane, AltCoinAccountsViewModel> {
private ListView<PaymentAccount> paymentAccountsListView;
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 AltCoinAddressValidator altCoinAddressValidator;
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 AltCoinAccountsView(AltCoinAccountsViewModel model,
IBANValidator ibanValidator,
BICValidator bicValidator,
InputValidator inputValidator,
OKPayValidator okPayValidator,
AliPayValidator aliPayValidator,
PerfectMoneyValidator perfectMoneyValidator,
SwishValidator swishValidator,
AltCoinAddressValidator altCoinAddressValidator,
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.altCoinAddressValidator = altCoinAddressValidator;
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) {
TradeCurrency selectedTradeCurrency = paymentAccount.getSelectedTradeCurrency();
String code = selectedTradeCurrency.getCode();
if (selectedTradeCurrency instanceof CryptoCurrency && ((CryptoCurrency) selectedTradeCurrency).isAsset()) {
new Popup().information("Please be sure that you follow the requirements for the usage of " +
selectedTradeCurrency.getCodeAndName() + " wallets as described on the " +
selectedTradeCurrency.getName() + " web page.\n" +
"Using wallets from centralized exchanges where you don't have your keys under your control or " +
"using a not compatible wallet software can lead to loss of the traded funds!\n" +
"The arbitrator is not a " + selectedTradeCurrency.getName() + " specialist and cannot help in such cases.")
.closeButtonText("I understand and confirm that I know which wallet I need to use.")
.show();
}
if (code.equals("XMR")) {
new Popup().information("If you want to trade XMR on Bitsquare please be sure you understand and fulfill " +
"the following requirements:\n\n" +
"For sending XMR you need to use the Monero simple wallet with the " +
"store-tx-info flag enabled (default in new versions).\n" +
"Please be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that " +
"would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with " +
"the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\n" +
"At normal block explorers the transfer is not verifiable.\n\n" +
"You need to provide the arbitrator the following data in case of a dispute:\n" +
"- The tx private key\n" +
"- The transaction hash\n" +
"- The recipient's public address\n\n" +
"If you cannot provide the above data or if you used an incompatible wallet it would result in losing the " +
"dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the " +
"arbitrator in case of a dispute.\n\n" +
"There is no payment ID required, just the normal public address.\n\n" +
"If you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.")
.closeButtonText("I understand")
.show();
} else if (code.equals("ZEC")) {
new Popup().information("When using ZEC you can only use the transparent addresses (starting with t) not " +
"the z-addresses, because the arbitrator would not be able to verify the transaction with z-addresses.")
.closeButtonText("I understand")
.show();
} else if (code.equals("XZC")) {
new Popup().information("When using XZC you can only use the transparent transactions not " +
"the private transactions, because the arbitrator would not be able to verify the private transactions.")
.closeButtonText("I understand")
.show();
}
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 altcoin accounts:", Layout.FIRST_ROW_DISTANCE);
GridPane.setValignment(tuple.first, VPos.TOP);
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);
if (paymentMethodForm != null) {
FormBuilder.removeRowsFromGridPane(root, 3, paymentMethodForm.getGridRow() + 1);
GridPane.setRowSpan(accountTitledGroupBg, paymentMethodForm.getRowSpan() + 1);
}
gridRow = 2;
paymentMethodForm = getPaymentMethodForm(PaymentMethod.BLOCK_CHAINS);
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(PaymentMethod paymentMethod) {
return getPaymentMethodForm(PaymentAccountFactory.getPaymentAccount(paymentMethod));
}
private PaymentMethodForm getPaymentMethodForm(PaymentAccount paymentAccount) {
return new CryptoCurrencyForm(paymentAccount, altCoinAddressValidator, inputValidator, root, gridRow, formatter);
}
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;
}
}