mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-06-29 09:07:27 -04:00
Savings wallet (WIP)
This commit is contained in:
parent
d0e4792094
commit
04845e4382
13 changed files with 280 additions and 127 deletions
|
@ -221,6 +221,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
User user,
|
User user,
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
|
boolean useSavingsWallet,
|
||||||
Coin fundsNeededForTrade) {
|
Coin fundsNeededForTrade) {
|
||||||
Log.traceCall();
|
Log.traceCall();
|
||||||
processModel.onAllServicesInitialized(offer,
|
processModel.onAllServicesInitialized(offer,
|
||||||
|
@ -232,6 +233,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
arbitratorManager,
|
arbitratorManager,
|
||||||
user,
|
user,
|
||||||
keyRing,
|
keyRing,
|
||||||
|
useSavingsWallet,
|
||||||
fundsNeededForTrade);
|
fundsNeededForTrade);
|
||||||
|
|
||||||
createProtocol();
|
createProtocol();
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class TradeManager {
|
||||||
else {*/
|
else {*/
|
||||||
trade.setStorage(tradableListStorage);
|
trade.setStorage(tradableListStorage);
|
||||||
trade.updateDepositTxFromWallet(tradeWalletService);
|
trade.updateDepositTxFromWallet(tradeWalletService);
|
||||||
initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
|
initTrade(trade, trade.getProcessModel().getUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTrade());
|
||||||
|
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
@ -209,7 +209,7 @@ public class TradeManager {
|
||||||
trade = new SellerAsOffererTrade(offer, tradableListStorage);
|
trade = new SellerAsOffererTrade(offer, tradableListStorage);
|
||||||
|
|
||||||
trade.setStorage(tradableListStorage);
|
trade.setStorage(tradableListStorage);
|
||||||
initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
|
initTrade(trade, trade.getProcessModel().getUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTrade());
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress);
|
((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress);
|
||||||
} else {
|
} else {
|
||||||
|
@ -220,7 +220,7 @@ public class TradeManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initTrade(Trade trade, Coin fundsNeededForTrade) {
|
private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededForTrade) {
|
||||||
trade.init(p2PService,
|
trade.init(p2PService,
|
||||||
walletService,
|
walletService,
|
||||||
tradeWalletService,
|
tradeWalletService,
|
||||||
|
@ -229,6 +229,7 @@ public class TradeManager {
|
||||||
openOfferManager,
|
openOfferManager,
|
||||||
user,
|
user,
|
||||||
keyRing,
|
keyRing,
|
||||||
|
useSavingsWallet,
|
||||||
fundsNeededForTrade);
|
fundsNeededForTrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,12 +261,13 @@ public class TradeManager {
|
||||||
Coin fundsNeededForTrade,
|
Coin fundsNeededForTrade,
|
||||||
Offer offer,
|
Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
boolean useSavingsWallet,
|
||||||
TradeResultHandler tradeResultHandler) {
|
TradeResultHandler tradeResultHandler) {
|
||||||
final OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
|
final OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
|
||||||
offer.checkOfferAvailability(model,
|
offer.checkOfferAvailability(model,
|
||||||
() -> {
|
() -> {
|
||||||
if (offer.getState() == Offer.State.AVAILABLE)
|
if (offer.getState() == Offer.State.AVAILABLE)
|
||||||
createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, model, tradeResultHandler);
|
createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, useSavingsWallet, model, tradeResultHandler);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,6 +275,7 @@ public class TradeManager {
|
||||||
Coin fundsNeededForTrade,
|
Coin fundsNeededForTrade,
|
||||||
Offer offer,
|
Offer offer,
|
||||||
String paymentAccountId,
|
String paymentAccountId,
|
||||||
|
boolean useSavingsWallet,
|
||||||
OfferAvailabilityModel model,
|
OfferAvailabilityModel model,
|
||||||
TradeResultHandler tradeResultHandler) {
|
TradeResultHandler tradeResultHandler) {
|
||||||
Trade trade;
|
Trade trade;
|
||||||
|
@ -285,7 +288,7 @@ public class TradeManager {
|
||||||
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
|
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
|
||||||
trade.setTakerPaymentAccountId(paymentAccountId);
|
trade.setTakerPaymentAccountId(paymentAccountId);
|
||||||
|
|
||||||
initTrade(trade, fundsNeededForTrade);
|
initTrade(trade, useSavingsWallet, fundsNeededForTrade);
|
||||||
|
|
||||||
trades.add(trade);
|
trades.add(trade);
|
||||||
((TakerTrade) trade).takeAvailableOffer();
|
((TakerTrade) trade).takeAvailableOffer();
|
||||||
|
|
|
@ -141,8 +141,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
log.info("remove all open offers at shutDown");
|
log.info("remove all open offers at shutDown");
|
||||||
// we remove own offers from offerbook when we go offline
|
// we remove own offers from offerbook when we go offline
|
||||||
// Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out
|
// Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out
|
||||||
openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer()));
|
closeAllOpenOffers(completeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeAllOpenOffers(@Nullable Runnable completeHandler) {
|
||||||
|
openOffers.forEach(openOffer -> offerBookService.removeOfferAtShutDown(openOffer.getOffer()));
|
||||||
if (completeHandler != null)
|
if (completeHandler != null)
|
||||||
UserThread.runAfter(completeHandler::run, openOffers.size() * 200 + 300, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(completeHandler::run, openOffers.size() * 200 + 300, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class ProcessModel implements Model, Serializable {
|
||||||
@Nullable
|
@Nullable
|
||||||
private String changeOutputAddress;
|
private String changeOutputAddress;
|
||||||
private Transaction takeOfferFeeTx;
|
private Transaction takeOfferFeeTx;
|
||||||
public boolean useSavingsWallet;
|
private boolean useSavingsWallet;
|
||||||
private Coin fundsNeededForTrade;
|
private Coin fundsNeededForTrade;
|
||||||
|
|
||||||
public ProcessModel() {
|
public ProcessModel() {
|
||||||
|
@ -107,6 +107,7 @@ public class ProcessModel implements Model, Serializable {
|
||||||
ArbitratorManager arbitratorManager,
|
ArbitratorManager arbitratorManager,
|
||||||
User user,
|
User user,
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
|
boolean useSavingsWallet,
|
||||||
Coin fundsNeededForTrade) {
|
Coin fundsNeededForTrade) {
|
||||||
this.offer = offer;
|
this.offer = offer;
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
|
@ -117,6 +118,7 @@ public class ProcessModel implements Model, Serializable {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.useSavingsWallet = useSavingsWallet;
|
||||||
this.fundsNeededForTrade = fundsNeededForTrade;
|
this.fundsNeededForTrade = fundsNeededForTrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,4 +293,8 @@ public class ProcessModel implements Model, Serializable {
|
||||||
public Transaction getTakeOfferFeeTx() {
|
public Transaction getTakeOfferFeeTx() {
|
||||||
return takeOfferFeeTx;
|
return takeOfferFeeTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getUseSavingsWallet() {
|
||||||
|
return useSavingsWallet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class CreateTakeOfferFeeTx extends TradeTask {
|
||||||
processModel.getAddressEntry(),
|
processModel.getAddressEntry(),
|
||||||
processModel.getUnusedSavingsAddress(),
|
processModel.getUnusedSavingsAddress(),
|
||||||
processModel.getFundsNeededForTrade(),
|
processModel.getFundsNeededForTrade(),
|
||||||
processModel.useSavingsWallet,
|
processModel.getUseSavingsWallet(),
|
||||||
FeePolicy.getTakeOfferFee(),
|
FeePolicy.getTakeOfferFee(),
|
||||||
selectedArbitrator.getBtcAddress());
|
selectedArbitrator.getBtcAddress());
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ public class BalanceTextField extends AnchorPane {
|
||||||
|
|
||||||
private static WalletService walletService;
|
private static WalletService walletService;
|
||||||
private BalanceListener balanceListener;
|
private BalanceListener balanceListener;
|
||||||
|
private Coin targetAmount;
|
||||||
|
|
||||||
public static void setWalletService(WalletService walletService) {
|
public static void setWalletService(WalletService walletService) {
|
||||||
BalanceTextField.walletService = walletService;
|
BalanceTextField.walletService = walletService;
|
||||||
|
@ -84,6 +85,10 @@ public class BalanceTextField extends AnchorPane {
|
||||||
updateBalance(balance);
|
updateBalance(balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTargetAmount(Coin targetAmount) {
|
||||||
|
this.targetAmount = targetAmount;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Private methods
|
// Private methods
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -91,10 +96,12 @@ public class BalanceTextField extends AnchorPane {
|
||||||
private void updateBalance(Coin balance) {
|
private void updateBalance(Coin balance) {
|
||||||
if (formatter != null)
|
if (formatter != null)
|
||||||
textField.setText(formatter.formatCoinWithCode(balance));
|
textField.setText(formatter.formatCoinWithCode(balance));
|
||||||
if (balance.isPositive())
|
if (targetAmount != null) {
|
||||||
textField.setEffect(fundedEffect);
|
if (balance.compareTo(targetAmount) >= 0)
|
||||||
else
|
textField.setEffect(fundedEffect);
|
||||||
textField.setEffect(notFundedEffect);
|
else
|
||||||
|
textField.setEffect(notFundedEffect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -454,9 +454,11 @@ public class MainViewModel implements ViewModel {
|
||||||
updateBalance();
|
updateBalance();
|
||||||
setupDevDummyPaymentAccount();
|
setupDevDummyPaymentAccount();
|
||||||
setupMarketPriceFeed();
|
setupMarketPriceFeed();
|
||||||
|
swapPendingTradeAddressEntriesToSavingsWallet();
|
||||||
|
|
||||||
showAppScreen.set(true);
|
showAppScreen.set(true);
|
||||||
|
|
||||||
|
|
||||||
// We want to test if the client is compiled with the correct crypto provider (BountyCastle)
|
// We want to test if the client is compiled with the correct crypto provider (BountyCastle)
|
||||||
// and if the unlimited Strength for cryptographic keys is set.
|
// and if the unlimited Strength for cryptographic keys is set.
|
||||||
// If users compile themselves they might miss that step and then would get an exception in the trade.
|
// If users compile themselves they might miss that step and then would get an exception in the trade.
|
||||||
|
@ -667,6 +669,12 @@ public class MainViewModel implements ViewModel {
|
||||||
typeProperty.bind(priceFeed.typeProperty());
|
typeProperty.bind(priceFeed.typeProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void swapPendingTradeAddressEntriesToSavingsWallet() {
|
||||||
|
TradableCollections.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService).stream()
|
||||||
|
.filter(addressEntry -> addressEntry.getOfferId() != null)
|
||||||
|
.forEach(addressEntry -> walletService.swapTradeToSavings(addressEntry.getOfferId()));
|
||||||
|
}
|
||||||
|
|
||||||
private void displayAlertIfPresent(Alert alert) {
|
private void displayAlertIfPresent(Alert alert) {
|
||||||
boolean alreadyDisplayed = alert != null && alert.equals(user.getDisplayedAlert());
|
boolean alreadyDisplayed = alert != null && alert.equals(user.getDisplayedAlert());
|
||||||
user.setDisplayedAlert(alert);
|
user.setDisplayedAlert(alert);
|
||||||
|
|
|
@ -183,9 +183,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
calculateTotalToPay();
|
calculateTotalToPay();
|
||||||
updateBalance();
|
updateBalance();
|
||||||
|
|
||||||
if (direction == Offer.Direction.BUY)
|
|
||||||
calculateTotalToPay();
|
|
||||||
|
|
||||||
if (isTabSelected)
|
if (isTabSelected)
|
||||||
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
|
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
|
||||||
}
|
}
|
||||||
|
@ -402,7 +399,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateBalance() {
|
void updateBalance() {
|
||||||
Coin tradeWalletBalance = walletService.getBalanceForAddress(getAddressEntry().getAddress());
|
Coin tradeWalletBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||||
if (useSavingsWallet) {
|
if (useSavingsWallet) {
|
||||||
Coin savingWalletBalance = walletService.getSavingWalletBalance();
|
Coin savingWalletBalance = walletService.getSavingWalletBalance();
|
||||||
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
||||||
|
@ -419,7 +416,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
|
|
||||||
isWalletFunded.set(isBalanceSufficient(balance.get()));
|
isWalletFunded.set(isBalanceSufficient(balance.get()));
|
||||||
if (isWalletFunded.get()) {
|
if (isWalletFunded.get()) {
|
||||||
walletService.removeBalanceListener(balanceListener);
|
//walletService.removeBalanceListener(balanceListener);
|
||||||
if (walletFundedNotification == null) {
|
if (walletFundedNotification == null) {
|
||||||
walletFundedNotification = new Notification()
|
walletFundedNotification = new Notification()
|
||||||
.headLine("Trading wallet update")
|
.headLine("Trading wallet update")
|
||||||
|
@ -457,6 +454,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapTradeToSavings() {
|
public void swapTradeToSavings() {
|
||||||
walletService.swapTradeToSavings(getOfferId());
|
walletService.swapTradeToSavings(offerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
onPaymentAccountsComboBoxSelected();
|
onPaymentAccountsComboBoxSelected();
|
||||||
|
|
||||||
balanceTextField.setBalance(model.dataModel.balance.get());
|
balanceTextField.setBalance(model.dataModel.balance.get());
|
||||||
|
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -268,9 +269,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
currencyComboBox.setMouseTransparent(true);
|
currencyComboBox.setMouseTransparent(true);
|
||||||
paymentAccountsComboBox.setMouseTransparent(true);
|
paymentAccountsComboBox.setMouseTransparent(true);
|
||||||
|
|
||||||
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not());
|
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||||
fundingHBox.managedProperty().bind(model.dataModel.isWalletFunded.not());
|
|
||||||
|
|
||||||
if (!BitsquareApp.DEV_MODE) {
|
if (!BitsquareApp.DEV_MODE) {
|
||||||
String key = "securityDepositInfo";
|
String key = "securityDepositInfo";
|
||||||
new Popup().backgroundInfo("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
|
new Popup().backgroundInfo("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
|
||||||
|
@ -405,8 +405,11 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
placeOfferButton.visibleProperty().bind(model.dataModel.isWalletFunded);
|
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||||
placeOfferButton.managedProperty().bind(model.dataModel.isWalletFunded);
|
fundingHBox.managedProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||||
|
|
||||||
|
placeOfferButton.visibleProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
|
||||||
|
placeOfferButton.managedProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
|
||||||
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
|
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
|
||||||
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
|
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty placeOfferCompleted = new SimpleBooleanProperty();
|
final BooleanProperty placeOfferCompleted = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new
|
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new
|
||||||
|
@ -109,7 +110,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
private ChangeListener<String> errorMessageListener;
|
private ChangeListener<String> errorMessageListener;
|
||||||
private Offer offer;
|
private Offer offer;
|
||||||
private Timer timeoutTimer;
|
private Timer timeoutTimer;
|
||||||
private boolean showPayFundsScreenDisplayed;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -391,7 +391,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
void onShowPayFundsScreen() {
|
void onShowPayFundsScreen() {
|
||||||
showPayFundsScreenDisplayed = true;
|
showPayFundsScreenDisplayed.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean useSavingsWalletForFunding() {
|
boolean useSavingsWalletForFunding() {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import io.bitsquare.btc.listeners.BalanceListener;
|
||||||
import io.bitsquare.btc.pricefeed.PriceFeed;
|
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||||
import io.bitsquare.common.UserThread;
|
import io.bitsquare.common.UserThread;
|
||||||
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
import io.bitsquare.gui.common.model.ActivatableDataModel;
|
||||||
|
import io.bitsquare.gui.main.overlays.notifications.Notification;
|
||||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
|
import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
|
@ -46,6 +47,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
|
import org.bitcoinj.core.TransactionConfidence;
|
||||||
import org.bitcoinj.utils.ExchangeRate;
|
import org.bitcoinj.utils.ExchangeRate;
|
||||||
import org.bitcoinj.utils.Fiat;
|
import org.bitcoinj.utils.Fiat;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
@ -75,21 +77,27 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
private final Coin takerFeeAsCoin;
|
private final Coin takerFeeAsCoin;
|
||||||
private final Coin networkFeeAsCoin;
|
private final Coin networkFeeAsCoin;
|
||||||
private final Coin securityDepositAsCoin;
|
private final Coin securityDepositAsCoin;
|
||||||
|
Coin feeFromFundingTx = Coin.NEGATIVE_SATOSHI;
|
||||||
|
|
||||||
private Offer offer;
|
private Offer offer;
|
||||||
private AddressEntry addressEntry;
|
|
||||||
|
|
||||||
|
private AddressEntry addressEntry;
|
||||||
final StringProperty btcCode = new SimpleStringProperty();
|
final StringProperty btcCode = new SimpleStringProperty();
|
||||||
final BooleanProperty useMBTC = new SimpleBooleanProperty();
|
|
||||||
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
|
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty isFeeFromFundingTxSufficient = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty isMainNet = new SimpleBooleanProperty();
|
||||||
final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
|
final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
|
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||||
final ObjectProperty<Coin> feeFromFundingTxProperty = new SimpleObjectProperty(Coin.NEGATIVE_SATOSHI);
|
final ObjectProperty<Coin> balance = new SimpleObjectProperty<>();
|
||||||
|
final ObjectProperty<Coin> missingCoin = new SimpleObjectProperty<>(Coin.ZERO);
|
||||||
|
|
||||||
private BalanceListener balanceListener;
|
private BalanceListener balanceListener;
|
||||||
PaymentAccount paymentAccount;
|
PaymentAccount paymentAccount;
|
||||||
private boolean isTabSelected;
|
private boolean isTabSelected;
|
||||||
|
boolean useSavingsWallet;
|
||||||
|
Coin totalAvailableBalance;
|
||||||
|
private Notification walletFundedNotification;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -115,6 +123,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
takerFeeAsCoin = FeePolicy.getTakeOfferFee();
|
takerFeeAsCoin = FeePolicy.getTakeOfferFee();
|
||||||
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
||||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||||
|
|
||||||
|
isMainNet.set(preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,13 +134,15 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
|
|
||||||
addBindings();
|
addBindings();
|
||||||
addListeners();
|
addListeners();
|
||||||
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
|
|
||||||
|
calculateTotalToPay();
|
||||||
|
updateBalance();
|
||||||
|
|
||||||
// TODO In case that we have funded but restarted, or canceled but took again the offer we would need to
|
// TODO In case that we have funded but restarted, or canceled but took again the offer we would need to
|
||||||
// store locally the result when we received the funding tx(s).
|
// store locally the result when we received the funding tx(s).
|
||||||
// For now we just ignore that rare case and bypass the check by setting a sufficient value
|
// For now we just ignore that rare case and bypass the check by setting a sufficient value
|
||||||
if (isWalletFunded.get())
|
// if (isWalletFunded.get())
|
||||||
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
|
// feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
|
||||||
|
|
||||||
if (isTabSelected)
|
if (isTabSelected)
|
||||||
priceFeed.setCurrencyCode(offer.getCurrencyCode());
|
priceFeed.setCurrencyCode(offer.getCurrencyCode());
|
||||||
|
@ -167,19 +179,24 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
if (BitsquareApp.DEV_MODE)
|
if (BitsquareApp.DEV_MODE)
|
||||||
amountAsCoin.set(offer.getAmount());
|
amountAsCoin.set(offer.getAmount());
|
||||||
|
|
||||||
|
calculateTotalToPay();
|
||||||
calculateVolume();
|
calculateVolume();
|
||||||
calculateTotalToPay();
|
calculateTotalToPay();
|
||||||
|
|
||||||
balanceListener = new BalanceListener(addressEntry.getAddress()) {
|
balanceListener = new BalanceListener(addressEntry.getAddress()) {
|
||||||
@Override
|
@Override
|
||||||
public void onBalanceChanged(Coin balance, Transaction tx) {
|
public void onBalanceChanged(Coin balance, Transaction tx) {
|
||||||
updateBalance(balance);
|
updateBalance();
|
||||||
|
|
||||||
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
|
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMainNet.get()) {
|
||||||
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
|
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
|
||||||
Futures.addCallback(future, new FutureCallback<Coin>() {
|
Futures.addCallback(future, new FutureCallback<Coin>() {
|
||||||
public void onSuccess(Coin fee) {
|
public void onSuccess(Coin fee) {
|
||||||
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
|
UserThread.execute(() -> setFeeFromFundingTx(fee));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFailure(@NotNull Throwable throwable) {
|
public void onFailure(@NotNull Throwable throwable) {
|
||||||
|
@ -189,14 +206,15 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
"Are you sure you used a sufficiently high fee of at least " +
|
"Are you sure you used a sufficiently high fee of at least " +
|
||||||
formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?")
|
formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?")
|
||||||
.actionButtonText("Yes, I used a sufficiently high fee.")
|
.actionButtonText("Yes, I used a sufficiently high fee.")
|
||||||
.onAction(() -> feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx()))
|
.onAction(() -> setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx()))
|
||||||
.closeButtonText("No. Let's cancel that payment.")
|
.closeButtonText("No. Let's cancel that payment.")
|
||||||
.onClose(() -> feeFromFundingTxProperty.set(Coin.ZERO))
|
.onClose(() -> setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI))
|
||||||
.show());
|
.show());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
|
setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx());
|
||||||
|
isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -205,7 +223,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
priceFeed.setCurrencyCode(offer.getCurrencyCode());
|
priceFeed.setCurrencyCode(offer.getCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void onTabSelected(boolean isSelected) {
|
void onTabSelected(boolean isSelected) {
|
||||||
this.isTabSelected = isSelected;
|
this.isTabSelected = isSelected;
|
||||||
if (isTabSelected)
|
if (isTabSelected)
|
||||||
|
@ -235,6 +252,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
||||||
offer,
|
offer,
|
||||||
paymentAccount.getId(),
|
paymentAccount.getId(),
|
||||||
|
useSavingsWallet,
|
||||||
tradeResultHandler
|
tradeResultHandler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -244,6 +262,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
this.paymentAccount = paymentAccount;
|
this.paymentAccount = paymentAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void useSavingsWalletForFunding() {
|
||||||
|
useSavingsWallet = true;
|
||||||
|
updateBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
|
@ -275,10 +298,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
return user.getAcceptedArbitrators().size() > 0;
|
return user.getAcceptedArbitrators().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isFeeFromFundingTxSufficient() {
|
|
||||||
return feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Bindings, listeners
|
// Bindings, listeners
|
||||||
|
@ -311,7 +330,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
!amountAsCoin.get().isZero()) {
|
!amountAsCoin.get().isZero()) {
|
||||||
volumeAsFiat.set(new ExchangeRate(offer.getPrice()).coinToFiat(amountAsCoin.get()));
|
volumeAsFiat.set(new ExchangeRate(offer.getPrice()).coinToFiat(amountAsCoin.get()));
|
||||||
|
|
||||||
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
|
updateBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,11 +341,49 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||||
totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get()));
|
totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBalance(@NotNull Coin balance) {
|
void updateBalance() {
|
||||||
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
|
Coin tradeWalletBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||||
|
if (useSavingsWallet) {
|
||||||
|
Coin savingWalletBalance = walletService.getSavingWalletBalance();
|
||||||
|
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
||||||
|
|
||||||
if (isWalletFunded.get())
|
if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
|
||||||
walletService.removeBalanceListener(balanceListener);
|
balance.set(totalToPayAsCoin.get());
|
||||||
|
else
|
||||||
|
balance.set(totalAvailableBalance);
|
||||||
|
} else {
|
||||||
|
balance.set(tradeWalletBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
missingCoin.set(totalToPayAsCoin.get().subtract(balance.get()));
|
||||||
|
|
||||||
|
isWalletFunded.set(isBalanceSufficient(balance.get()));
|
||||||
|
if (isWalletFunded.get()) {
|
||||||
|
// walletService.removeBalanceListener(balanceListener);
|
||||||
|
if (walletFundedNotification == null) {
|
||||||
|
walletFundedNotification = new Notification()
|
||||||
|
.headLine("Trading wallet update")
|
||||||
|
.notification("Your trading wallet is sufficiently funded.\n" +
|
||||||
|
"Amount: " + formatter.formatCoinWithCode(totalToPayAsCoin.get()))
|
||||||
|
.autoClose();
|
||||||
|
|
||||||
|
walletFundedNotification.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBalanceSufficient(Coin balance) {
|
||||||
|
return totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapTradeToSavings() {
|
||||||
|
walletService.swapTradeToSavings(offer.getId());
|
||||||
|
setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFeeFromFundingTx(Coin fee) {
|
||||||
|
feeFromFundingTx = fee;
|
||||||
|
isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMinAmountLessOrEqualAmount() {
|
boolean isMinAmountLessOrEqualAmount() {
|
||||||
|
|
|
@ -60,15 +60,16 @@ import javafx.stage.Window;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import net.glxn.qrgen.QRCode;
|
import net.glxn.qrgen.QRCode;
|
||||||
import net.glxn.qrgen.image.ImageType;
|
import net.glxn.qrgen.image.ImageType;
|
||||||
import org.bitcoinj.core.Coin;
|
|
||||||
import org.bitcoinj.uri.BitcoinURI;
|
import org.bitcoinj.uri.BitcoinURI;
|
||||||
import org.controlsfx.control.PopOver;
|
import org.controlsfx.control.PopOver;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
import org.fxmisc.easybind.Subscription;
|
import org.fxmisc.easybind.Subscription;
|
||||||
|
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||||
|
@ -89,7 +90,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
private BalanceTextField balanceTextField;
|
private BalanceTextField balanceTextField;
|
||||||
private ProgressIndicator spinner, offerAvailabilitySpinner;
|
private ProgressIndicator spinner, offerAvailabilitySpinner;
|
||||||
private TitledGroupBg payFundsPane;
|
private TitledGroupBg payFundsPane;
|
||||||
private Button nextButton, takeOfferButton, cancelButton1, cancelButton2;
|
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, takeOfferButton;
|
||||||
private InputTextField amountTextField;
|
private InputTextField amountTextField;
|
||||||
private TextField paymentMethodTextField, currencyTextField, priceTextField, volumeTextField, amountRangeTextField;
|
private TextField paymentMethodTextField, currencyTextField, priceTextField, volumeTextField, amountRangeTextField;
|
||||||
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
|
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
|
||||||
|
@ -109,11 +110,14 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
private Subscription showWarningInvalidBtcDecimalPlacesSubscription;
|
private Subscription showWarningInvalidBtcDecimalPlacesSubscription;
|
||||||
private Subscription showTransactionPublishedScreenSubscription;
|
private Subscription showTransactionPublishedScreenSubscription;
|
||||||
private SimpleBooleanProperty errorPopupDisplayed;
|
private SimpleBooleanProperty errorPopupDisplayed;
|
||||||
private ChangeListener<Coin> feeFromFundingTxListener;
|
|
||||||
private boolean offerDetailsWindowDisplayed;
|
private boolean offerDetailsWindowDisplayed;
|
||||||
private Notification walletFundedNotification;
|
private Notification walletFundedNotification;
|
||||||
private Subscription isWalletFundedSubscription;
|
|
||||||
private ImageView qrCodeImageView;
|
private ImageView qrCodeImageView;
|
||||||
|
private HBox fundingHBox;
|
||||||
|
private Subscription balanceSubscription;
|
||||||
|
private Subscription noSufficientFeeSubscription;
|
||||||
|
private MonadicBinding<Boolean> noSufficientFeeBinding;
|
||||||
|
private Subscription totalToPaySubscription;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -155,12 +159,19 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
amountTextField.textProperty().bindBidirectional(model.amount);
|
amountTextField.textProperty().bindBidirectional(model.amount);
|
||||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||||
addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin);
|
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
|
||||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||||
takeOfferButton.disableProperty().bind(model.isTakeOfferButtonDisabled);
|
takeOfferButton.disableProperty().bind(model.isTakeOfferButtonDisabled);
|
||||||
|
takeOfferButton.visibleProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
|
||||||
|
takeOfferButton.managedProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
|
||||||
|
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||||
|
fundingHBox.managedProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||||
|
|
||||||
spinner.visibleProperty().bind(model.isSpinnerVisible);
|
spinner.visibleProperty().bind(model.isSpinnerVisible);
|
||||||
|
spinner.managedProperty().bind(model.isSpinnerVisible);
|
||||||
|
|
||||||
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
|
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
|
||||||
|
spinnerInfoLabel.managedProperty().bind(model.isSpinnerVisible);
|
||||||
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
|
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
|
||||||
|
|
||||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() ->
|
priceCurrencyLabel.textProperty().bind(createStringBinding(() ->
|
||||||
|
@ -256,14 +267,15 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
paymentAccountsComboBox.getSelectionModel().select(0);
|
paymentAccountsComboBox.getSelectionModel().select(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
feeFromFundingTxListener = (observable, oldValue, newValue) -> {
|
noSufficientFeeBinding = EasyBind.combine(model.dataModel.isWalletFunded, model.dataModel.isMainNet, model.dataModel.isFeeFromFundingTxSufficient,
|
||||||
log.debug("feeFromFundingTxListener " + newValue);
|
(isWalletFunded, isMainNet, isFeeSufficient) -> isWalletFunded && isMainNet && !isFeeSufficient);
|
||||||
if (!model.dataModel.isFeeFromFundingTxSufficient()) {
|
noSufficientFeeSubscription = noSufficientFeeBinding.subscribe((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue)
|
||||||
new Popup().warning("The mining fee from your funding transaction is not sufficiently high.\n\n" +
|
new Popup().warning("The mining fee from your funding transaction is not sufficiently high.\n\n" +
|
||||||
"You need to use at least a mining fee of " +
|
"You need to use at least a mining fee of " +
|
||||||
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
|
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
|
||||||
"The fee used in your funding transaction was only " +
|
"The fee used in your funding transaction was only " +
|
||||||
model.formatter.formatCoinWithCode(newValue) + ".\n\n" +
|
model.formatter.formatCoinWithCode(model.dataModel.feeFromFundingTx) + ".\n\n" +
|
||||||
"The trade transactions might take too much time to be included in " +
|
"The trade transactions might take too much time to be included in " +
|
||||||
"a block if the fee is too low.\n" +
|
"a block if the fee is too low.\n" +
|
||||||
"Please check at your external wallet that you set the required fee and " +
|
"Please check at your external wallet that you set the required fee and " +
|
||||||
|
@ -275,15 +287,16 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
|
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
}
|
});
|
||||||
};
|
|
||||||
model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
|
||||||
|
|
||||||
if (offerAvailabilitySpinner != null && offerAvailabilitySpinner.isVisible())
|
if (offerAvailabilitySpinner != null && offerAvailabilitySpinner.isVisible())
|
||||||
offerAvailabilitySpinner.setProgress(-1);
|
offerAvailabilitySpinner.setProgress(-1);
|
||||||
|
|
||||||
if (spinner != null && spinner.isVisible())
|
if (spinner != null && spinner.isVisible())
|
||||||
spinner.setProgress(-1);
|
spinner.setProgress(-1);
|
||||||
|
|
||||||
|
balanceSubscription = EasyBind.subscribe(model.dataModel.balance, newValue -> balanceTextField.setBalance(newValue));
|
||||||
|
totalToPaySubscription = EasyBind.subscribe(model.dataModel.totalToPayAsCoin, newValue -> balanceTextField.setTargetAmount(newValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -298,16 +311,23 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
totalToPayTextField.textProperty().unbind();
|
totalToPayTextField.textProperty().unbind();
|
||||||
addressTextField.amountAsCoinProperty().unbind();
|
addressTextField.amountAsCoinProperty().unbind();
|
||||||
amountTextField.validationResultProperty().unbind();
|
amountTextField.validationResultProperty().unbind();
|
||||||
takeOfferButton.disableProperty().unbind();
|
|
||||||
spinner.visibleProperty().unbind();
|
|
||||||
spinnerInfoLabel.visibleProperty().unbind();
|
|
||||||
spinnerInfoLabel.textProperty().unbind();
|
|
||||||
priceCurrencyLabel.textProperty().unbind();
|
priceCurrencyLabel.textProperty().unbind();
|
||||||
volumeCurrencyLabel.textProperty().unbind();
|
volumeCurrencyLabel.textProperty().unbind();
|
||||||
amountRangeBtcLabel.textProperty().unbind();
|
amountRangeBtcLabel.textProperty().unbind();
|
||||||
priceDescriptionLabel.textProperty().unbind();
|
priceDescriptionLabel.textProperty().unbind();
|
||||||
volumeDescriptionLabel.textProperty().unbind();
|
volumeDescriptionLabel.textProperty().unbind();
|
||||||
|
|
||||||
|
fundingHBox.visibleProperty().unbind();
|
||||||
|
fundingHBox.managedProperty().unbind();
|
||||||
|
takeOfferButton.visibleProperty().unbind();
|
||||||
|
takeOfferButton.managedProperty().unbind();
|
||||||
|
takeOfferButton.disableProperty().unbind();
|
||||||
|
spinner.visibleProperty().unbind();
|
||||||
|
spinner.managedProperty().unbind();
|
||||||
|
spinnerInfoLabel.visibleProperty().unbind();
|
||||||
|
spinnerInfoLabel.managedProperty().unbind();
|
||||||
|
spinnerInfoLabel.textProperty().unbind();
|
||||||
|
|
||||||
offerWarningSubscription.unsubscribe();
|
offerWarningSubscription.unsubscribe();
|
||||||
errorMessageSubscription.unsubscribe();
|
errorMessageSubscription.unsubscribe();
|
||||||
isOfferAvailableSubscription.unsubscribe();
|
isOfferAvailableSubscription.unsubscribe();
|
||||||
|
@ -315,7 +335,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe();
|
showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe();
|
||||||
showTransactionPublishedScreenSubscription.unsubscribe();
|
showTransactionPublishedScreenSubscription.unsubscribe();
|
||||||
|
|
||||||
model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
noSufficientFeeSubscription.unsubscribe();
|
||||||
if (balanceTextField != null)
|
if (balanceTextField != null)
|
||||||
balanceTextField.cleanup();
|
balanceTextField.cleanup();
|
||||||
|
|
||||||
|
@ -325,8 +345,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
if (spinner != null)
|
if (spinner != null)
|
||||||
spinner.setProgress(0);
|
spinner.setProgress(0);
|
||||||
|
|
||||||
if (isWalletFundedSubscription != null)
|
balanceSubscription.unsubscribe();
|
||||||
isWalletFundedSubscription.unsubscribe();
|
totalToPaySubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -382,6 +402,15 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
|
|
||||||
// called form parent as the view does not get notified when the tab is closed
|
// called form parent as the view does not get notified when the tab is closed
|
||||||
public void onClose() {
|
public void onClose() {
|
||||||
|
if (model.dataModel.balance.get().isPositive() && !model.takeOfferCompleted.get()) {
|
||||||
|
model.dataModel.swapTradeToSavings();
|
||||||
|
new Popup().information("You have already funds paid in.\n" +
|
||||||
|
"In the \"Funds/Available for withdrawal\" section you can withdraw those funds.")
|
||||||
|
.actionButtonText("Go to \"Funds/Available for withdrawal\"")
|
||||||
|
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO need other implementation as it is displayed also if there are old funds in the wallet
|
// TODO need other implementation as it is displayed also if there are old funds in the wallet
|
||||||
/*
|
/*
|
||||||
if (model.dataModel.isWalletFunded.get())
|
if (model.dataModel.isWalletFunded.get())
|
||||||
|
@ -461,7 +490,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
offerAvailabilitySpinnerLabel.setVisible(false);
|
offerAvailabilitySpinnerLabel.setVisible(false);
|
||||||
cancelButton1.setVisible(false);
|
cancelButton1.setVisible(false);
|
||||||
cancelButton1.setOnAction(null);
|
cancelButton1.setOnAction(null);
|
||||||
takeOfferButton.setVisible(true);
|
|
||||||
cancelButton2.setVisible(true);
|
cancelButton2.setVisible(true);
|
||||||
|
|
||||||
spinner.setProgress(-1);
|
spinner.setProgress(-1);
|
||||||
|
@ -487,19 +515,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
.autoClose();
|
.autoClose();
|
||||||
walletFundedNotification.show();
|
walletFundedNotification.show();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
isWalletFundedSubscription = EasyBind.subscribe(model.dataModel.isWalletFunded, isFunded -> {
|
|
||||||
if (isFunded) {
|
|
||||||
if (walletFundedNotification == null) {
|
|
||||||
walletFundedNotification = new Notification()
|
|
||||||
.headLine("Trading wallet update")
|
|
||||||
.notification("Your trading wallet is sufficiently funded.\n" +
|
|
||||||
"Amount: " + formatter.formatCoinWithCode(model.dataModel.totalToPayAsCoin.get()))
|
|
||||||
.autoClose();
|
|
||||||
walletFundedNotification.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] imageBytes = QRCode
|
final byte[] imageBytes = QRCode
|
||||||
|
@ -689,9 +704,41 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
balanceTextField = balanceTuple.second;
|
balanceTextField = balanceTuple.second;
|
||||||
balanceTextField.setVisible(false);
|
balanceTextField.setVisible(false);
|
||||||
|
|
||||||
Tuple3<Button, ProgressIndicator, Label> takeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "");
|
fundingHBox = new HBox();
|
||||||
takeOfferButton = takeOfferTuple.first;
|
fundingHBox.setVisible(false);
|
||||||
|
fundingHBox.setManaged(false);
|
||||||
|
fundingHBox.setSpacing(10);
|
||||||
|
fundFromSavingsWalletButton = new Button("Transfer funds from Bitsquare wallet");
|
||||||
|
fundFromSavingsWalletButton.setDefaultButton(true);
|
||||||
|
fundFromSavingsWalletButton.setDefaultButton(false);
|
||||||
|
fundFromSavingsWalletButton.setOnAction(e -> model.useSavingsWalletForFunding());
|
||||||
|
Label label = new Label("OR");
|
||||||
|
label.setPadding(new Insets(5, 0, 0, 0));
|
||||||
|
fundFromExternalWalletButton = new Button("Pay in funds from external wallet");
|
||||||
|
fundFromExternalWalletButton.setDefaultButton(false);
|
||||||
|
fundFromExternalWalletButton.setOnAction(e -> {
|
||||||
|
try {
|
||||||
|
Utilities.openURI(URI.create(getBitcoinURI()));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn(ex.getMessage());
|
||||||
|
new Popup().warning("Opening a default bitcoin wallet application has failed. " +
|
||||||
|
"Perhaps you don't have one installed?").show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spinner = new ProgressIndicator(0);
|
||||||
|
spinner.setPrefHeight(18);
|
||||||
|
spinner.setPrefWidth(18);
|
||||||
|
spinnerInfoLabel = new Label();
|
||||||
|
spinnerInfoLabel.setPadding(new Insets(5, 0, 0, 0));
|
||||||
|
fundingHBox.getChildren().addAll(fundFromSavingsWalletButton, label, fundFromExternalWalletButton, spinner, spinnerInfoLabel);
|
||||||
|
GridPane.setRowIndex(fundingHBox, ++gridRow);
|
||||||
|
GridPane.setColumnIndex(fundingHBox, 1);
|
||||||
|
GridPane.setMargin(fundingHBox, new Insets(15, 10, 0, 0));
|
||||||
|
gridPane.getChildren().add(fundingHBox);
|
||||||
|
|
||||||
|
takeOfferButton = addButtonAfterGroup(gridPane, gridRow, "");
|
||||||
takeOfferButton.setVisible(false);
|
takeOfferButton.setVisible(false);
|
||||||
|
takeOfferButton.setManaged(false);
|
||||||
takeOfferButton.setMinHeight(40);
|
takeOfferButton.setMinHeight(40);
|
||||||
takeOfferButton.setPadding(new Insets(0, 20, 0, 20));
|
takeOfferButton.setPadding(new Insets(0, 20, 0, 20));
|
||||||
takeOfferButton.setOnAction(e -> {
|
takeOfferButton.setOnAction(e -> {
|
||||||
|
@ -699,9 +746,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
balanceTextField.cleanup();
|
balanceTextField.cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
spinner = takeOfferTuple.second;
|
|
||||||
spinnerInfoLabel = takeOfferTuple.third;
|
|
||||||
|
|
||||||
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
|
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
|
||||||
cancelButton2.setOnAction(e -> {
|
cancelButton2.setOnAction(e -> {
|
||||||
if (model.dataModel.isWalletFunded.get())
|
if (model.dataModel.isWalletFunded.get())
|
||||||
|
@ -716,12 +760,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||||
});
|
});
|
||||||
cancelButton2.setDefaultButton(false);
|
cancelButton2.setDefaultButton(false);
|
||||||
cancelButton2.setVisible(false);
|
cancelButton2.setVisible(false);
|
||||||
cancelButton2.setId("cancel-button");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private String getBitcoinURI() {
|
private String getBitcoinURI() {
|
||||||
return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.totalToPayAsCoin.get(),
|
return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.dataModel.missingCoin.get(),
|
||||||
model.getPaymentLabel(), null) : "";
|
model.getPaymentLabel(), null) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,13 @@
|
||||||
package io.bitsquare.gui.main.offer.takeoffer;
|
package io.bitsquare.gui.main.offer.takeoffer;
|
||||||
|
|
||||||
import io.bitsquare.arbitration.Arbitrator;
|
import io.bitsquare.arbitration.Arbitrator;
|
||||||
import io.bitsquare.btc.FeePolicy;
|
import io.bitsquare.gui.Navigation;
|
||||||
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
import io.bitsquare.gui.common.model.ActivatableWithDataModel;
|
||||||
import io.bitsquare.gui.common.model.ViewModel;
|
import io.bitsquare.gui.common.model.ViewModel;
|
||||||
|
import io.bitsquare.gui.main.MainView;
|
||||||
|
import io.bitsquare.gui.main.funds.FundsView;
|
||||||
|
import io.bitsquare.gui.main.funds.deposit.DepositView;
|
||||||
|
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||||
import io.bitsquare.gui.util.BSFormatter;
|
import io.bitsquare.gui.util.BSFormatter;
|
||||||
import io.bitsquare.gui.util.validation.BtcValidator;
|
import io.bitsquare.gui.util.validation.BtcValidator;
|
||||||
import io.bitsquare.gui.util.validation.InputValidator;
|
import io.bitsquare.gui.util.validation.InputValidator;
|
||||||
|
@ -38,6 +42,8 @@ import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.bitcoinj.core.Address;
|
import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.Subscription;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -46,8 +52,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||||
|
|
||||||
class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel {
|
class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel {
|
||||||
|
final TakeOfferDataModel dataModel;
|
||||||
private final BtcValidator btcValidator;
|
private final BtcValidator btcValidator;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private final Navigation navigation;
|
||||||
final BSFormatter formatter;
|
final BSFormatter formatter;
|
||||||
|
|
||||||
private String amountRange;
|
private String amountRange;
|
||||||
|
@ -75,11 +83,12 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
|
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||||
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty takeOfferCompleted = new SimpleBooleanProperty();
|
||||||
|
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||||
|
|
||||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
// Those are needed for the addressTextField
|
// Those are needed for the addressTextField
|
||||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
|
||||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private ChangeListener<String> amountListener;
|
private ChangeListener<String> amountListener;
|
||||||
|
@ -90,9 +99,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
private ChangeListener<Offer.State> offerStateListener;
|
private ChangeListener<Offer.State> offerStateListener;
|
||||||
private ChangeListener<String> offerErrorListener;
|
private ChangeListener<String> offerErrorListener;
|
||||||
private ConnectionListener connectionListener;
|
private ConnectionListener connectionListener;
|
||||||
private ChangeListener<Coin> feeFromFundingTxListener;
|
private Subscription isFeeSufficientSubscription;
|
||||||
private Runnable takeOfferSucceededHandler;
|
private Runnable takeOfferSucceededHandler;
|
||||||
private boolean showPayFundsScreenDisplayed;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -101,11 +109,13 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService,
|
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService,
|
||||||
BSFormatter formatter) {
|
Navigation navigation, BSFormatter formatter) {
|
||||||
super(dataModel);
|
super(dataModel);
|
||||||
|
this.dataModel = dataModel;
|
||||||
|
|
||||||
this.btcValidator = btcValidator;
|
this.btcValidator = btcValidator;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.navigation = navigation;
|
||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
|
|
||||||
createListeners();
|
createListeners();
|
||||||
|
@ -190,6 +200,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
trade.errorMessageProperty().addListener(tradeErrorListener);
|
trade.errorMessageProperty().addListener(tradeErrorListener);
|
||||||
applyTradeErrorMessage(trade.errorMessageProperty().get());
|
applyTradeErrorMessage(trade.errorMessageProperty().get());
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
|
takeOfferCompleted.set(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,10 +211,30 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onShowPayFundsScreen() {
|
public void onShowPayFundsScreen() {
|
||||||
showPayFundsScreenDisplayed = true;
|
showPayFundsScreenDisplayed.set(true);
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean useSavingsWalletForFunding() {
|
||||||
|
dataModel.useSavingsWalletForFunding();
|
||||||
|
if (dataModel.isWalletFunded.get()) {
|
||||||
|
updateButtonDisableState();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
new Popup().warning("You don't have enough funds in your Bitsquare wallet.\n" +
|
||||||
|
"You need " + formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()) + " but you have only " +
|
||||||
|
formatter.formatCoinWithCode(dataModel.totalAvailableBalance) + " in your Bitsquare wallet.\n\n" +
|
||||||
|
"Please fund that trade from an external Bitcoin wallet or fund your Bitsquare " +
|
||||||
|
"wallet at \"Funds/Depost funds\".")
|
||||||
|
.actionButtonText("Go to \"Funds/Depost funds\"")
|
||||||
|
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
|
||||||
|
.show();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Handle focus
|
// Handle focus
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -286,10 +317,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (offerWarning.get() != null) {
|
updateSpinnerInfo();
|
||||||
isSpinnerVisible.set(false);
|
|
||||||
spinnerInfoText.set("");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
}
|
}
|
||||||
|
@ -328,8 +356,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
this.errorMessage.set(errorMessage + appendMsg);
|
this.errorMessage.set(errorMessage + appendMsg);
|
||||||
|
|
||||||
isSpinnerVisible.set(false);
|
updateSpinnerInfo();
|
||||||
spinnerInfoText.set("");
|
|
||||||
|
|
||||||
if (takeOfferSucceededHandler != null)
|
if (takeOfferSucceededHandler != null)
|
||||||
takeOfferSucceededHandler.run();
|
takeOfferSucceededHandler.run();
|
||||||
|
@ -349,9 +376,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
if (takeOfferSucceededHandler != null)
|
if (takeOfferSucceededHandler != null)
|
||||||
takeOfferSucceededHandler.run();
|
takeOfferSucceededHandler.run();
|
||||||
|
|
||||||
isSpinnerVisible.set(false);
|
|
||||||
spinnerInfoText.set("");
|
|
||||||
showTransactionPublishedScreen.set(true);
|
showTransactionPublishedScreen.set(true);
|
||||||
|
updateSpinnerInfo();
|
||||||
} else {
|
} else {
|
||||||
log.error("trade.getDepositTx() == null. That must not happen");
|
log.error("trade.getDepositTx() == null. That must not happen");
|
||||||
}
|
}
|
||||||
|
@ -364,10 +390,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
&& !dataModel.isAmountLargerThanOfferAmount()
|
&& !dataModel.isAmountLargerThanOfferAmount()
|
||||||
&& isOfferAvailable.get();
|
&& isOfferAvailable.get();
|
||||||
isNextButtonDisabled.set(!inputDataValid);
|
isNextButtonDisabled.set(!inputDataValid);
|
||||||
isTakeOfferButtonDisabled.set(!(inputDataValid
|
boolean notSufficientFees = dataModel.isWalletFunded.get() && dataModel.isMainNet.get() && !dataModel.isFeeFromFundingTxSufficient.get();
|
||||||
&& dataModel.isWalletFunded.get()
|
isTakeOfferButtonDisabled.set(takeOfferRequested || !inputDataValid || notSufficientFees);
|
||||||
&& !takeOfferRequested
|
|
||||||
&& dataModel.isFeeFromFundingTxSufficient()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -385,7 +409,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
volumeDescriptionLabel.set(BSResources.get("createOffer.amountPriceBox.sell.volumeDescription", dataModel.getCurrencyCode()));
|
volumeDescriptionLabel.set(BSResources.get("createOffer.amountPriceBox.sell.volumeDescription", dataModel.getCurrencyCode()));
|
||||||
}
|
}
|
||||||
totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), dataModel.totalToPayAsCoin));
|
totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), dataModel.totalToPayAsCoin));
|
||||||
totalToPayAsCoin.bind(dataModel.totalToPayAsCoin);
|
|
||||||
btcCode.bind(dataModel.btcCode);
|
btcCode.bind(dataModel.btcCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,7 +417,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
volumeDescriptionLabel.unbind();
|
volumeDescriptionLabel.unbind();
|
||||||
volume.unbind();
|
volume.unbind();
|
||||||
totalToPay.unbind();
|
totalToPay.unbind();
|
||||||
totalToPayAsCoin.unbind();
|
|
||||||
btcCode.unbind();
|
btcCode.unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,16 +432,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue));
|
amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue));
|
||||||
isWalletFundedListener = (ov, oldValue, newValue) -> {
|
isWalletFundedListener = (ov, oldValue, newValue) -> {
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
isSpinnerVisible.set(true);
|
|
||||||
spinnerInfoText.set("Checking funding tx miner fee...");
|
|
||||||
};
|
|
||||||
feeFromFundingTxListener = (ov, oldValue, newValue) -> {
|
|
||||||
updateButtonDisableState();
|
|
||||||
if (newValue.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0) {
|
|
||||||
isSpinnerVisible.set(false);
|
|
||||||
spinnerInfoText.set("");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tradeStateListener = (ov, oldValue, newValue) -> applyTradeState(newValue);
|
tradeStateListener = (ov, oldValue, newValue) -> applyTradeState(newValue);
|
||||||
tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(newValue);
|
tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(newValue);
|
||||||
offerStateListener = (ov, oldValue, newValue) -> applyOfferState(newValue);
|
offerStateListener = (ov, oldValue, newValue) -> applyOfferState(newValue);
|
||||||
|
@ -432,8 +446,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
"He might have gone offline or has closed the connection to you because of too " +
|
"He might have gone offline or has closed the connection to you because of too " +
|
||||||
"many open connections.\n\n" +
|
"many open connections.\n\n" +
|
||||||
"If you can still see his offer in the offerbook you can try to take the offer again.");
|
"If you can still see his offer in the offerbook you can try to take the offer again.");
|
||||||
isSpinnerVisible.set(false);
|
updateSpinnerInfo();
|
||||||
spinnerInfoText.set("");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,15 +461,23 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSpinnerInfo() {
|
private void updateSpinnerInfo() {
|
||||||
if (dataModel.isWalletFunded.get() || !showPayFundsScreenDisplayed) {
|
if (!showPayFundsScreenDisplayed.get() ||
|
||||||
isSpinnerVisible.set(false);
|
offerWarning.get() != null ||
|
||||||
|
errorMessage.get() != null ||
|
||||||
|
showTransactionPublishedScreen.get()) {
|
||||||
spinnerInfoText.set("");
|
spinnerInfoText.set("");
|
||||||
} else if (showPayFundsScreenDisplayed) {
|
} else if (dataModel.isWalletFunded.get()) {
|
||||||
spinnerInfoText.set("Waiting for receiving funds...");
|
if (dataModel.isFeeFromFundingTxSufficient.get()) {
|
||||||
isSpinnerVisible.set(true);
|
spinnerInfoText.set("");
|
||||||
|
} else {
|
||||||
|
spinnerInfoText.set("Check if funding tx miner fee is sufficient...");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spinnerInfoText.set("Waiting for funds...");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
isSpinnerVisible.set(!spinnerInfoText.get().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
private void addListeners() {
|
private void addListeners() {
|
||||||
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
|
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
|
||||||
|
@ -468,7 +489,10 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
|
|
||||||
dataModel.isWalletFunded.addListener(isWalletFundedListener);
|
dataModel.isWalletFunded.addListener(isWalletFundedListener);
|
||||||
p2PService.getNetworkNode().addConnectionListener(connectionListener);
|
p2PService.getNetworkNode().addConnectionListener(connectionListener);
|
||||||
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
isFeeSufficientSubscription = EasyBind.subscribe(dataModel.isFeeFromFundingTxSufficient, newValue -> {
|
||||||
|
updateButtonDisableState();
|
||||||
|
updateSpinnerInfo();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeListeners() {
|
private void removeListeners() {
|
||||||
|
@ -488,7 +512,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
trade.errorMessageProperty().removeListener(tradeErrorListener);
|
trade.errorMessageProperty().removeListener(tradeErrorListener);
|
||||||
}
|
}
|
||||||
p2PService.getNetworkNode().removeConnectionListener(connectionListener);
|
p2PService.getNetworkNode().removeConnectionListener(connectionListener);
|
||||||
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
isFeeSufficientSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue