Refactor fee handling, add user defined fee to settings, UI fixes

This commit is contained in:
Manfred Karrer 2016-01-21 02:40:49 +01:00
parent 35d6612820
commit f723bf5737
36 changed files with 255 additions and 143 deletions

View file

@ -195,7 +195,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
private Tuple2<TextField, VBox> getBalanceBox(String text) {
TextField textField = new TextField();
textField.setEditable(false);
textField.setPrefWidth(100);
textField.setPrefWidth(120);
textField.setMouseTransparent(true);
textField.setFocusTraversable(false);
textField.setStyle("-fx-alignment: center; -fx-background-color: white;");

View file

@ -313,7 +313,6 @@ public class MainViewModel implements ViewModel {
});
pendingTradesChanged();
addDisputeStateListeners(tradeManager.getTrades());
tradeManager.onAllServicesInitialized();
// arbitratorManager

View file

@ -406,7 +406,7 @@ public class DisputeSummaryPopup extends Popup {
private void calculatePayoutAmounts(DisputeResult.FeePaymentPolicy feePayment) {
Contract contract = dispute.getContract();
Coin refund = FeePolicy.SECURITY_DEPOSIT;
Coin refund = FeePolicy.getSecurityDeposit();
Coin winnerRefund;
Coin loserRefund;
switch (feePayment) {

View file

@ -105,28 +105,29 @@ public class ReservedListItem {
switch (phase) {
case PREPARATION:
case TAKER_FEE_PAID:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " locked in deposit");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (locally reserved)");
break;
case DEPOSIT_REQUESTED:
case DEPOSIT_PAID:
case FIAT_SENT:
case FIAT_RECEIVED:
// We ignore the tx fee as it will be paid by both (once deposit, once payout)
Coin balanceInDeposit = FeePolicy.SECURITY_DEPOSIT;
Coin balanceInDeposit = FeePolicy.getSecurityDeposit();
// For the seller we add the trade amount
if (trade.getContract().getSellerAddress().equals(getAddress()))
balanceInDeposit.add(trade.getTradeAmount());
balanceLabel.setText(formatter.formatCoinWithCode(balanceInDeposit) + " locked in deposit");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (in MS escrow)");
break;
case PAYOUT_PAID:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " in wallet");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " (in local wallet)");
break;
case WITHDRAWN:
log.error("Invalid state at updateBalance (WITHDRAWN)");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " already withdrawn");
break;
case DISPUTE:
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " locked because of open ticket");
balanceLabel.setText(formatter.formatCoinWithCode(balance) + " open dispute/ticket");
break;
default:
log.warn("Not supported tradePhase: " + phase);

View file

