Add isOnline check at take offer screen #313, fix #315

This commit is contained in:
Manfred Karrer 2014-12-01 02:57:23 +01:00
parent f903491abb
commit 5645f9ba10
10 changed files with 225 additions and 22 deletions

View File

@ -234,8 +234,10 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void close() {
TabPane tabPane = ((TabPane) (root.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
// Might fix #315 Offerbook tab gets closed
// tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
tabPane.getTabs().remove(1);
navigation.navigateTo(MainView.class, PortfolioView.class, OffersView.class);
}

View File

@ -21,11 +21,16 @@ import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.msg.MessageService;
import io.bitsquare.msg.listeners.GetPeerAddressListener;
import io.bitsquare.msg.listeners.OutgoingMessageListener;
import io.bitsquare.network.Peer;
import io.bitsquare.offer.Offer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.settings.Preferences;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.protocol.trade.taker.messages.RequestIsOfferAvailableMessage;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
@ -56,8 +61,15 @@ import org.slf4j.LoggerFactory;
class TakeOfferDataModel implements Activatable, DataModel {
private static final Logger log = LoggerFactory.getLogger(TakeOfferDataModel.class);
enum OfferAvailableState {
UNKNOWN,
OFFER_NOT_AVAILABLE,
OFFER_AVAILABLE
}
private final TradeManager tradeManager;
private final WalletService walletService;
private MessageService messageService;
private final Preferences preferences;
private final Persistence persistence;
@ -79,12 +91,19 @@ class TakeOfferDataModel implements Activatable, DataModel {
final ObjectProperty<Coin> offerFeeAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<Coin> networkFeeAsCoin = new SimpleObjectProperty<>();
final ObjectProperty<OfferAvailableState> offerIsAvailable = new SimpleObjectProperty<>(OfferAvailableState
.UNKNOWN);
//
private boolean isActivated;
@Inject
public TakeOfferDataModel(TradeManager tradeManager, WalletService walletService, Preferences preferences,
public TakeOfferDataModel(TradeManager tradeManager, WalletService walletService, MessageService messageService,
Preferences preferences,
Persistence persistence) {
this.tradeManager = tradeManager;
this.walletService = walletService;
this.messageService = messageService;
this.preferences = preferences;
this.persistence = persistence;
@ -94,11 +113,13 @@ class TakeOfferDataModel implements Activatable, DataModel {
@Override
public void activate() {
isActivated = true;
btcCode.bind(preferences.btcDenominationProperty());
}
@Override
public void deactivate() {
isActivated = false;
btcCode.unbind();
}
@ -126,8 +147,50 @@ class TakeOfferDataModel implements Activatable, DataModel {
}
});
updateBalance(walletService.getBalanceForAddress(addressEntry.getAddress()));
getPeerAddress(offer);
}
// TODO: Should be moved to a domain and handled with add/remove listeners instead of isActivated
// or maybe with rx?
private void getPeerAddress(Offer offer) {
messageService.getPeerAddress(offer.getMessagePublicKey(), new GetPeerAddressListener() {
@Override
public void onResult(Peer peer) {
if (isActivated)
isOfferAvailable(peer, offer.getId());
}
@Override
public void onFailed() {
if (isActivated)
log.error("The offerers address have not been found. That should never happen.");
}
});
}
private void isOfferAvailable(Peer peer, String offerId) {
messageService.sendMessage(peer, new RequestIsOfferAvailableMessage(offerId),
new OutgoingMessageListener() {
@Override
public void onResult() {
if (isActivated) {
log.trace("RequestIsOfferAvailableMessage successfully arrived at peer");
offerIsAvailable.set(OfferAvailableState.OFFER_AVAILABLE);
}
}
@Override
public void onFailed() {
if (isActivated) {
log.error("RequestIsOfferAvailableMessage did not arrive at peer");
offerIsAvailable.set(OfferAvailableState.OFFER_NOT_AVAILABLE);
}
}
});
}
void takeOffer() {
final Trade trade = tradeManager.takeOffer(amountAsCoin.get(), offer);
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
@ -214,7 +277,6 @@ class TakeOfferDataModel implements Activatable, DataModel {
}
WalletService getWalletService() {
return walletService;
}

View File

@ -61,7 +61,8 @@
</Label>
</VBox>
<HBox GridPane.columnIndex="1" alignment="CENTER_LEFT" spacing="5">
<HBox GridPane.columnIndex="1" alignment="CENTER_LEFT" spacing="5"
>
<GridPane.margin>
<Insets right="10.0" top="20.0"/>
</GridPane.margin>
@ -108,7 +109,8 @@
</VBox>
</HBox>
<VBox GridPane.columnIndex="1" GridPane.rowIndex="1" spacing="4">
<VBox GridPane.columnIndex="1" GridPane.rowIndex="1" spacing="4"
>
<GridPane.margin>
<Insets right="10.0" top="5.0" bottom="5.0"/>
</GridPane.margin>
@ -121,8 +123,19 @@
<InfoDisplay gridPane="$gridPane" onAction="#onOpenGeneralHelp" rowIndex="2"
text="%takeOffer.amountPriceBox.info"/>
<Label fx:id="isOfferAvailableLabel" text="%takeOffer.fundsBox.isOfferAvailable" GridPane.rowIndex="3">
<GridPane.margin>
<Insets top="15.0"/>
</GridPane.margin>
</Label>
<ProgressIndicator fx:id="isOfferAvailableProgressIndicator" progress="-1" maxWidth="24" maxHeight="24"
GridPane.rowIndex="3" GridPane.columnIndex="1" GridPane.halignment="LEFT">
<GridPane.margin>
<Insets top="15.0"/>
</GridPane.margin>
</ProgressIndicator>
<Button fx:id="showPaymentInfoScreenButton" text="%takeOffer.amountPriceBox.next" id="show-details-button"
GridPane.columnIndex="1" GridPane.rowIndex="3" defaultButton="true"
GridPane.columnIndex="1" GridPane.rowIndex="3" defaultButton="true" visible="false"
onAction="#onShowPayFundsScreen">
<GridPane.margin>
<Insets top="15.0"/>

View File

@ -82,14 +82,13 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
@FXML TextField priceTextField, volumeTextField, acceptedArbitratorsTextField, totalToPayTextField,
bankAccountTypeTextField, bankAccountCurrencyTextField, bankAccountCountyTextField,
acceptedCountriesTextField, acceptedLanguagesTextField;
@FXML Label buyLabel, addressLabel, amountRangeTextField, balanceLabel, totalToPayLabel, totalToPayInfoIconLabel,
@FXML Label isOfferAvailableLabel, buyLabel, addressLabel, amountRangeTextField, balanceLabel, totalToPayLabel,
totalToPayInfoIconLabel,
bankAccountTypeLabel, bankAccountCurrencyLabel, bankAccountCountyLabel, acceptedCountriesLabel,
acceptedLanguagesLabel, acceptedArbitratorsLabel, amountBtcLabel, priceDescriptionLabel,
volumeDescriptionLabel, takeOfferSpinnerInfoLabel;
@FXML ProgressIndicator isOfferAvailableProgressIndicator;
private BooleanProperty tabIsClosable;
private boolean detailsVisible;
private boolean advancedScreenInited;
private ImageView expand;
private ImageView collapse;
private PopOver totalToPayInfoPopover;
@ -140,10 +139,26 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
acceptedCountriesTextField.setText(model.getAcceptedCountries());
acceptedLanguagesTextField.setText(model.getAcceptedLanguages());
acceptedArbitratorsTextField.setText(model.getAcceptedArbitrators());
model.offerIsAvailable.addListener((ov, oldValue, newValue) -> {
isOfferAvailableLabel.setVisible(false);
isOfferAvailableLabel.setManaged(false);
isOfferAvailableProgressIndicator.setProgress(0);
isOfferAvailableProgressIndicator.setVisible(false);
isOfferAvailableProgressIndicator.setManaged(false);
if ((newValue == TakeOfferDataModel.OfferAvailableState.OFFER_AVAILABLE)) {
showPaymentInfoScreenButton.setVisible(true);
}
else if ((newValue == TakeOfferDataModel.OfferAvailableState.OFFER_NOT_AVAILABLE)) {
Popups.openWarningPopup("You cannot take that offer",
"The offerer is either offline or the offer was already taken by another trader.");
close();
}
});
}
public void configCloseHandlers(BooleanProperty tabIsClosable) {
this.tabIsClosable = tabIsClosable;
tabIsClosable.bind(model.tabIsClosable);
}
@ -206,8 +221,8 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
@FXML
void onToggleShowAdvancedSettings() {
detailsVisible = !detailsVisible;
if (detailsVisible) {
model.detailsVisible = !model.detailsVisible;
if (model.detailsVisible) {
showAdvancedSettingsButton.setText(BSResources.get("takeOffer.fundsBox.hideAdvanced"));
showAdvancedSettingsButton.setGraphic(collapse);
showDetailsScreen();
@ -236,7 +251,10 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void close() {
TabPane tabPane = ((TabPane) (root.getParent().getParent()));
tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
// Might fix #315 Offerbook tab gets closed
//tabPane.getTabs().remove(tabPane.getSelectionModel().getSelectedItem());
tabPane.getTabs().remove(1);
}
private void setupListeners() {
@ -329,7 +347,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.layout();
advancedScreenInited = !advancedScreenInited;
model.advancedScreenInited = !model.advancedScreenInited;
toggleDetailsScreen(true);
}

View File

@ -64,6 +64,8 @@ class TakeOfferViewModel extends ActivatableWithDelegate<TakeOfferDataModel> imp
private final BSFormatter formatter;
private final String offerFee;
private final String networkFee;
boolean detailsVisible;
boolean advancedScreenInited;
final StringProperty amount = new SimpleStringProperty();
final StringProperty volume = new SimpleStringProperty();
@ -80,6 +82,8 @@ class TakeOfferViewModel extends ActivatableWithDelegate<TakeOfferDataModel> imp
final BooleanProperty showWarningInvalidBtcDecimalPlaces = new SimpleBooleanProperty();
final BooleanProperty showTransactionPublishedScreen = new SimpleBooleanProperty();
final BooleanProperty tabIsClosable = new SimpleBooleanProperty(true);
final ObjectProperty<TakeOfferDataModel.OfferAvailableState> offerIsAvailable =
new SimpleObjectProperty<>(TakeOfferDataModel.OfferAvailableState.UNKNOWN);
final ObjectProperty<InputValidator.ValidationResult> amountValidationResult = new SimpleObjectProperty<>();
@ -136,7 +140,6 @@ class TakeOfferViewModel extends ActivatableWithDelegate<TakeOfferDataModel> imp
bankAccountCounty = BSResources.get(offer.getBankAccountCountry().getName());
}
void takeOffer() {
delegate.requestTakeOfferErrorMessage.set(null);
delegate.requestTakeOfferSuccess.set(false);
@ -294,7 +297,8 @@ class TakeOfferViewModel extends ActivatableWithDelegate<TakeOfferDataModel> imp
delegate.volumeAsFiat));
totalToPay.bind(createStringBinding(() -> formatter.formatCoinWithCode(delegate.totalToPayAsCoin.get()),
delegate.totalToPayAsCoin));
securityDeposit.bind(createStringBinding(() -> formatter.formatCoinWithCode(delegate.securityDepositAsCoin.get()),
securityDeposit.bind(createStringBinding(() -> formatter.formatCoinWithCode(delegate.securityDepositAsCoin
.get()),
delegate.securityDepositAsCoin));
totalToPayAsCoin.bind(delegate.totalToPayAsCoin);
@ -302,6 +306,7 @@ class TakeOfferViewModel extends ActivatableWithDelegate<TakeOfferDataModel> imp
requestTakeOfferErrorMessage.bind(delegate.requestTakeOfferErrorMessage);
showTransactionPublishedScreen.bind(delegate.requestTakeOfferSuccess);
transactionId.bind(delegate.transactionId);
offerIsAvailable.bind(delegate.offerIsAvailable);
btcCode.bind(delegate.btcCode);
}

View File

@ -24,6 +24,7 @@ import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService;
import io.bitsquare.msg.Message;
import io.bitsquare.msg.MessageService;
import io.bitsquare.msg.listeners.OutgoingMessageListener;
import io.bitsquare.network.Peer;
import io.bitsquare.offer.Direction;
import io.bitsquare.offer.Offer;
@ -37,10 +38,12 @@ import io.bitsquare.trade.protocol.trade.offerer.BuyerAcceptsOfferProtocolListen
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferInitedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.RequestTakerDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.RespondToIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.RespondToTakeOfferRequestMessage;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocol;
import io.bitsquare.trade.protocol.trade.taker.SellerTakesOfferProtocolListener;
import io.bitsquare.trade.protocol.trade.taker.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.taker.messages.RequestIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.trade.taker.messages.RequestOffererPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.taker.messages.RequestTakeOfferMessage;
import io.bitsquare.trade.protocol.trade.taker.messages.TakeOfferFeePayedMessage;
@ -419,7 +422,25 @@ public class TradeManager {
String tradeId = tradeMessage.getTradeId();
if (tradeId != null) {
if (tradeMessage instanceof RequestTakeOfferMessage) {
if (tradeMessage instanceof RequestIsOfferAvailableMessage) {
// TODO Does not fit in any of the 2 protocols, but should not be here as well...
// Lets keep it until we refactor the trade process
boolean isOfferOpen = getTrade(tradeId) == null;
RespondToIsOfferAvailableMessage replyMessage =
new RespondToIsOfferAvailableMessage(tradeId, isOfferOpen);
messageService.sendMessage(sender, replyMessage, new OutgoingMessageListener() {
@Override
public void onResult() {
log.trace("RespondToTakeOfferRequestMessage successfully arrived at peer");
}
@Override
public void onFailed() {
log.error("AcceptTakeOfferRequestMessage did not arrive at peer");
}
});
}
else if (tradeMessage instanceof RequestTakeOfferMessage) {
createOffererAsBuyerProtocol(tradeId, sender);
}
else if (tradeMessage instanceof RespondToTakeOfferRequestMessage) {

View File

@ -0,0 +1,42 @@
/*
* 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.trade.protocol.trade.offerer.messages;
import io.bitsquare.trade.protocol.trade.TradeMessage;
import java.io.Serializable;
public class RespondToIsOfferAvailableMessage implements Serializable, TradeMessage {
private static final long serialVersionUID = 6177387534187739018L;
private final String tradeId;
private final boolean isOfferOpen;
public RespondToIsOfferAvailableMessage(String tradeId, boolean isOfferOpen) {
this.tradeId = tradeId;
this.isOfferOpen = isOfferOpen;
}
@Override
public String getTradeId() {
return tradeId;
}
public boolean isOfferOpen() {
return isOfferOpen;
}
}

View File

@ -33,17 +33,17 @@ public class HandleTakeOfferRequest {
public static void run(ResultHandler resultHandler, ExceptionHandler exceptionHandler, Peer peer,
MessageService messageService, Trade.State tradeState, String tradeId) {
log.trace("Run task");
boolean takeOfferRequestAccepted = tradeState == Trade.State.OPEN;
if (!takeOfferRequestAccepted) {
boolean isTradeIsOpen = tradeState == Trade.State.OPEN;
if (!isTradeIsOpen) {
log.warn("Received take offer request but the offer not marked as open anymore.");
}
RespondToTakeOfferRequestMessage tradeMessage =
new RespondToTakeOfferRequestMessage(tradeId, takeOfferRequestAccepted);
new RespondToTakeOfferRequestMessage(tradeId, isTradeIsOpen);
messageService.sendMessage(peer, tradeMessage, new OutgoingMessageListener() {
@Override
public void onResult() {
log.trace("RespondToTakeOfferRequestMessage successfully arrived at peer");
resultHandler.onResult(takeOfferRequestAccepted);
resultHandler.onResult(isTradeIsOpen);
}
@Override

View File

@ -0,0 +1,39 @@
/*
* 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.trade.protocol.trade.taker.messages;
import io.bitsquare.trade.protocol.trade.TradeMessage;
import java.io.Serializable;
// That msg is used to ping the offerer if he is online and if the offer is still available
public class RequestIsOfferAvailableMessage implements Serializable, TradeMessage {
private static final long serialVersionUID = 4630151440192191798L;
private final String tradeId;
public RequestIsOfferAvailableMessage(String tradeId) {
this.tradeId = tradeId;
}
@Override
public String getTradeId() {
return tradeId;
}
}

View File

@ -96,6 +96,7 @@ takeOffer.validation.amountSmallerThanMinAmount=Amount cannot be smaller than mi
takeOffer.validation.amountLargerThanOfferAmount=Input amount cannot be higher than the amount defined in the offer.
takeOffer.fundsBox.title=Fund your trade wallet
takeOffer.fundsBox.isOfferAvailable=Check if offer is available:
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: