Add new Take offer screen

This commit is contained in:
Manfred Karrer 2014-09-17 23:51:54 +02:00
parent e04ed033e0
commit 30751b017b
28 changed files with 1508 additions and 596 deletions

View File

@ -187,7 +187,7 @@ public class MainViewCB extends ViewCB<MainPM> {
icon.setId("image-alert-round");
final Button alertButton = new Button("", icon);
alertButton.setId("nav-alert-button");
alertButton.relocate(36, 19);
alertButton.relocate(30, 9);
alertButton.setOnAction((e) ->
navigation.navigationTo(Navigation.Item.MAIN,
Navigation.Item.ORDERS,

View File

@ -30,6 +30,7 @@ import java.util.ResourceBundle;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.*;
@ -41,10 +42,11 @@ class AccountViewCB extends CachedViewCB<AccountPM> {
private static final Logger log = LoggerFactory.getLogger(AccountViewCB.class);
Tab tab;
private Navigation navigation;
private Navigation.Listener listener;
@FXML Tab tab;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor

View File

@ -40,9 +40,9 @@ class ChangePasswordViewCB extends CachedViewCB<ChangePasswordPM> implements Con
private static final Logger log = LoggerFactory.getLogger(ChangePasswordViewCB.class);
@FXML private HBox buttonsHBox;
@FXML private Button saveButton, skipButton;
@FXML private PasswordField passwordField, repeatedPasswordField;
@FXML HBox buttonsHBox;
@FXML Button saveButton, skipButton;
@FXML PasswordField passwordField, repeatedPasswordField;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -56,14 +56,14 @@ public class FiatAccountViewCB extends CachedViewCB<FiatAccountPm> implements Co
private static final Logger log = LoggerFactory.getLogger(FiatAccountViewCB.class);
@FXML private HBox buttonsHBox;
@FXML private ComboBox<Region> regionComboBox;
@FXML private ComboBox<Country> countryComboBox;
@FXML private InputTextField titleTextField, holderNameTextField, primaryIDTextField, secondaryIDTextField;
@FXML private Button saveButton, completedButton, removeBankAccountButton;
@FXML private ComboBox<BankAccount> selectionComboBox;
@FXML private ComboBox<BankAccountType> typesComboBox;
@FXML private ComboBox<Currency> currencyComboBox;
@FXML HBox buttonsHBox;
@FXML ComboBox<Region> regionComboBox;
@FXML ComboBox<Country> countryComboBox;
@FXML InputTextField titleTextField, holderNameTextField, primaryIDTextField, secondaryIDTextField;
@FXML Button saveButton, completedButton, removeBankAccountButton;
@FXML ComboBox<BankAccount> selectionComboBox;
@FXML ComboBox<BankAccountType> typesComboBox;
@FXML ComboBox<Currency> currencyComboBox;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -40,9 +40,9 @@ public class PasswordViewCB extends CachedViewCB<PasswordPM> implements ContextA
private static final Logger log = LoggerFactory.getLogger(PasswordViewCB.class);
@FXML private HBox buttonsHBox;
@FXML private Button saveButton, skipButton;
@FXML private PasswordField oldPasswordField, passwordField, repeatedPasswordField;
@FXML HBox buttonsHBox;
@FXML Button saveButton, skipButton;
@FXML PasswordField oldPasswordField, passwordField, repeatedPasswordField;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -55,10 +55,10 @@ public class RegistrationViewCB extends CachedViewCB<RegistrationPM> implements
private OverlayManager overlayManager;
@FXML private TextField feeTextField;
@FXML private AddressTextField addressTextField;
@FXML private BalanceTextField balanceTextField;
@FXML private Button payButton;
@FXML TextField feeTextField;
@FXML AddressTextField addressTextField;
@FXML BalanceTextField balanceTextField;
@FXML Button payButton;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -57,11 +57,11 @@ public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements
private static final Logger log = LoggerFactory.getLogger(RestrictionsViewCB.class);
@FXML private ListView languagesListView, countriesListView, arbitratorsListView;
@FXML private ComboBox<Locale> languageComboBox;
@FXML private ComboBox<Region> regionComboBox;
@FXML private ComboBox<Country> countryComboBox;
@FXML private Button completedButton, addAllEuroCountriesButton;
@FXML ListView languagesListView, countriesListView, arbitratorsListView;
@FXML ComboBox<Locale> languageComboBox;
@FXML ComboBox<Region> regionComboBox;
@FXML ComboBox<Country> countryComboBox;
@FXML Button completedButton, addAllEuroCountriesButton;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -40,8 +40,8 @@ public class SeedWordsViewCB extends CachedViewCB<SeedWordsPM> implements Contex
private static final Logger log = LoggerFactory.getLogger(SeedWordsViewCB.class);
@FXML private Button completedButton;
@FXML private TextArea seedWordsTextArea;
@FXML Button completedButton;
@FXML TextArea seedWordsTextArea;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -65,8 +65,8 @@ public class ArbitratorBrowserController extends CachedViewController implements
private ArbitratorProfileController arbitratorProfileController;
private int index = -1;
@FXML private Button prevButton, nextButton, selectButton, closeButton;
@FXML private Pane arbitratorProfile;
@FXML Button prevButton, nextButton, selectButton, closeButton;
@FXML Pane arbitratorProfile;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor

View File

@ -43,11 +43,11 @@ public class ArbitratorProfileController extends CachedViewController {
private Arbitrator arbitrator;
@FXML private Label nameLabel;
@FXML private TextField nameTextField, languagesTextField, reputationTextField, maxTradeVolumeTextField,
@FXML Label nameLabel;
@FXML TextField nameTextField, languagesTextField, reputationTextField, maxTradeVolumeTextField,
passiveServiceFeeTextField, arbitrationFeeTextField, methodsTextField,
idVerificationsTextField, webPageTextField;
@FXML private TextArea descriptionTextArea;
@FXML TextArea descriptionTextArea;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -85,20 +85,20 @@ public class ArbitratorRegistrationController extends CachedViewController {
private Arbitrator.ID_TYPE idType;
private ConfidenceDisplay confidenceDisplay;
@FXML private Accordion accordion;
@FXML private TitledPane profileTitledPane, payCollateralTitledPane;
@FXML private Button saveProfileButton, paymentDoneButton;
@FXML private Label nameLabel, infoLabel, copyIcon, confirmationLabel;
@FXML private ComboBox<Locale> languageComboBox;
@FXML private ComboBox<Arbitrator.ID_TYPE> idTypeComboBox;
@FXML private ComboBox<Arbitrator.METHOD> methodsComboBox;
@FXML private ComboBox<Arbitrator.ID_VERIFICATION> idVerificationsComboBox;
@FXML private TextField nameTextField, idTypeTextField, languagesTextField, maxTradeVolumeTextField,
@FXML Accordion accordion;
@FXML TitledPane profileTitledPane, payCollateralTitledPane;
@FXML Button saveProfileButton, paymentDoneButton;
@FXML Label nameLabel, infoLabel, copyIcon, confirmationLabel;
@FXML ComboBox<Locale> languageComboBox;
@FXML ComboBox<Arbitrator.ID_TYPE> idTypeComboBox;
@FXML ComboBox<Arbitrator.METHOD> methodsComboBox;
@FXML ComboBox<Arbitrator.ID_VERIFICATION> idVerificationsComboBox;
@FXML TextField nameTextField, idTypeTextField, languagesTextField, maxTradeVolumeTextField,
passiveServiceFeeTextField, minPassiveServiceFeeTextField, arbitrationFeeTextField,
minArbitrationFeeTextField, methodsTextField, idVerificationsTextField, webPageTextField,
collateralAddressTextField, balanceTextField;
@FXML private TextArea descriptionTextArea;
@FXML private ConfidenceProgressIndicator progressIndicator;
@FXML TextArea descriptionTextArea;
@FXML ConfidenceProgressIndicator progressIndicator;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -50,8 +50,8 @@ public class DepositController extends CachedViewController {
private final WalletFacade walletFacade;
private ObservableList<DepositListItem> addressList;
@FXML private TableView<DepositListItem> tableView;
@FXML private TableColumn<String, DepositListItem> labelColumn, addressColumn, balanceColumn, copyColumn,
@FXML TableView<DepositListItem> tableView;
@FXML TableColumn<String, DepositListItem> labelColumn, addressColumn, balanceColumn, copyColumn,
confidenceColumn;

View File

@ -46,10 +46,10 @@ public class TransactionsController extends CachedViewController {
private final WalletFacade walletFacade;
private ObservableList<TransactionsListItem> transactionsListItems;
@FXML private TableView<TransactionsListItem> tableView;
@FXML private TableColumn<String, TransactionsListItem> dateColumn, addressColumn, amountColumn, typeColumn,
@FXML TableView<TransactionsListItem> tableView;
@FXML TableColumn<String, TransactionsListItem> dateColumn, addressColumn, amountColumn, typeColumn,
confidenceColumn;
@FXML private Button addNewAddressButton;
@FXML Button addNewAddressButton;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -65,11 +65,11 @@ public class WithdrawalController extends CachedViewController {
private final WalletFacade walletFacade;
private ObservableList<WithdrawalListItem> addressList;
@FXML private TableView<WithdrawalListItem> tableView;
@FXML private TableColumn<String, WithdrawalListItem> labelColumn, addressColumn, balanceColumn, copyColumn,
@FXML TableView<WithdrawalListItem> tableView;
@FXML TableColumn<String, WithdrawalListItem> labelColumn, addressColumn, balanceColumn, copyColumn,
confidenceColumn;
@FXML private Button addNewAddressButton;
@FXML private TextField withdrawFromTextField, withdrawToTextField, amountTextField, changeAddressTextField;
@FXML Button addNewAddressButton;
@FXML TextField withdrawFromTextField, withdrawToTextField, amountTextField, changeAddressTextField;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -21,6 +21,9 @@ public enum HelpId {
CREATE_OFFER_GENERAL,
CREATE_OFFER_FUNDING,
CREATE_OFFER_ADVANCED,
TAKE_OFFER_GENERAL,
TAKE_OFFER_FUNDING,
TAKE_OFFER_ADVANCED,
SETUP_SEED_WORDS,
SETUP_PASSWORD,
SETUP_RESTRICTION_LANGUAGES,

View File

@ -48,9 +48,9 @@ public class OfferController extends CachedViewController {
private final TradeManager tradeManager;
private ObservableList<OfferListItem> offerListItems;
@FXML private TableColumn<String, OfferListItem> offerIdColumn, dateColumn, amountColumn, priceColumn,
@FXML TableColumn<String, OfferListItem> offerIdColumn, dateColumn, amountColumn, priceColumn,
volumeColumn, removeColumn;
@FXML private TableView<OfferListItem> offerTable;
@FXML TableView<OfferListItem> offerTable;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -80,17 +80,17 @@ public class PendingTradeController extends CachedViewController {
private ConfidenceDisplay confidenceDisplay;
@FXML private TableView openTradesTable;
@FXML private TableColumn<String, PendingTradesListItem> directionColumn, countryColumn, bankAccountTypeColumn,
@FXML TableView openTradesTable;
@FXML TableColumn<String, PendingTradesListItem> directionColumn, countryColumn, bankAccountTypeColumn,
priceColumn, amountColumn, volumeColumn, statusColumn, selectColumn;
@FXML private ConfidenceProgressIndicator progressIndicator;
@FXML private Label txTitleLabel, txHeaderLabel, confirmationLabel, txIDCopyIcon, holderNameCopyIcon,
@FXML ConfidenceProgressIndicator progressIndicator;
@FXML Label txTitleLabel, txHeaderLabel, confirmationLabel, txIDCopyIcon, holderNameCopyIcon,
primaryBankAccountIDCopyIcon, secondaryBankAccountIDCopyIcon, bankAccountDetailsHeaderLabel,
bankAccountTypeTitleLabel, holderNameTitleLabel, primaryBankAccountIDTitleLabel,
secondaryBankAccountIDTitleLabel;
@FXML private TextField txTextField, bankAccountTypeTextField, holderNameTextField, primaryBankAccountIDTextField,
@FXML TextField txTextField, bankAccountTypeTextField, holderNameTextField, primaryBankAccountIDTextField,
secondaryBankAccountIDTextField;
@FXML private Button bankTransferInitedButton;
@FXML Button bankTransferInitedButton;
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -22,7 +22,7 @@ import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.main.trade.createoffer.CreateOfferViewCB;
import io.bitsquare.gui.main.trade.orderbook.OrderBookViewCB;
import io.bitsquare.gui.main.trade.takeoffer.TakeOfferController;
import io.bitsquare.gui.main.trade.takeoffer.TakeOfferViewCB;
import io.bitsquare.trade.Direction;
import io.bitsquare.util.ViewLoader;
@ -48,8 +48,9 @@ public class TradeViewCB extends CachedViewCB {
private final OrderBookInfo orderBookInfo = new OrderBookInfo();
private OrderBookViewCB orderBookViewCB;
private CreateOfferViewCB createOfferViewCB;
private TakeOfferController takeOfferController;
private TakeOfferViewCB takeOfferViewCB;
private Node createOfferView;
private Node takeOfferView;
private Navigation navigation;
private Navigation.Listener listener;
private Navigation.Item navigationItem;
@ -170,21 +171,21 @@ public class TradeViewCB extends CachedViewCB {
log.error(e.getMessage());
}
}
else if (navigationItem == Navigation.Item.TAKE_OFFER && takeOfferController == null) {
else if (navigationItem == Navigation.Item.TAKE_OFFER && takeOfferViewCB == null &&
orderBookInfo.getOffer() != null) {
// CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times
// in different graphs
ViewLoader loader = new ViewLoader(getClass().getResource(navigationItem.getFxmlUrl()), false);
ViewLoader loader = new ViewLoader(getClass().getResource(Navigation.Item.TAKE_OFFER.getFxmlUrl()), false);
try {
final Parent view = loader.load();
takeOfferController = loader.getController();
takeOfferController.setParentController(this);
takeOfferController.initWithData(orderBookInfo);
takeOfferView = loader.load();
takeOfferViewCB = loader.getController();
takeOfferViewCB.setParent(this);
takeOfferViewCB.initWithOrderBookInfo(orderBookInfo);
final Tab tab = new Tab("Take offer");
tab.setContent(view);
tab.setContent(takeOfferView);
tabPane.getTabs().add(tab);
tabPane.getSelectionModel().select(tab);
return takeOfferController;
return takeOfferViewCB;
} catch (IOException e) {
log.error(e.getMessage());
}
@ -202,7 +203,7 @@ public class TradeViewCB extends CachedViewCB {
//TODO takeOfferController is not updated yet to new UI pattern
public void onTakeOfferViewRemoved() {
takeOfferController = null;
takeOfferViewCB = null;
}

View File

@ -64,7 +64,7 @@ import static io.bitsquare.gui.util.BSFormatter.reduceTo4Decimals;
* Note that the create offer domain has a deeper scope in the application domain (TradeManager).
* That model is just responsible for the domain specific parts displayed needed in that UI element.
*/
public class CreateOfferModel extends UIModel {
class CreateOfferModel extends UIModel {
private static final Logger log = LoggerFactory.getLogger(CreateOfferModel.class);
private final TradeManager tradeManager;

View File

@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.BSFormatter.*;
import static javafx.beans.binding.Bindings.createStringBinding;
public class CreateOfferPM extends PresentationModel<CreateOfferModel> {
class CreateOfferPM extends PresentationModel<CreateOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferPM.class);
private final BtcValidator btcValidator;
@ -154,7 +154,7 @@ public class CreateOfferPM extends PresentationModel<CreateOfferModel> {
///////////////////////////////////////////////////////////////////////////////////////////
// setOrderBookFilter is a one time call
void setOrderBookFilter(@NotNull OrderBookInfo orderBookInfo) {
void setOrderBookInfo(@NotNull OrderBookInfo orderBookInfo) {
model.setDirection(orderBookInfo.getDirection());
directionLabel.set(model.getDirection() == Direction.BUY ? BSResources.get("shared.buy") : BSResources.get
("shared.sell"));
@ -176,7 +176,7 @@ public class CreateOfferPM extends PresentationModel<CreateOfferModel> {
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
void onPlaceOffer() {
void placeOffer() {
model.requestPlaceOfferErrorMessage.set(null);
model.requestPlaceOfferSuccess.set(false);

View File

@ -38,7 +38,7 @@
<!--suppress JavaFxUnresolvedFxIdReference -->
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
<padding>
<padding>
<Insets bottom="-10.0" left="25.0" top="30.0" right="25"/>
</padding>
@ -196,20 +196,6 @@
<TitledGroupBg fx:id="showDetailsPane" text="%createOffer.advancedBox.title" GridPane.columnSpan="3"
GridPane.rowIndex="9" GridPane.rowSpan="7" visible="false"/>
<!-- <Pane fx:id="showDetailsPane" id="form-group-background-active" GridPane.columnSpan="3"
GridPane.rowIndex="9" GridPane.rowSpan="7" visible="false">
<GridPane.margin>
<Insets right="-10" bottom="-10" left="-10" top="-10"/>
</GridPane.margin>
<Label fx:id="showDetailsTitleLabel" text="%createOffer.advancedBox.title" id="form-group-title-active"
layoutX="8"
layoutY="-8">
<padding>
<Insets left="5" right="7"/>
</padding>
</Label>
</Pane> -->
<HBox GridPane.rowIndex="9" spacing="4" alignment="CENTER_RIGHT">
<Label fx:id="acceptedCountriesLabel" text="%createOffer.advancedBox.countries" visible="false"/>
<Label fx:id="acceptedCountriesLabelIcon" visible="false"/>

View File

@ -159,7 +159,7 @@ public class CreateOfferViewCB extends CachedViewCB<CreateOfferPM> {
///////////////////////////////////////////////////////////////////////////////////////////
public void initWithOrderBookInfo(OrderBookInfo orderBookInfo) {
presentationModel.setOrderBookFilter(orderBookInfo);
presentationModel.setOrderBookInfo(orderBookInfo);
if (orderBookInfo.getDirection() == Direction.BUY)
imageView.setId("image-buy-large");
@ -177,7 +177,7 @@ public class CreateOfferViewCB extends CachedViewCB<CreateOfferPM> {
@FXML
void onPlaceOffer() {
presentationModel.onPlaceOffer();
presentationModel.placeOffer();
}
@FXML
@ -360,8 +360,6 @@ public class CreateOfferViewCB extends CachedViewCB<CreateOfferPM> {
priceFiatLabel.textProperty().bind(presentationModel.fiatCode);
volumeFiatLabel.textProperty().bind(presentationModel.fiatCode);
minAmountBtcLabel.textProperty().bind(presentationModel.btcCode);
priceDescriptionLabel.textProperty().bind(presentationModel.fiatCode);
volumeDescriptionLabel.textProperty().bind(presentationModel.fiatCode);//Price per Bitcoin in EUR
priceDescriptionLabel.textProperty().bind(createStringBinding(() ->
BSResources.get("createOffer.amountPriceBox.priceDescription",
@ -402,7 +400,6 @@ public class CreateOfferViewCB extends CachedViewCB<CreateOfferPM> {
// buttons
placeOfferButton.visibleProperty().bind(presentationModel.isPlaceOfferButtonVisible);
placeOfferButton.disableProperty().bind(presentationModel.isPlaceOfferButtonDisabled);
// closeButton.visibleProperty().bind(presentationModel.isCloseButtonVisible);
}
private void showDetailsScreen() {
@ -459,7 +456,6 @@ public class CreateOfferViewCB extends CachedViewCB<CreateOfferPM> {
}
private void initEditIcons() {
advancedScreenInited = true;
acceptedCountriesLabelIcon.setId("clickable-icon");
AwesomeDude.setIcon(acceptedCountriesLabelIcon, AwesomeIcon.EDIT_SIGN);
Tooltip.install(acceptedCountriesLabelIcon, new Tooltip(BSResources.get("shared.openSettings")));

View File

@ -1,337 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.CachedViewController;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.components.ValidatedTextField;
import io.bitsquare.gui.main.trade.OrderBookInfo;
import io.bitsquare.gui.main.trade.TradeViewCB;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.BitSquareValidator;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocol;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocolListener;
import com.google.bitcoin.core.Coin;
import com.google.bitcoin.utils.Fiat;
import java.net.URL;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TakeOfferController extends CachedViewController {
private static final Logger log = LoggerFactory.getLogger(TakeOfferController.class);
private final TradeManager tradeManager;
private final WalletFacade walletFacade;
private Offer offer;
private Coin requestedAmount;
private String tradeId;
private String depositTxId;
@FXML private Accordion accordion;
@FXML private TitledPane takeOfferTitledPane, waitBankTxTitledPane, summaryTitledPane;
@FXML private ValidatedTextField amountTextField;
@FXML private TextField priceTextField, volumeTextField, collateralTextField, feeTextField, totalTextField,
bankAccountTypeTextField, countryTextField, arbitratorsTextField,
supportedLanguagesTextField, supportedCountriesTextField, depositTxIdTextField, summaryPaidTextField,
summaryReceivedTextField, summaryFeesTextField, summaryCollateralTextField,
summaryDepositTxIdTextField, summaryPayoutTxIdTextField;
@FXML private Label infoLabel, headLineLabel, collateralLabel;
@FXML private Button takeOfferButton, receivedFiatButton;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private TakeOfferController(TradeManager tradeManager, WalletFacade walletFacade) {
this.tradeManager = tradeManager;
this.walletFacade = walletFacade;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb) {
super.initialize(url, rb);
accordion.setExpandedPane(takeOfferTitledPane);
}
@Override
public void deactivate() {
super.deactivate();
((TradeViewCB) parentController).onTakeOfferViewRemoved();
}
@Override
public void activate() {
super.activate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void initWithData(OrderBookInfo orderBookInfo) {
this.offer = orderBookInfo.getOffer();
if (orderBookInfo.getAmount() != null && !orderBookInfo.getAmount().isZero())
requestedAmount = orderBookInfo.getAmount();
else
requestedAmount = offer.getAmount();
if (amountTextField != null) {
applyData();
}
}
public void applyData() {
amountTextField.setText(requestedAmount.toPlainString());
amountTextField.setPromptText(BSFormatter.formatCoinWithCode(
offer.getMinAmount()) + " - " + BSFormatter.formatCoinWithCode(offer.getAmount()));
priceTextField.setText(BSFormatter.formatFiat(offer.getPrice()));
applyVolume();
collateralLabel.setText("Collateral (" + getCollateralAsPercent() + "):");
applyCollateral();
applyTotal();
feeTextField.setText(BSFormatter.formatCoinWithCode(getFee()));
totalTextField.setText(getFormattedTotal());
bankAccountTypeTextField.setText(offer.getBankAccountType().toString());
countryTextField.setText(offer.getBankAccountCountry().getName());
//todo list
// arbitratorsTextField.setText(offer.getArbitrator().getName());
supportedLanguagesTextField.setText(BSFormatter.languageLocalesToString(
offer.getAcceptedLanguageLocales()));
supportedCountriesTextField.setText(BSFormatter.countryLocalesToString(offer.getAcceptedCountries()));
amountTextField.textProperty().addListener(e -> {
applyVolume();
applyCollateral();
applyTotal();
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// GUI handlers
///////////////////////////////////////////////////////////////////////////////////////////
@FXML
public void onTakeOffer() {
AddressEntry addressEntry = walletFacade.getAddressInfoByTradeID(offer.getId());
Coin amount = BSFormatter.parseToCoin(getAmountString());
// TODO more validation (fee payment, blacklist,...)
if (amountTextField.isInvalid()) {
Popups.openErrorPopup("Invalid input", "The requested amount you entered is not a valid amount.");
}
else if (BitSquareValidator.tradeAmountOutOfRange(amount, offer)) {
Popups.openErrorPopup(
"Invalid input", "The requested amount you entered is outside of the range of the offered amount.");
}
else if (addressEntry == null ||
getTotal().compareTo(walletFacade.getBalanceForAddress(addressEntry.getAddress())) > 0) {
Popups.openErrorPopup("Insufficient money", "You don't have enough funds for that trade.");
}
else if (tradeManager.isOfferAlreadyInTrades(offer)) {
Popups.openErrorPopup("Offer previously accepted",
"You have that offer already taken. Open the offer section to find that trade.");
}
else {
takeOfferButton.setDisable(true);
amountTextField.setEditable(false);
tradeManager.takeOffer(amount, offer, new SellerTakesOfferProtocolListener() {
@Override
public void onDepositTxPublished(String depositTxId) {
setDepositTxId(depositTxId);
accordion.setExpandedPane(waitBankTxTitledPane);
infoLabel.setText("Deposit transaction published by offerer.\n" +
"As soon as the offerer starts the \n" +
"Bank transfer, you will be informed.");
depositTxIdTextField.setText(depositTxId);
}
@Override
public void onBankTransferInited(String tradeId) {
setTradeId(tradeId);
headLineLabel.setText("Bank transfer initiated");
infoLabel.setText("Check your bank account and continue \n" + "when you have received the money.");
receivedFiatButton.setDisable(false);
}
@Override
public void onPayoutTxPublished(Trade trade, String payoutTxId) {
accordion.setExpandedPane(summaryTitledPane);
summaryPaidTextField.setText(BSFormatter.formatCoinWithCode(trade.getTradeAmount()));
summaryReceivedTextField.setText(BSFormatter.formatFiat(trade.getTradeVolume()));
summaryFeesTextField.setText(BSFormatter.formatCoinWithCode(
FeePolicy.TAKE_OFFER_FEE.add(FeePolicy.TX_FEE)));
summaryCollateralTextField.setText(BSFormatter.formatCoinWithCode(
trade.getCollateralAmount()));
summaryDepositTxIdTextField.setText(depositTxId);
summaryPayoutTxIdTextField.setText(payoutTxId);
}
@Override
public void onFault(Throwable throwable, SellerTakesOfferProtocol.State state) {
log.error("Error while executing trade process at state: " + state + " / " + throwable);
Popups.openErrorPopup("Error while executing trade process",
"Error while executing trade process at state: " + state + " / " + throwable);
}
@Override
public void onWaitingForPeerResponse(SellerTakesOfferProtocol.State state) {
log.debug("Waiting for peers response at state " + state);
}
@Override
public void onCompleted(SellerTakesOfferProtocol.State state) {
log.debug("Trade protocol completed at state " + state);
}
@Override
public void onTakeOfferRequestRejected(Trade trade) {
log.error("Take offer request rejected");
Popups.openErrorPopup("Take offer request rejected",
"Your take offer request has been rejected. It might be that the offerer got another " +
"request shortly before your request arrived.");
}
});
}
}
@FXML
public void onReceivedFiat() {
tradeManager.onFiatReceived(tradeId);
}
@FXML
public void onClose() {
TabPane tabPane = ((TabPane) (root.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////////////////
private void applyCollateral() {
collateralTextField.setText(getFormattedCollateralAsBtc());
}
private void applyVolume() {
volumeTextField.setText(getFormattedVolume());
}
private void applyTotal() {
totalTextField.setText(getFormattedTotal());
}
// formatted
private String getFormattedVolume() {
return BSFormatter.formatFiat(getVolume());
}
private String getFormattedTotal() {
return BSFormatter.formatCoinWithCode(getTotal());
}
// values
private double getAmountAsDouble() {
return BSFormatter.parseToDouble(getAmountString());
}
private Coin getAmountInSatoshis() {
return BSFormatter.parseToCoin(getAmountString());
}
private String getAmountString() {
try {
BitSquareValidator.textFieldsHasPositiveDoubleValueWithReset(amountTextField);
return amountTextField.getText();
} catch (BitSquareValidator.ValidationException e) {
return "0";
}
}
private Fiat getVolume() {
//TODO
return Fiat.valueOf("EUR", (long) (offer.getPrice().longValue() * getAmountAsDouble()));
}
private Coin getFee() {
return FeePolicy.TAKE_OFFER_FEE.add(FeePolicy.TX_FEE);
}
private Coin getTotal() {
return getFee().add(getAmountInSatoshis()).add(getCollateralAsCoin());
}
private Coin getCollateralAsCoin() {
Coin amountAsCoin = BSFormatter.parseToCoin(getAmountString());
return amountAsCoin.multiply(getCollateral()).divide(1000L);
}
private String getFormattedCollateralAsBtc() {
return BSFormatter.formatCoin(getCollateralAsCoin());
}
private String getCollateralAsPercent() {
return BSFormatter.formatCollateralPercent(getCollateral());
}
private long getCollateral() {
return offer.getCollateral();
}
public void setTradeId(String tradeId) {
this.tradeId = tradeId;
}
public void setDepositTxId(String depositTxId) {
this.depositTxId = depositTxId;
}
}

View File

@ -0,0 +1,329 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.gui.UIModel;
import io.bitsquare.gui.main.trade.OrderBookInfo;
import io.bitsquare.settings.Settings;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocol;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocolListener;
import com.google.bitcoin.core.Coin;
import com.google.bitcoin.utils.ExchangeRate;
import com.google.bitcoin.utils.Fiat;
import com.google.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Domain for that UI element.
* Note that the create offer domain has a deeper scope in the application domain (TradeManager).
* That model is just responsible for the domain specific parts displayed needed in that UI element.
*/
class TakeOfferModel extends UIModel {
private static final Logger log = LoggerFactory.getLogger(TakeOfferModel.class);
private final TradeManager tradeManager;
private final WalletFacade walletFacade;
private final Settings settings;
private OrderBookInfo orderBookInfo;
private AddressEntry addressEntry;
final StringProperty requestTakeOfferErrorMessage = new SimpleStringProperty();
final StringProperty transactionId = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty();
final BooleanProperty requestTakeOfferSuccess = new SimpleBooleanProperty();
final BooleanProperty isWalletFunded = new SimpleBooleanProperty();
final BooleanProperty useMBTC = new SimpleBooleanProperty();
final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> minAmountAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Fiat> volumeAsFiat = new SimpleObjectProperty<>();
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> collateralAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
TakeOfferModel(TradeManager tradeManager, WalletFacade walletFacade, Settings settings) {
this.tradeManager = tradeManager;
this.walletFacade = walletFacade;
this.settings = settings;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize() {
super.initialize();
offerFeeAsCoin.set(FeePolicy.CREATE_OFFER_FEE);
networkFeeAsCoin.set(FeePolicy.TX_FEE);
}
@Override
public void activate() {
super.activate();
btcCode.bind(settings.btcDenominationProperty());
}
@SuppressWarnings("EmptyMethod")
@Override
public void deactivate() {
super.deactivate();
btcCode.unbind();
}
@SuppressWarnings("EmptyMethod")
@Override
public void terminate() {
super.terminate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////////////////////////
void takeOffer() {
// data validation is done in the trade domain
/*tradeManager.requestPlaceOffer(orderBookInfo.getOffer().getId(),
orderBookInfo.getOffer().getDirection(),
priceAsFiat.get(),
amountAsCoin.get(),
minAmountAsCoin.get(),
(transaction) -> {
transactionId.set(transaction.getHashAsString());
requestTakeOfferSuccess.set(true);
},
requestTakeOfferErrorMessage::set
);*/
SellerTakesOfferProtocolListener listener = new SellerTakesOfferProtocolListener() {
@Override
public void onDepositTxPublished(String depositTxId) {
transactionId.set(depositTxId);
requestTakeOfferSuccess.set(true);
}
@Override
public void onBankTransferInited(String tradeId) {
}
@Override
public void onPayoutTxPublished(Trade trade, String hashAsString) {
}
@Override
public void onFault(Throwable throwable, SellerTakesOfferProtocol.State state) {
requestTakeOfferErrorMessage.set("An error occurred. Error: " + throwable.getMessage());
}
@Override
public void onWaitingForPeerResponse(SellerTakesOfferProtocol.State state) {
}
@Override
public void onCompleted(SellerTakesOfferProtocol.State state) {
}
@Override
public void onTakeOfferRequestRejected(Trade trade) {
requestTakeOfferErrorMessage.set("Take offer request got rejected.");
}
};
tradeManager.takeOffer(amountAsCoin.get(), orderBookInfo.getOffer(), listener);
/*new SellerTakesOfferProtocolListener() {
@Override
public void onDepositTxPublished(String depositTxId) {
setDepositTxId(depositTxId);
accordion.setExpandedPane(waitBankTxTitledPane);
infoLabel.setText("Deposit transaction published by offerer.\n" +
"As soon as the offerer starts the \n" +
"Bank transfer, you will be informed.");
depositTxIdTextField.setText(depositTxId);
}
@Override
public void onBankTransferInited(String tradeId) {
setTradeId(tradeId);
headLineLabel.setText("Bank transfer initiated");
infoLabel.setText("Check your bank account and continue \n" + "when you have received the money.");
receivedFiatButton.setDisable(false);
}
@Override
public void onPayoutTxPublished(Trade trade, String payoutTxId) {
accordion.setExpandedPane(summaryTitledPane);
summaryPaidTextField.setText(BSFormatter.formatCoinWithCode(trade.getTradeAmount()));
summaryReceivedTextField.setText(BSFormatter.formatFiat(trade.getTradeVolume()));
summaryFeesTextField.setText(BSFormatter.formatCoinWithCode(
FeePolicy.TAKE_OFFER_FEE.add(FeePolicy.TX_FEE)));
summaryCollateralTextField.setText(BSFormatter.formatCoinWithCode(
trade.getCollateralAmount()));
summaryDepositTxIdTextField.setText(depositTxId);
summaryPayoutTxIdTextField.setText(payoutTxId);
}
@Override
public void onFault(Throwable throwable, SellerTakesOfferProtocol.State state) {
log.error("Error while executing trade process at state: " + state + " / " + throwable);
Popups.openErrorPopup("Error while executing trade process",
"Error while executing trade process at state: " + state + " / " + throwable);
}
@Override
public void onWaitingForPeerResponse(SellerTakesOfferProtocol.State state) {
log.debug("Waiting for peers response at state " + state);
}
@Override
public void onCompleted(SellerTakesOfferProtocol.State state) {
log.debug("Trade protocol completed at state " + state);
}
@Override
public void onTakeOfferRequestRejected(Trade trade) {
log.error("Take offer request rejected");
Popups.openErrorPopup("Take offer request rejected",
"Your take offer request has been rejected. It might be that the offerer got another " +
"request shortly before your request arrived.");
}
});*/
}
void calculateVolume() {
try {
if (priceAsFiat.get() != null &&
amountAsCoin.get() != null &&
!amountAsCoin.get().isZero() &&
!priceAsFiat.get().isZero()) {
volumeAsFiat.set(new ExchangeRate(priceAsFiat.get()).coinToFiat(amountAsCoin.get()));
}
} catch (Throwable t) {
// Should be never reached
log.error(t.toString());
}
}
void calculateTotalToPay() {
calculateCollateral();
try {
if (collateralAsCoin.get() != null) {
totalToPayAsCoin.set(offerFeeAsCoin.get().add(amountAsCoin.get()).add(networkFeeAsCoin.get()).add
(collateralAsCoin.get()));
}
} catch (Throwable t) {
// Should be never reached
log.error(t.toString());
}
}
void calculateCollateral() {
try {
if (amountAsCoin.get() != null && orderBookInfo != null)
collateralAsCoin.set(amountAsCoin.get().multiply(orderBookInfo.getOffer().getCollateral()).
divide(1000L));
} catch (Throwable t) {
// The multiply might lead to too large numbers, we don't handle it but it should not break the app
log.error(t.toString());
}
}
boolean isMinAmountLessOrEqualAmount() {
//noinspection SimplifiableIfStatement
if (minAmountAsCoin.get() != null && amountAsCoin.get() != null)
return !minAmountAsCoin.get().isGreaterThan(amountAsCoin.get());
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setter
///////////////////////////////////////////////////////////////////////////////////////////
void setOrderBookInfo(@NotNull OrderBookInfo orderBookInfo) {
this.orderBookInfo = orderBookInfo;
addressEntry = walletFacade.getAddressInfoByTradeID(orderBookInfo.getOffer().getId());
walletFacade.addBalanceListener(new BalanceListener(addressEntry.getAddress()) {
@Override
public void onBalanceChanged(@NotNull Coin balance) {
updateBalance(balance);
}
});
updateBalance(walletFacade.getBalanceForAddress(addressEntry.getAddress()));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
///////////////////////////////////////////////////////////////////////////////////////////
WalletFacade getWalletFacade() {
return walletFacade;
}
AddressEntry getAddressEntry() {
return addressEntry;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateBalance(@NotNull Coin balance) {
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
}
}

View File

@ -0,0 +1,362 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.btc.WalletFacade;
import io.bitsquare.gui.PresentationModel;
import io.bitsquare.gui.main.trade.OrderBookInfo;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.validation.BtcValidator;
import io.bitsquare.gui.util.validation.InputValidator;
import io.bitsquare.locale.BSResources;
import io.bitsquare.trade.Direction;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.Coin;
import javax.inject.Inject;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.BSFormatter.*;
import static javafx.beans.binding.Bindings.createStringBinding;
class TakeOfferPM extends PresentationModel<TakeOfferModel> {
private static final Logger log = LoggerFactory.getLogger(TakeOfferPM.class);
private String offerFee;
private String networkFee;
private String fiatCode;
private String minAmount;
private String price;
private String directionLabel;
private String collateralLabel;
private String bankAccountType;
private String bankAccountCurrency;
private String bankAccountCounty;
private String acceptedCountries;
private String acceptedLanguages;
private String acceptedArbitrators;
private String addressAsString;
private String paymentLabel;
// Needed for the addressTextField
final ObjectProperty<Address> address = new SimpleObjectProperty<>();
private final BtcValidator btcValidator;
final StringProperty amount = new SimpleStringProperty();
final StringProperty volume = new SimpleStringProperty();
final StringProperty collateral = new SimpleStringProperty();
final StringProperty totalToPay = new SimpleStringProperty();
final StringProperty transactionId = new SimpleStringProperty();
final StringProperty requestTakeOfferErrorMessage = new SimpleStringProperty();
final StringProperty btcCode = new SimpleStringProperty();
final BooleanProperty isTakeOfferButtonVisible = new SimpleBooleanProperty(false);
final BooleanProperty isTakeOfferButtonDisabled = new SimpleBooleanProperty(true);
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
// Needed for the addressTextField
final ObjectProperty<Coin> totalToPayAsCoin = new SimpleObjectProperty<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
// non private for testing
@Inject
TakeOfferPM(TakeOfferModel model, BtcValidator btcValidator) {
super(model);
this.btcValidator = btcValidator;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize() {
super.initialize();
// static
offerFee = formatCoinWithCode(model.offerFeeAsCoin.get());
networkFee = formatCoinWithCode(model.networkFeeAsCoin.get());
setupBindings();
setupListeners();
}
@SuppressWarnings("EmptyMethod")
@Override
public void activate() {
super.activate();
}
@SuppressWarnings("EmptyMethod")
@Override
public void deactivate() {
super.deactivate();
}
@SuppressWarnings("EmptyMethod")
@Override
public void terminate() {
super.terminate();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setter
///////////////////////////////////////////////////////////////////////////////////////////
// setOrderBookFilter is a one time call
void setOrderBookInfo(@NotNull OrderBookInfo orderBookInfo) {
model.setOrderBookInfo(orderBookInfo);
directionLabel = orderBookInfo.getDirection() == Direction.BUY ?
BSResources.get("shared.buy") : BSResources.get("shared.sell");
fiatCode = orderBookInfo.getOffer().getCurrency().getCurrencyCode();
model.priceAsFiat.set(orderBookInfo.getOffer().getPrice());
model.minAmountAsCoin.set(orderBookInfo.getOffer().getMinAmount());
if (orderBookInfo.getAmount() != null &&
isBtcInputValid(orderBookInfo.getAmount().toPlainString()).isValid &&
!orderBookInfo.getAmount().isGreaterThan(orderBookInfo.getOffer().getAmount())) {
model.amountAsCoin.set(orderBookInfo.getAmount());
}
else {
model.amountAsCoin.set(orderBookInfo.getOffer().getAmount());
}
model.volumeAsFiat.set(orderBookInfo.getOffer().getVolumeByAmount(model.amountAsCoin.get()));
minAmount = BSFormatter.formatCoinWithCode(orderBookInfo.getOffer().getMinAmount());
price = BSFormatter.formatFiatWithCode(orderBookInfo.getOffer().getPrice());
paymentLabel = BSResources.get("takeOffer.fundsBox.paymentLabel", orderBookInfo.getOffer().getId());
if (model.getAddressEntry() != null) {
addressAsString = model.getAddressEntry().getAddress().toString();
address.set(model.getAddressEntry().getAddress());
}
collateralLabel = BSResources.get("takeOffer.fundsBox.collateral",
BSFormatter.formatCollateralPercent(orderBookInfo.getOffer().getCollateral()));
acceptedCountries = BSFormatter.countryLocalesToString(orderBookInfo.getOffer().getAcceptedCountries());
acceptedLanguages = BSFormatter.languageLocalesToString(orderBookInfo.getOffer().getAcceptedLanguageLocales());
acceptedArbitrators = BSFormatter.arbitratorsToString(orderBookInfo.getOffer().getArbitrators());
bankAccountType = BSResources.get(orderBookInfo.getOffer().getBankAccountType().toString());
bankAccountCurrency = BSResources.get(orderBookInfo.getOffer().getCurrency().getDisplayName());
bankAccountCounty = BSResources.get(orderBookInfo.getOffer().getBankAccountCountry().getName());
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
void takeOffer() {
model.requestTakeOfferErrorMessage.set(null);
model.requestTakeOfferSuccess.set(false);
isTakeOfferButtonDisabled.set(true);
model.takeOffer();
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI events
///////////////////////////////////////////////////////////////////////////////////////////
void onShowPayFundsScreen() {
isTakeOfferButtonVisible.set(true);
}
// On focus out we do validation and apply the data to the model
void onFocusOutAmountTextField(Boolean oldValue, Boolean newValue, String userInput) {
if (oldValue && !newValue) {
InputValidator.ValidationResult result = isBtcInputValid(amount.get());
amountValidationResult.set(result);
if (result.isValid) {
showWarningInvalidBtcDecimalPlaces.set(!hasBtcValidDecimals(userInput));
// only allow max 4 decimal places for btc values
setAmountToModel();
// reformat input
amount.set(formatCoin(model.amountAsCoin.get()));
calculateVolume();
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
WalletFacade getWalletFacade() {
return model.getWalletFacade();
}
String getOfferFee() {
return offerFee;
}
String getNetworkFee() {
return networkFee;
}
String getFiatCode() {
return fiatCode;
}
String getMinAmount() {
return minAmount;
}
String getPrice() {
return price;
}
String getDirectionLabel() {
return directionLabel;
}
String getCollateralLabel() {
return collateralLabel;
}
String getBankAccountType() {
return bankAccountType;
}
String getBankAccountCurrency() {
return bankAccountCurrency;
}
String getBankAccountCounty() {
return bankAccountCounty;
}
String getAcceptedCountries() {
return acceptedCountries;
}
String getAcceptedLanguages() {
return acceptedLanguages;
}
String getAcceptedArbitrators() {
return acceptedArbitrators;
}
String getAddressAsString() {
return addressAsString;
}
String getPaymentLabel() {
return paymentLabel;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void setupListeners() {
// Bidirectional bindings are used for all input fields: amount, price, volume and minAmount
// We do volume/amount calculation during input, so user has immediate feedback
amount.addListener((ov, oldValue, newValue) -> {
if (isBtcInputValid(newValue).isValid) {
setAmountToModel();
calculateVolume();
model.calculateTotalToPay();
model.calculateCollateral();
}
validateInput();
});
model.isWalletFunded.addListener((ov, oldValue, newValue) -> {
if (newValue)
validateInput();
});
// Binding with Bindings.createObjectBinding does not work because of bi-directional binding
model.amountAsCoin.addListener((ov, oldValue, newValue) -> amount.set(formatCoin(newValue)));
model.requestTakeOfferErrorMessage.addListener((ov, oldValue, newValue) -> {
if (newValue != null)
isTakeOfferButtonDisabled.set(false);
});
model.requestTakeOfferSuccess.addListener((ov, oldValue, newValue) -> isTakeOfferButtonVisible.set
(!newValue));
}
private void setupBindings() {
volume.bind(createStringBinding(() -> formatFiatWithCode(model.volumeAsFiat.get()), model.volumeAsFiat));
totalToPay.bind(createStringBinding(() -> formatCoinWithCode(model.totalToPayAsCoin.get()),
model.totalToPayAsCoin));
collateral.bind(createStringBinding(() -> formatCoinWithCode(model.collateralAsCoin.get()),
model.collateralAsCoin));
totalToPayAsCoin.bind(model.totalToPayAsCoin);
requestTakeOfferErrorMessage.bind(model.requestTakeOfferErrorMessage);
showTransactionPublishedScreen.bind(model.requestTakeOfferSuccess);
transactionId.bind(model.transactionId);
btcCode.bind(model.btcCode);
}
private void calculateVolume() {
setAmountToModel();
model.calculateVolume();
}
private void setAmountToModel() {
model.amountAsCoin.set(parseToCoinWith4Decimals(amount.get()));
}
private void validateInput() {
isTakeOfferButtonDisabled.set(!(isBtcInputValid(amount.get()).isValid &&
model.isMinAmountLessOrEqualAmount() &&
model.isWalletFunded.get())
);
}
private InputValidator.ValidationResult isBtcInputValid(String input) {
return btcValidator.validate(input);
}
}

View File

@ -17,192 +17,248 @@
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
-->
<?import io.bitsquare.gui.components.ValidatedTextField?>
<?import io.bitsquare.gui.components.btc.AddressTextField?>
<?import io.bitsquare.gui.components.btc.BalanceTextField?>
<?import io.bitsquare.gui.components.InfoDisplay?>
<?import io.bitsquare.gui.components.InputTextField?>
<?import io.bitsquare.gui.components.TitledGroupBg?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.trade.takeoffer.TakeOfferController"
<?import javafx.scene.text.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.trade.takeoffer.TakeOfferViewCB"
AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">
<Accordion fx:id="accordion" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<panes>
<TitledPane fx:id="takeOfferTitledPane" text="Offer details">
<ScrollPane fitToWidth="true">
<GridPane hgap="5.0" vgap="5.0">
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
</padding>
<ScrollPane fx:id="scrollPane" hbarPolicy="NEVER" vbarPolicy="NEVER" fitToWidth="true" fitToHeight="true"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
AnchorPane.bottomAnchor="0.0">
<Label text="Take offer:" id="form-header-text"/>
<!--suppress JavaFxUnresolvedFxIdReference -->
<GridPane fx:id="gridPane" hgap="5.0" vgap="5.0">
<padding>
<Insets bottom="-10.0" left="25.0" top="30.0" right="25"/>
</padding>
<Label text="Amount in BTC:" GridPane.rowIndex="1"/>
<ValidatedTextField fx:id="amountTextField" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
<!--
Amount/Price group
-->
<TitledGroupBg fx:id="priceAmountPane" text="%takeOffer.amountPriceBox.title"
GridPane.rowSpan="3" GridPane.columnSpan="3"/>
<Label text="Price (EUR/BTC):" GridPane.rowIndex="2"/>
<TextField fx:id="priceTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="2" GridPane.columnIndex="1"/>
<Label text="Amount in EUR:" GridPane.rowIndex="3"/>
<TextField fx:id="volumeTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="3" GridPane.columnIndex="1"/>
<Label fx:id="collateralLabel" text="Collateral:" GridPane.rowIndex="4"/>
<TextField fx:id="collateralTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="4" GridPane.columnIndex="1"/>
<Label text="Total Fees (Offer + tx):" GridPane.rowIndex="5"/>
<TextField fx:id="feeTextField" editable="false" focusTraversable="false" GridPane.rowIndex="5"
GridPane.columnIndex="1"/>
<Label text="Total:" GridPane.rowIndex="6"/>
<TextField fx:id="totalTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="6" GridPane.columnIndex="1"/>
<Button fx:id="takeOfferButton" text="Take offer and pay" onAction="#onTakeOffer"
defaultButton="true" GridPane.rowIndex="7" GridPane.columnIndex="1"/>
<Label text="Offer details:" id="form-header-text" GridPane.rowIndex="8"/>
<Label text="Bank account type:" GridPane.rowIndex="9"/>
<TextField fx:id="bankAccountTypeTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="9" GridPane.columnIndex="1"/>
<Label text="Country:" GridPane.rowIndex="10"/>
<TextField fx:id="countryTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="10" GridPane.columnIndex="1"/>
<Label text="Arbitrators:" GridPane.rowIndex="11"/>
<TextField fx:id="arbitratorsTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="11" GridPane.columnIndex="1"/>
<Label text="Supported languages:" GridPane.rowIndex="12"/>
<TextField fx:id="supportedLanguagesTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="12" GridPane.columnIndex="1"/>
<Label text="Supported countries:" GridPane.rowIndex="13"/>
<TextField fx:id="supportedCountriesTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="13" GridPane.columnIndex="1"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
</rowConstraints>
</GridPane>
</ScrollPane>
</TitledPane>
<TitledPane fx:id="waitBankTxTitledPane" text="Wait for bank transfer">
<GridPane hgap="5.0" vgap="5.0">
<VBox alignment="CENTER" spacing="6" GridPane.rowSpan="2">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="20.0"/>
</GridPane.margin>
<ImageView fx:id="imageView" pickOnBounds="true"/>
<Label fx:id="buyLabel" id="direction-icon-label" text="%takeOffer.amountPriceBox.subTitle"
alignment="CENTER">
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
<Insets top="-5.0"/>
</padding>
</Label>
</VBox>
<Label fx:id="headLineLabel" text="Deposit transaction published" id="form-header-text"/>
<Label text="Status information:" GridPane.rowIndex="1" GridPane.valignment="TOP"/>
<Label fx:id="infoLabel" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.valignment="TOP"/>
<Label text="Deposit transaction ID:" GridPane.rowIndex="2"/>
<TextField fx:id="depositTxIdTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="2" GridPane.columnIndex="1"/>
<Button fx:id="receivedFiatButton" text="I have received the money at my bank account"
onAction="#onReceivedFiat" defaultButton="true" disable="true" GridPane.rowIndex="3"
GridPane.columnIndex="1"/>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
</rowConstraints>
</GridPane>
</TitledPane>
<TitledPane fx:id="summaryTitledPane" text="Summary">
<GridPane hgap="5.0" vgap="5.0">
<HBox GridPane.columnIndex="1" alignment="CENTER_LEFT" spacing="5">
<GridPane.margin>
<Insets right="10.0" top="20.0"/>
</GridPane.margin>
<VBox spacing="4">
<Label id="input-description-label" text="%takeOffer.amountPriceBox.amountDescription"
prefWidth="170"/>
<HBox>
<InputTextField fx:id="amountTextField" id="text-input-with-currency-text-field"
promptText="%takeOffer.amount.prompt" prefWidth="170"
alignment="CENTER_RIGHT"/>
<Label fx:id="amountBtcLabel" id="currency-info-label"/>
</HBox>
</VBox>
<Label text="x">
<font>
<Font name="Helvetica" size="20.0"/>
</font>
<padding>
<Insets left="10" right="10" top="10" bottom="10"/>
<Insets top="14.0" left="3" right="3"/>
</padding>
</Label>
<Label text="Trade completed" id="form-header-text"/>
<VBox spacing="4">
<Label fx:id="priceDescriptionLabel" id="input-description-label" prefWidth="170"/>
<TextField fx:id="priceTextField" id="text-input-with-currency-text-field"
prefWidth="170" alignment="CENTER_RIGHT" editable="false"/>
</VBox>
<Label text="You have sold:" GridPane.rowIndex="2"/>
<ValidatedTextField fx:id="summaryPaidTextField" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
<Label text="=">
<font>
<Font name="Helvetica" size="20.0"/>
</font>
<padding>
<Insets top="14.0" left="2" right="2"/>
</padding>
</Label>
<Label text="You have received (EUR):" GridPane.rowIndex="3"/>
<TextField fx:id="summaryReceivedTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="3" GridPane.columnIndex="1"/>
<VBox spacing="4">
<Label fx:id="volumeDescriptionLabel" id="input-description-label" prefWidth="170"/>
<TextField fx:id="volumeTextField" id="text-input-with-currency-text-field"
prefWidth="170" alignment="CENTER_RIGHT" editable="false"/>
</VBox>
</HBox>
<Label text="Details" GridPane.rowIndex="4" id="form-header-text"/>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="1" spacing="4">
<GridPane.margin>
<Insets right="10.0" top="5.0" bottom="5.0"/>
</GridPane.margin>
<Label id="input-description-label" text="%takeOffer.amountPriceBox.minAmountDescription"
prefWidth="170.0"/>
<TextField fx:id="minAmountTextField" id="text-input-with-currency-text-field"
prefWidth="170.0" editable="false"/>
</VBox>
<Label text="Total fees (take offer fee + tx fee):" GridPane.rowIndex="5"/>
<TextField fx:id="summaryFeesTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="5" GridPane.columnIndex="1"/>
<InfoDisplay gridPane="$gridPane" onAction="#onOpenGeneralHelp" rowIndex="2" prefWidth="740"
text="%takeOffer.amountPriceBox.info"/>
<Label text="Refunded collateral:" GridPane.rowIndex="6"/>
<TextField fx:id="summaryCollateralTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="6" GridPane.columnIndex="1"/>
<Button fx:id="showPaymentInfoScreenButton" text="%takeOffer.amountPriceBox.next" id="show-details-button"
GridPane.columnIndex="1" GridPane.rowIndex="3" defaultButton="true"
onAction="#onShowPayFundsScreen">
<GridPane.margin>
<Insets top="15.0"/>
</GridPane.margin>
</Button>
<Label text="Deposit transaction ID:" GridPane.rowIndex="7"/>
<TextField fx:id="summaryDepositTxIdTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="7" GridPane.columnIndex="1"/>
<!--
Pay funds group
-->
<TitledGroupBg fx:id="payFundsPane" text="%takeOffer.fundsBox.title" GridPane.rowIndex="4"
GridPane.rowSpan="4" GridPane.columnSpan="3" visible="false"/>
<Label text="Payout transaction ID:" GridPane.rowIndex="8"/>
<TextField fx:id="summaryPayoutTxIdTextField" editable="false" focusTraversable="false"
GridPane.rowIndex="8" GridPane.columnIndex="1"/>
<HBox GridPane.rowIndex="4" spacing="4" alignment="CENTER_RIGHT">
<Label fx:id="totalToPayLabel" text="%takeOffer.fundsBox.totalsNeeded" visible="false"/>
<Label fx:id="totalToPayInfoIconLabel" visible="false"/>
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</HBox>
<TextField fx:id="totalToPayTextField" promptText="%takeOffer.fundsBox.totalsNeeded.prompt"
GridPane.columnIndex="1"
GridPane.rowIndex="4"
editable="false" focusTraversable="false" visible="false">
<GridPane.margin>
<Insets top="10.0"/>
</GridPane.margin>
</TextField>
<Label fx:id="addressLabel" text="%takeOffer.fundsBox.address" GridPane.rowIndex="5" visible="false"/>
<AddressTextField fx:id="addressTextField" GridPane.columnIndex="1" GridPane.rowIndex="5"
focusTraversable="true" visible="false"/>
<Button text="Close" onAction="#onClose" defaultButton="true" GridPane.rowIndex="9"
GridPane.columnIndex="1"/>
<Label fx:id="balanceLabel" text="%takeOffer.fundsBox.balance" GridPane.rowIndex="6" visible="false">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</Label>
<BalanceTextField fx:id="balanceTextField" GridPane.columnIndex="1" GridPane.rowIndex="6"
focusTraversable="false" visible="false">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</BalanceTextField>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0"/>
</columnConstraints>
<InfoDisplay fx:id="fundsBoxInfoDisplay" gridPane="$gridPane" onAction="#onOpenFundingHelp" rowIndex="7"
text="%takeOffer.fundsBox.info" visible="false" prefWidth="740"/>
<rowConstraints>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
<RowConstraints minHeight="30.0" prefHeight="30.0" vgrow="NEVER"/>
</rowConstraints>
<HBox spacing="10" GridPane.columnIndex="1" GridPane.rowIndex="8">
<Button fx:id="showAdvancedSettingsButton" text="%takeOffer.fundsBox.showAdvanced"
onAction="#onToggleShowAdvancedSettings" visible="false"/>
<Button fx:id="takeOfferButton" text="%takeOffer.fundsBox.takeOffer" visible="false"
defaultButton="true"
onAction="#onTakeOffer"/>
<GridPane.margin>
<Insets bottom="30" top="15.0"/>
</GridPane.margin>
</HBox>
</GridPane>
</TitledPane>
<!--
Advanced settings group
-->
<TitledGroupBg fx:id="showDetailsPane" text="%takeOffer.advancedBox.title" GridPane.columnSpan="3"
GridPane.rowIndex="9" GridPane.rowSpan="7" visible="false"/>
</panes>
</Accordion>
<Label fx:id="acceptedCountriesLabel" text="%takeOffer.advancedBox.countries" GridPane.rowIndex="9"
visible="false">
<GridPane.margin>
<Insets top="0.0"/>
</GridPane.margin>
</Label>
<TextField fx:id="acceptedCountriesTextField" GridPane.columnIndex="1" GridPane.rowIndex="9"
visible="false"
editable="false" focusTraversable="false"/>
<Label fx:id="acceptedLanguagesLabel" text="%takeOffer.advancedBox.languages" GridPane.rowIndex="10"
visible="false"/>
<TextField fx:id="acceptedLanguagesTextField" GridPane.columnIndex="1" GridPane.rowIndex="10"
editable="false" focusTraversable="false" visible="false"/>
<Label fx:id="acceptedArbitratorsLabel" text="%takeOffer.advancedBox.arbitrators" GridPane.rowIndex="11"
visible="false"/>
<TextField fx:id="acceptedArbitratorsTextField" GridPane.columnIndex="1" GridPane.rowIndex="11"
editable="false" focusTraversable="false" visible="false"/>
<Label fx:id="bankAccountTypeLabel" text="%takeOffer.advancedBox.txType" GridPane.rowIndex="12"
visible="false"/>
<TextField fx:id="bankAccountTypeTextField" GridPane.columnIndex="1" GridPane.rowIndex="12" editable="false"
focusTraversable="false" visible="false"/>
<Label fx:id="bankAccountCurrencyLabel" text="%takeOffer.advancedBox.currency" GridPane.rowIndex="13"
visible="false"/>
<TextField fx:id="bankAccountCurrencyTextField" GridPane.rowIndex="13" GridPane.columnIndex="1"
editable="false" focusTraversable="false" visible="false"/>
<Label fx:id="bankAccountCountyLabel" text="%takeOffer.advancedBox.county" GridPane.rowIndex="14"
visible="false">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</Label>
<TextField fx:id="bankAccountCountyTextField" GridPane.rowIndex="14" GridPane.columnIndex="1"
editable="false" focusTraversable="false" visible="false">
<GridPane.margin>
<Insets bottom="5.0"/>
</GridPane.margin>
</TextField>
<InfoDisplay fx:id="advancedInfoDisplay" gridPane="$gridPane" onAction="#onOpenAdvancedSettingsHelp"
rowIndex="15" visible="false" prefWidth="740"
text="%takeOffer.advancedBox.info">
</InfoDisplay>
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" minWidth="200"/>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints/>
<RowConstraints minHeight="35"/>
</rowConstraints>
</GridPane>
</ScrollPane>
</AnchorPane>

View File

@ -0,0 +1,463 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.trade.takeoffer;
import io.bitsquare.gui.CachedViewCB;
import io.bitsquare.gui.CloseListener;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.OverlayManager;
import io.bitsquare.gui.components.InfoDisplay;
import io.bitsquare.gui.components.InputTextField;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.components.TitledGroupBg;
import io.bitsquare.gui.components.btc.AddressTextField;
import io.bitsquare.gui.components.btc.BalanceTextField;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.gui.main.trade.OrderBookInfo;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.locale.BSResources;
import io.bitsquare.trade.Direction;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.VPos;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.stage.Window;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import org.controlsfx.control.PopOver;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TakeOfferViewCB extends CachedViewCB<TakeOfferPM> {
private static final Logger log = LoggerFactory.getLogger(TakeOfferViewCB.class);
private Navigation navigation;
private OverlayManager overlayManager;
private CloseListener closeListener;
private boolean detailsVisible;
private boolean advancedScreenInited;
private ImageView expand;
private ImageView collapse;
private PopOver totalToPayInfoPopover;
@FXML InfoDisplay advancedInfoDisplay, fundsBoxInfoDisplay;
@FXML ScrollPane scrollPane;
@FXML ImageView imageView;
@FXML TitledGroupBg priceAmountPane, payFundsPane, showDetailsPane;
@FXML Label buyLabel, addressLabel,
balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
bankAccountTypeLabel, bankAccountCurrencyLabel, bankAccountCountyLabel,
acceptedCountriesLabel, acceptedLanguagesLabel,
acceptedArbitratorsLabel, amountBtcLabel,
priceDescriptionLabel, volumeDescriptionLabel;
@FXML Button showPaymentInfoScreenButton, showAdvancedSettingsButton, takeOfferButton;
@FXML InputTextField amountTextField;
@FXML TextField minAmountTextField, priceTextField, volumeTextField, acceptedArbitratorsTextField,
totalToPayTextField,
bankAccountTypeTextField,
bankAccountCurrencyTextField, bankAccountCountyTextField, acceptedCountriesTextField,
acceptedLanguagesTextField;
@FXML AddressTextField addressTextField;
@FXML BalanceTextField balanceTextField;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private TakeOfferViewCB(TakeOfferPM presentationModel, Navigation navigation,
OverlayManager overlayManager) {
super(presentationModel);
this.navigation = navigation;
this.overlayManager = overlayManager;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void initialize(URL url, ResourceBundle rb) {
super.initialize(url, rb);
setupListeners();
setupBindings();
}
@SuppressWarnings("EmptyMethod")
public void activate() {
super.activate();
}
@SuppressWarnings("EmptyMethod")
public void deactivate() {
super.deactivate();
}
@Override
public void terminate() {
super.terminate();
// Inform parent that we gor removed.
// Needed to reset disable state of createOfferButton in OrderBookController
if (closeListener != null)
closeListener.onClosed();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods (called form other views/CB)
///////////////////////////////////////////////////////////////////////////////////////////
public void initWithOrderBookInfo(OrderBookInfo orderBookInfo) {
presentationModel.setOrderBookInfo(orderBookInfo);
if (orderBookInfo.getDirection() == Direction.BUY)
imageView.setId("image-buy-large");
else
imageView.setId("image-sell-large");
priceDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.priceDescription",
presentationModel.getFiatCode()));
volumeDescriptionLabel.setText(BSResources.get("takeOffer.amountPriceBox.volumeDescription",
presentationModel.getFiatCode()));
balanceTextField.setup(presentationModel.getWalletFacade(), presentationModel.address.get());
buyLabel.setText(presentationModel.getDirectionLabel());
minAmountTextField.setText(presentationModel.getMinAmount());
priceTextField.setText(presentationModel.getPrice());
addressTextField.setPaymentLabel(presentationModel.getPaymentLabel());
addressTextField.setAddress(presentationModel.getAddressAsString());
bankAccountTypeTextField.setText(presentationModel.getBankAccountType());
bankAccountTypeTextField.setText(presentationModel.getBankAccountType());
bankAccountCurrencyTextField.setText(presentationModel.getBankAccountCurrency());
bankAccountCountyTextField.setText(presentationModel.getBankAccountCounty());
acceptedCountriesTextField.setText(presentationModel.getAcceptedCountries());
acceptedLanguagesTextField.setText(presentationModel.getAcceptedLanguages());
acceptedArbitratorsTextField.setText(presentationModel.getAcceptedArbitrators());
}
//TODO not used yet
public void setCloseListener(CloseListener closeListener) {
this.closeListener = closeListener;
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI Handlers
///////////////////////////////////////////////////////////////////////////////////////////
@FXML
void onTakeOffer() {
presentationModel.takeOffer();
}
@FXML
void onShowPayFundsScreen() {
priceAmountPane.setInactive();
showPaymentInfoScreenButton.setVisible(false);
payFundsPane.setVisible(true);
totalToPayLabel.setVisible(true);
totalToPayInfoIconLabel.setVisible(true);
totalToPayTextField.setVisible(true);
addressLabel.setVisible(true);
addressTextField.setVisible(true);
balanceLabel.setVisible(true);
balanceTextField.setVisible(true);
fundsBoxInfoDisplay.setVisible(true);
showAdvancedSettingsButton.setVisible(true);
if (expand == null) {
expand = ImageUtil.getImageViewById(ImageUtil.EXPAND);
collapse = ImageUtil.getImageViewById(ImageUtil.COLLAPSE);
}
showAdvancedSettingsButton.setGraphic(expand);
setupTotalToPayInfoIconLabel();
presentationModel.onShowPayFundsScreen();
}
@FXML
void onToggleShowAdvancedSettings() {
detailsVisible = !detailsVisible;
if (detailsVisible) {
showAdvancedSettingsButton.setText(BSResources.get("takeOffer.fundsBox.hideAdvanced"));
showAdvancedSettingsButton.setGraphic(collapse);
showDetailsScreen();
}
else {
showAdvancedSettingsButton.setText(BSResources.get("takeOffer.fundsBox.showAdvanced"));
showAdvancedSettingsButton.setGraphic(expand);
hideDetailsScreen();
}
}
@FXML
void onOpenGeneralHelp() {
Help.openWindow(HelpId.TAKE_OFFER_GENERAL);
}
@FXML
void onOpenFundingHelp() {
Help.openWindow(HelpId.TAKE_OFFER_FUNDING);
}
@FXML
void onOpenAdvancedSettingsHelp() {
Help.openWindow(HelpId.TAKE_OFFER_ADVANCED);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Navigation
///////////////////////////////////////////////////////////////////////////////////////////
private void close() {
TabPane tabPane = ((TabPane) (root.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private Methods
///////////////////////////////////////////////////////////////////////////////////////////
private void setupListeners() {
scrollPane.setOnScroll(e -> InputTextField.hideErrorMessageDisplay());
// focus out
amountTextField.focusedProperty().addListener((o, oldValue, newValue) -> {
presentationModel.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
amountTextField.setText(presentationModel.amount.get());
});
// warnings
presentationModel.showWarningInvalidBtcDecimalPlaces.addListener((o, oldValue, newValue) -> {
if (newValue) {
Popups.openWarningPopup(BSResources.get("shared.warning"),
BSResources.get("takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces"));
presentationModel.showWarningInvalidBtcDecimalPlaces.set(false);
}
});
presentationModel.requestTakeOfferErrorMessage.addListener((o, oldValue, newValue) -> {
if (newValue != null) {
Popups.openErrorPopup(BSResources.get("shared.error"),
BSResources.get("takeOffer.amountPriceBox.error.message",
presentationModel.requestTakeOfferErrorMessage.get()));
}
});
presentationModel.showTransactionPublishedScreen.addListener((o, oldValue, newValue) -> {
if (newValue) {
overlayManager.blurContent();
// Dialogs are a bit limited. There is no callback for the InformationDialog button click, so we added
// our own actions.
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.copyTxId")) {
@Override
public void handle(ActionEvent actionEvent) {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(presentationModel.transactionId.get());
clipboard.setContent(content);
}
});
actions.add(new AbstractAction(BSResources.get("shared.close")) {
@Override
public void handle(ActionEvent actionEvent) {
try {
close();
} catch (Exception e) {
e.printStackTrace();
}
Dialog.Actions.CLOSE.handle(actionEvent);
overlayManager.removeBlurContent();
}
});
Popups.openInfo(BSResources.get("takeOffer.success.info",
presentationModel.transactionId.get()),
BSResources.get("takeOffer.success.headline"),
actions);
}
});
}
private void setupBindings() {
amountBtcLabel.textProperty().bind(presentationModel.btcCode);
amountTextField.textProperty().bindBidirectional(presentationModel.amount);
volumeTextField.textProperty().bindBidirectional(presentationModel.volume);
totalToPayTextField.textProperty().bind(presentationModel.totalToPay);
addressTextField.amountAsCoinProperty().bind(presentationModel.totalToPayAsCoin);
// Validation
amountTextField.validationResultProperty().bind(presentationModel.amountValidationResult);
// buttons
takeOfferButton.visibleProperty().bind(presentationModel.isTakeOfferButtonVisible);
takeOfferButton.disableProperty().bind(presentationModel.isTakeOfferButtonDisabled);
}
private void showDetailsScreen() {
payFundsPane.setInactive();
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.layout();
advancedScreenInited = !advancedScreenInited;
toggleDetailsScreen(true);
}
private void hideDetailsScreen() {
payFundsPane.setActive();
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.layout();
toggleDetailsScreen(false);
}
private void toggleDetailsScreen(boolean visible) {
scrollPane.setOnScroll(scrollEvent -> {
if (!visible)
scrollEvent.consume();
});
// deactivate mouse wheel scrolling if hidden
scrollPane.setVmax(visible ? scrollPane.getHeight() : 0);
scrollPane.setVvalue(visible ? scrollPane.getHeight() : 0);
showDetailsPane.setVisible(visible);
acceptedCountriesLabel.setVisible(visible);
acceptedCountriesTextField.setVisible(visible);
acceptedLanguagesLabel.setVisible(visible);
acceptedLanguagesTextField.setVisible(visible);
acceptedArbitratorsLabel.setVisible(visible);
acceptedArbitratorsTextField.setVisible(visible);
bankAccountTypeLabel.setVisible(visible);
bankAccountTypeTextField.setVisible(visible);
bankAccountCurrencyLabel.setVisible(visible);
bankAccountCurrencyTextField.setVisible(visible);
bankAccountCountyLabel.setVisible(visible);
bankAccountCountyTextField.setVisible(visible);
advancedInfoDisplay.setVisible(visible);
}
private void setupTotalToPayInfoIconLabel() {
totalToPayInfoIconLabel.setId("clickable-icon");
AwesomeDude.setIcon(totalToPayInfoIconLabel, AwesomeIcon.QUESTION_SIGN);
totalToPayInfoIconLabel.setOnMouseEntered(e -> createInfoPopover());
totalToPayInfoIconLabel.setOnMouseExited(e -> {
if (totalToPayInfoPopover != null)
totalToPayInfoPopover.hide();
});
}
// As we don't use binding here we need to recreate it on mouse over to reflect the current state
private void createInfoPopover() {
GridPane infoGridPane = new GridPane();
infoGridPane.setHgap(5);
infoGridPane.setVgap(5);
infoGridPane.setPadding(new Insets(10, 10, 10, 10));
addPayInfoEntry(infoGridPane, 0,
BSResources.get("takeOffer.fundsBox.amount"),
presentationModel.amount.get());
addPayInfoEntry(infoGridPane, 1,
presentationModel.getCollateralLabel(),
presentationModel.collateral.get());
addPayInfoEntry(infoGridPane, 2, BSResources.get("takeOffer.fundsBox.offerFee"),
presentationModel.getOfferFee());
addPayInfoEntry(infoGridPane, 3, BSResources.get("takeOffer.fundsBox.networkFee"),
presentationModel.getNetworkFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
separator.setStyle("-fx-background: #666;");
GridPane.setConstraints(separator, 1, 4);
infoGridPane.getChildren().add(separator);
addPayInfoEntry(infoGridPane, 5, BSResources.get("takeOffer.fundsBox.total"),
presentationModel.totalToPay.get());
totalToPayInfoPopover = new PopOver(infoGridPane);
if (totalToPayInfoIconLabel.getScene() != null) {
totalToPayInfoPopover.setDetachable(false);
totalToPayInfoPopover.setArrowIndent(5);
totalToPayInfoPopover.show(totalToPayInfoIconLabel.getScene().getWindow(),
getPopupPosition().getX(),
getPopupPosition().getY());
}
}
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
Label label = new Label(labelText);
TextField textField = new TextField(value);
textField.setEditable(false);
textField.setFocusTraversable(false);
textField.setId("payment-info");
GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER);
GridPane.setConstraints(textField, 1, row);
infoGridPane.getChildren().addAll(label, textField);
}
private Point2D getPopupPosition() {
Window window = totalToPayInfoIconLabel.getScene().getWindow();
Point2D point = totalToPayInfoIconLabel.localToScene(0, 0);
double x = point.getX() + window.getX() + totalToPayInfoIconLabel.getWidth() - 3;
double y = point.getY() + window.getY() + Math.floor(totalToPayInfoIconLabel.getHeight() / 2) - 9;
return new Point2D(x, y);
}
}

View File

@ -35,7 +35,7 @@ createOffer.amountPriceBox.amountDescription=Amount of Bitcoin to buy
createOffer.amountPriceBox.priceDescription=Price per Bitcoin in {0}
createOffer.amountPriceBox.volumeDescription=Amount in {0} to spend
createOffer.amountPriceBox.minAmountDescription=Minimum amount of Bitcoin
createOffer.amountPriceBox.info=Define a price for which you want to byu Bitcoin and either enter the amount or the trade volume. With the minimum amount you can attract more potential traders with giving them more flexibility. But note that there is no automatic creation of a new offer for the remaining amount in the case that a trader takes your offer with a lower amount as defined in the amount field. Your offer will be removed from the orderbook once a trader has taken your offer.
createOffer.amountPriceBox.info=Define a price for which you want to buy Bitcoin and either enter the amount or the trade volume. With the minimum amount you can attract more potential traders with giving them more flexibility. But note that there is no automatic creation of a new offer for the remaining amount in the case that a trader takes your offer with a lower amount as defined in the amount field. Your offer will be removed from the orderbook once a trader has taken your offer.
createOffer.amountPriceBox.next=Next step
createOffer.amountPriceBox.warning.invalidBtcDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places.\nThe amount has been adjusted to 4 decimal places.
createOffer.amountPriceBox.warning.invalidFiatDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places. The amount has been adjusted to 2 decimal places.
@ -73,6 +73,57 @@ createOffer.success.info=The Transaction ID for the offer payment is: {0}
createOffer.error.message=An error occurred when placing the offer.\n{0}
# Take offer
takeOffer.amount.prompt=Enter amount in BTC
takeOffer.price.prompt=Enter price
takeOffer.volume.prompt=Enter amount in {0}
takeOffer.minAmount.prompt=Enter min. amount
takeOffer.amountPriceBox.title=Take offer
takeOffer.amountPriceBox.subTitle=Buy Bitcoin
takeOffer.amountPriceBox.amountDescription=Amount of Bitcoin to sell
takeOffer.amountPriceBox.priceDescription=Price per Bitcoin in {0}
takeOffer.amountPriceBox.volumeDescription=Receiving amount in {0}
takeOffer.amountPriceBox.minAmountDescription=The offer requires that minimum amount of Bitcoin
takeOffer.amountPriceBox.info=Enter the amount of Bitcoin you want to sell. You can choose an amount between the minimum amount and the amount.
takeOffer.amountPriceBox.next=Next step
takeOffer.amountPriceBox.warning.invalidBtcDecimalPlaces=The amount you have entered exceeds the number of allowed decimal places.\nThe amount has been adjusted to 4 decimal places.
takeOffer.amountPriceBox.error.message=An error occurred when taking the offer:\n\n {0}
takeOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than minimum amount.
takeOffer.fundsBox.title=Fund your trade wallet
takeOffer.fundsBox.totalsNeeded=Funds needed for that trade:
takeOffer.fundsBox.totalsNeeded.prompt=Will be calculated from the Bitcoin amount entered above
takeOffer.fundsBox.address=Trade wallet address:
takeOffer.fundsBox.balance=Trade wallet balance:
takeOffer.fundsBox.info=For every offer there is a dedicated trade wallet. You need to fund that trade wallet with the necessary Bitcoin amount. Those funds will be paid in to a locked deposit address. At the end of a successful trade you will get back your collateral and the Bitcoin amount you sold will be transferred to the buyer.
takeOffer.fundsBox.amount=Amount to sell:
takeOffer.fundsBox.collateral=Refundable collateral ({0}):
takeOffer.fundsBox.offerFee=Offer fee:
takeOffer.fundsBox.networkFee=Bitcoin network fee:
takeOffer.fundsBox.total=Total:
takeOffer.fundsBox.showAdvanced=Show advanced settings
takeOffer.fundsBox.hideAdvanced=Hide advanced settings
takeOffer.fundsBox.takeOffer=Take offer
takeOffer.fundsBox.paymentLabel=Bitsquare trade ({0})
takeOffer.advancedBox.title=Advanced settings
takeOffer.advancedBox.countries=Accepted countries:
takeOffer.advancedBox.languages=Accepted languages:
takeOffer.advancedBox.arbitrators=Accepted arbitrators:
takeOffer.advancedBox.txType=Payments method:
takeOffer.advancedBox.currency=Currency:
takeOffer.advancedBox.county=Payments account country:
takeOffer.advancedBox.info=These are the offer restrictions your trading partner has defined in his offer. Your \
settings are matching those constraints and you are able to trade with him.
takeOffer.success.headline=Your have successfully published the deposit.
takeOffer.success.info=You need to wait now for the Bitcoin buyer to transfer the money to you. \nYou will get a \
notification when he has started the EUR payment.You can see the status of your trade in the orders section under \
pending orders.\n\nThe Transaction ID for the deposit payment is: {0}
takeOffer.error.message=An error occurred when taking the offer.\n{0}
# TODO Update the following string when doing the UI (old stuff...)
# generic