@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.withdrawal;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
@ -65,9 +64,12 @@ import java.util.stream.Stream;
@FxmlView
public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML Button withdrawButton;
@FXML TableView<WithdrawalListItem> table;
@FXML TextField withdrawFromTextField, withdrawToTextField, amountTextField;
@FXML
Button withdrawButton;
@FXML
TableView<WithdrawalListItem> table;
@FXML
TextField withdrawFromTextField, withdrawToTextField, amountTextField;
@FXML
TableColumn<WithdrawalListItem, WithdrawalListItem> labelColumn, addressColumn, balanceColumn, confidenceColumn;
@ -114,7 +116,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
setConfidenceColumnCellFactory();
if (BitsquareApp.DEV_MODE)
withdrawToTextField.setText("mwajQdfYnve1knXnmv7JdeiVpeogTsck6S");
withdrawToTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
}
private boolean areInputsValid() {
@ -144,8 +146,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
if (Coin.ZERO.compareTo(newValue.getBalance()) <= 0) {
amountTextField.setText(newValue.getBalance().toPlainString());
withdrawFromTextField.setText(newValue.getAddressEntry().getAddressString());
}
else {
} else {
withdrawFromTextField.setText("");
withdrawFromTextField.setPromptText("No fund to withdrawal on that address.");
amountTextField.setText("");
@ -173,15 +174,14 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
@FXML
public void onWithdraw() {
Coin amount = formatter.parseToCoin(amountTextField.getText());
if (Restrictions.isMinSpendableAmount(amount)) {
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
if (Restrictions.isAboveDust(senderAmount)) {
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) {
log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
}
else {
} else {
log.error("onWithdraw transaction is null");
}
}
@ -191,26 +191,29 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
log.error("onWithdraw onFailure");
}
};
try {
Coin requiredFee = walletService.getRequiredFee(withdrawFromTextField.getText(),
withdrawToTextField.getText(), senderAmount);
Coin receiverAmount = senderAmount.subtract(requiredFee);
if (BitsquareApp.DEV_MODE) {
doWithdraw(receiverAmount, callback);
} else {
new Popup().headLine("Confirm your withdrawal request")
.message("Sending: " + formatter.formatCoinWithCode(senderAmount) + "\n" +
"From address: " + withdrawFromTextField.getText() + "\n" +
"To receiving address: " + withdrawToTextField.getText() + ".\n\n" +
"Required transaction fee is: " + formatter.formatCoinWithCode(requiredFee) + "\n" +
"Recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" +
"Are you sure you want to withdraw that amount?")
.onAction(() -> doWithdraw(receiverAmount, callback))
.show();
if (BitsquareApp.DEV_MODE) {
doWithdraw(amount, callback);
} else {
new Popup().headLine("Confirm your withdrawal request").message("Amount: " + amountTextField.getText() + " " +
"BTC\n" +
"Sending" +
" address: " + withdrawFromTextField.getText() + "\n" + "Receiving address: " +
withdrawToTextField.getText() + "\n" + "Transaction fee: " +
formatter.formatCoinWithCode(FeePolicy.TX_FEE) + "\n" +
"Receivers amount: " +
formatter.formatCoinWithCode(amount.subtract(FeePolicy.TX_FEE)) + " BTC\n\n" +
"Are you sure you want to withdraw that amount?")
.onAction(() -> {
doWithdraw(amount, callback);
})
.show();
}
} catch (AddressFormatException | InsufficientMoneyException e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
else {
} else {
new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value.").show();
}
}
@ -284,8 +287,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
hyperlink.setOnAction(event -> openDetails(item));
}
setGraphic(hyperlink);
}
else {
} else {
setGraphic(null);
setId(null);
}
@ -363,8 +365,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
if (item != null && !empty) {
setGraphic(item.getProgressIndicator());
}
else {
} else {
setGraphic(null);
}
}

View file

