Savings wallet (WIP)

This commit is contained in:
Manfred Karrer 2016-04-01 01:30:25 +02:00
parent 1d57bd194e
commit f36d8241f3
10 changed files with 212 additions and 186 deletions

View file

@ -18,7 +18,6 @@
package io.bitsquare.gui.components;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.gui.util.BSFormatter;
import javafx.scene.control.TextField;
import javafx.scene.effect.BlurType;
@ -26,14 +25,11 @@ import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
public class BalanceTextField extends AnchorPane {
private static WalletService walletService;
private BalanceListener balanceListener;
private Coin targetAmount;
public static void setWalletService(WalletService walletService) {
@ -65,22 +61,6 @@ public class BalanceTextField extends AnchorPane {
this.formatter = formatter;
}
public void setupBalanceListener(Address address) {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
};
walletService.addBalanceListener(balanceListener);
updateBalance(walletService.getBalanceForAddress(address));
}
public void cleanup() {
if (balanceListener != null)
walletService.removeBalanceListener(balanceListener);
}
public void setBalance(Coin balance) {
updateBalance(balance);
}

View file

@ -37,6 +37,7 @@ import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.user.Preferences;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
@ -91,6 +92,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
private InputTextField amountTextField;
private Subscription amountTextFieldSubscription;
private String paymentLabel;
private ChangeListener<DepositListItem> tableViewSelectionListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -108,8 +110,15 @@ public class DepositView extends ActivatableView<VBox, Void> {
@Override
public void initialize() {
// trigger creation of at least 1 savings address
walletService.getUnusedSavingsAddressEntry();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No deposit addresses are generated yet"));
tableViewSelectionListener = (observableValue, oldValue, newValue) -> {
if (newValue != null)
fillForm(newValue.getAddressString());
};
setSelectColumnCellFactory();
setAddressColumnCellFactory();
@ -174,12 +183,17 @@ public class DepositView extends ActivatableView<VBox, Void> {
generateNewAddressButton.setOnAction(event -> {
boolean hasUnUsedAddress = observableList.stream().filter(e -> e.getNumTxOutputs() == 0).findAny().isPresent();
if (hasUnUsedAddress) {
new Popup().warning("You have addresses which are not used in any transaction.\n" +
"Please select in the address table any unused address.").show();
new Popup().warning("You have already at least one address which is not used yet in any transaction.\n" +
"Please select in the address table an unused address.").show();
} else {
AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry();
fillForm(newSavingsAddressEntry.getAddressString());
//fillForm(newSavingsAddressEntry.getAddressString());
updateList();
observableList.stream()
.filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString()))
.findAny()
.ifPresent(depositListItem -> tableView.getSelectionModel().select(depositListItem));
}
});
@ -212,6 +226,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
@Override
protected void activate() {
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
updateList();
@ -225,6 +240,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
@Override
protected void deactivate() {
tableView.getSelectionModel().selectedItemProperty().removeListener(tableViewSelectionListener);
sortedList.comparatorProperty().unbind();
observableList.forEach(DepositListItem::cleanup);
walletService.removeBalanceListener(balanceListener);
@ -256,7 +272,6 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressTextField.setAddress(address);
updateQRCode();
}
private void updateQRCode() {
@ -343,7 +358,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
if (button == null) {
button = new Button("Select");
button.setOnAction(e -> fillForm(item.getAddressString()));
button.setOnAction(e -> tableView.getSelectionModel().select(item));
setGraphic(button);
}
} else {
@ -379,7 +394,10 @@ public class DepositView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
String addressString = item.getAddressString();
field = new HyperlinkWithIcon(addressString, AwesomeIcon.EXTERNAL_LINK);
field.setOnAction(event -> openBlockExplorer(item));
field.setOnAction(event -> {
openBlockExplorer(item);
tableView.getSelectionModel().select(item);
});
field.setTooltip(new Tooltip("Open external blockchain explorer for " +
"address: " + addressString));
setGraphic(field);

View file

@ -178,11 +178,11 @@ class CreateOfferDataModel extends ActivatableDataModel {
addListeners();
paymentAccounts.setAll(user.getPaymentAccounts());
calculateTotalToPay();
updateBalance();
if (isTabSelected)
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
updateBalance();
}
@Override
@ -225,6 +225,10 @@ class CreateOfferDataModel extends ActivatableDataModel {
paymentAccount = account;
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
calculateVolume();
calculateTotalToPay();
updateBalance();
}
void onTabSelected(boolean isSelected) {
@ -373,6 +377,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
!priceAsFiat.get().isZero()) {
volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get()));
}
updateBalance();
}
void calculateAmount() {
@ -402,7 +408,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
Coin savingWalletBalance = walletService.getSavingWalletBalance();
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
if (totalToPayAsCoin.get() != null && totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
balance.set(totalToPayAsCoin.get());
else
balance.set(totalAvailableBalance);
@ -410,22 +416,21 @@ class CreateOfferDataModel extends ActivatableDataModel {
balance.set(tradeWalletBalance);
}
missingCoin.set(totalToPayAsCoin.get().subtract(balance.get()));
if (missingCoin.get().isNegative())
missingCoin.set(Coin.ZERO);
if (totalToPayAsCoin.get() != null) {
missingCoin.set(totalToPayAsCoin.get().subtract(balance.get()));
if (missingCoin.get().isNegative())
missingCoin.set(Coin.ZERO);
}
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();
if (totalToPayAsCoin.get() != null && isWalletFunded.get() && 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();
}
walletFundedNotification.show();
}
}

View file

@ -62,7 +62,6 @@ 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;
@ -120,10 +119,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private final Preferences preferences;
private ChangeListener<String> tradeCurrencyCodeListener;
private ImageView qrCodeImageView;
private ChangeListener<Coin> balanceListener;
private HBox fundingHBox;
private Subscription isSpinnerVisibleSubscription;
private Subscription cancelButton2StyleSubscription;
private Subscription balanceSubscription;
///////////////////////////////////////////////////////////////////////////////////////////
@ -150,7 +149,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
createListeners();
balanceTextField.setFormatter(model.getFormatter());
balanceListener = (observable, oldValue, newValue) -> balanceTextField.setBalance(newValue);
paymentAccountsComboBox.setConverter(new StringConverter<PaymentAccount>() {
@Override
@ -185,7 +183,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
onPaymentAccountsComboBoxSelected();
balanceTextField.setBalance(model.dataModel.balance.get());
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
}
@ -197,9 +194,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
if (spinner != null)
spinner.setProgress(0);
if (balanceTextField != null)
balanceTextField.cleanup();
}
@ -487,11 +481,14 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
cancelButton2StyleSubscription = EasyBind.subscribe(placeOfferButton.visibleProperty(),
isVisible -> cancelButton2.setId(isVisible ? "cancel-button" : null));
balanceSubscription = EasyBind.subscribe(model.dataModel.balance, newValue -> balanceTextField.setBalance(newValue));
}
private void removeSubscriptions() {
isSpinnerVisibleSubscription.unsubscribe();
cancelButton2StyleSubscription.unsubscribe();
balanceSubscription.unsubscribe();
}
private void createListeners() {
@ -600,7 +597,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void addListeners() {
model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener);
model.dataModel.balance.addListener(balanceListener);
// focus out
amountTextField.focusedProperty().addListener(amountFocusedListener);
@ -624,7 +620,6 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void removeListeners() {
model.tradeCurrencyCode.removeListener(tradeCurrencyCodeListener);
model.dataModel.balance.removeListener(balanceListener);
// focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener);
@ -846,9 +841,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
if (model.dataModel.isWalletFunded.get()) {
new Popup().warning("You have already paid in the funds.\n" +
"Are you sure you want to cancel.")
.actionButtonText("No")
.closeButtonText("Yes, close")
.onClose(() -> {
.closeButtonText("No")
.actionButtonText("Yes, cancel")
.onAction(() -> {
close();
model.dataModel.swapTradeToSavings();
})

View file

@ -131,7 +131,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
addBindings();
addListeners();
calculateTotalToPay();
updateBalance();
// TODO In case that we have funded but restarted, or canceled but took again the offer we would need to
@ -353,9 +352,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
}
isWalletFunded.set(isBalanceSufficient(balance.get()));
if (isWalletFunded.get()) {
// walletService.removeBalanceListener(balanceListener);
if (totalToPayAsCoin.get() != null && walletFundedNotification == null) {
if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) {
walletFundedNotification = new Notification()
.headLine("Trading wallet update")
.notification("Your trading wallet is sufficiently funded.\n" +
@ -364,7 +361,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
walletFundedNotification.show();
}
}
}
private boolean isBalanceSufficient(Coin balance) {

View file

@ -116,7 +116,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private Subscription balanceSubscription;
// private Subscription noSufficientFeeSubscription;
// private MonadicBinding<Boolean> noSufficientFeeBinding;
private Subscription totalToPaySubscription;
private Subscription cancelButton2StyleSubscription;
@ -143,6 +142,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
addAmountPriceGroup();
addFundingGroup();
balanceTextField.setFormatter(model.getFormatter());
amountFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
amountTextField.setText(model.amount.get());
@ -170,6 +171,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
paymentAccountsComboBox.setItems(model.getPossiblePaymentAccounts());
paymentAccountsComboBox.getSelectionModel().select(0);
}
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
}
@Override
@ -183,9 +186,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
if (spinner != null)
spinner.setProgress(0);
if (balanceTextField != null)
balanceTextField.cleanup();
}
@ -212,8 +212,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setText("Review take offer for selling bitcoin");
}
balanceTextField.setFormatter(model.getFormatter());
balanceTextField.setupBalanceListener(model.address.get());
boolean showComboBox = model.getPossiblePaymentAccounts().size() > 1;
paymentAccountsLabel.setVisible(showComboBox);
@ -232,7 +230,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
amountRangeTextField.setText(model.getAmountRange());
priceTextField.setText(model.getPrice());
addressTextField.setPaymentLabel(model.getPaymentLabel());
addressTextField.setAddress(model.getAddressAsString());
addressTextField.setAddress(model.dataModel.getAddressEntry().getAddressString());
}
public void setCloseHandler(OfferView.CloseHandler closeHandler) {
@ -290,6 +288,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
priceTextField.setMouseTransparent(true);
volumeTextField.setMouseTransparent(true);
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" +
@ -309,7 +310,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
"The amount is the sum of " + tradeAmountText + "the security deposit, the trading fee and " +
"the bitcoin mining fee.\n\n" +
"Please send from your external Bitcoin wallet the exact amount to the address: " +
model.getAddressAsString() + "\n(you can copy the address in the screen below after closing that popup)\n\n" +
model.dataModel.getAddressEntry().getAddressString() + "\n(you can copy the address in the screen below after closing that popup)\n\n" +
"Make sure you use a sufficiently high mining fee of at least " +
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) +
" to avoid problems that your transaction does not get confirmed in the blockchain.\n" +
@ -530,7 +531,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
});*/
balanceSubscription = EasyBind.subscribe(model.dataModel.balance, newValue -> balanceTextField.setBalance(newValue));
totalToPaySubscription = EasyBind.subscribe(model.dataModel.totalToPayAsCoin, newValue -> balanceTextField.setTargetAmount(newValue));
cancelButton2StyleSubscription = EasyBind.subscribe(takeOfferButton.visibleProperty(),
isVisible -> cancelButton2.setId(isVisible ? "cancel-button" : null));
}
@ -544,7 +544,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
showTransactionPublishedScreenSubscription.unsubscribe();
// noSufficientFeeSubscription.unsubscribe();
balanceSubscription.unsubscribe();
totalToPaySubscription.unsubscribe();
cancelButton2StyleSubscription.unsubscribe();
}
@ -757,22 +756,19 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setManaged(false);
takeOfferButton.setMinHeight(40);
takeOfferButton.setPadding(new Insets(0, 20, 0, 20));
takeOfferButton.setOnAction(e -> {
onTakeOffer();
balanceTextField.cleanup();
});
takeOfferButton.setOnAction(e -> onTakeOffer());
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
cancelButton2.setOnAction(e -> {
if (model.dataModel.isWalletFunded.get()) {
new Popup().warning("You have already paid in the funds.\n" +
"Are you sure you want to cancel.")
.actionButtonText("No")
.onClose(() -> {
.closeButtonText("No")
.actionButtonText("Yes, cancel")
.onAction(() -> {
model.dataModel.swapTradeToSavings();
close();
})
.closeButtonText("Yes, cancel")
.show();
} else {
close();
@ -785,7 +781,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
@NotNull
private String getBitcoinURI() {
return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.dataModel.missingCoin.get(),
String addressString = model.dataModel.getAddressEntry().getAddressString();
return addressString != null ? BitcoinURI.convertToBitcoinURI(addressString, model.dataModel.missingCoin.get(),
model.getPaymentLabel(), null) : "";
}

View file

@ -40,7 +40,6 @@ import io.bitsquare.trade.offer.Offer;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
@ -57,7 +56,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
final BSFormatter formatter;
private String amountRange;
private String addressAsString;
private String paymentLabel;
private boolean takeOfferRequested;
private Trade trade;
@ -86,9 +84,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
// Those are needed for the addressTextField
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
private ChangeListener<String> amountListener;
private ChangeListener<Coin> amountAsCoinListener;
private ChangeListener<Boolean> isWalletFundedListener;
@ -169,9 +164,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
checkNotNull(dataModel.getAddressEntry(), "dataModel.getAddressEntry() must not be null");
addressAsString = dataModel.getAddressEntry().getAddressString();
address.set(dataModel.getAddressEntry().getAddress());
offerErrorListener = (observable, oldValue, newValue) -> {
if (newValue != null)
errorMessage.set(newValue);
@ -560,10 +552,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return paymentLabel;
}
public String getAddressAsString() {
return addressAsString;
}
public String getPrice() {
return price;
}