Fixed state handling in views

This commit is contained in:
Manfred Karrer 2015-03-26 11:51:42 +01:00
parent 9936dca851
commit ccd2e6b676
10 changed files with 235 additions and 168 deletions

View file

@ -78,7 +78,7 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
case FAILED: case FAILED:
return "Failed"; return "Failed";
case PENDING: case PENDING:
throw new RuntimeException("Wrong state: " + lifeCycleState); throw new RuntimeException("That must not happen. We got a pending state but we are in the closed trades list.");
} }
} }
else if (lifeCycleState instanceof OffererTrade.OffererLifeCycleState) { else if (lifeCycleState instanceof OffererTrade.OffererLifeCycleState) {
@ -91,11 +91,10 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
return "Failed"; return "Failed";
case OPEN_OFFER: case OPEN_OFFER:
case PENDING: case PENDING:
throw new RuntimeException("Wrong state: " + lifeCycleState); throw new RuntimeException("That must not happen. We got a pending state but we are in the closed trades list.");
} }
} }
throw new RuntimeException("That must not happen. We got no defined state.");
return "Undefined";
} }
else { else {
return ""; return "";

View file

@ -17,22 +17,19 @@
package io.bitsquare.gui.main.portfolio.pending; package io.bitsquare.gui.main.portfolio.pending;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.offer.Offer; import io.bitsquare.offer.Offer;
import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -44,13 +41,10 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -65,28 +59,27 @@ class PendingTradesDataModel implements Activatable, DataModel {
private PendingTradesListItem selectedItem; private PendingTradesListItem selectedItem;
private boolean isOfferer; private boolean isOfferer;
private Trade closedTrade;
private final ChangeListener<Trade.ProcessState> tradeStateChangeListener;
private final ListChangeListener<Trade> tradesListChangeListener; private final ListChangeListener<Trade> tradesListChangeListener;
final StringProperty txId = new SimpleStringProperty(); final StringProperty txId = new SimpleStringProperty();
final ObjectProperty<Trade.ProcessState> tradeState = new SimpleObjectProperty<>();
final IntegerProperty selectedIndex = new SimpleIntegerProperty(-1); final IntegerProperty selectedIndex = new SimpleIntegerProperty(-1);
final ObjectProperty<TakerTrade.TakerProcessState> takerProcessState = new SimpleObjectProperty<>();
final ObjectProperty<OffererTrade.OffererProcessState> offererProcessState = new SimpleObjectProperty<>();
@Inject @Inject
public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, User user) { public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, User user) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.walletService = walletService; this.walletService = walletService;
this.user = user; this.user = user;
tradeStateChangeListener = (ov, oldValue, newValue) -> tradeState.set(newValue); tradesListChangeListener = change -> onListChanged();
tradesListChangeListener = change -> applyList();
} }
@Override @Override
public void activate() { public void activate() {
applyList(); onListChanged();
tradeManager.getPendingTrades().addListener(tradesListChangeListener); tradeManager.getPendingTrades().addListener(tradesListChangeListener);
if (list.size() > 0) { if (list.size() > 0) {
@ -98,10 +91,10 @@ class PendingTradesDataModel implements Activatable, DataModel {
@Override @Override
public void deactivate() { public void deactivate() {
tradeManager.getPendingTrades().removeListener(tradesListChangeListener); tradeManager.getPendingTrades().removeListener(tradesListChangeListener);
cleanUpSelectedTrade(); removeListenerFromSelectedTrade();
} }
private void applyList() { private void onListChanged() {
list.clear(); list.clear();
list.addAll(tradeManager.getPendingTrades().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); list.addAll(tradeManager.getPendingTrades().stream().map(PendingTradesListItem::new).collect(Collectors.toList()));
@ -116,7 +109,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
void selectTrade(PendingTradesListItem item) { void selectTrade(PendingTradesListItem item) {
// clean up previous selectedItem // clean up previous selectedItem
cleanUpSelectedTrade(); removeListenerFromSelectedTrade();
selectedItem = item; selectedItem = item;
@ -124,8 +117,11 @@ class PendingTradesDataModel implements Activatable, DataModel {
isOfferer = getTrade().getOffer().getP2PSigPubKey().equals(user.getP2PSigPubKey()); isOfferer = getTrade().getOffer().getP2PSigPubKey().equals(user.getP2PSigPubKey());
Trade trade = getTrade(); Trade trade = getTrade();
trade.processStateProperty().addListener(tradeStateChangeListener); if (trade instanceof TakerTrade)
tradeState.set(trade.processStateProperty().get()); takerProcessState.bind(((TakerTrade) trade).processStateProperty());
else
offererProcessState.bind(((OffererTrade) trade).processStateProperty());
log.trace("selectTrade trade.stateProperty().get() " + trade.processStateProperty().get()); log.trace("selectTrade trade.stateProperty().get() " + trade.processStateProperty().get());
if (trade.getDepositTx() != null) if (trade.getDepositTx() != null)
@ -133,7 +129,6 @@ class PendingTradesDataModel implements Activatable, DataModel {
} }
else { else {
txId.set(null); txId.set(null);
tradeState.set(null);
} }
} }
@ -146,34 +141,14 @@ class PendingTradesDataModel implements Activatable, DataModel {
} }
void withdraw(String toAddress) { void withdraw(String toAddress) {
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() { tradeManager.requestWithdraw(toAddress,
@Override getTrade(),
public void onSuccess(@javax.annotation.Nullable Transaction transaction) { () -> log.debug("requestWithdraw was successful"),
if (transaction != null) { (errorMessage, throwable) -> {
log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString()); log.error(errorMessage);
Popups.openExceptionPopup(throwable);
});
if (closedTrade != null) {
list.removeIf(e -> e.getTrade().getId().equals(closedTrade.getId()));
}
}
}
@Override
public void onFailure(@NotNull Throwable t) {
log.debug("onWithdraw onFailure");
}
};
AddressEntry addressEntry = walletService.getAddressEntry(getTrade().getId());
String fromAddress = addressEntry.getAddressString();
try {
walletService.sendFunds(fromAddress, toAddress, getAmountToWithdraw(), callback);
} catch (AddressFormatException | InsufficientMoneyException e) {
e.printStackTrace();
log.error(e.getMessage());
}
tradeManager.onWithdrawAtTradeCompleted(getTrade());
/* /*
Action response = Popups.openConfirmPopup( Action response = Popups.openConfirmPopup(
@ -203,7 +178,6 @@ class PendingTradesDataModel implements Activatable, DataModel {
}*/ }*/
} }
ObservableList<PendingTradesListItem> getList() { ObservableList<PendingTradesListItem> getList() {
return list; return list;
} }
@ -232,32 +206,36 @@ class PendingTradesDataModel implements Activatable, DataModel {
return selectedItem.getTrade().getOffer().getCurrencyCode(); return selectedItem.getTrade().getOffer().getCurrencyCode();
} }
Throwable getTradeException() {
return getTrade().getThrowable();
}
String getErrorMessage() {
return getTrade().getErrorMessage();
}
public Offer.Direction getDirection(Offer offer) { public Offer.Direction getDirection(Offer offer) {
return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey()) ? return offer.getP2PSigPubKey().equals(user.getP2PSigPubKey()) ?
offer.getDirection() : offer.getMirroredDirection(); offer.getDirection() : offer.getMirroredDirection();
} }
Coin getAmountToWithdraw() { private void removeListenerFromSelectedTrade() {
AddressEntry addressEntry = walletService.getAddressEntry(getTrade().getId());
log.debug("trade id " + getTrade().getId());
log.debug("getAddressString " + addressEntry.getAddressString());
log.debug("funds " + walletService.getBalanceForAddress(addressEntry.getAddress()).subtract(FeePolicy
.TX_FEE).toString());
// return walletService.getBalanceForAddress(addressEntry.getAddress()).subtract(FeePolicy.TX_FEE);
// TODO handle overpaid securityDeposit
if (isOfferer())
return getTrade().getTradeAmount().add(getTrade().getOffer().getSecurityDeposit());
else
return getTrade().getSecurityDeposit();
}
private void cleanUpSelectedTrade() {
if (selectedItem != null) { if (selectedItem != null) {
selectedItem.getTrade().processStateProperty().removeListener(tradeStateChangeListener); Trade trade = selectedItem.getTrade();
if (trade instanceof TakerTrade)
takerProcessState.unbind();
else
offererProcessState.unbind();
} }
} }
public Coin getAmountToWithdraw() {
Trade trade = selectedItem.getTrade();
Coin amountToWithdraw = trade.getSecurityDeposit();
if (trade instanceof OffererTrade)
amountToWithdraw = amountToWithdraw.add(trade.getTradeAmount());
return amountToWithdraw;
}
} }