@ -117,9 +117,9 @@ class CreateOfferDataModel extends ActivatableDataModel {
offerId = UUID.randomUUID().toString();
addressEntry = walletService.getAddressEntryByOfferId(offerId);
offerFeeAsCoin = FeePolicy.CREATE_OFFER_FEE;
networkFeeAsCoin = FeePolicy.TX_FEE;
securityDepositAsCoin = FeePolicy.SECURITY_DEPOSIT;
offerFeeAsCoin = FeePolicy.getCreateOfferFee();
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
@Override

View file

@ -436,7 +436,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
() -> {
new Popup().headLine(BSResources.get("createOffer.success.headline"))
.message(BSResources.get("createOffer.success.info"))
.actionButtonText("Go to \"Open offers\"")
.actionButtonText("Go to \"My offers\"")
.onAction(() -> {
close();
FxTimer.runLater(Duration.ofMillis(100),

View file

@ -20,7 +20,10 @@ package io.bitsquare.gui.main.offer.takeoffer;
import com.google.inject.Inject;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.*;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.gui.common.model.ActivatableDataModel;
@ -94,9 +97,9 @@ class TakeOfferDataModel extends ActivatableDataModel {
this.walletPasswordPopup = walletPasswordPopup;
this.preferences = preferences;
offerFeeAsCoin = FeePolicy.CREATE_OFFER_FEE;
networkFeeAsCoin = FeePolicy.TX_FEE;
securityDepositAsCoin = FeePolicy.SECURITY_DEPOSIT;
offerFeeAsCoin = FeePolicy.getCreateOfferFee();
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
securityDepositAsCoin = FeePolicy.getSecurityDeposit();
}
@Override
@ -127,7 +130,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
addressEntry = walletService.getAddressEntryByOfferId(offer.getId());
checkNotNull(addressEntry, "addressEntry must not be null");
ObservableList<PaymentAccount> possiblePaymentAccounts = getPossiblePaymentAccounts();
checkArgument(!possiblePaymentAccounts.isEmpty(), "possiblePaymentAccounts.isEmpty()");
paymentAccount = possiblePaymentAccounts.get(0);
@ -135,7 +138,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
amountAsCoin.set(offer.getAmount());
if (BitsquareApp.DEV_MODE)
amountAsCoin.set(Restrictions.MIN_TRADE_AMOUNT);
amountAsCoin.set(offer.getAmount());
calculateVolume();
calculateTotalToPay();
@ -274,7 +277,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
boolean isMinAmountLessOrEqualAmount() {
//noinspection SimplifiableIfStatement
if (offer != null && offer.getMinAmount() != null && amountAsCoin.get() != null)
if (offer != null && offer.getMinAmount() != null && amountAsCoin.get() != null)
return !offer.getMinAmount().isGreaterThan(amountAsCoin.get());
return true;
}

View file

@ -196,7 +196,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
model.isOfferAvailable,
(a, b, c, d) -> a == null && b == null && !c && !d)
.subscribe((observable, oldValue, newValue) -> {
if (newValue) {
if (!oldValue && newValue) {
isOfferAvailablePopup = new Popup().information(BSResources.get("takeOffer.fundsBox.isOfferAvailable"))
.show()
.onClose(() -> {

View file

@ -17,14 +17,15 @@
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.AnchorPane?>
<TabPane fx:id="root" fx:controller="io.bitsquare.gui.main.portfolio.PortfolioView"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" tabClosingPolicy="UNAVAILABLE"
xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="openOffersTab" text="Open offers"/>
<Tab fx:id="openOffersTab" text="My offers"/>
<Tab fx:id="pendingTradesTab" text="Open trades"/>
<Tab fx:id="closedTradesTab" text="History"/>

View file

@ -295,7 +295,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
}
Coin getTotalFees() {
return FeePolicy.TX_FEE.add(isOfferer() ? FeePolicy.CREATE_OFFER_FEE : FeePolicy.TAKE_OFFER_FEE);
return FeePolicy.getFixedTxFeeForTrades().add(isOfferer() ? FeePolicy.getCreateOfferFee() : FeePolicy.getTakeOfferFee());
}
PendingTradesListItem getSelectedItem() {

View file

@ -324,7 +324,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
}
public String getSecurityDeposit() {
return formatter.formatCoinWithCode(FeePolicy.SECURITY_DEPOSIT);
return formatter.formatCoinWithCode(FeePolicy.getSecurityDeposit());
}
public boolean isBlockChainMethod() {

View file

@ -116,7 +116,7 @@ public class CompletedView extends TradeStepDetailsView {
});
if (BitsquareApp.DEV_MODE)
withdrawAddressTextField.setText("mwajQdfYnve1knXnmv7JdeiVpeogTsck6S");
withdrawAddressTextField.setText("mxAkWWaQBqwqcYstKzqLku3kzR6pbu2zHq");
}
public void setBtcTradeAmountLabelText(String text) {

View file

@ -19,10 +19,12 @@ package io.bitsquare.gui.main.settings.preferences;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.user.BlockChainExplorer;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.GridPane;
@ -44,6 +46,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private CheckBox useAnimationsCheckBox, useEffectsCheckBox, showPlaceOfferConfirmationCheckBox, showTakeOfferConfirmationCheckBox,
autoSelectArbitratorsCheckBox;
private int gridRow = 0;
private InputTextField transactionFeeInputTextField;
private ChangeListener<Boolean> transactionFeeFocusedListener;
@Inject
public PreferencesView(PreferencesViewModel model) {
@ -52,12 +56,16 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 3, "Preferences");
addTitledGroupBg(root, gridRow, 4, "Preferences");
tradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second;
languageComboBox = addLabelComboBox(root, ++gridRow, "Language:").second;
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
blockExplorerComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin block explorer:").second;
transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutTransactionFeeTextField(oldValue, newValue, transactionFeeInputTextField.getText());
};
addTitledGroupBg(root, ++gridRow, 5, "Display options", Layout.GROUP_DISTANCE);
useAnimationsCheckBox = addLabelCheckBox(root, gridRow, "Use animations:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
useEffectsCheckBox = addLabelCheckBox(root, ++gridRow, "Use effects:", "").second;
@ -119,6 +127,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
});
blockExplorerComboBox.setOnAction(e -> model.onSelectBlockExplorer(blockExplorerComboBox.getSelectionModel().getSelectedItem()));
transactionFeeInputTextField.textProperty().bindBidirectional(model.transactionFeePerByte);
transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
useAnimationsCheckBox.setSelected(model.getUseAnimations());
useAnimationsCheckBox.setOnAction(e -> model.onSelectUseAnimations(useAnimationsCheckBox.isSelected()));
@ -142,6 +152,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
languageComboBox.setOnAction(null);
tradeCurrencyComboBox.setOnAction(null);
blockExplorerComboBox.setOnAction(null);
transactionFeeInputTextField.textProperty().unbind();
transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
useAnimationsCheckBox.setOnAction(null);
useEffectsCheckBox.setOnAction(null);
showPlaceOfferConfirmationCheckBox.setOnAction(null);

View file

@ -18,15 +18,20 @@
package io.bitsquare.gui.main.settings.preferences;
import com.google.inject.Inject;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.model.ActivatableViewModel;
import io.bitsquare.gui.popups.Popup;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.user.BlockChainExplorer;
import io.bitsquare.user.Preferences;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
class PreferencesViewModel extends ActivatableViewModel {
@ -35,7 +40,8 @@ class PreferencesViewModel extends ActivatableViewModel {
final ObservableList<BlockChainExplorer> blockExplorers;
final ObservableList<TradeCurrency> tradeCurrencies;
final ObservableList<String> languageCodes;
final StringProperty transactionFeePerByte = new SimpleStringProperty();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialisation
@ -52,13 +58,13 @@ class PreferencesViewModel extends ActivatableViewModel {
@Override
protected void activate() {
transactionFeePerByte.set(String.valueOf(preferences.getTxFeePerKB() / 1000));
}
@Override
protected void deactivate() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
@ -99,6 +105,19 @@ class PreferencesViewModel extends ActivatableViewModel {
preferences.setPreferredLocale(new Locale(code, preferences.getPreferredLocale().getCountry()));
}
public void onFocusOutTransactionFeeTextField(Boolean oldValue, Boolean newValue, String text) {
if (oldValue && !newValue) {
try {
preferences.setTxFeePerKB(Long.valueOf(transactionFeePerByte.get()) * 1000);
} catch (Exception e) {
new Popup().warning(e.getMessage())
.onClose(() -> UserThread.runAfter(
() -> transactionFeePerByte.set(String.valueOf(preferences.getTxFeePerKB() / 1000)),
100, TimeUnit.MILLISECONDS))
.show();
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
@ -139,4 +158,5 @@ class PreferencesViewModel extends ActivatableViewModel {
public TradeCurrency getTradeCurrency() {
return preferences.getPreferredTradeCurrency();
}
}

View file

@ -99,7 +99,7 @@ public class EmptyWalletPopup extends Popup {
Tuple2<Label, InputTextField> tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:");
addressInputTextField = tuple.second;
emptyWalletButton = new Button("Empty wallet");
boolean isBalanceSufficient = Restrictions.isMinSpendableAmount(totalBalance);
boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance);
emptyWalletButton.setDefaultButton(isBalanceSufficient);
closeButton.setDefaultButton(!isBalanceSufficient);
emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0);

View file

@ -76,7 +76,7 @@ createOffer.advancedBox.county=Payments account country:
createOffer.advancedBox.info=Your trading partners must fulfill your offer restrictions. You can edit the accepted countries, languages and arbitrators in the settings. The payments account details are used from your current selected payments account (if you have multiple payments accounts).
createOffer.success.headline=Your offer has been published to the P2P network.
createOffer.success.info=You can manage your open offers in the \"Portfolio\" screen under \"Open offers\".
createOffer.success.info=You can manage your open offers in the \"Portfolio\" screen under \"My offers\".
createOffer.error.message=An error occurred when placing the offer.\n\n{0}