Add feature for deactivating an offer #1368

This commit is contained in:
Manfred Karrer 2018-02-18 14:45:02 -05:00
parent 521dd62114
commit beaaf59b70
No known key found for this signature in database
GPG Key ID: 401250966A6B2C46
10 changed files with 180 additions and 34 deletions

View File

@ -1099,6 +1099,7 @@ message OpenOffer {
RESERVED = 2;
CLOSED = 3;
CANCELED = 4;
DEACTIVATED = 5;
}
Offer offer = 1;

View File

@ -84,6 +84,8 @@ shared.bankName=Bank name
shared.acceptedBanks=Accepted banks
shared.amountMinMax=Amount (min - max)
shared.remove=Remove
shared.deactivate=Deactivate
shared.activate=Activate
shared.goTo=Go to {0}
shared.BTCMinMax=BTC (min - max)
shared.removeOffer=Remove offer
@ -315,6 +317,8 @@ offerbook.yesCreateOffer=Yes, create offer
offerbook.setupNewAccount=Set up a new trading account
offerbook.removeOffer.success=Remove offer was successful.
offerbook.removeOffer.failed=Remove offer failed:\n{0}
offerbook.deactivateOffer.failed=Deactivating of offer failed:\n{0}
offerbook.activateOffer.failed=Publishing of offer failed:\n{0}
offerbook.withdrawFundsHint=You can withdraw the funds you paid in from the {0} screen.
offerbook.warning.noTradingAccountForCurrency.headline=No trading account for selected currency

View File

@ -144,6 +144,14 @@ public class OfferBookService {
}
}
public void activateOffer(Offer offer, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
addOffer(offer, resultHandler, errorMessageHandler);
}
public void deactivateOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
removeOffer(offerPayload, resultHandler, errorMessageHandler);
}
public void removeOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
if (p2PService.removeData(offerPayload, true)) {
log.trace("Remove offer from network was successful. OfferPayload ID = " + offerPayload.getId());

View File

@ -19,6 +19,7 @@ package io.bisq.core.offer;
import io.bisq.common.Timer;
import io.bisq.common.UserThread;
import io.bisq.common.proto.ProtoUtil;
import io.bisq.common.storage.Storage;
import io.bisq.core.trade.Tradable;
import io.bisq.core.trade.TradableList;
@ -40,29 +41,34 @@ public final class OpenOffer implements Tradable {
AVAILABLE,
RESERVED,
CLOSED,
CANCELED
CANCELED,
DEACTIVATED
}
@Getter
private final Offer offer;
@Getter
private State state = State.AVAILABLE;
private State state;
transient private Storage<TradableList<OpenOffer>> storage;
public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) {
this.offer = offer;
this.storage = storage;
state = State.AVAILABLE;
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private OpenOffer(Offer offer) {
private OpenOffer(Offer offer, State state) {
this.offer = offer;
}
this.state = state;
if (this.state == State.RESERVED)
setState(State.AVAILABLE);
}
@Override
public PB.Tradable toProtoMessage() {
@ -73,11 +79,8 @@ public final class OpenOffer implements Tradable {
}
public static Tradable fromProto(PB.OpenOffer proto) {
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()));
// If we have a reserved state from the local db we reset it
if (openOffer.getState() == State.RESERVED)
openOffer.setState(State.AVAILABLE);
return openOffer;
return new OpenOffer(Offer.fromProto(proto.getOffer()),
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()));
}
@ -118,6 +121,10 @@ public final class OpenOffer implements Tradable {
stopTimeout();
}
public boolean isDeactivated() {
return state == State.DEACTIVATED;
}
private void startTimeout() {
stopTimeout();

View File

@ -327,7 +327,30 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
}
// Remove from my offers
public void activateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Offer offer = openOffer.getOffer();
openOffer.setStorage(openOfferTradableListStorage);
offerBookService.activateOffer(offer,
() -> {
openOffer.setState(OpenOffer.State.AVAILABLE);
log.debug("activateOpenOffer, offerId={}", offer.getId());
resultHandler.handleResult();
},
errorMessageHandler);
}
public void deactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Offer offer = openOffer.getOffer();
openOffer.setStorage(openOfferTradableListStorage);
offerBookService.deactivateOffer(offer.getOfferPayload(),
() -> {
openOffer.setState(OpenOffer.State.DEACTIVATED);
log.debug("deactivateOpenOffer, offerId={}", offer.getId());
resultHandler.handleResult();
},
errorMessageHandler);
}
public void removeOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Offer offer = openOffer.getOffer();
offerBookService.removeOffer(offer.getOfferPayload(),
@ -483,13 +506,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
if (openOffers.contains(openOffer)) {
// The openOffer.getId().contains("_") check is because there was once a version
// where we encoded the version nr in the offer id with a "_" as separator.
// That caused several issues and was reverted. So if there are still old offers out with that
// special offer ID format those must not be published as they cause failed taker attempts
// with lost taker fee.
String id = openOffer.getId();
if (id != null && !id.contains("_"))
if (id != null && !openOffer.isDeactivated())
republishOffer(openOffer);
else
log.warn("You have an offer with an invalid offer ID: offerID=" + id);
@ -567,7 +585,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
// we need to check if in the meantime the offer has been removed
if (openOffers.contains(openOffer))
if (openOffers.contains(openOffer) && !openOffer.isDeactivated())
refreshOffer(openOffer);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}

View File

@ -35,8 +35,8 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
final AccountAgeWitnessService accountAgeWitnessService;
@Inject
public ClosedTradesViewModel(ClosedTradesDataModel dataModel,
AccountAgeWitnessService accountAgeWitnessService,
public ClosedTradesViewModel(ClosedTradesDataModel dataModel,
AccountAgeWitnessService accountAgeWitnessService,
BSFormatter formatter) {
super(dataModel);
this.accountAgeWitnessService = accountAgeWitnessService;
@ -118,6 +118,9 @@ class ClosedTradesViewModel extends ActivatableWithDataModel<ClosedTradesDataMod
return state.toString();
case CANCELED:
return Res.get("portfolio.closed.canceled");
case DEACTIVATED:
log.error("Invalid state {}", state);
return state.toString();
default:
log.error("Unhandled state {}", state);
return state.toString();

View File

@ -63,7 +63,15 @@ class OpenOffersDataModel extends ActivatableDataModel {
priceFeedService.updateCounterProperty().removeListener(currenciesUpdateFlagPropertyListener);
}
void onCancelOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
void onActivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
openOfferManager.activateOpenOffer(openOffer, resultHandler, errorMessageHandler);
}
void onDeactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
openOfferManager.deactivateOpenOffer(openOffer, resultHandler, errorMessageHandler);
}
void onRemoveOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
openOfferManager.removeOpenOffer(openOffer, resultHandler, errorMessageHandler);
}