View file

@ -85,8 +85,8 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
private ChangeListener<Number> selectedIndexChangeListener; private ChangeListener<Number> selectedIndexChangeListener;
private ListChangeListener<PendingTradesListItem> listChangeListener; private ListChangeListener<PendingTradesListItem> listChangeListener;
private ChangeListener<String> txIdChangeListener; private ChangeListener<String> txIdChangeListener;
private ChangeListener<PendingTradesViewModel.State> offererStateChangeListener; private ChangeListener<PendingTradesViewModel.ViewState> offererStateChangeListener;
private ChangeListener<PendingTradesViewModel.State> takerStateChangeListener; private ChangeListener<PendingTradesViewModel.ViewState> takerStateChangeListener;
private final Navigation navigation; private final Navigation navigation;
private ChangeListener<Boolean> focusedPropertyListener; private ChangeListener<Boolean> focusedPropertyListener;
@ -170,8 +170,8 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
public void doDeactivate() { public void doDeactivate() {
model.getList().removeListener(listChangeListener); model.getList().removeListener(listChangeListener);
model.txId.removeListener(txIdChangeListener); model.txId.removeListener(txIdChangeListener);
model.state.removeListener(offererStateChangeListener); model.viewState.removeListener(offererStateChangeListener);
model.state.removeListener(takerStateChangeListener); model.viewState.removeListener(takerStateChangeListener);
model.selectedIndex.removeListener(selectedIndexChangeListener); model.selectedIndex.removeListener(selectedIndexChangeListener);
table.getSelectionModel().selectedItemProperty().removeListener(selectedItemChangeListener); table.getSelectionModel().selectedItemProperty().removeListener(selectedItemChangeListener);
@ -258,8 +258,8 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
processBar.setProcessStepItems(items); processBar.setProcessStepItems(items);
} }
model.state.addListener(offererStateChangeListener); model.viewState.addListener(offererStateChangeListener);
applyOffererState(model.state.get()); applyOffererState(model.viewState.get());
} }
private void setupScreenForTaker() { private void setupScreenForTaker() {
@ -273,18 +273,18 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
processBar.setProcessStepItems(items); processBar.setProcessStepItems(items);
} }
model.state.addListener(takerStateChangeListener); model.viewState.addListener(takerStateChangeListener);
applyTakerState(model.state.get()); applyTakerState(model.viewState.get());
} }
private void applyOffererState(PendingTradesViewModel.State state) { private void applyOffererState(PendingTradesViewModel.ViewState viewState) {
setPaymentsControlsVisible(false); setPaymentsControlsVisible(false);
setSummaryControlsVisible(false); setSummaryControlsVisible(false);
log.debug("applyOffererState " + state); log.debug("applyOffererState " + viewState);
processBar.reset(); processBar.reset();
if (state != null) { if (viewState != null) {
switch (state) { switch (viewState) {
case OFFERER_BUYER_WAIT_TX_CONF: case OFFERER_BUYER_WAIT_TX_CONF:
processBar.setSelectedIndex(0); processBar.setSelectedIndex(0);
@ -347,21 +347,27 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
withdrawAmountTextField.setText(model.getAmountToWithdraw()); withdrawAmountTextField.setText(model.getAmountToWithdraw());
break; break;
case MESSAGE_SENDING_FAILED:
Popups.openWarningPopup("Sending message to trading peer failed.", model.getErrorMessage());
break;
case EXCEPTION:
Popups.openExceptionPopup(model.getTradeException());
break;
} }
} }
} }
private void applyTakerState(PendingTradesViewModel.State state) { private void applyTakerState(PendingTradesViewModel.ViewState viewState) {
confirmPaymentReceiptButton.setVisible(false); confirmPaymentReceiptButton.setVisible(false);
confirmPaymentReceiptButton.setManaged(false); confirmPaymentReceiptButton.setManaged(false);
setSummaryControlsVisible(false); setSummaryControlsVisible(false);
processBar.reset(); processBar.reset();
log.debug("applyTakerState " + state); log.debug("applyTakerState " + viewState);
if (state != null) { if (viewState != null) {
switch (state) { switch (viewState) {
case TAKER_SELLER_WAIT_TX_CONF: case TAKER_SELLER_WAIT_TX_CONF:
processBar.setSelectedIndex(0); processBar.setSelectedIndex(0);
@ -422,6 +428,12 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
withdrawAmountTextField.setText(model.getAmountToWithdraw()); withdrawAmountTextField.setText(model.getAmountToWithdraw());
break; break;
case MESSAGE_SENDING_FAILED:
Popups.openWarningPopup("Sending message to trading peer failed.", model.getErrorMessage());
break;
case EXCEPTION:
Popups.openExceptionPopup(model.getTradeException());
break;
} }
} }
} }

