/*
* 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.portfolio.pendingtrades.steps.buyer;
import io.bitsquare.app.DevFlags;
import io.bitsquare.app.Log;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.AddressEntryException;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.funds.transactions.TransactionsView;
import io.bitsquare.gui.main.overlays.notifications.Notification;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeStepView;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
public class BuyerStep5View extends TradeStepView {
private final ChangeListener<Boolean> focusedPropertyListener;
protected Label btcTradeAmountLabel;
protected Label fiatTradeAmountLabel;
private InputTextField withdrawAddressTextField;
private Button withdrawToExternalWalletButton, useSavingsWalletButton;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialisation
///////////////////////////////////////////////////////////////////////////////////////////
public BuyerStep5View(PendingTradesViewModel model) {
super(model);
focusedPropertyListener = (ov, oldValue, newValue) -> {
if (oldValue && !newValue)
model.withdrawAddressFocusOut(withdrawAddressTextField.getText());
};
}
@Override
public void activate() {
super.activate();
// TODO valid. handler need improvement
//withdrawAddressTextField.focusedProperty().addListener(focusedPropertyListener);
//withdrawAddressTextField.setValidator(model.getBtcAddressValidator());
// withdrawButton.disableProperty().bind(model.getWithdrawalButtonDisable());
// We need to handle both cases: Address not set and address already set (when returning from other view)
// We get address validation after focus out, so first make sure we loose focus and then set it again as hint for user to put address in
//TODO app wide focus
/* UserThread.execute(() -> {
withdrawAddressTextField.requestFocus();
UserThread.execute(() -> {
this.requestFocus();
UserThread.execute(() -> withdrawAddressTextField.requestFocus());
});
});*/
hideNotificationGroup();
}
@Override
public void deactivate() {
Log.traceCall();
super.deactivate();
//withdrawAddressTextField.focusedProperty().removeListener(focusedPropertyListener);
// withdrawButton.disableProperty().unbind();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Content
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void addContent() {
addTitledGroupBg(gridPane, gridRow, 4, "Summary of completed trade ", 0);
Tuple2<Label, TextField> btcTradeAmountPair = addLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.FIRST_ROW_DISTANCE);
btcTradeAmountLabel = btcTradeAmountPair.first;
Tuple2<Label, TextField> fiatTradeAmountPair = addLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume());
fiatTradeAmountLabel = fiatTradeAmountPair.first;
addLabelTextField(gridPane, ++gridRow, "Total fees paid:", model.getTotalFees());
addLabelTextField(gridPane, ++gridRow, "Refunded security deposit:", model.getSecurityDeposit());
addTitledGroupBg(gridPane, ++gridRow, 2, "Withdraw your bitcoins", Layout.GROUP_DISTANCE);
addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
withdrawAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Withdraw to address:").second;
HBox hBox = new HBox();
hBox.setSpacing(10);
useSavingsWalletButton = new Button("Move funds to Bitsquare wallet");
useSavingsWalletButton.setDefaultButton(false);
Label label = new Label("OR");
label.setPadding(new Insets(5, 0, 0, 0));
withdrawToExternalWalletButton = new Button("Withdraw to external wallet");
withdrawToExternalWalletButton.setDefaultButton(false);
hBox.getChildren().addAll(useSavingsWalletButton, label, withdrawToExternalWalletButton);
GridPane.setRowIndex(hBox, ++gridRow);
GridPane.setColumnIndex(hBox, 1);
GridPane.setMargin(hBox, new Insets(15, 10, 0, 0));
gridPane.getChildren().add(hBox);
useSavingsWalletButton.setOnAction(e -> {
model.dataModel.walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
handleTradeCompleted();
model.dataModel.tradeManager.addTradeToClosedTrades(trade);
});
withdrawToExternalWalletButton.setOnAction(e -> reviewWithdrawal());
if (DevFlags.DEV_MODE) {
withdrawAddressTextField.setText("mjYhQYSbET2bXJDyCdNqYhqSye5QX2WHPz");
} else {
String key = "tradeCompleted" + trade.getId();
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
preferences.dontShowAgain(key, true);
new Notification().headLine("Trade completed")
.notification("You can withdraw your funds now to your external Bitcoin wallet or transfer it to the Bitsquare wallet.")
.autoClose()
.show();
}
}
}
private void reviewWithdrawal() {
Coin senderAmount = trade.getPayoutAmount();
WalletService walletService = model.dataModel.walletService;
AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
String fromAddresses = fromAddressesEntry.getAddressString();
String toAddresses = withdrawAddressTextField.getText();
// TODO at some error situation it can be tha the funds are already paid out and we get stuck here
// need handling to remove the trade (planned for next release)
Coin balance = walletService.getBalanceForAddress(fromAddressesEntry.getAddress());
try {
Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, AddressEntry.Context.TRADE_PAYOUT);
Coin receiverAmount = senderAmount.subtract(requiredFee);
if (balance.isZero()) {
new Popup().warning("Your funds have already been withdrawn.\nPlease check the transaction history.").show();
model.dataModel.tradeManager.addTradeToClosedTrades(trade);
} else {
if (toAddresses.isEmpty()) {
validateWithdrawAddress();
} else if (Restrictions.isAboveFixedTxFeeForTradesAndDust(senderAmount)) {
if (DevFlags.DEV_MODE) {
doWithdrawal(receiverAmount);
} else {
BSFormatter formatter = model.formatter;
String key = "reviewWithdrawalAtTradeComplete";
if (!DevFlags.DEV_MODE && preferences.showAgain(key)) {
new Popup().headLine("Confirm withdrawal request")
.confirmation("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" +
"From address: " + fromAddresses + "\n" +
"To receiving address: " + toAddresses + ".\n" +
"Required transaction fee is: " + formatter.formatCoinWithCode(requiredFee) + "\n\n" +
"The recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" +
"Are you sure you want to proceed with the withdrawal?")
.closeButtonText("Cancel")
.onClose(() -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
})
.actionButtonText("Yes")
.onAction(() -> doWithdrawal(receiverAmount))
.dontShowAgainId(key, preferences)
.show();
} else {
doWithdrawal(receiverAmount);
}
}
} else {
new Popup()
.warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).")
.show();
}
}
} catch (AddressFormatException e) {
validateWithdrawAddress();
} catch (AddressEntryException e) {
log.error(e.getMessage());
}
}
private void doWithdrawal(Coin receiverAmount) {
String toAddress = withdrawAddressTextField.getText();
ResultHandler resultHandler = this::handleTradeCompleted;
FaultHandler faultHandler = (errorMessage, throwable) -> {
useSavingsWalletButton.setDisable(false);
withdrawToExternalWalletButton.setDisable(false);
if (throwable != null && throwable.getMessage() != null)
new Popup().error(errorMessage + "\n\n" + throwable.getMessage()).show();
else
new Popup().error(errorMessage).show();
};
if (model.dataModel.walletService.getWallet().isEncrypted()) {
UserThread.runAfter(() -> model.dataModel.walletPasswordWindow.onAesKey(aesKey ->
doWithdrawRequest(toAddress, receiverAmount, aesKey, resultHandler, faultHandler))
.show(), 300, TimeUnit.MILLISECONDS);
} else
doWithdrawRequest(toAddress, receiverAmount, null, resultHandler, faultHandler);
}
private void doWithdrawRequest(String toAddress, Coin receiverAmount, KeyParameter aesKey, ResultHandler resultHandler, FaultHandler faultHandler) {
useSavingsWalletButton.setDisable(true);
withdrawToExternalWalletButton.setDisable(true);
model.dataModel.onWithdrawRequest(toAddress,
receiverAmount,
aesKey,
resultHandler,
faultHandler);
}
private void handleTradeCompleted() {
if (!DevFlags.DEV_MODE) {
String key = "tradeCompleteWithdrawCompletedInfo";
new Popup().headLine("Withdrawal completed")
.feedback("Your completed trades are stored under \"Portfolio/History\".\n" +
"You can review all your bitcoin transactions under \"Funds/Transactions\"")
.actionButtonText("Go to \"Transactions\"")
.onAction(() -> model.dataModel.navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class))
.dontShowAgainId(key, preferences)
.show();
}
useSavingsWalletButton.setDisable(true);
withdrawToExternalWalletButton.setDisable(true);
}
private void validateWithdrawAddress() {
withdrawAddressTextField.setValidator(model.btcAddressValidator);
withdrawAddressTextField.requestFocus();
useSavingsWalletButton.requestFocus();
}
protected String getBtcTradeAmountLabel() {
return "You have bought:";
}
protected String getFiatTradeAmountLabel() {
return "You have paid:";
}
}