View File

@ -28,15 +28,16 @@
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="offerIdColumn" minWidth="120" maxWidth="130"/>
<TableColumn fx:id="dateColumn" minWidth="200"/>
<TableColumn fx:id="marketColumn" minWidth="100"/>
<TableColumn fx:id="priceColumn" minWidth="160"/>
<TableColumn fx:id="amountColumn" minWidth="160"/>
<TableColumn fx:id="volumeColumn" minWidth="180"/>
<TableColumn fx:id="directionColumn" minWidth="100"/>
<TableColumn fx:id="removeItemColumn" minWidth="120" maxWidth="120" sortable="false"/>
<TableColumn fx:id="offerIdColumn" minWidth="110" maxWidth="130"/>
<TableColumn fx:id="dateColumn" minWidth="180"/>
<TableColumn fx:id="marketColumn" minWidth="90"/>
<TableColumn fx:id="priceColumn" minWidth="150"/>
<TableColumn fx:id="amountColumn" minWidth="150"/>
<TableColumn fx:id="volumeColumn" minWidth="170"/>
<TableColumn fx:id="directionColumn" minWidth="80"/>
<TableColumn fx:id="deactivateItemColumn" minWidth="120" maxWidth="120" sortable="false"/>
<TableColumn fx:id="removeItemColumn" minWidth="110" maxWidth="120" sortable="false"/>
</columns>
</TableView>
</VBox>
</VBox>

View File