View file

@ -25,7 +25,6 @@ import io.bitsquare.gui.util.validation.BtcAddressValidator;
import io.bitsquare.locale.BSResources; import io.bitsquare.locale.BSResources;
import io.bitsquare.trade.OffererTrade; import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade; import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat; import org.bitcoinj.utils.Fiat;
@ -51,7 +50,7 @@ import org.slf4j.LoggerFactory;
class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataModel> implements ViewModel { class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataModel> implements ViewModel {
private static final Logger log = LoggerFactory.getLogger(PendingTradesViewModel.class); private static final Logger log = LoggerFactory.getLogger(PendingTradesViewModel.class);
enum State { enum ViewState {
TAKER_SELLER_WAIT_TX_CONF, TAKER_SELLER_WAIT_TX_CONF,
TAKER_SELLER_WAIT_PAYMENT_STARTED, TAKER_SELLER_WAIT_PAYMENT_STARTED,
TAKER_SELLER_CONFIRM_RECEIVE_PAYMENT, TAKER_SELLER_CONFIRM_RECEIVE_PAYMENT,
@ -61,16 +60,20 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
OFFERER_BUYER_START_PAYMENT, OFFERER_BUYER_START_PAYMENT,
OFFERER_BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED, OFFERER_BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED,
OFFERER_BUYER_COMPLETED, OFFERER_BUYER_COMPLETED,
MESSAGE_SENDING_FAILED,
EXCEPTION
} }
private final BSFormatter formatter; private final BSFormatter formatter;
private final InvalidationListener stateChangeListener; private final InvalidationListener takerStateListener;
private final InvalidationListener offererStateListener;
private final BtcAddressValidator btcAddressValidator; private final BtcAddressValidator btcAddressValidator;
final StringProperty txId = new SimpleStringProperty(); final StringProperty txId = new SimpleStringProperty();
final ObjectProperty<State> state = new SimpleObjectProperty<>();
final BooleanProperty withdrawalButtonDisable = new SimpleBooleanProperty(true); final BooleanProperty withdrawalButtonDisable = new SimpleBooleanProperty(true);
final IntegerProperty selectedIndex = new SimpleIntegerProperty(-1); final IntegerProperty selectedIndex = new SimpleIntegerProperty(-1);
final ObjectProperty<ViewState> viewState = new SimpleObjectProperty<>();
@Inject @Inject
@ -80,7 +83,8 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
this.formatter = formatter; this.formatter = formatter;
this.btcAddressValidator = btcAddressValidator; this.btcAddressValidator = btcAddressValidator;
this.stateChangeListener = (ov) -> updateState(); this.takerStateListener = (ov) -> updateTakerState();
this.offererStateListener = (ov) -> updateOffererState();
} }
@Override @Override
@ -88,8 +92,11 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
txId.bind(dataModel.txId); txId.bind(dataModel.txId);
selectedIndex.bind(dataModel.selectedIndex); selectedIndex.bind(dataModel.selectedIndex);
dataModel.tradeState.addListener(stateChangeListener); dataModel.takerProcessState.addListener(takerStateListener);
updateState(); dataModel.offererProcessState.addListener(offererStateListener);
updateTakerState();
updateOffererState();
} }
@Override @Override
@ -97,13 +104,15 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
txId.unbind(); txId.unbind();
selectedIndex.unbind(); selectedIndex.unbind();
dataModel.tradeState.removeListener(stateChangeListener); dataModel.takerProcessState.removeListener(takerStateListener);
dataModel.offererProcessState.removeListener(offererStateListener);
} }
void selectTrade(PendingTradesListItem item) { void selectTrade(PendingTradesListItem item) {
dataModel.selectTrade(item); dataModel.selectTrade(item);
updateState(); /* updateTakerState();
updateOffererState();*/
} }
void fiatPaymentStarted() { void fiatPaymentStarted() {
@ -123,7 +132,7 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
} }
String getAmountToWithdraw() { String getAmountToWithdraw() {
return formatter.formatCoinWithCode(dataModel.getAmountToWithdraw()); //.subtract(FeePolicy.TX_FEE)); return formatter.formatCoinWithCode(dataModel.getAmountToWithdraw());
} }
ObservableList<PendingTradesListItem> getList() { ObservableList<PendingTradesListItem> getList() {
@ -218,63 +227,88 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
return btcAddressValidator; return btcAddressValidator;
} }
Throwable getTradeException() {
return dataModel.getTradeException();
}
private void updateState() { String getErrorMessage() {
Trade.ProcessState processState = dataModel.tradeState.get(); return dataModel.getErrorMessage();
log.trace("tradeState " + processState); }
private void updateTakerState() {
TakerTrade.TakerProcessState processState = dataModel.takerProcessState.get();
log.debug("updateTakerState " + processState);
if (processState != null) { if (processState != null) {
if (processState instanceof TakerTrade.TakerProcessState) { switch (processState) {
switch ((TakerTrade.TakerProcessState) processState) { case TAKE_OFFER_FEE_TX_CREATED:
break;
case TAKE_OFFER_FEE_PUBLISHED:
break;
case TAKE_OFFER_FEE_PUBLISH_FAILED:
break;
case DEPOSIT_PUBLISHED: case DEPOSIT_PUBLISHED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_WAIT_TX_CONF : State.TAKER_SELLER_WAIT_TX_CONF); viewState.set(ViewState.TAKER_SELLER_WAIT_TX_CONF);
break; break;
case DEPOSIT_CONFIRMED: case DEPOSIT_CONFIRMED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_START_PAYMENT : viewState.set(ViewState.TAKER_SELLER_WAIT_PAYMENT_STARTED);
State.TAKER_SELLER_WAIT_PAYMENT_STARTED);
break; break;
case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_STARTED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED : viewState.set(ViewState.TAKER_SELLER_CONFIRM_RECEIVE_PAYMENT);
State.TAKER_SELLER_CONFIRM_RECEIVE_PAYMENT); break;
case FIAT_PAYMENT_RECEIVED:
viewState.set(ViewState.TAKER_SELLER_COMPLETED);
break; break;
case PAYOUT_PUBLISHED: case PAYOUT_PUBLISHED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_COMPLETED : State.TAKER_SELLER_COMPLETED);
break; break;
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
// TODO error states not implemented yet viewState.set(ViewState.MESSAGE_SENDING_FAILED);
break; break;
case EXCEPTION:
viewState.set(ViewState.EXCEPTION);
break;
default: default:
log.warn("unhandled state " + processState); log.warn("unhandled processState " + processState);
break; break;
} }
} }
else if (processState instanceof OffererTrade.OffererProcessState) {
switch ((OffererTrade.OffererProcessState) processState) {
case DEPOSIT_PUBLISHED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_WAIT_TX_CONF : State.TAKER_SELLER_WAIT_TX_CONF);
break;
case DEPOSIT_CONFIRMED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_START_PAYMENT :
State.TAKER_SELLER_WAIT_PAYMENT_STARTED);
break;
case FIAT_PAYMENT_STARTED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED :
State.TAKER_SELLER_CONFIRM_RECEIVE_PAYMENT);
break;
case PAYOUT_PUBLISHED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_COMPLETED : State.TAKER_SELLER_COMPLETED);
break;
case MESSAGE_SENDING_FAILED:
// TODO error states not implemented yet
break;
default:
log.warn("unhandled state " + processState);
break;
}
}
}
else {
state.set(null);
}
} }
private void updateOffererState() {
OffererTrade.OffererProcessState processState = dataModel.offererProcessState.get();
log.debug("updateOffererState " + processState);
if (processState != null) {
switch (processState) {
case DEPOSIT_PUBLISHED:
viewState.set(ViewState.OFFERER_BUYER_WAIT_TX_CONF);
break;
case DEPOSIT_CONFIRMED:
viewState.set(ViewState.OFFERER_BUYER_START_PAYMENT);
break;
case FIAT_PAYMENT_STARTED:
viewState.set(ViewState.OFFERER_BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED);
break;
case PAYOUT_PUBLISHED:
viewState.set(ViewState.OFFERER_BUYER_COMPLETED);
break;
case MESSAGE_SENDING_FAILED:
viewState.set(ViewState.MESSAGE_SENDING_FAILED);
break;
case EXCEPTION:
viewState.set(ViewState.EXCEPTION);
break;
default:
log.warn("unhandled viewState " + processState);
break;
}
}
}
} }

