Add fee check to take offer, display spinner and txt, cleanup eventhandlers, hide support tickets tab for arbitr. if not arbitr.

This commit is contained in:
Manfred Karrer 2016-02-10 22:15:34 +01:00
parent eefafab977
commit 2d7aecd2ed
41 changed files with 583 additions and 286 deletions

View file

@ -44,6 +44,8 @@ import java.util.concurrent.*;
public class Utilities {
private static final Logger log = LoggerFactory.getLogger(Utilities.class);
private static long lastTimeStamp = System.currentTimeMillis();
public static final String LB = System.getProperty("line.separator");
public static final String LB2 = LB + LB;
public static String objectToJson(Object object) {
Gson gson = new GsonBuilder()

View file

@ -284,27 +284,24 @@ public class WalletService {
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) {
public void addAddressConfidenceListener(AddressConfidenceListener listener) {
addressConfidenceListeners.add(listener);
return listener;
}
public void removeAddressConfidenceListener(AddressConfidenceListener listener) {
addressConfidenceListeners.remove(listener);
}
public TxConfidenceListener addTxConfidenceListener(TxConfidenceListener listener) {
public void addTxConfidenceListener(TxConfidenceListener listener) {
txConfidenceListeners.add(listener);
return listener;
}
public void removeTxConfidenceListener(TxConfidenceListener listener) {
txConfidenceListeners.remove(listener);
}
public BalanceListener addBalanceListener(BalanceListener listener) {
public void addBalanceListener(BalanceListener listener) {
balanceListeners.add(listener);
return listener;
}
public void removeBalanceListener(BalanceListener listener) {
@ -667,12 +664,12 @@ public class WalletService {
private class BitsquareWalletEventListener extends AbstractWalletEventListener {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners();
notifyBalanceListeners(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners();
notifyBalanceListeners(tx);
}
@Override
@ -691,7 +688,7 @@ public class WalletService {
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
}
private void notifyBalanceListeners() {
private void notifyBalanceListeners(Transaction tx) {
for (BalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getAddress() != null)
@ -699,7 +696,7 @@ public class WalletService {
else
balance = getAvailableBalance();
balanceListener.onBalanceChanged(balance);
balanceListener.onBalanceChanged(balance, tx);
}
}
}

View file

@ -19,6 +19,7 @@ package io.bitsquare.btc.listeners;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
public class BalanceListener {
private Address address;
@ -35,6 +36,6 @@ public class BalanceListener {
}
@SuppressWarnings("UnusedParameters")
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
}
}

View file

@ -26,6 +26,7 @@ import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
@ -51,12 +52,12 @@ public class SetupDepositBalanceListener extends TradeTask {
WalletService walletService = processModel.getWalletService();
Address address = walletService.getAddressEntryByOfferId(trade.getId()).getAddress();
balanceListener = walletService.addBalanceListener(new BalanceListener(address) {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
});
};
walletService.addBalanceListener(balanceListener);
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {

View file

@ -153,7 +153,7 @@ public class BitsquareApp extends Application {
mainView.setPersistedFilesCorrupted(corruptedDatabaseFiles);
});*/
scene = new Scene(mainView.getRoot(), 1100, 740);
scene = new Scene(mainView.getRoot(), 1060, 740);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");

View file

@ -36,7 +36,6 @@ bg color of non edit textFields: fafafa
-bs-red-soft: derive(-bs-error-red, 60%);
-bs-orange: #dd8f05;
-bs-blue-transparent: #0f87c344;
-bs-green-transparent: #00aa3344;
@ -678,10 +677,6 @@ textfield */
-fx-background-color: -bs-content-bg-grey;
}
/* TitledGroupBg */
#titled-group-bg-label {
-fx-font-weight: bold;
@ -876,3 +871,22 @@ textfield */
-fx-font-size: 16;
-fx-alignment: center;
}
/********************************************************************************************************************
*
* Popups
*
********************************************************************************************************************/
#popup-headline {
-fx-font-size: 18;
-fx-text-fill: #333;
}
#popup-message {
-fx-font-size: 15;
}
#popup-button {
-fx-font-size: 15;
}

View file

@ -28,6 +28,7 @@ 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 {
@ -64,7 +65,7 @@ public class BalanceTextField extends AnchorPane {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
};
@ -72,7 +73,7 @@ public class BalanceTextField extends AnchorPane {
updateBalance(walletService.getBalanceForAddress(address));
}
public void disarm() {
public void cleanup() {
walletService.removeBalanceListener(balanceListener);
}

View file

@ -31,11 +31,14 @@ 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;
import org.bitcoinj.core.TransactionConfidence;
public class BalanceWithConfirmationTextField extends AnchorPane {
private static WalletService walletService;
private BalanceListener balanceListener;
private AddressConfidenceListener confidenceListener;
public static void setWalletService(WalletService walletService) {
BalanceWithConfirmationTextField.walletService = walletService;
@ -77,22 +80,29 @@ public class BalanceWithConfirmationTextField extends AnchorPane {
getChildren().addAll(textField, progressIndicator);
}
public void cleanup() {
walletService.removeBalanceListener(balanceListener);
walletService.removeAddressConfidenceListener(confidenceListener);
}
public void setup(Address address, BSFormatter formatter) {
this.formatter = formatter;
walletService.addAddressConfidenceListener(new AddressConfidenceListener(address) {
confidenceListener = new AddressConfidenceListener(address) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
});
};
walletService.addAddressConfidenceListener(confidenceListener);
updateConfidence(walletService.getConfidenceForAddress(address));
walletService.addBalanceListener(new BalanceListener(address) {
balanceListener = new BalanceListener(address) {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
});
};
walletService.addBalanceListener(balanceListener);
updateBalance(walletService.getBalanceForAddress(address));
}

View file

@ -408,7 +408,7 @@ public class MainViewModel implements ViewModel {
walletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
}
});

View file

@ -42,7 +42,6 @@ public class AccountView extends ActivatableView<TabPane, AccountViewModel> {
@FXML
Tab accountSettingsTab;
private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener;

View file

@ -19,18 +19,10 @@ package io.bitsquare.gui.main.account;
import com.google.inject.Inject;
import io.bitsquare.gui.common.model.ViewModel;
import io.bitsquare.user.User;
class AccountViewModel implements ViewModel {
private final User user;
@Inject
public AccountViewModel(User user) {
this.user = user;
}
boolean getNeedRegistration() {
return user.getAccountId() == null;
public AccountViewModel() {
}
}

View file

@ -58,6 +58,7 @@ public class ArbitratorRegistrationView extends ActivatableViewAndModel<VBox, Ar
private ChangeListener<Arbitrator> arbitratorChangeListener;
private EnterPrivKeyPopup enterPrivKeyPopup;
private ListChangeListener<String> listChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -84,6 +85,8 @@ public class ArbitratorRegistrationView extends ActivatableViewAndModel<VBox, Ar
@Override
protected void deactivate() {
model.myArbitratorProperty.removeListener(arbitratorChangeListener);
languagesListView.getItems().removeListener(listChangeListener);
}
public void onTabSelection(boolean isSelectedTab) {
@ -106,8 +109,8 @@ public class ArbitratorRegistrationView extends ActivatableViewAndModel<VBox, Ar
private void updateLanguageList() {
languagesListView.setItems(model.languageCodes);
languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
languagesListView.getItems().addListener((ListChangeListener<String>)
c -> languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2));
listChangeListener = c -> languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
languagesListView.getItems().addListener(listChangeListener);
}
private void buildUI() {

View file

@ -26,7 +26,9 @@ import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.LanguageUtil;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
@ -52,6 +54,9 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
private TableView<ArbitratorListItem> table;
private int gridRow = 0;
private CheckBox autoSelectAllMatchingCheckBox;
private ListChangeListener<String> listChangeListener;
private ListChangeListener<String> languageCodesListChangeListener;
private ChangeListener<Boolean> isSelectedChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -68,13 +73,12 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
public void initialize() {
addLanguageGroup();
addArbitratorsGroup();
listChangeListener = c -> languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
}
@Override
protected void activate() {
languagesListView.getItems().addListener((ListChangeListener<String>) c -> {
languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
});
languagesListView.getItems().addListener(listChangeListener);
languageComboBox.setItems(model.allLanguageCodes);
languagesListView.setItems(model.languageCodes);
languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
@ -85,6 +89,8 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
@Override
protected void deactivate() {
languagesListView.getItems().removeListener(listChangeListener);
model.languageCodes.removeListener(languageCodesListChangeListener);
}
@ -222,6 +228,7 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
return new TableCell<ArbitratorListItem, ArbitratorListItem>() {
private final CheckBox checkBox = new CheckBox();
private TableRow tableRow;
private BooleanProperty selectedProperty;
private void updateDisableState(final ArbitratorListItem item) {
boolean selected = model.isAcceptedArbitrator(item.arbitrator);
@ -261,8 +268,12 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
super.updateItem(item, empty);
if (item != null && !empty) {
model.languageCodes.addListener((ListChangeListener<String>) c -> updateDisableState(item));
item.isSelectedProperty().addListener((observable, oldValue, newValue) -> checkBox.setSelected(newValue));
selectedProperty = item.isSelectedProperty();
languageCodesListChangeListener = c -> updateDisableState(item);
model.languageCodes.addListener(languageCodesListChangeListener);
isSelectedChangeListener = (observable, oldValue, newValue) -> checkBox.setSelected(newValue);
selectedProperty.addListener(isSelectedChangeListener);
checkBox.setSelected(model.isAcceptedArbitrator(item.arbitrator));
checkBox.setOnAction(e -> {
@ -281,6 +292,10 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, A
updateDisableState(item);
setGraphic(checkBox);
} else {
model.languageCodes.removeListener(languageCodesListChangeListener);
if (selectedProperty != null)
selectedProperty.removeListener(isSelectedChangeListener);
setGraphic(null);
if (checkBox != null)

View file

@ -45,7 +45,6 @@ class ChangePasswordViewModel implements ViewModel {
repeatedPasswordField.addListener((ov) -> saveButtonDisabled.set(!validate()));
}
boolean requestSavePassword() {
if (validate()) {
savePassword(passwordField.get());

View file

@ -30,6 +30,7 @@ import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.gui.util.validation.PasswordValidator;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
@ -54,6 +55,8 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
private TitledGroupBg headline;
private int gridRow = 0;
private Label repeatedPasswordLabel;
private ChangeListener<String> passwordFieldChangeListener;
private ChangeListener<String> repeatedPasswordFieldChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -72,17 +75,13 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
headline = addTitledGroupBg(root, gridRow, 3, "");
passwordField = addLabelPasswordTextField(root, gridRow, "Enter password:", Layout.FIRST_ROW_DISTANCE).second;
passwordField.setValidator(passwordValidator);
passwordField.textProperty().addListener((observable, oldValue, newValue) -> {
validatePasswords();
});
passwordFieldChangeListener = (observable, oldValue, newValue) -> validatePasswords();
Tuple2<Label, PasswordTextField> tuple2 = addLabelPasswordTextField(root, ++gridRow, "Repeat password:");
repeatedPasswordLabel = tuple2.first;
repeatedPasswordField = tuple2.second;
repeatedPasswordField.setValidator(passwordValidator);
repeatedPasswordField.textProperty().addListener((observable, oldValue, newValue) -> {
validatePasswords();
});
repeatedPasswordFieldChangeListener = (observable, oldValue, newValue) -> validatePasswords();
Tuple3<Button, ProgressIndicator, Label> tuple = addButtonWithStatus(root, ++gridRow, "", 0);
pwButton = tuple.first;
@ -105,7 +104,6 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
else
keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt();
ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, passwordField.getText(), aesKey -> {
deriveStatusLabel.setText("");
progressIndicator.setVisible(false);
@ -169,10 +167,16 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
@Override
protected void activate() {
passwordField.textProperty().addListener(passwordFieldChangeListener);
repeatedPasswordField.textProperty().addListener(repeatedPasswordFieldChangeListener);
}
@Override
protected void deactivate() {
passwordField.textProperty().removeListener(passwordFieldChangeListener);
repeatedPasswordField.textProperty().removeListener(repeatedPasswordFieldChangeListener);
}
private void validatePasswords() {

View file

@ -64,6 +64,7 @@ public class PaymentAccountView extends ActivatableViewAndModel<GridPane, Paymen
private Button addAccountButton;
private Button saveNewAccountButton;
private int gridRow = 0;
private ListChangeListener<PaymentAccount> paymentAccountListChangeListener;
@Inject
public PaymentAccountView(PaymentAccountViewModel model,
@ -90,6 +91,7 @@ public class PaymentAccountView extends ActivatableViewAndModel<GridPane, Paymen
@Override
public void initialize() {
buildForm();
paymentAccountListChangeListener = c -> paymentAccountsComboBox.setDisable(model.getPaymentAccounts().size() == 0);
}
@Override
@ -102,13 +104,13 @@ public class PaymentAccountView extends ActivatableViewAndModel<GridPane, Paymen
paymentAccountsComboBox.setOnAction(paymentAccountsComboBoxHandler);
paymentAccountsComboBox.setVisibleRowCount(20);
model.getPaymentAccounts().addListener(
(ListChangeListener<PaymentAccount>) c -> paymentAccountsComboBox.setDisable(model.getPaymentAccounts().size() == 0));
model.getPaymentAccounts().addListener(paymentAccountListChangeListener);
paymentAccountsComboBox.setDisable(model.getPaymentAccounts().size() == 0);
}
@Override
protected void deactivate() {
model.getPaymentAccounts().removeListener(paymentAccountListChangeListener);
paymentAccountsComboBox.setOnAction(null);
}

View file

@ -29,6 +29,7 @@ import io.bitsquare.gui.popups.WalletPasswordPopup;
import io.bitsquare.gui.util.Layout;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextArea;
@ -61,6 +62,12 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
private int gridRow = 0;
private DeterministicSeed keyChainSeed;
private ChangeListener<Boolean> seedWordsValidChangeListener;
private SimpleBooleanProperty seedWordsValid;
private SimpleBooleanProperty dateValid;
private ChangeListener<String> seedWordsTextAreaChangeListener;
private ChangeListener<Boolean> datePickerChangeListener;
private ChangeListener<LocalDate> dateChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -105,6 +112,11 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
@Override
protected void deactivate() {
seedWordsValid.removeListener(seedWordsValidChangeListener);
seedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener);
dateValid.removeListener(datePickerChangeListener);
datePicker.valueProperty().removeListener(dateChangeListener);
seedWordsTextArea.setText("");
datePicker.setValue(null);
restoreButton.disableProperty().unbind();
@ -134,15 +146,18 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
restoreButton.setOnAction(e -> onRestore());
BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
BooleanProperty seedWordsValid = new SimpleBooleanProperty(true);
seedWordsValid.addListener((observable, oldValue, newValue) -> {
seedWordsValid = new SimpleBooleanProperty(true);
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
if (newValue) {
seedWordsTextArea.getStyleClass().remove("validation_error");
} else {
seedWordsTextArea.getStyleClass().add("validation_error");
}
});
seedWordsTextArea.textProperty().addListener((observable, oldValue, newValue) -> {
};
seedWordsValid.addListener(seedWordsValidChangeListener);
seedWordsTextAreaChangeListener = (observable, oldValue, newValue) -> {
seedWordsEdited.set(true);
try {
MnemonicCode codec = new MnemonicCode();
@ -154,18 +169,22 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
if (creationDate.equals(datePicker.getValue()))
datePicker.setValue(null);
});
};
seedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener);
BooleanProperty dateValid = new SimpleBooleanProperty(true);
dateValid.addListener((observable, oldValue, newValue) -> {
dateValid = new SimpleBooleanProperty(true);
datePickerChangeListener = (observable, oldValue, newValue) -> {
if (newValue)
datePicker.getStyleClass().remove("validation_error");
else
datePicker.getStyleClass().add("validation_error");
});
datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
};
dateValid.addListener(datePickerChangeListener);
dateChangeListener = (observable, oldValue, newValue) -> {
dateValid.set(newValue != null && !newValue.isAfter(LocalDate.now()));
});
};
datePicker.valueProperty().addListener(dateChangeListener);
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
seedWordsValid, dateValid, seedWordsEdited));

View file

@ -29,6 +29,7 @@ import io.bitsquare.gui.main.account.content.password.PasswordView;
import io.bitsquare.gui.main.account.content.paymentsaccount.PaymentAccountView;
import io.bitsquare.gui.main.account.content.seedwords.SeedWordsView;
import io.bitsquare.gui.util.Colors;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@ -95,11 +96,22 @@ public class AccountSettingsView extends ActivatableViewAndModel {
selecteedViewClass = viewPath.get(3);
loadView(selecteedViewClass);
}
paymentAccount.activate();
arbitratorSelection.activate();
password.activate();
seedWords.activate();
backup.activate();
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
paymentAccount.deactivate();
arbitratorSelection.deactivate();
password.deactivate();
seedWords.deactivate();
backup.deactivate();
}
private void loadView(Class<? extends View> viewClass) {
@ -133,7 +145,14 @@ public class AccountSettingsView extends ActivatableViewAndModel {
class MenuItem extends ToggleButton {
private final ChangeListener<Boolean> selectedPropertyChangeListener;
private final ChangeListener<Boolean> disablePropertyChangeListener;
private Navigation navigation;
private Class<? extends View> viewClass;
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
this.navigation = navigation;
this.viewClass = viewClass;
setToggleGroup(toggleGroup);
setText(title);
@ -151,10 +170,7 @@ class MenuItem extends ToggleButton {
icon.setMaxWidth(25);
setGraphic(icon);
setOnAction((event) ->
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, viewClass));
selectedProperty().addListener((ov, oldValue, newValue) -> {
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-selected");
icon.setTextFill(Colors.BLUE);
@ -162,9 +178,9 @@ class MenuItem extends ToggleButton {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
});
};
disableProperty().addListener((ov, oldValue, newValue) -> {
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
if (newValue) {
setId("account-settings-item-background-disabled");
icon.setTextFill(Paint.valueOf("#ccc"));
@ -172,7 +188,19 @@ class MenuItem extends ToggleButton {
setId("account-settings-item-background-active");
icon.setTextFill(Paint.valueOf("#333"));
}
});
};
}
public void activate() {
setOnAction((event) -> navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, viewClass));
selectedProperty().addListener(selectedPropertyChangeListener);
disableProperty().addListener(disablePropertyChangeListener);
}
public void deactivate() {
setOnAction(null);
selectedProperty().removeListener(selectedPropertyChangeListener);
disableProperty().removeListener(disablePropertyChangeListener);
}
}

View file

@ -26,7 +26,6 @@
AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="tradersDisputesTab" text="Traders support tickets" closable="false"></Tab>
<Tab fx:id="arbitratorsDisputesTab" text="Arbitrators support tickets" closable="false"/>
<Tab fx:id="tradersDisputesTab" text="Support tickets" closable="false"></Tab>
</TabPane>

View file

@ -40,7 +40,9 @@ import javax.inject.Inject;
public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable> {
@FXML
Tab tradersDisputesTab, arbitratorsDisputesTab;
Tab tradersDisputesTab;
private Tab arbitratorsDisputesTab;
private final Navigation navigation;
private final ArbitratorManager arbitratorManager;
@ -51,6 +53,7 @@ public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable>
private Tab currentTab;
private final ViewLoader viewLoader;
private MapChangeListener<NodeAddress, Arbitrator> arbitratorMapChangeListener;
private boolean isArbitrator;
@Inject
public DisputesView(CachingViewLoader viewLoader, Navigation navigation, ArbitratorManager arbitratorManager, KeyRing keyRing) {
@ -58,6 +61,8 @@ public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable>
this.navigation = navigation;
this.arbitratorManager = arbitratorManager;
this.keyRing = keyRing;
}
@Override
@ -79,12 +84,16 @@ public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable>
}
private void updateArbitratorsDisputesTabDisableState() {
boolean isArbitrator = arbitratorManager.getArbitratorsObservableMap().values().stream()
isArbitrator = arbitratorManager.getArbitratorsObservableMap().values().stream()
.filter(e -> e.getPubKeyRing() != null && e.getPubKeyRing().equals(keyRing.getPubKeyRing()))
.findAny().isPresent();
arbitratorsDisputesTab.setDisable(!isArbitrator);
if (arbitratorsDisputesTab.getContent() != null)
arbitratorsDisputesTab.getContent().setDisable(!isArbitrator);
if (arbitratorsDisputesTab == null && isArbitrator) {
arbitratorsDisputesTab = new Tab("Arbitrators support tickets");
arbitratorsDisputesTab.setClosable(false);
root.getTabs().add(arbitratorsDisputesTab);
tradersDisputesTab.setText("Traders support tickets");
}
}
@Override
@ -96,10 +105,10 @@ public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable>
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
navigation.addListener(navigationListener);
if (root.getSelectionModel().getSelectedItem() == tradersDisputesTab)
navigation.navigateTo(MainView.class, DisputesView.class, TraderDisputeView.class);
else if (root.getSelectionModel().getSelectedItem() == arbitratorsDisputesTab)
if (arbitratorsDisputesTab != null && root.getSelectionModel().getSelectedItem() == arbitratorsDisputesTab)
navigation.navigateTo(MainView.class, DisputesView.class, ArbitratorDisputeView.class);
else
navigation.navigateTo(MainView.class, DisputesView.class, TraderDisputeView.class);
}
@Override
@ -117,9 +126,9 @@ public class DisputesView extends ActivatableViewAndModel<TabPane, Activatable>
View view = viewLoader.load(viewClass);
if (view instanceof ArbitratorDisputeView)
if (arbitratorsDisputesTab != null && view instanceof ArbitratorDisputeView)
currentTab = arbitratorsDisputesTab;
else if (view instanceof TraderDisputeView)
else
currentTab = tradersDisputesTab;
currentTab.setContent(view.getRoot());

View file

@ -38,6 +38,7 @@ import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
@ -56,6 +57,7 @@ import javafx.util.Callback;
import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.File;
import java.io.FileOutputStream;
@ -92,6 +94,13 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
private VBox messagesInputBox;
private ProgressIndicator sendMsgProgressIndicator;
private Label sendMsgInfoLabel;
private ChangeListener<Boolean> arrivedPropertyListener;
private ChangeListener<Boolean> storedInMailboxPropertyListener;
@Nullable
private DisputeDirectMessage disputeDirectMessage;
private ListChangeListener<DisputeDirectMessage> disputeDirectMessageListListener;
private ChangeListener<String> inputTextAreaListener;
private ChangeListener<Boolean> selectedDisputeClosedPropertyListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -159,6 +168,23 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override
protected void deactivate() {
disputesTable.getSelectionModel().selectedItemProperty().removeListener(disputeChangeListener);
if (disputeDirectMessage != null) {
disputeDirectMessage.arrivedProperty().removeListener(arrivedPropertyListener);
disputeDirectMessage.storedInMailboxProperty().removeListener(storedInMailboxPropertyListener);
}
if (selectedDispute != null) {
selectedDispute.isClosedProperty().removeListener(selectedDisputeClosedPropertyListener);
ObservableList<DisputeDirectMessage> disputeDirectMessages = selectedDispute.getDisputeDirectMessagesAsObservableList();
if (disputeDirectMessages != null) {
disputeDirectMessages.removeListener(disputeDirectMessageListListener);
}
}
if (inputTextArea != null)
inputTextArea.textProperty().removeListener(inputTextAreaListener);
}
protected void setFilteredListPredicate(FilteredList<Dispute> filteredList) {
@ -175,7 +201,12 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
}
private void onSendMessage(String inputText, Dispute dispute) {
DisputeDirectMessage disputeDirectMessage = disputeManager.sendDisputeDirectMessage(dispute, inputText, new ArrayList<>(tempAttachments));
if (disputeDirectMessage != null) {
disputeDirectMessage.arrivedProperty().removeListener(arrivedPropertyListener);
disputeDirectMessage.storedInMailboxProperty().removeListener(storedInMailboxPropertyListener);
}
disputeDirectMessage = disputeManager.sendDisputeDirectMessage(dispute, inputText, new ArrayList<>(tempAttachments));
tempAttachments.clear();
scrollToBottom();
@ -192,19 +223,21 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
sendMsgProgressIndicator.setManaged(true);
});
disputeDirectMessage.arrivedProperty().addListener((observable, oldValue, newValue) -> {
arrivedPropertyListener = (observable, oldValue, newValue) -> {
if (newValue) {
hideSendMsgInfo(timer);
}
});
disputeDirectMessage.storedInMailboxProperty().addListener((observable, oldValue, newValue) -> {
};
disputeDirectMessage.arrivedProperty().addListener(arrivedPropertyListener);
storedInMailboxPropertyListener = (observable, oldValue, newValue) -> {
if (newValue) {
sendMsgInfoLabel.setVisible(true);
sendMsgInfoLabel.setManaged(true);
sendMsgInfoLabel.setText("Receiver is not online. Message is saved to his mailbox.");
hideSendMsgInfo(timer);
}
});
};
disputeDirectMessage.storedInMailboxProperty().addListener(storedInMailboxPropertyListener);
}
private void hideSendMsgInfo(Timer timer) {
@ -221,7 +254,8 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
}
private void onCloseDispute(Dispute dispute) {
disputeSummaryPopup.onFinalizeDispute(() -> messagesAnchorPane.getChildren().remove(messagesInputBox)).show(dispute);
disputeSummaryPopup.onFinalizeDispute(() -> messagesAnchorPane.getChildren().remove(messagesInputBox))
.show(dispute);
}
private void onRequestUpload() {
@ -275,6 +309,14 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
}
private void onSelectDispute(Dispute dispute) {
if (selectedDispute != null) {
selectedDispute.isClosedProperty().removeListener(selectedDisputeClosedPropertyListener);
ObservableList<DisputeDirectMessage> disputeDirectMessages = selectedDispute.getDisputeDirectMessagesAsObservableList();
if (disputeDirectMessages != null) {
disputeDirectMessages.removeListener(disputeDirectMessageListListener);
}
}
if (dispute == null) {
if (root.getChildren().size() > 1)
root.getChildren().remove(1);
@ -283,7 +325,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
} else if (selectedDispute != dispute) {
this.selectedDispute = dispute;
boolean isTrader = disputeManager.isTrader(dispute);
boolean isTrader = disputeManager.isTrader(selectedDispute);
TableGroupHeadline tableGroupHeadline = new TableGroupHeadline();
tableGroupHeadline.setText("Messages");
@ -293,10 +335,11 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
AnchorPane.setBottomAnchor(tableGroupHeadline, 0d);
AnchorPane.setLeftAnchor(tableGroupHeadline, 0d);
ObservableList<DisputeDirectMessage> list = dispute.getDisputeDirectMessagesAsObservableList();
SortedList<DisputeDirectMessage> sortedList = new SortedList<>(list);
ObservableList<DisputeDirectMessage> disputeDirectMessages = selectedDispute.getDisputeDirectMessagesAsObservableList();
SortedList<DisputeDirectMessage> sortedList = new SortedList<>(disputeDirectMessages);
sortedList.setComparator((o1, o2) -> o1.getDate().compareTo(o2.getDate()));
list.addListener((ListChangeListener<DisputeDirectMessage>) c -> scrollToBottom());
disputeDirectMessageListListener = c -> scrollToBottom();
disputeDirectMessages.addListener(disputeDirectMessageListListener);
messageListView = new ListView<>(sortedList);
messageListView.setId("message-list-view");
messageListView.prefWidthProperty().bind(root.widthProperty());
@ -315,11 +358,13 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
Button sendButton = new Button("Send");
sendButton.setDefaultButton(true);
sendButton.setOnAction(e -> onSendMessage(inputTextArea.getText(), dispute));
sendButton.setOnAction(e -> onSendMessage(inputTextArea.getText(), selectedDispute));
sendButton.setDisable(true);
inputTextArea.textProperty().addListener((observable, oldValue, newValue) -> {
sendButton.setDisable(newValue.length() == 0 && tempAttachments.size() == 0 && dispute.disputeResultProperty().get() == null);
});
inputTextAreaListener = (observable, oldValue, newValue) ->
sendButton.setDisable(newValue.length() == 0
&& tempAttachments.size() == 0 &&
selectedDispute.disputeResultProperty().get() == null);
inputTextArea.textProperty().addListener(inputTextAreaListener);
Button uploadButton = new Button("Add attachments");
uploadButton.setOnAction(e -> onRequestUpload());
@ -335,19 +380,20 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
sendMsgProgressIndicator.setVisible(false);
sendMsgProgressIndicator.setManaged(false);
dispute.isClosedProperty().addListener((observable, oldValue, newValue) -> {
selectedDisputeClosedPropertyListener = (observable, oldValue, newValue) -> {
messagesInputBox.setVisible(!newValue);
messagesInputBox.setManaged(!newValue);
AnchorPane.setBottomAnchor(messageListView, newValue ? 0d : 120d);
});
if (!dispute.isClosed()) {
};
selectedDispute.isClosedProperty().addListener(selectedDisputeClosedPropertyListener);
if (!selectedDispute.isClosed()) {
HBox buttonBox = new HBox();
buttonBox.setSpacing(10);
buttonBox.getChildren().addAll(sendButton, uploadButton, sendMsgProgressIndicator, sendMsgInfoLabel);
if (!isTrader) {
Button closeDisputeButton = new Button("Close ticket");
closeDisputeButton.setOnAction(e -> onCloseDispute(dispute));
closeDisputeButton.setOnAction(e -> onCloseDispute(selectedDispute));
closeDisputeButton.setDefaultButton(true);
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
@ -375,6 +421,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
@Override
public ListCell<DisputeDirectMessage> call(ListView<DisputeDirectMessage> list) {
return new ListCell<DisputeDirectMessage>() {
public ChangeListener<Number> sendMsgProgressIndicatorListener;
final Pane bg = new Pane();
final ImageView arrow = new ImageView();
final Label headerLabel = new Label();
@ -434,14 +481,15 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
else
arrow.setId("bubble_arrow_blue_right");
sendMsgProgressIndicator.progressProperty().addListener((observable, oldValue, newValue) -> {
sendMsgProgressIndicatorListener = (observable, oldValue, newValue) -> {
if ((double) oldValue == -1 && (double) newValue == 0) {
if (item.arrivedProperty().get())
showArrivedIcon();
else if (item.storedInMailboxProperty().get())
showMailboxIcon();
}
});
};
sendMsgProgressIndicator.progressProperty().addListener(sendMsgProgressIndicatorListener);
if (item.arrivedProperty().get())
showArrivedIcon();
@ -527,6 +575,9 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
// TODO There are still some cell rendering issues on updates
setGraphic(messageAnchorPane);
} else {
if (sendMsgProgressIndicator != null)
sendMsgProgressIndicator.progressProperty().removeListener(sendMsgProgressIndicatorListener);
messageAnchorPane.prefWidthProperty().unbind();
AnchorPane.clearConstraints(bg);
@ -726,18 +777,26 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
return new TableCell<Dispute, Dispute>() {
public ReadOnlyBooleanProperty closedProperty;
public ChangeListener<Boolean> listener;
@Override
public void updateItem(final Dispute item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
item.isClosedProperty().addListener((observable, oldValue, newValue) -> {
listener = (observable, oldValue, newValue) -> {
setText(newValue ? "Closed" : "Open");
getTableRow().setOpacity(newValue ? 0.4 : 1);
});
};
closedProperty = item.isClosedProperty();
closedProperty.addListener(listener);
boolean isClosed = item.isClosed();
setText(isClosed ? "Closed" : "Open");
getTableRow().setOpacity(isClosed ? 0.4 : 1);
} else {
if (closedProperty != null)
closedProperty.removeListener(listener);
setText("");
}
}

View file

@ -30,6 +30,7 @@ import javafx.beans.property.StringProperty;
import javafx.scene.control.Label;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,13 +59,13 @@ public class ReservedListItem {
// balance
balanceLabel = new Label();
balanceListener = walletService.addBalanceListener(new BalanceListener(getAddress()) {
balanceListener = new BalanceListener(getAddress()) {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
});
};
walletService.addBalanceListener(balanceListener);
updateBalance(walletService.getBalanceForAddress(getAddress()));
}

View file

@ -43,6 +43,7 @@ import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import javax.inject.Inject;
import java.util.Optional;
@ -95,7 +96,7 @@ public class ReservedView extends ActivatableView<VBox, Void> {
table.getSortOrder().add(dateColumn);
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateList();
}
};

View file

@ -154,13 +154,13 @@ public class TransactionsListItem {
Tooltip.install(progressIndicator, tooltip);
if (address != null) {
txConfidenceListener = walletService.addTxConfidenceListener(new TxConfidenceListener(txId) {
txConfidenceListener = new TxConfidenceListener(txId) {
@Override
public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
updateConfidence(confidence);
}
});
};
walletService.addTxConfidenceListener(txConfidenceListener);
updateConfidence(transaction.getConfidence());
}
}

View file

@ -24,6 +24,7 @@ import io.bitsquare.gui.util.BSFormatter;
import javafx.scene.control.Label;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
public class WithdrawalListItem {
private final BalanceListener balanceListener;
@ -42,12 +43,13 @@ public class WithdrawalListItem {
// balance
balanceLabel = new Label();
balanceListener = walletService.addBalanceListener(new BalanceListener(getAddress()) {
balanceListener = new BalanceListener(getAddress()) {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
}
});
};
walletService.addBalanceListener(balanceListener);
updateBalance(walletService.getBalanceForAddress(getAddress()));
}

View file

@ -133,7 +133,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
balanceListener = new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateList();
}
};

View file

@ -32,6 +32,7 @@ import io.bitsquare.gui.popups.Popup;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.trade.offer.Offer;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
@ -46,7 +47,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
private TakeOfferView takeOfferView;
private AnchorPane createOfferPane;
private AnchorPane takeOfferPane;
private Navigation.Listener listener;
private Navigation.Listener navigationListener;
private Offer offer;
private final ViewLoader viewLoader;
@ -56,6 +57,8 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
private Tab takeOfferTab, createOfferTab, offerBookTab;
private TradeCurrency tradeCurrency;
private boolean createOfferViewOpen, takeOfferViewOpen;
private ChangeListener<Tab> tabChangeListener;
private ListChangeListener<Tab> tabListChangeListener;
protected OfferView(ViewLoader viewLoader, Navigation navigation, MarketPriceFeed marketPriceFeed) {
this.viewLoader = viewLoader;
@ -66,20 +69,11 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
@Override
protected void initialize() {
listener = viewPath -> {
navigationListener = viewPath -> {
if (viewPath.size() == 3 && viewPath.indexOf(this.getClass()) == 1)
loadView(viewPath.tip());
};
}
@Override
protected void activate() {
// We need to remove open validation error popups
// UserThread.execute needed as focus-out event is called after selectedIndexProperty changed
// TODO Find a way to do that in the InputTextField directly, but a tab change does not trigger any event...
TabPane tabPane = root;
tabPane.getSelectionModel().selectedItemProperty()
.addListener((observableValue, oldValue, newValue) -> {
tabChangeListener = (observableValue, oldValue, newValue) -> {
UserThread.execute(InputTextField::hideErrorMessageDisplay);
if (newValue != null) {
if (newValue.equals(createOfferTab) && createOfferView != null) {
@ -99,10 +93,8 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
offerBookView.onTabSelected(false);
}
}
});
// We want to get informed when a tab get closed
tabPane.getTabs().addListener((ListChangeListener<Tab>) change -> {
};
tabListChangeListener = (ListChangeListener<Tab>) change -> {
change.next();
List<? extends Tab> removedTabs = change.getRemoved();
if (removedTabs.size() == 1) {
@ -111,17 +103,23 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
else if (removedTabs.get(0).getContent().equals(takeOfferPane))
onTakeOfferViewRemoved();
}
});
};
}
@Override
protected void activate() {
tradeCurrency = CurrencyUtil.getDefaultTradeCurrency();
navigation.addListener(listener);
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
root.getTabs().addListener(tabListChangeListener);
navigation.addListener(navigationListener);
navigation.navigateTo(MainView.class, this.getClass(), OfferBookView.class);
}
@Override
protected void deactivate() {
navigation.removeListener(listener);
navigation.removeListener(navigationListener);
root.getSelectionModel().selectedItemProperty().removeListener(tabChangeListener);
root.getTabs().removeListener(tabListChangeListener);
}
private void loadView(Class<? extends View> viewClass) {

View file

@ -46,8 +46,8 @@ import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import org.jetbrains.annotations.NotNull;
@ -102,7 +102,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
final ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList();
private PaymentAccount paymentAccount;
private WalletEventListener walletEventListener;
private boolean isTabSelected;
@ -134,42 +133,36 @@ class CreateOfferDataModel extends ActivatableDataModel {
balanceListener = new BalanceListener(getAddressEntry().getAddress()) {
@Override
public void onBalanceChanged(@NotNull Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
Futures.addCallback(future, new FutureCallback<Coin>() {
public void onSuccess(Coin fee) {
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
}
public void onFailure(@NotNull Throwable throwable) {
UserThread.execute(() -> new Popup()
.warning("We did not get a response for the request of the mining fee used " +
"in the funding transaction.\n\n" +
"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()))
.closeButtonText("No. Let's cancel that payment.")
.onClose(() -> feeFromFundingTxProperty.set(Coin.ZERO))
.show());
}
});
} else {
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
}
}
};
paymentAccountsChangeListener = change -> paymentAccounts.setAll(user.getPaymentAccounts());
walletEventListener = new WalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
requestFeeFromBlockchain(tx.getHashAsString());
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
}
@Override
public void onReorganize(Wallet wallet) {
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
}
@Override
public void onWalletChanged(Wallet wallet) {
}
@Override
public void onScriptsChanged(Wallet wallet, List<Script> scripts, boolean isAddingScripts) {
}
@Override
public void onKeysAdded(List<ECKey> keys) {
}
};
}
@Override
@ -203,14 +196,12 @@ class CreateOfferDataModel extends ActivatableDataModel {
private void addListeners() {
walletService.addBalanceListener(balanceListener);
walletService.getWallet().addEventListener(walletEventListener);
user.getPaymentAccountsAsObservable().addListener(paymentAccountsChangeListener);
}
private void removeListeners() {
walletService.removeBalanceListener(balanceListener);
walletService.getWallet().removeEventListener(walletEventListener);
user.getPaymentAccountsAsObservable().removeListener(paymentAccountsChangeListener);
}
@ -281,9 +272,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
}
private void doPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
openOfferManager.onPlaceOffer(offer,
resultHandler
);
openOfferManager.onPlaceOffer(offer, resultHandler);
}
public void onPaymentAccountSelected(PaymentAccount paymentAccount) {
@ -349,25 +338,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private void requestFeeFromBlockchain(String transactionId) {
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(transactionId);
Futures.addCallback(future, new FutureCallback<Coin>() {
public void onSuccess(Coin fee) {
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
}
public void onFailure(@NotNull Throwable throwable) {
UserThread.execute(() -> new Popup()
.warning("We did not get a response for the request of the mining fee used in the funding transaction.")
.show());
}
});
} else {
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
}
}
void calculateVolume() {
if (priceAsFiat.get() != null &&
amountAsCoin.get() != null &&
@ -400,6 +370,9 @@ class CreateOfferDataModel extends ActivatableDataModel {
private void updateBalance(Coin balance) {
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
if (isWalletFunded.get())
walletService.removeBalanceListener(balanceListener);
}
public Coin getOfferFeeAsCoin() {

View file

@ -451,8 +451,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
if (!model.dataModel.isFeeFromFundingTxSufficient()) {
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.formatCoin(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
"The fee used in your funding transaction was only " + model.formatCoin(newValue) + ".\n\n" +
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
"The fee used in your funding transaction was only " +
model.formatter.formatCoinWithCode(newValue) + ".\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 " +
@ -680,8 +681,9 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
placeOfferButton.setVisible(false);
placeOfferButton.setOnAction(e -> onPlaceOffer());
placeOfferSpinner = placeOfferTuple.second;
placeOfferSpinner.setPrefSize(18, 18);
placeOfferSpinnerInfoLabel = placeOfferTuple.third;
placeOfferSpinnerInfoLabel.setText(BSResources.get("createOffer.fundsBox.placeOfferSpinnerInfo"));
placeOfferSpinnerInfoLabel.textProperty().bind(model.placeOfferSpinnerInfoText);
placeOfferSpinnerInfoLabel.setVisible(false);
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));

View file

@ -64,6 +64,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
final StringProperty errorMessage = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty();
final StringProperty tradeCurrencyCode = new SimpleStringProperty();
final StringProperty placeOfferSpinnerInfoText = new SimpleStringProperty();
final BooleanProperty isPlaceOfferButtonVisible = new SimpleBooleanProperty(false);
final BooleanProperty isPlaceOfferButtonDisabled = new SimpleBooleanProperty(true);
@ -231,15 +232,30 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
priceAsFiatListener = (ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue));
volumeAsFiatListener = (ov, oldValue, newValue) -> volume.set(formatter.formatFiat(newValue));
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
feeFromFundingTxListener = (ov, oldValue, newValue) -> updateButtonDisableState();
isWalletFundedListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
isPlaceOfferSpinnerVisible.set(true);
placeOfferSpinnerInfoText.set("Checking funding tx miner fee...");
};
feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
if (newValue.isPositive()) {
isPlaceOfferSpinnerVisible.set(false);
placeOfferSpinnerInfoText.set("");
}
};
requestPlaceOfferSuccessListener = (ov, oldValue, newValue) -> {
if (newValue) {
isPlaceOfferButtonVisible.set(!newValue);
isPlaceOfferSpinnerVisible.set(false);
placeOfferSpinnerInfoText.set("");
}
};
requestPlaceOfferErrorMessageListener = (ov, oldValue, newValue) -> {
if (newValue != null)
if (newValue != null) {
isPlaceOfferSpinnerVisible.set(false);
placeOfferSpinnerInfoText.set("");
}
};
}
@ -256,6 +272,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.minAmountAsCoin.addListener(minAmountAsCoinListener);
dataModel.priceAsFiat.addListener(priceAsFiatListener);
dataModel.volumeAsFiat.addListener(volumeAsFiatListener);
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
dataModel.isWalletFunded.addListener(isWalletFundedListener);
requestPlaceOfferSuccess.addListener(requestPlaceOfferSuccessListener);
@ -275,7 +292,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.priceAsFiat.removeListener(priceAsFiatListener);
dataModel.volumeAsFiat.removeListener(volumeAsFiatListener);
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
dataModel.isWalletFunded.removeListener(isWalletFundedListener);
requestPlaceOfferSuccess.removeListener(requestPlaceOfferSuccessListener);
errorMessage.removeListener(requestPlaceOfferErrorMessageListener);
@ -302,6 +319,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
errorMessage.set(null);
isPlaceOfferSpinnerVisible.set(true);
requestPlaceOfferSuccess.set(false);
placeOfferSpinnerInfoText.set(BSResources.get("createOffer.fundsBox.placeOfferSpinnerInfo"));
errorMessageListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
@ -315,7 +333,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
}
};
offer.errorMessageProperty().addListener(errorMessageListener);
dataModel.onPlaceOffer(offer, (transaction) -> requestPlaceOfferSuccess.set(true));
dataModel.onPlaceOffer(offer, transaction -> requestPlaceOfferSuccess.set(true));
}
void onShowPayFundsScreen() {

View file

@ -110,6 +110,7 @@ class OfferBookViewModel extends ActivatableViewModel {
@Override
protected void deactivate() {
btcCode.unbind();
offerBookListItems.removeListener(listChangeListener);
}
@ -168,7 +169,6 @@ class OfferBookViewModel extends ActivatableViewModel {
public ObservableList<TradeCurrency> getTradeCurrencies() {
ObservableList<TradeCurrency> list = preferences.getTradeCurrenciesAsObservable();
/* list.add(0, new AllTradeCurrenciesEntry());*/
return list;
}

View file

@ -17,18 +17,22 @@
package io.bitsquare.gui.main.offer.takeoffer;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.*;
import io.bitsquare.btc.blockchain.BlockchainService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.MarketPriceFeed;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.popups.Popup;
import io.bitsquare.gui.popups.WalletPasswordPopup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.payment.PaymentMethod;
@ -65,6 +69,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
private final WalletPasswordPopup walletPasswordPopup;
private final Preferences preferences;
private MarketPriceFeed marketPriceFeed;
private BlockchainService blockchainService;
private BSFormatter formatter;
private final Coin offerFeeAsCoin;
private final Coin networkFeeAsCoin;
@ -79,6 +85,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
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);
private BalanceListener balanceListener;
private PaymentAccount paymentAccount;
@ -93,7 +100,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
@Inject
TakeOfferDataModel(TradeManager tradeManager, TradeWalletService tradeWalletService,
WalletService walletService, User user, WalletPasswordPopup walletPasswordPopup,
Preferences preferences, MarketPriceFeed marketPriceFeed) {
Preferences preferences, MarketPriceFeed marketPriceFeed, BlockchainService blockchainService,
BSFormatter formatter) {
this.tradeManager = tradeManager;
this.tradeWalletService = tradeWalletService;
this.walletService = walletService;
@ -101,6 +109,8 @@ class TakeOfferDataModel extends ActivatableDataModel {
this.walletPasswordPopup = walletPasswordPopup;
this.preferences = preferences;
this.marketPriceFeed = marketPriceFeed;
this.blockchainService = blockchainService;
this.formatter = formatter;
offerFeeAsCoin = FeePolicy.getCreateOfferFee();
networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades();
@ -116,6 +126,12 @@ class TakeOfferDataModel extends ActivatableDataModel {
addListeners();
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
// 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 (isTabSelected)
marketPriceFeed.setCurrencyCode(offer.getCurrencyCode());
}
@ -153,8 +169,32 @@ class TakeOfferDataModel extends ActivatableDataModel {
balanceListener = new BalanceListener(addressEntry.getAddress()) {
@Override
public void onBalanceChanged(@NotNull Coin balance) {
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance(balance);
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) {
SettableFuture<Coin> future = blockchainService.requestFee(tx.getHashAsString());
Futures.addCallback(future, new FutureCallback<Coin>() {
public void onSuccess(Coin fee) {
UserThread.execute(() -> feeFromFundingTxProperty.set(fee));
}
public void onFailure(@NotNull Throwable throwable) {
UserThread.execute(() -> new Popup()
.warning("We did not get a response for the request of the mining fee used " +
"in the funding transaction.\n\n" +
"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()))
.closeButtonText("No. Let's cancel that payment.")
.onClose(() -> feeFromFundingTxProperty.set(Coin.ZERO))
.show());
}
});
} else {
feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
}
}
};
@ -233,6 +273,10 @@ class TakeOfferDataModel extends ActivatableDataModel {
return user.getAcceptedArbitrators().size() > 0;
}
boolean isFeeFromFundingTxSufficient() {
return feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Bindings, listeners
@ -279,6 +323,9 @@ class TakeOfferDataModel extends ActivatableDataModel {
private void updateBalance(@NotNull Coin balance) {
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
if (isWalletFunded.get())
walletService.removeBalanceListener(balanceListener);
}
boolean isMinAmountLessOrEqualAmount() {

View file

@ -32,6 +32,8 @@ import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.account.AccountView;
import io.bitsquare.gui.main.account.content.arbitratorselection.ArbitratorSelectionView;
import io.bitsquare.gui.main.account.settings.AccountSettingsView;
import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
import io.bitsquare.gui.main.offer.OfferView;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView;
@ -51,6 +53,7 @@ import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Window;
import javafx.util.StringConverter;
import org.bitcoinj.core.Coin;
import org.controlsfx.control.PopOver;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
@ -99,6 +102,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private Subscription showCheckAvailabilityPopupSubscription;
private SimpleBooleanProperty errorPopupDisplayed;
private Popup isOfferAvailablePopup;
private ChangeListener<Coin> feeFromFundingTxListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -233,6 +237,30 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
paymentAccountsComboBox.setItems(model.getPossiblePaymentAccounts());
paymentAccountsComboBox.getSelectionModel().select(0);
}
feeFromFundingTxListener = (observable, oldValue, newValue) -> {
log.debug("feeFromFundingTxListener " + newValue);
if (!model.dataModel.isFeeFromFundingTxSufficient()) {
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" +
"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 " +
"do a funding again with the correct fee.\n\n" +
"In the \"Funds/Open for withdrawal\" section you can withdraw those funds.")
.closeButtonText("Close")
.onClose(() -> {
close();
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
})
.show();
}
};
model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
}
@Override
@ -261,6 +289,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
showWarningInvalidBtcDecimalPlacesSubscription.unsubscribe();
showTransactionPublishedScreenSubscription.unsubscribe();
showCheckAvailabilityPopupSubscription.unsubscribe();
model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
}
@ -530,8 +560,9 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
takeOfferButton.setVisible(false);
takeOfferButton.setOnAction(e -> onTakeOffer());
takeOfferSpinner = takeOfferTuple.second;
takeOfferSpinner.setPrefSize(18, 18);
takeOfferSpinnerInfoLabel = takeOfferTuple.third;
takeOfferSpinnerInfoLabel.setText(BSResources.get("takeOffer.fundsBox.takeOfferSpinnerInfo"));
takeOfferSpinnerInfoLabel.textProperty().bind(model.takeOfferSpinnerInfoText);
takeOfferSpinnerInfoLabel.setVisible(false);
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));

View file

@ -68,6 +68,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
final StringProperty errorMessage = new SimpleStringProperty();
final StringProperty offerWarning = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty();
final StringProperty takeOfferSpinnerInfoText = new SimpleStringProperty();
final BooleanProperty isOfferAvailable = new SimpleBooleanProperty();
final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true);
@ -89,6 +90,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
private ChangeListener<Offer.State> offerStateListener;
private ChangeListener<String> offerErrorListener;
private ConnectionListener connectionListener;
private ChangeListener<Coin> feeFromFundingTxListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -177,9 +179,9 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
void onTakeOffer() {
takeOfferRequested = true;
applyOnTakeOfferResult(false);
showTransactionPublishedScreen.set(false);
isTakeOfferSpinnerVisible.set(true);
takeOfferSpinnerInfoText.set(BSResources.get("takeOffer.fundsBox.takeOfferSpinnerInfo"));
dataModel.onTakeOffer(trade -> {
this.trade = trade;
trade.stateProperty().addListener(tradeStateListener);
@ -328,26 +330,23 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|| trade.getState() == Trade.State.DEPOSIT_PUBLISHED_MSG_SENT
|| trade.getState() == Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED) {
if (trade.getDepositTx() != null)
applyOnTakeOfferResult(true);
showTransactionPublishedScreen.set(true);
else
log.error("trade.getDepositTx() == null. That must not happen");
}
if (errorMessage.get() != null)
takeOfferSpinnerInfoText.set("");
if (errorMessage.get() == null)
isTakeOfferSpinnerVisible.set(false);
}
private void applyOnTakeOfferResult(boolean success) {
isTakeOfferSpinnerVisible.set(false);
showTransactionPublishedScreen.set(success);
}
private void updateButtonDisableState() {
isTakeOfferButtonDisabled.set(!(isBtcInputValid(amount.get()).isValid
&& dataModel.isMinAmountLessOrEqualAmount()
&& !dataModel.isAmountLargerThanOfferAmount()
&& dataModel.isWalletFunded.get()
&& !takeOfferRequested)
&& !takeOfferRequested
&& dataModel.isFeeFromFundingTxSufficient())
);
}
@ -389,7 +388,11 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
updateButtonDisableState();
};
amountAsCoinListener = (ov, oldValue, newValue) -> amount.set(formatter.formatCoin(newValue));
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
isWalletFundedListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
isTakeOfferSpinnerVisible.set(true);
takeOfferSpinnerInfoText.set("Checking funding tx miner fee...");
};
tradeStateListener = (ov, oldValue, newValue) -> applyTradeState(newValue);
tradeErrorListener = (ov, oldValue, newValue) -> applyTradeErrorMessage(newValue);
offerStateListener = (ov, oldValue, newValue) -> applyOfferState(newValue);
@ -411,6 +414,13 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
public void onError(Throwable throwable) {
}
};
feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
if (newValue.isPositive()) {
isTakeOfferSpinnerVisible.set(false);
takeOfferSpinnerInfoText.set("");
}
};
}
private void addListeners() {
@ -423,6 +433,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
dataModel.isWalletFunded.addListener(isWalletFundedListener);
p2PService.getNetworkNode().addConnectionListener(connectionListener);
dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
}
private void removeListeners() {
@ -434,7 +445,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
dataModel.isWalletFunded.removeListener(isWalletFundedListener);
if (offer != null) {
offer.stateProperty().removeListener(offerStateListener);
offer.errorMessageProperty().addListener(offerErrorListener);
offer.errorMessageProperty().removeListener(offerErrorListener);
}
if (trade != null) {
@ -442,6 +453,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
trade.errorMessageProperty().removeListener(tradeErrorListener);
}
p2PService.getNetworkNode().removeConnectionListener(connectionListener);
dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
}

View file

@ -148,20 +148,6 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
updateSelectedItem();
}
private void updateSelectedItem() {
PendingTradesListItem selectedItem = model.getSelectedItem();
if (selectedItem != null) {
// Select and focus selectedItem from model
int index = table.getItems().indexOf(selectedItem);
UserThread.execute(() -> {
//TODO app wide focus
table.getSelectionModel().select(index);
//table.requestFocus();
//UserThread.execute(() -> table.getFocusModel().focus(index));
});
}
}
@Override
protected void deactivate() {
table.getSelectionModel().selectedItemProperty().removeListener(selectedItemChangeListener);
@ -183,6 +169,20 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
private void updateSelectedItem() {
PendingTradesListItem selectedItem = model.getSelectedItem();
if (selectedItem != null) {
// Select and focus selectedItem from model
int index = table.getItems().indexOf(selectedItem);
UserThread.execute(() -> {
//TODO app wide focus
table.getSelectionModel().select(index);
//table.requestFocus();
//UserThread.execute(() -> table.getFocusModel().focus(index));
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Subviews

View file

@ -105,13 +105,14 @@ public abstract class TradeStepView extends AnchorPane {
}
});
timer = FxTimer.runPeriodically(Duration.ofSeconds(1), this::updateTimeLeft);
tradePeriodStateSubscription = EasyBind.subscribe(trade.getTradePeriodStateProperty(), newValue -> {
if (newValue != null) {
updateTradePeriodState(newValue);
}
});
timer = FxTimer.runPeriodically(Duration.ofSeconds(1), this::updateTimeLeft);
}
public void doDeactivate() {
@ -130,11 +131,11 @@ public abstract class TradeStepView extends AnchorPane {
if (tradePeriodStateSubscription != null)
tradePeriodStateSubscription.unsubscribe();
if (notificationGroup != null)
notificationGroup.button.setOnAction(null);
if (timer != null)
timer.stop();
if (notificationGroup != null)
notificationGroup.button.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -32,6 +32,7 @@ import io.bitsquare.gui.util.Transitions;
import io.bitsquare.trade.Contract;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.*;
@ -66,6 +67,10 @@ public class DisputeSummaryPopup extends Popup {
private ToggleGroup feeToggleGroup;
private String role;
private TextArea summaryNotesTextArea;
private ObjectBinding<Tuple2<DisputeResult.FeePaymentPolicy, Toggle>> feePaymentPolicyChanged;
private ChangeListener<Tuple2<DisputeResult.FeePaymentPolicy, Toggle>> feePaymentPolicyListener;
private ChangeListener<Boolean> shareRadioButtonSelectedListener;
private ChangeListener<Toggle> feeToggleSelectionListener;
// keep a reference to not get GCed
@ -106,6 +111,18 @@ public class DisputeSummaryPopup extends Popup {
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void cleanup() {
if (feePaymentPolicyChanged != null)
feePaymentPolicyChanged.removeListener(feePaymentPolicyListener);
if (shareRadioButton != null)
shareRadioButton.selectedProperty().removeListener(shareRadioButtonSelectedListener);
if (feeToggleGroup != null)
feeToggleGroup.selectedToggleProperty().removeListener(feeToggleSelectionListener);
}
@Override
protected void createGridPane() {
super.createGridPane();
@ -164,13 +181,14 @@ public class DisputeSummaryPopup extends Popup {
applyTradeAmountRadioButtonStates();
} else {
applyPayoutAmounts(disputeResult.feePaymentPolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get());
ObjectBinding<Tuple2<DisputeResult.FeePaymentPolicy, Toggle>> changed = Bindings.createObjectBinding(
feePaymentPolicyChanged = Bindings.createObjectBinding(
() -> new Tuple2(disputeResult.feePaymentPolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get()),
disputeResult.feePaymentPolicyProperty(),
tradeAmountToggleGroup.selectedToggleProperty());
changed.addListener((observable, oldValue, newValue) -> {
feePaymentPolicyListener = (observable, oldValue, newValue) -> {
applyPayoutAmounts(newValue.first, newValue.second);
});
};
feePaymentPolicyChanged.addListener(feePaymentPolicyListener);
}
setFeeRadioButtonState();
@ -242,7 +260,7 @@ public class DisputeSummaryPopup extends Popup {
sellerIsWinnerRadioButton.setToggleGroup(tradeAmountToggleGroup);
shareRadioButton.setToggleGroup(tradeAmountToggleGroup);
shareRadioButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
shareRadioButtonSelectedListener = (observable, oldValue, newValue) -> {
if (newValue) {
loserPaysFeeRadioButton.setSelected(false);
@ -254,7 +272,8 @@ public class DisputeSummaryPopup extends Popup {
}
loserPaysFeeRadioButton.setDisable(newValue);
});
};
shareRadioButton.selectedProperty().addListener(shareRadioButtonSelectedListener);
}
private void addFeeControls() {
@ -277,16 +296,15 @@ public class DisputeSummaryPopup extends Popup {
splitFeeRadioButton.setToggleGroup(feeToggleGroup);
waiveFeeRadioButton.setToggleGroup(feeToggleGroup);
//setFeeRadioButtonState();
feeToggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
feeToggleSelectionListener = (observable, oldValue, newValue) -> {
if (newValue == loserPaysFeeRadioButton)
disputeResult.setFeePaymentPolicy(DisputeResult.FeePaymentPolicy.LOSER);
else if (newValue == splitFeeRadioButton)
disputeResult.setFeePaymentPolicy(DisputeResult.FeePaymentPolicy.SPLIT);
else if (newValue == waiveFeeRadioButton)
disputeResult.setFeePaymentPolicy(DisputeResult.FeePaymentPolicy.WAIVE);
});
};
feeToggleGroup.selectedToggleProperty().addListener(feeToggleSelectionListener);
if (dispute.isSupportTicket())
feeToggleGroup.selectToggle(waiveFeeRadioButton);

View file

@ -19,6 +19,7 @@ package io.bitsquare.gui.popups;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.gui.components.InputTextField;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
@ -31,6 +32,7 @@ public class EnterPrivKeyPopup extends Popup {
private Button unlockButton;
private InputTextField keyInputTextField;
private PrivKeyHandler privKeyHandler;
private ChangeListener<String> changeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -47,6 +49,8 @@ public class EnterPrivKeyPopup extends Popup {
///////////////////////////////////////////////////////////////////////////////////////////
public EnterPrivKeyPopup() {
if (keyInputTextField != null)
keyInputTextField.textProperty().addListener(changeListener);
}
public void show() {
@ -80,6 +84,10 @@ public class EnterPrivKeyPopup extends Popup {
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void cleanup() {
}
private void addInputFields() {
Label label = new Label("Enter private key:");
label.setWrapText(true);
@ -92,9 +100,10 @@ public class EnterPrivKeyPopup extends Popup {
GridPane.setMargin(keyInputTextField, new Insets(3, 0, 0, 0));
GridPane.setRowIndex(keyInputTextField, rowIndex);
GridPane.setColumnIndex(keyInputTextField, 1);
keyInputTextField.textProperty().addListener((observable, oldValue, newValue) -> {
changeListener = (observable, oldValue, newValue) -> {
unlockButton.setDisable(newValue.length() == 0);
});
};
keyInputTextField.textProperty().addListener(changeListener);
gridPane.getChildren().addAll(label, keyInputTextField);
}

View file

@ -100,9 +100,15 @@ public class Popup {
stage.hide();
else
log.warn("Stage is null");
cleanup();
PopupManager.isHidden(this);
}
protected void cleanup() {
}
public Popup onClose(Runnable closeHandler) {
this.closeHandlerOptional = Optional.of(closeHandler);
return this;
@ -249,7 +255,7 @@ public class Popup {
if (headLine != null) {
headLineLabel = new Label(BSResources.get(headLine));
headLineLabel.setMouseTransparent(true);
headLineLabel.setStyle("-fx-font-size: 16; -fx-text-fill: #333;");
headLineLabel.setId("popup-headline");
GridPane.setHalignment(headLineLabel, HPos.LEFT);
GridPane.setRowIndex(headLineLabel, ++rowIndex);
GridPane.setColumnSpan(headLineLabel, 2);
@ -271,6 +277,7 @@ public class Popup {
messageLabel = new Label(truncatedMessage);
messageLabel.setMouseTransparent(true);
messageLabel.setWrapText(true);
messageLabel.setId("popup-message");
GridPane.setHalignment(messageLabel, HPos.LEFT);
GridPane.setHgrow(messageLabel, Priority.ALWAYS);
GridPane.setMargin(messageLabel, new Insets(3, 0, 0, 0));
@ -288,6 +295,7 @@ public class Popup {
"It will make debugging easier if you can attach the bitsquare.log file which you can find in the application directory.");
Button githubButton = new Button("Report to Github issue tracker");
githubButton.setId("popup-button");
GridPane.setMargin(githubButton, new Insets(20, 0, 0, 0));
GridPane.setHalignment(githubButton, HPos.RIGHT);
GridPane.setRowIndex(githubButton, ++rowIndex);
@ -300,6 +308,7 @@ public class Popup {
});
Button mailButton = new Button("Report by email");
mailButton.setId("popup-button");
GridPane.setHalignment(mailButton, HPos.RIGHT);
GridPane.setRowIndex(mailButton, ++rowIndex);
GridPane.setColumnIndex(mailButton, 1);
@ -337,6 +346,7 @@ public class Popup {
protected void addCloseButton() {
closeButton = new Button(closeButtonText == null ? "Close" : closeButtonText);
closeButton.setId("popup-button");
closeButton.setOnAction(event -> {
hide();
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
@ -344,6 +354,7 @@ public class Popup {
if (actionHandlerOptional.isPresent() || actionButtonText != null) {
actionButton = new Button(actionButtonText == null ? "Ok" : actionButtonText);
actionButton.setId("popup-button");
actionButton.setDefaultButton(true);
//TODO app wide focus
//actionButton.requestFocus();

View file

@ -27,6 +27,7 @@ import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.Offer;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
@ -46,6 +47,8 @@ public class TradeDetailsPopup extends Popup {
private final BSFormatter formatter;
private DisputeManager disputeManager;
private Trade trade;
private ChangeListener<Number> changeListener;
private TextArea textArea;
///////////////////////////////////////////////////////////////////////////////////////////
@ -78,6 +81,12 @@ public class TradeDetailsPopup extends Popup {
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void cleanup() {
if (textArea != null)
textArea.scrollTopProperty().addListener(changeListener);
}
@Override
protected void createGridPane() {
super.createGridPane();
@ -173,17 +182,18 @@ public class TradeDetailsPopup extends Popup {
}
if (trade.errorMessageProperty().get() != null) {
TextArea textArea = addLabelTextArea(gridPane, ++rowIndex, "Error message:", "").second;
textArea = addLabelTextArea(gridPane, ++rowIndex, "Error message:", "").second;
textArea.setText(trade.errorMessageProperty().get());
textArea.setEditable(false);
IntegerProperty count = new SimpleIntegerProperty(20);
int rowHeight = 10;
textArea.prefHeightProperty().bindBidirectional(count);
textArea.scrollTopProperty().addListener((ov, old, newVal) -> {
changeListener = (ov, old, newVal) -> {
if (newVal.intValue() > rowHeight)
count.setValue(count.get() + newVal.intValue() + 10);
});
};
textArea.scrollTopProperty().addListener(changeListener);
textArea.setScrollTop(30);
TextField state = addLabelTextField(gridPane, ++rowIndex, "Trade state:").second;

View file

@ -22,6 +22,7 @@ import io.bitsquare.crypto.ScryptUtil;
import io.bitsquare.gui.components.PasswordTextField;
import io.bitsquare.gui.util.Transitions;
import io.bitsquare.gui.util.validation.PasswordValidator;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
@ -44,6 +45,7 @@ public class WalletPasswordPopup extends Popup {
private Button unlockButton;
private AesKeyHandler aesKeyHandler;
private PasswordTextField passwordTextField;
private ChangeListener<String> changeListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -59,6 +61,12 @@ public class WalletPasswordPopup extends Popup {
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void cleanup() {
if (passwordTextField != null)
passwordTextField.textProperty().addListener(changeListener);
}
@Inject
public WalletPasswordPopup(WalletService walletService) {
this.walletService = walletService;
@ -106,9 +114,10 @@ public class WalletPasswordPopup extends Popup {
GridPane.setRowIndex(passwordTextField, rowIndex);
GridPane.setColumnIndex(passwordTextField, 1);
PasswordValidator passwordValidator = new PasswordValidator();
passwordTextField.textProperty().addListener((observable, oldValue, newValue) -> {
changeListener = (observable, oldValue, newValue) -> {
unlockButton.setDisable(!passwordValidator.validate(newValue).isValid);
});
};
passwordTextField.textProperty().addListener(changeListener);
gridPane.getChildren().addAll(label, passwordTextField);
}