mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-19 23:36:00 -04:00
Savings wallet (WIP)
This commit is contained in:
parent
d0e4792094
commit
04845e4382
@ -221,6 +221,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
OpenOfferManager openOfferManager,
|
||||
User user,
|
||||
KeyRing keyRing,
|
||||
boolean useSavingsWallet,
|
||||
Coin fundsNeededForTrade) {
|
||||
Log.traceCall();
|
||||
processModel.onAllServicesInitialized(offer,
|
||||
@ -232,6 +233,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
arbitratorManager,
|
||||
user,
|
||||
keyRing,
|
||||
useSavingsWallet,
|
||||
fundsNeededForTrade);
|
||||
|
||||
createProtocol();
|
||||
|
@ -178,7 +178,7 @@ public class TradeManager {
|
||||
else {*/
|
||||
trade.setStorage(tradableListStorage);
|
||||
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.setStorage(tradableListStorage);
|
||||
initTrade(trade, trade.getProcessModel().getFundsNeededForTrade());
|
||||
initTrade(trade, trade.getProcessModel().getUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTrade());
|
||||
trades.add(trade);
|
||||
((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress);
|
||||
} 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,
|
||||
walletService,
|
||||
tradeWalletService,
|
||||
@ -229,6 +229,7 @@ public class TradeManager {
|
||||
openOfferManager,
|
||||
user,
|
||||
keyRing,
|
||||
useSavingsWallet,
|
||||
fundsNeededForTrade);
|
||||
}
|
||||
|
||||
@ -260,12 +261,13 @@ public class TradeManager {
|
||||
Coin fundsNeededForTrade,
|
||||
Offer offer,
|
||||
String paymentAccountId,
|
||||
boolean useSavingsWallet,
|
||||
TradeResultHandler tradeResultHandler) {
|
||||
final OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
|
||||
offer.checkOfferAvailability(model,
|
||||
() -> {
|
||||
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,
|
||||
Offer offer,
|
||||
String paymentAccountId,
|
||||
boolean useSavingsWallet,
|
||||
OfferAvailabilityModel model,
|
||||
TradeResultHandler tradeResultHandler) {
|
||||
Trade trade;
|
||||
@ -285,7 +288,7 @@ public class TradeManager {
|
||||
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
|
||||
trade.setTakerPaymentAccountId(paymentAccountId);
|
||||
|
||||
initTrade(trade, fundsNeededForTrade);
|
||||
initTrade(trade, useSavingsWallet, fundsNeededForTrade);
|
||||
|
||||
trades.add(trade);
|
||||
((TakerTrade) trade).takeAvailableOffer();
|
||||
|
@ -141,8 +141,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
log.info("remove all open offers at shutDown");
|
||||
// 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
|
||||
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)
|
||||
UserThread.runAfter(completeHandler::run, openOffers.size() * 200 + 300, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ public class ProcessModel implements Model, Serializable {
|
||||
@Nullable
|
||||
private String changeOutputAddress;
|
||||
private Transaction takeOfferFeeTx;
|
||||
public boolean useSavingsWallet;
|
||||
private boolean useSavingsWallet;
|
||||
private Coin fundsNeededForTrade;
|
||||
|
||||
public ProcessModel() {
|
||||
@ -107,6 +107,7 @@ public class ProcessModel implements Model, Serializable {
|
||||
ArbitratorManager arbitratorManager,
|
||||
User user,
|
||||
KeyRing keyRing,
|
||||
boolean useSavingsWallet,
|
||||
Coin fundsNeededForTrade) {
|
||||
this.offer = offer;
|
||||
this.tradeManager = tradeManager;
|
||||
@ -117,6 +118,7 @@ public class ProcessModel implements Model, Serializable {
|
||||
this.user = user;
|
||||
this.keyRing = keyRing;
|
||||
this.p2PService = p2PService;
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
this.fundsNeededForTrade = fundsNeededForTrade;
|
||||
}
|
||||
|
||||
@ -291,4 +293,8 @@ public class ProcessModel implements Model, Serializable {
|
||||
public Transaction getTakeOfferFeeTx() {
|
||||
return takeOfferFeeTx;
|
||||
}
|
||||
|
||||
public boolean getUseSavingsWallet() {
|
||||
return useSavingsWallet;
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class CreateTakeOfferFeeTx extends TradeTask {
|
||||
processModel.getAddressEntry(),
|
||||
processModel.getUnusedSavingsAddress(),
|
||||
processModel.getFundsNeededForTrade(),
|
||||
processModel.useSavingsWallet,
|
||||
processModel.getUseSavingsWallet(),
|
||||
FeePolicy.getTakeOfferFee(),
|
||||
selectedArbitrator.getBtcAddress());
|
||||
|
||||
|
@ -34,6 +34,7 @@ public class BalanceTextField extends AnchorPane {
|
||||
|
||||
private static WalletService walletService;
|
||||
private BalanceListener balanceListener;
|
||||
private Coin targetAmount;
|
||||
|
||||
public static void setWalletService(WalletService walletService) {
|
||||
BalanceTextField.walletService = walletService;
|
||||
@ -84,6 +85,10 @@ public class BalanceTextField extends AnchorPane {
|
||||
updateBalance(balance);
|
||||
}
|
||||
|
||||
public void setTargetAmount(Coin targetAmount) {
|
||||
this.targetAmount = targetAmount;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -91,10 +96,12 @@ public class BalanceTextField extends AnchorPane {
|
||||
private void updateBalance(Coin balance) {
|
||||
if (formatter != null)
|
||||
textField.setText(formatter.formatCoinWithCode(balance));
|
||||
if (balance.isPositive())
|
||||
textField.setEffect(fundedEffect);
|
||||
else
|
||||
textField.setEffect(notFundedEffect);
|
||||
if (targetAmount != null) {
|
||||
if (balance.compareTo(targetAmount) >= 0)
|
||||
textField.setEffect(fundedEffect);
|
||||
else
|
||||
textField.setEffect(notFundedEffect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -454,9 +454,11 @@ public class MainViewModel implements ViewModel {
|
||||
updateBalance();
|
||||
setupDevDummyPaymentAccount();
|
||||
setupMarketPriceFeed();
|
||||
swapPendingTradeAddressEntriesToSavingsWallet();
|
||||
|
||||
showAppScreen.set(true);
|
||||
|
||||
|
||||
// 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.
|
||||
// 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());
|
||||
}
|
||||
|
||||
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) {
|
||||
boolean alreadyDisplayed = alert != null && alert.equals(user.getDisplayedAlert());
|
||||
user.setDisplayedAlert(alert);
|
||||
|
@ -183,9 +183,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
calculateTotalToPay();
|
||||
updateBalance();
|
||||
|
||||
if (direction == Offer.Direction.BUY)
|
||||
calculateTotalToPay();
|
||||
|
||||
if (isTabSelected)
|
||||
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
|
||||
}
|
||||
@ -402,7 +399,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
|
||||
void updateBalance() {
|
||||
Coin tradeWalletBalance = walletService.getBalanceForAddress(getAddressEntry().getAddress());
|
||||
Coin tradeWalletBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
if (useSavingsWallet) {
|
||||
Coin savingWalletBalance = walletService.getSavingWalletBalance();
|
||||
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
||||
@ -419,7 +416,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
|
||||
isWalletFunded.set(isBalanceSufficient(balance.get()));
|
||||
if (isWalletFunded.get()) {
|
||||
walletService.removeBalanceListener(balanceListener);
|
||||
//walletService.removeBalanceListener(balanceListener);
|
||||
if (walletFundedNotification == null) {
|
||||
walletFundedNotification = new Notification()
|
||||
.headLine("Trading wallet update")
|
||||
@ -457,6 +454,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
|
||||
}
|
||||
|
||||
public void swapTradeToSavings() {
|
||||
walletService.swapTradeToSavings(getOfferId());
|
||||
walletService.swapTradeToSavings(offerId);
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +177,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
onPaymentAccountsComboBoxSelected();
|
||||
|
||||
balanceTextField.setBalance(model.dataModel.balance.get());
|
||||
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -268,9 +269,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
||||
currencyComboBox.setMouseTransparent(true);
|
||||
paymentAccountsComboBox.setMouseTransparent(true);
|
||||
|
||||
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not());
|
||||
fundingHBox.managedProperty().bind(model.dataModel.isWalletFunded.not());
|
||||
|
||||
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
|
||||
|
||||
if (!BitsquareApp.DEV_MODE) {
|
||||
String key = "securityDepositInfo";
|
||||
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);
|
||||
|
||||
// buttons
|
||||
placeOfferButton.visibleProperty().bind(model.dataModel.isWalletFunded);
|
||||
placeOfferButton.managedProperty().bind(model.dataModel.isWalletFunded);
|
||||
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
|
||||
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);
|
||||
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
|
||||
|
||||
|
@ -86,6 +86,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
final BooleanProperty showWarningInvalidFiatDecimalPlaces = new SimpleBooleanProperty();
|
||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||
final BooleanProperty placeOfferCompleted = new SimpleBooleanProperty();
|
||||
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||
|
||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> minAmountValidationResult = new
|
||||
@ -109,7 +110,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
private ChangeListener<String> errorMessageListener;
|
||||
private Offer offer;
|
||||
private Timer timeoutTimer;
|
||||
private boolean showPayFundsScreenDisplayed;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -391,7 +391,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
||||
}
|
||||
|
||||
void onShowPayFundsScreen() {
|
||||
showPayFundsScreenDisplayed = true;
|
||||
showPayFundsScreenDisplayed.set(true);
|
||||
}
|
||||
|
||||
boolean useSavingsWalletForFunding() {
|
||||
|
@ -29,6 +29,7 @@ import io.bitsquare.btc.listeners.BalanceListener;
|
||||
import io.bitsquare.btc.pricefeed.PriceFeed;
|
||||
import io.bitsquare.common.UserThread;
|
||||
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.windows.WalletPasswordWindow;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
@ -46,6 +47,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionConfidence;
|
||||
import org.bitcoinj.utils.ExchangeRate;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -75,21 +77,27 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
private final Coin takerFeeAsCoin;
|
||||
private final Coin networkFeeAsCoin;
|
||||
private final Coin securityDepositAsCoin;
|
||||
|
||||
Coin feeFromFundingTx = Coin.NEGATIVE_SATOSHI;
|
||||
|
||||
private Offer offer;
|
||||
private AddressEntry addressEntry;
|
||||
|
||||
private AddressEntry addressEntry;
|
||||
final StringProperty btcCode = new SimpleStringProperty();
|
||||
final BooleanProperty useMBTC = 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<Fiat> volumeAsFiat = 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;
|
||||
PaymentAccount paymentAccount;
|
||||
private boolean isTabSelected;
|
||||
boolean useSavingsWallet;
|
||||
Coin totalAvailableBalance;
|
||||
private Notification walletFundedNotification;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -115,6 +123,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
takerFeeAsCoin = FeePolicy.getTakeOfferFee();
|
||||
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
|
||||
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
|
||||
|
||||
isMainNet.set(preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -124,13 +134,15 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
|
||||
addBindings();
|
||||
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
|
||||
// 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
|
||||
if (isWalletFunded.get())
|
||||
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
|
||||
// if (isWalletFunded.get())
|
||||
// feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
|
||||
|
||||
if (isTabSelected)
|
||||
priceFeed.setCurrencyCode(offer.getCurrencyCode());
|
||||
@ -167,19 +179,24 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
if (BitsquareApp.DEV_MODE)
|
||||
amountAsCoin.set(offer.getAmount());
|
||||
|
||||
calculateTotalToPay();
|
||||
calculateVolume();
|
||||
calculateTotalToPay();
|
||||
|
||||
balanceListener = new BalanceListener(addressEntry.getAddress()) {
|
||||
@Override
|
||||
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());
|
||||
Futures.addCallback(future, new FutureCallback<Coin>() {
|
||||
public void onSuccess(Coin fee) {
|
||||
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
|
||||
UserThread.execute(() -> setFeeFromFundingTx(fee));
|
||||
}
|
||||
|
||||
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 " +
|
||||
formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + "?")
|
||||
.actionButtonText("Yes, I used a sufficiently high fee.")
|
||||
.onAction(() -> feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx()))
|
||||
.onAction(() -> setFeeFromFundingTx(FeePolicy.getMinRequiredFeeForFundingTx()))
|
||||
.closeButtonText("No. Let's cancel that payment.")
|
||||
.onClose(() -> feeFromFundingTxProperty.set(Coin.ZERO))
|
||||
.onClose(() -> setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI))
|
||||
.show());
|
||||
}
|
||||
});
|
||||
} 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());
|
||||
}
|
||||
|
||||
|
||||
void onTabSelected(boolean isSelected) {
|
||||
this.isTabSelected = isSelected;
|
||||
if (isTabSelected)
|
||||
@ -235,6 +252,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
totalToPayAsCoin.get().subtract(takerFeeAsCoin),
|
||||
offer,
|
||||
paymentAccount.getId(),
|
||||
useSavingsWallet,
|
||||
tradeResultHandler
|
||||
);
|
||||
}
|
||||
@ -244,6 +262,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
this.paymentAccount = paymentAccount;
|
||||
}
|
||||
|
||||
void useSavingsWalletForFunding() {
|
||||
useSavingsWallet = true;
|
||||
updateBalance();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
@ -275,10 +298,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
return user.getAcceptedArbitrators().size() > 0;
|
||||
}
|
||||
|
||||
boolean isFeeFromFundingTxSufficient() {
|
||||
return feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Bindings, listeners
|
||||
@ -311,7 +330,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
||||
!amountAsCoin.get().isZero()) {
|
||||
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()));
|
||||
}
|
||||
|
||||
private void updateBalance(@NotNull Coin balance) {
|
||||
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
|
||||
void updateBalance() {
|
||||
Coin tradeWalletBalance = walletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
if (useSavingsWallet) {
|
||||
Coin savingWalletBalance = walletService.getSavingWalletBalance();
|
||||
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
||||
|
||||
if (isWalletFunded.get())
|
||||
walletService.removeBalanceListener(balanceListener);
|
||||
if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
|
||||
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() {
|
||||
|
@ -60,15 +60,16 @@ import javafx.stage.Window;
|
||||
import javafx.util.StringConverter;
|
||||
import net.glxn.qrgen.QRCode;
|
||||
import net.glxn.qrgen.image.ImageType;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.uri.BitcoinURI;
|
||||
import org.controlsfx.control.PopOver;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||
@ -89,7 +90,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
private BalanceTextField balanceTextField;
|
||||
private ProgressIndicator spinner, offerAvailabilitySpinner;
|
||||
private TitledGroupBg payFundsPane;
|
||||
private Button nextButton, takeOfferButton, cancelButton1, cancelButton2;
|
||||
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, takeOfferButton;
|
||||
private InputTextField amountTextField;
|
||||
private TextField paymentMethodTextField, currencyTextField, priceTextField, volumeTextField, amountRangeTextField;
|
||||
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
|
||||
@ -109,11 +110,14 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
private Subscription showWarningInvalidBtcDecimalPlacesSubscription;
|
||||
private Subscription showTransactionPublishedScreenSubscription;
|
||||
private SimpleBooleanProperty errorPopupDisplayed;
|
||||
private ChangeListener<Coin> feeFromFundingTxListener;
|
||||
private boolean offerDetailsWindowDisplayed;
|
||||
private Notification walletFundedNotification;
|
||||
private Subscription isWalletFundedSubscription;
|
||||
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);
|
||||
volumeTextField.textProperty().bindBidirectional(model.volume);
|
||||
totalToPayTextField.textProperty().bind(model.totalToPay);
|
||||
addressTextField.amountAsCoinProperty().bind(model.totalToPayAsCoin);
|
||||
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
|
||||
amountTextField.validationResultProperty().bind(model.amountValidationResult);
|
||||
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.managedProperty().bind(model.isSpinnerVisible);
|
||||
|
||||
spinnerInfoLabel.visibleProperty().bind(model.isSpinnerVisible);
|
||||
spinnerInfoLabel.managedProperty().bind(model.isSpinnerVisible);
|
||||
spinnerInfoLabel.textProperty().bind(model.spinnerInfoText);
|
||||
|
||||
priceCurrencyLabel.textProperty().bind(createStringBinding(() ->
|
||||
@ -256,14 +267,15 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
paymentAccountsComboBox.getSelectionModel().select(0);
|
||||
}
|
||||
|
||||
feeFromFundingTxListener = (observable, oldValue, newValue) -> {
|
||||
log.debug("feeFromFundingTxListener " + newValue);
|
||||
if (!model.dataModel.isFeeFromFundingTxSufficient()) {
|
||||
noSufficientFeeBinding = EasyBind.combine(model.dataModel.isWalletFunded, model.dataModel.isMainNet, model.dataModel.isFeeFromFundingTxSufficient,
|
||||
(isWalletFunded, isMainNet, isFeeSufficient) -> isWalletFunded && isMainNet && !isFeeSufficient);
|
||||
noSufficientFeeSubscription = noSufficientFeeBinding.subscribe((observable, oldValue, newValue) -> {
|
||||
if (newValue)
|
||||
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 " +
|
||||
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
|
||||
"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 " +
|
||||
"a block if the fee is too low.\n" +
|
||||
"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);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
};
|
||||
model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
||||
});
|
||||
|
||||
if (offerAvailabilitySpinner != null && offerAvailabilitySpinner.isVisible())
|
||||
offerAvailabilitySpinner.setProgress(-1);
|
||||
|
||||
if (spinner != null && spinner.isVisible())
|
||||
spinner.setProgress(-1);
|
||||
|
||||
balanceSubscription = EasyBind.subscribe(model.dataModel.balance, newValue -> balanceTextField.setBalance(newValue));
|
||||
totalToPaySubscription = EasyBind.subscribe(model.dataModel.totalToPayAsCoin, newValue -> balanceTextField.setTargetAmount(newValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -298,16 +311,23 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
totalToPayTextField.textProperty().unbind();
|
||||
addressTextField.amountAsCoinProperty().unbind();
|
||||
amountTextField.validationResultProperty().unbind();
|
||||
takeOfferButton.disableProperty().unbind();
|
||||
spinner.visibleProperty().unbind();
|
||||
spinnerInfoLabel.visibleProperty().unbind();
|
||||
spinnerInfoLabel.textProperty().unbind();
|
||||
priceCurrencyLabel.textProperty().unbind();
|
||||
volumeCurrencyLabel.textProperty().unbind();
|
||||
amountRangeBtcLabel.textProperty().unbind();
|
||||
priceDescriptionLabel.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();
|
||||
errorMessageSubscription.unsubscribe();
|
||||
isOfferAvailableSubscription.unsubscribe();
|
||||
@ -315,7 +335,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe();
|
||||
showTransactionPublishedScreenSubscription.unsubscribe();
|
||||
|
||||
model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
||||
noSufficientFeeSubscription.unsubscribe();
|
||||
if (balanceTextField != null)
|
||||
balanceTextField.cleanup();
|
||||
|
||||
@ -325,8 +345,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
if (spinner != null)
|
||||
spinner.setProgress(0);
|
||||
|
||||
if (isWalletFundedSubscription != null)
|
||||
isWalletFundedSubscription.unsubscribe();
|
||||
balanceSubscription.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
|
||||
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
|
||||
/*
|
||||
if (model.dataModel.isWalletFunded.get())
|
||||
@ -461,7 +490,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
offerAvailabilitySpinnerLabel.setVisible(false);
|
||||
cancelButton1.setVisible(false);
|
||||
cancelButton1.setOnAction(null);
|
||||
takeOfferButton.setVisible(true);
|
||||
cancelButton2.setVisible(true);
|
||||
|
||||
spinner.setProgress(-1);
|
||||
@ -487,19 +515,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
.autoClose();
|
||||
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
|
||||
@ -689,9 +704,41 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
balanceTextField = balanceTuple.second;
|
||||
balanceTextField.setVisible(false);
|
||||
|
||||
Tuple3<Button, ProgressIndicator, Label> takeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, "");
|
||||
takeOfferButton = takeOfferTuple.first;
|
||||
fundingHBox = new HBox();
|
||||
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.setManaged(false);
|
||||
takeOfferButton.setMinHeight(40);
|
||||
takeOfferButton.setPadding(new Insets(0, 20, 0, 20));
|
||||
takeOfferButton.setOnAction(e -> {
|
||||
@ -699,9 +746,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
balanceTextField.cleanup();
|
||||
});
|
||||
|
||||
spinner = takeOfferTuple.second;
|
||||
spinnerInfoLabel = takeOfferTuple.third;
|
||||
|
||||
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
|
||||
cancelButton2.setOnAction(e -> {
|
||||
if (model.dataModel.isWalletFunded.get())
|
||||
@ -716,12 +760,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
||||
});
|
||||
cancelButton2.setDefaultButton(false);
|
||||
cancelButton2.setVisible(false);
|
||||
cancelButton2.setId("cancel-button");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
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) : "";
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,13 @@
|
||||
package io.bitsquare.gui.main.offer.takeoffer;
|
||||
|
||||
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.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.validation.BtcValidator;
|
||||
import io.bitsquare.gui.util.validation.InputValidator;
|
||||
@ -38,6 +42,8 @@ import javafx.beans.value.ChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.bitcoinj.core.Address;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
@ -46,8 +52,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static javafx.beans.binding.Bindings.createStringBinding;
|
||||
|
||||
class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> implements ViewModel {
|
||||
final TakeOfferDataModel dataModel;
|
||||
private final BtcValidator btcValidator;
|
||||
private final P2PService p2PService;
|
||||
private final Navigation navigation;
|
||||
final BSFormatter formatter;
|
||||
|
||||
private String amountRange;
|
||||
@ -75,11 +83,12 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
final BooleanProperty isSpinnerVisible = new SimpleBooleanProperty();
|
||||
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
|
||||
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
|
||||
final BooleanProperty takeOfferCompleted = new SimpleBooleanProperty();
|
||||
final BooleanProperty showPayFundsScreenDisplayed = new SimpleBooleanProperty();
|
||||
|
||||
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
// Those are needed for the addressTextField
|
||||
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
|
||||
|
||||
private ChangeListener<String> amountListener;
|
||||
@ -90,9 +99,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
private ChangeListener<Offer.State> offerStateListener;
|
||||
private ChangeListener<String> offerErrorListener;
|
||||
private ConnectionListener connectionListener;
|
||||
private ChangeListener<Coin> feeFromFundingTxListener;
|
||||
private Subscription isFeeSufficientSubscription;
|
||||
private Runnable takeOfferSucceededHandler;
|
||||
private boolean showPayFundsScreenDisplayed;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -101,11 +109,13 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
|
||||
@Inject
|
||||
public TakeOfferViewModel(TakeOfferDataModel dataModel, BtcValidator btcValidator, P2PService p2PService,
|
||||
BSFormatter formatter) {
|
||||
Navigation navigation, BSFormatter formatter) {
|
||||
super(dataModel);
|
||||
this.dataModel = dataModel;
|
||||
|
||||
this.btcValidator = btcValidator;
|
||||
this.p2PService = p2PService;
|
||||
this.navigation = navigation;
|
||||
this.formatter = formatter;
|
||||
|
||||
createListeners();
|
||||
@ -190,6 +200,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
trade.errorMessageProperty().addListener(tradeErrorListener);
|
||||
applyTradeErrorMessage(trade.errorMessageProperty().get());
|
||||
updateButtonDisableState();
|
||||
takeOfferCompleted.set(true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -200,10 +211,30 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
}
|
||||
|
||||
public void onShowPayFundsScreen() {
|
||||
showPayFundsScreenDisplayed = true;
|
||||
showPayFundsScreenDisplayed.set(true);
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -286,10 +317,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
break;
|
||||
}
|
||||
|
||||
if (offerWarning.get() != null) {
|
||||
isSpinnerVisible.set(false);
|
||||
spinnerInfoText.set("");
|
||||
}
|
||||
updateSpinnerInfo();
|
||||
|
||||
updateButtonDisableState();
|
||||
}
|
||||
@ -328,8 +356,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
}
|
||||
this.errorMessage.set(errorMessage + appendMsg);
|
||||
|
||||
isSpinnerVisible.set(false);
|
||||
spinnerInfoText.set("");
|
||||
updateSpinnerInfo();
|
||||
|
||||
if (takeOfferSucceededHandler != null)
|
||||
takeOfferSucceededHandler.run();
|
||||
@ -349,9 +376,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
if (takeOfferSucceededHandler != null)
|
||||
takeOfferSucceededHandler.run();
|
||||
|
||||
isSpinnerVisible.set(false);
|
||||
spinnerInfoText.set("");
|
||||
showTransactionPublishedScreen.set(true);
|
||||
updateSpinnerInfo();
|
||||
} else {
|
||||
log.error("trade.getDepositTx() == null. That must not happen");
|
||||
}
|
||||
@ -364,10 +390,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
&& !dataModel.isAmountLargerThanOfferAmount()
|
||||
&& isOfferAvailable.get();
|
||||
isNextButtonDisabled.set(!inputDataValid);
|
||||
isTakeOfferButtonDisabled.set(!(inputDataValid
|
||||
&& dataModel.isWalletFunded.get()
|
||||
&& !takeOfferRequested
|
||||
&& dataModel.isFeeFromFundingTxSufficient()));
|
||||
boolean notSufficientFees = dataModel.isWalletFunded.get() && dataModel.isMainNet.get() && !dataModel.isFeeFromFundingTxSufficient.get();
|
||||
isTakeOfferButtonDisabled.set(takeOfferRequested || !inputDataValid || notSufficientFees);
|
||||
}
|
||||
|
||||
|
||||
@ -385,7 +409,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
volumeDescriptionLabel.set(BSResources.get("createOffer.amountPriceBox.sell.volumeDescription", dataModel.getCurrencyCode()));
|
||||
}
|
||||
totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(dataModel.totalToPayAsCoin.get()), dataModel.totalToPayAsCoin));
|
||||
totalToPayAsCoin.bind(dataModel.totalToPayAsCoin);
|
||||
btcCode.bind(dataModel.btcCode);
|
||||
}
|
||||
|
||||
@ -394,7 +417,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
volumeDescriptionLabel.unbind();
|
||||
volume.unbind();
|
||||
totalToPay.unbind();
|
||||
totalToPayAsCoin.unbind();
|
||||
btcCode.unbind();
|
||||
}
|
||||
|
||||
@ -410,16 +432,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue));
|
||||
isWalletFundedListener = (ov, oldValue, newValue) -> {
|
||||
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);
|
||||
tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(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 " +
|
||||
"many open connections.\n\n" +
|
||||
"If you can still see his offer in the offerbook you can try to take the offer again.");
|
||||
isSpinnerVisible.set(false);
|
||||
spinnerInfoText.set("");
|
||||
updateSpinnerInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,15 +461,23 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
}
|
||||
|
||||
private void updateSpinnerInfo() {
|
||||
if (dataModel.isWalletFunded.get() || !showPayFundsScreenDisplayed) {
|
||||
isSpinnerVisible.set(false);
|
||||
if (!showPayFundsScreenDisplayed.get() ||
|
||||
offerWarning.get() != null ||
|
||||
errorMessage.get() != null ||
|
||||
showTransactionPublishedScreen.get()) {
|
||||
spinnerInfoText.set("");
|
||||
} else if (showPayFundsScreenDisplayed) {
|
||||
spinnerInfoText.set("Waiting for receiving funds...");
|
||||
isSpinnerVisible.set(true);
|
||||
} else if (dataModel.isWalletFunded.get()) {
|
||||
if (dataModel.isFeeFromFundingTxSufficient.get()) {
|
||||
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() {
|
||||
// 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);
|
||||
p2PService.getNetworkNode().addConnectionListener(connectionListener);
|
||||
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
|
||||
isFeeSufficientSubscription = EasyBind.subscribe(dataModel.isFeeFromFundingTxSufficient, newValue -> {
|
||||
updateButtonDisableState();
|
||||
updateSpinnerInfo();
|
||||
});
|
||||
}
|
||||
|
||||
private void removeListeners() {
|
||||
@ -488,7 +512,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||
trade.errorMessageProperty().removeListener(tradeErrorListener);
|
||||
}
|
||||
p2PService.getNetworkNode().removeConnectionListener(connectionListener);
|
||||
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
|
||||
isFeeSufficientSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user