@ -38,6 +38,7 @@ import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
@ -48,7 +49,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
TableView<OpenOfferListItem> tableView;
@FXML
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, offerIdColumn, removeItemColumn;
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn, removeItemColumn;
private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow;
private SortedList<OpenOfferListItem> sortedList;
@ -69,6 +70,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
directionColumn.setText(Res.get("shared.offerType"));
dateColumn.setText(Res.get("shared.dateTime"));
offerIdColumn.setText(Res.get("shared.offerId"));
deactivateItemColumn.setText("");
removeItemColumn.setText("");
setOfferIdColumnCellFactory();
@ -78,6 +80,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setAmountColumnCellFactory();
setVolumeColumnCellFactory();
setDateColumnCellFactory();
setDeactivateColumnCellFactory();
setRemoveColumnCellFactory();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -115,6 +118,36 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
sortedList.comparatorProperty().unbind();
}
private void onDeactivateOpenOffer(OpenOffer openOffer) {
if (model.isBootstrapped()) {
model.onDeactivateOpenOffer(openOffer,
() -> {
log.debug("Deactivate offer was successful");
},
(message) -> {
log.error(message);
new Popup<>().warning(Res.get("offerbook.deactivateOffer.failed", message)).show();
});
} else {
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
}
}
private void onActivateOpenOffer(OpenOffer openOffer) {
if (model.isBootstrapped()) {
model.onActivateOpenOffer(openOffer,
() -> {
log.debug("Activate offer was successful");
},
(message) -> {
log.error(message);
new Popup<>().warning(Res.get("offerbook.activateOffer.failed", message)).show();
});
} else {
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
}
}
private void onRemoveOpenOffer(OpenOffer openOffer) {
if (model.isBootstrapped()) {
String key = "RemoveOfferWarning";
@ -133,7 +166,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
}
private void doRemoveOpenOffer(OpenOffer openOffer) {
model.onCancelOpenOffer(openOffer,
model.onRemoveOpenOffer(openOffer,
() -> {
log.debug("Remove offer was successful");
String key = "WithdrawFundsAfterRemoveOfferInfo";
@ -302,6 +335,61 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
});
}
private void setDeactivateColumnCellFactory() {
deactivateItemColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
deactivateItemColumn.setCellFactory(
new Callback<TableColumn<OpenOfferListItem, OpenOfferListItem>, TableCell<OpenOfferListItem, OpenOfferListItem>>() {
@Override
public TableCell<OpenOfferListItem, OpenOfferListItem> call(TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
return new TableCell<OpenOfferListItem, OpenOfferListItem>() {
final ImageView iconView = new ImageView();
Button button;
private void updateState(@NotNull OpenOffer openOffer) {
if (openOffer.isDeactivated()) {
button.setText(Res.get("shared.activate"));
iconView.setId("image-alert-round");
button.setGraphic(iconView);
} else {
button.setText(Res.get("shared.deactivate"));
iconView.setId("image-green_circle");
button.setGraphic(iconView);
}
}
@Override
public void updateItem(final OpenOfferListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = new Button();
button.setGraphic(iconView);
updateState(item.getOpenOffer());
button.setMinWidth(70);
setGraphic(button);
}
button.setOnAction(event -> {
if (item.getOpenOffer().isDeactivated()) {
onActivateOpenOffer(item.getOpenOffer());
} else {
onDeactivateOpenOffer(item.getOpenOffer());
}
updateState(item.getOpenOffer());
});
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
}
private void setRemoveColumnCellFactory() {
removeItemColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
removeItemColumn.setCellFactory(
@ -318,9 +406,9 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
if (item != null && !empty) {
if (button == null) {
iconView.setId("image-remove");
button = new Button(Res.get("shared.remove"));
button.setMinWidth(70);
iconView.setId("image-remove");
button.setGraphic(iconView);
setGraphic(button);
}

View File

@ -45,8 +45,16 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
this.formatter = formatter;
}
void onCancelOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
dataModel.onCancelOpenOffer(openOffer, resultHandler, errorMessageHandler);
void onActivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
dataModel.onActivateOpenOffer(openOffer, resultHandler, errorMessageHandler);
}
void onDeactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
dataModel.onDeactivateOpenOffer(openOffer, resultHandler, errorMessageHandler);
}
void onRemoveOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
dataModel.onRemoveOpenOffer(openOffer, resultHandler, errorMessageHandler);
}
public ObservableList<OpenOfferListItem> getList() {