View file

@ -34,6 +34,7 @@
package io.bitsquare.storage; package io.bitsquare.storage;
import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.components.Popups;
import org.bitcoinj.core.Utils; import org.bitcoinj.core.Utils;

View file

@ -17,7 +17,7 @@
package io.bitsquare.storage; package io.bitsquare.storage;
import io.bitsquare.gui.components.Popups; import com.google.common.base.Throwables;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -132,7 +132,7 @@ public class Storage<T extends Serializable> {
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
Popups.openErrorPopup("An exception occurred at reading data from disc.", e.getMessage()); Throwables.propagate(e);
} }
return null; return null;

View file

@ -64,7 +64,7 @@ public class OffererTrade extends Trade implements Serializable {
PAYOUT_PUBLISHED, PAYOUT_PUBLISHED,
MESSAGE_SENDING_FAILED, MESSAGE_SENDING_FAILED,
UNSPECIFIC_FAULT EXCEPTION
} }
protected OffererProcessState processState; protected OffererProcessState processState;
@ -104,7 +104,7 @@ public class OffererTrade extends Trade implements Serializable {
processStateProperty.set(processState); processStateProperty.set(processState);
switch (processState) { switch (processState) {
case UNSPECIFIC_FAULT: case EXCEPTION:
disposeProtocol(); disposeProtocol();
setLifeCycleState(OffererLifeCycleState.FAILED); setLifeCycleState(OffererLifeCycleState.FAILED);
break; break;

View file

@ -69,8 +69,8 @@ abstract public class Trade implements Serializable {
transient protected String errorMessage; transient protected String errorMessage;
transient protected Throwable throwable; transient protected Throwable throwable;
transient protected ObjectProperty<Coin> tradeAmountProperty = new SimpleObjectProperty<>(tradeAmount); transient protected ObjectProperty<Coin> tradeAmountProperty;
transient protected ObjectProperty<Fiat> tradeVolumeProperty = new SimpleObjectProperty<>(getTradeVolume()); transient protected ObjectProperty<Fiat> tradeVolumeProperty;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -80,6 +80,8 @@ abstract public class Trade implements Serializable {
public Trade(Offer offer) { public Trade(Offer offer) {
this.offer = offer; this.offer = offer;
date = new Date(); date = new Date();
tradeAmountProperty = new SimpleObjectProperty<>(tradeAmount);
tradeVolumeProperty = new SimpleObjectProperty<>(getTradeVolume()); // cannot be set before offer is set
} }
// Serialized object does not create our transient objects // Serialized object does not create our transient objects

View file

@ -18,9 +18,11 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.arbitration.ArbitrationRepository; import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.BlockChainService; import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.EncryptionService; import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.crypto.SignatureService; import io.bitsquare.crypto.SignatureService;
@ -47,9 +49,14 @@ import io.bitsquare.trade.protocol.trade.taker.models.TakerAsSellerModel;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.utils.Fiat; import org.bitcoinj.utils.Fiat;
import com.google.common.util.concurrent.FutureCallback;
import java.io.File; import java.io.File;
import java.util.HashMap; import java.util.HashMap;
@ -62,6 +69,8 @@ import javax.inject.Named;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -259,7 +268,20 @@ public class TradeManager {
((TakerTrade) trade).onFiatPaymentReceived(); ((TakerTrade) trade).onFiatPaymentReceived();
} }
public void onWithdrawAtTradeCompleted(Trade trade) { public void requestWithdraw(String toAddress, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
AddressEntry addressEntry = walletService.getAddressEntry(trade.getId());
String fromAddress = addressEntry.getAddressString();
// TODO handle overpaid securityDeposit
Coin amountToWithdraw = trade.getSecurityDeposit();
if (trade instanceof OffererTrade)
amountToWithdraw = amountToWithdraw.add(trade.getTradeAmount());
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) {
log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
if (trade instanceof OffererTrade) if (trade instanceof OffererTrade)
((OffererTrade) trade).setLifeCycleState(OffererTrade.OffererLifeCycleState.COMPLETED); ((OffererTrade) trade).setLifeCycleState(OffererTrade.OffererLifeCycleState.COMPLETED);
else else
@ -267,6 +289,25 @@ public class TradeManager {
pendingTrades.remove(trade); pendingTrades.remove(trade);
closedTrades.add(trade); closedTrades.add(trade);
resultHandler.handleResult();
}
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t);
}
};
try {
walletService.sendFunds(fromAddress, toAddress, amountToWithdraw, callback);
} catch (AddressFormatException | InsufficientMoneyException e) {
e.printStackTrace();
log.error(e.getMessage());
faultHandler.handleFault("An exception occurred at requestWithdraw.", e);
}
} }
@ -437,5 +478,4 @@ public class TradeManager {
log.error(fault.getMessage()); log.error(fault.getMessage());
}); });
} }
} }

View file

@ -19,9 +19,10 @@ package io.bitsquare.user;
import io.bitsquare.crypto.EncryptionService; import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.fiat.FiatAccount; import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import com.google.common.base.Throwables;
import java.io.Serializable; import java.io.Serializable;
import java.security.KeyPair; import java.security.KeyPair;
@ -94,7 +95,7 @@ public class User implements Serializable {
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
Popups.openExceptionPopup(e); Throwables.propagate(e);
} }
} }
storage.save(); storage.save();