Merge pull request #5886 from ripcurlx/add-bsq-swap-duplicate-offer-support

Add support to duplicate BSQ swap offers
This commit is contained in:
Bisq GitHub Admin 2021-12-07 09:41:10 +01:00 committed by GitHub
commit 5041dc57ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 279 additions and 81 deletions

View file

@ -25,6 +25,7 @@ import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferUtil;
import bisq.core.payment.payload.PaymentMethod;
@ -124,10 +125,20 @@ public class BsqSwapOfferModel {
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void init(OfferDirection direction, boolean isMaker) {
public void init(OfferDirection direction, boolean isMaker, @Nullable Offer offer) {
this.direction = direction;
this.isMaker = isMaker;
if (offer != null) {
setPrice(offer.getPrice());
setBtcAmount(Coin.valueOf(Math.min(offer.getAmount().value, getMaxTradeLimit())));
calculateVolumeForAmount(getBtcAmount());
setMinAmount(offer.getMinAmount());
calculateMinVolume();
}
createListeners();
applyTxFeePerVbyte();

View file

@ -67,17 +67,9 @@ public class BsqSwapTakeOfferModel extends BsqSwapOfferModel {
///////////////////////////////////////////////////////////////////////////////////////////
public void initWithData(Offer offer) {
super.init(offer.getDirection(), false);
super.init(offer.getDirection(), false, offer);
this.offer = offer;
setPrice(offer.getPrice());
setBtcAmount(Coin.valueOf(Math.min(offer.getAmount().value, getMaxTradeLimit())));
calculateVolumeForAmount(getBtcAmount());
setMinAmount(offer.getMinAmount());
calculateMinVolume();
offer.resetState();
}

View file

@ -97,6 +97,7 @@ shared.BTCMinMax=BTC (min - max)
shared.removeOffer=Remove offer
shared.dontRemoveOffer=Don't remove offer
shared.editOffer=Edit offer
shared.duplicateOffer=Duplicate offer
shared.openLargeQRWindow=Open large QR code window
shared.tradingAccount=Trading account
shared.faq=Visit FAQ page

View file

@ -37,6 +37,7 @@ import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.user.Preferences;
@ -56,6 +57,8 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
public abstract class OfferView extends ActivatableView<TabPane, Void> {
private OfferBookView offerBookView;
@ -102,7 +105,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
protected void initialize() {
navigationListener = (viewPath, data) -> {
if (viewPath.size() == 3 && viewPath.indexOf(this.getClass()) == 1)
loadView(viewPath.tip());
loadView(viewPath.tip(), data);
};
tabChangeListener = (observableValue, oldValue, newValue) -> {
if (newValue != null) {
@ -198,7 +201,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
return Res.get("offerbook.takeOffer").toUpperCase();
}
private void loadView(Class<? extends View> viewClass) {
private void loadView(Class<? extends View> viewClass, @Nullable Object data) {
TabPane tabPane = root;
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);
View view;
@ -237,7 +240,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
} else if (viewClass == BsqSwapCreateOfferView.class && bsqSwapCreateOfferView == null) {
view = viewLoader.load(viewClass);
bsqSwapCreateOfferView = (BsqSwapCreateOfferView) view;
bsqSwapCreateOfferView.initWithData(direction, offerActionHandler);
bsqSwapCreateOfferView.initWithData(direction, offerActionHandler, (BsqSwapOfferPayload) data);
createOfferPane = bsqSwapCreateOfferView.getRoot();
createOfferTab = new Tab(getCreateOfferTabName(viewClass));
createOfferTab.setClosable(true);

View file

@ -28,6 +28,7 @@ import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.bsq_swap.BsqSwapOfferModel;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.offer.bsq_swap.OpenBsqSwapOfferService;
import bisq.core.payment.PaymentAccount;
import bisq.core.user.User;
@ -52,6 +53,8 @@ import java.util.function.Consumer;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Comparator.comparing;
@ -91,8 +94,8 @@ class BsqSwapCreateOfferDataModel extends BsqSwapOfferDataModel {
// API
///////////////////////////////////////////////////////////////////////////////////////////
void initWithData(OfferDirection direction) {
bsqSwapOfferModel.init(direction, true);
void initWithData(OfferDirection direction, @Nullable BsqSwapOfferPayload offerPayload) {
bsqSwapOfferModel.init(direction, true, offerPayload != null ? new Offer(offerPayload) : null);
fillPaymentAccounts();
applyPaymentAccount();

View file

@ -37,6 +37,7 @@ import bisq.desktop.util.Layout;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.payment.PaymentAccount;
import bisq.core.user.DontShowAgainLookup;
@ -74,6 +75,8 @@ import java.util.concurrent.TimeUnit;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ;
import static bisq.desktop.util.FormBuilder.*;
@ -159,9 +162,12 @@ public class BsqSwapCreateOfferView extends BsqSwapOfferView<BsqSwapCreateOfferV
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void initWithData(OfferDirection direction, OfferView.OfferActionHandler offerActionHandler) {
public void initWithData(OfferDirection direction,
OfferView.OfferActionHandler offerActionHandler,
@Nullable BsqSwapOfferPayload offerPayload) {
this.offerActionHandler = offerActionHandler;
model.initWithData(direction);
model.initWithData(offerPayload != null ? offerPayload.getDirection() : direction, offerPayload);
if (model.dataModel.isBuyOffer()) {
actionButton.setId("buy-button-big");

View file

@ -31,6 +31,7 @@ import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferRestrictions;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.util.FormattingUtils;
import bisq.core.util.VolumeUtil;
@ -60,6 +61,8 @@ import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static bisq.core.offer.bsq_swap.BsqSwapOfferModel.BSQ;
@Slf4j
@ -144,6 +147,8 @@ class BsqSwapCreateOfferViewModel extends BsqSwapOfferViewModel<BsqSwapCreateOff
addBindings();
addListeners();
maybeInitializeWithData();
updateButtonDisableState();
}
@ -159,8 +164,8 @@ class BsqSwapCreateOfferViewModel extends BsqSwapOfferViewModel<BsqSwapCreateOff
// API
///////////////////////////////////////////////////////////////////////////////////////////
void initWithData(OfferDirection direction) {
dataModel.initWithData(direction);
void initWithData(OfferDirection direction, @Nullable BsqSwapOfferPayload offerPayload) {
dataModel.initWithData(direction, offerPayload);
btcValidator.setMaxValue(PaymentMethod.BSQ_SWAP.getMaxTradeLimitAsCoin(BSQ));
btcValidator.setMaxTradeLimit(Coin.valueOf(dataModel.getMaxTradeLimit()));
@ -572,6 +577,33 @@ class BsqSwapCreateOfferViewModel extends BsqSwapOfferViewModel<BsqSwapCreateOff
isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || miningPoW.get());
}
private void maybeInitializeWithData() {
ObjectProperty<Coin> btcMinAmount = dataModel.getMinAmount();
if (btcMinAmount.get() != null) {
minAmountAsCoinListener.changed(btcMinAmount, null, btcMinAmount.get());
}
ObjectProperty<Coin> btcAmount = dataModel.getBtcAmount();
if (btcAmount.get() != null && btcMinAmount.get() != null) {
syncMinAmountWithAmount = btcMinAmount.get().equals(dataModel.getBtcAmount().get());
}
if (btcAmount.get() != null) {
amountAsCoinListener.changed(btcAmount, null, btcAmount.get());
}
ObjectProperty<Price> price = dataModel.getPrice();
if (price.get() != null) {
priceListener.changed(price, null, price.get());
}
ObjectProperty<Volume> volume = dataModel.getVolume();
if (volume.get() != null) {
volumeListener.changed(volume, null, volume.get());
}
}
private void stopTimeoutTimer() {
if (timeoutTimer != null) {
timeoutTimer.stop();

View file

@ -55,6 +55,7 @@
<TableColumn fx:id="sellerSecurityDepositColumn" visible="false" minWidth="75"/>
<TableColumn fx:id="directionColumn" minWidth="70"/>
<TableColumn fx:id="stateColumn" minWidth="80"/>
<TableColumn fx:id="duplicateColumn" minWidth="30" maxWidth="30" sortable="false"/>
<TableColumn fx:id="avatarColumn" minWidth="40" maxWidth="40"/>
</columns>
</TableView>

View file

@ -26,21 +26,19 @@ import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.PeerInfoIconTrading;
import bisq.desktop.main.MainView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.BsqTradeDetailsWindow;
import bisq.desktop.main.overlays.windows.ClosedTradesSummaryWindow;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView;
import bisq.desktop.main.portfolio.presentation.PortfolioUtil;
import bisq.desktop.util.GUIUtil;
import bisq.core.alert.PrivateNotificationManager;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayloadBase;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.bisq_v1.OfferPayload;
import bisq.core.trade.model.Tradable;
import bisq.core.trade.model.TradeModel;
import bisq.core.trade.model.bisq_v1.Contract;
@ -60,11 +58,14 @@ import com.googlecode.jcsv.writer.CSVEntryConverter;
import javax.inject.Inject;
import javax.inject.Named;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
@ -85,6 +86,8 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
@ -94,6 +97,8 @@ import java.util.Comparator;
import java.util.Date;
import java.util.function.Function;
import static bisq.desktop.util.FormBuilder.getRegularIconButton;
@FxmlView
public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTradesViewModel> {
private final boolean useDevPrivilegeKeys;
@ -132,7 +137,8 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
@FXML
TableColumn<Tradable, Tradable> priceColumn, deviationColumn, amountColumn, volumeColumn,
txFeeColumn, tradeFeeColumn, buyerSecurityDepositColumn, sellerSecurityDepositColumn,
marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn,
duplicateColumn, avatarColumn;
@FXML
HBox searchBox;
@FXML
@ -198,6 +204,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
dateColumn.setGraphic(new AutoTooltipLabel(ColumnNames.DATE.toString()));
tradeIdColumn.setGraphic(new AutoTooltipLabel(ColumnNames.TRADE_ID.toString()));
stateColumn.setGraphic(new AutoTooltipLabel(ColumnNames.STATUS.toString()));
duplicateColumn.setGraphic(new AutoTooltipLabel(""));
avatarColumn.setText("");
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -216,10 +223,11 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
setDateColumnCellFactory();
setMarketColumnCellFactory();
setStateColumnCellFactory();
setDuplicateColumnCellFactory();
setAvatarColumnCellFactory();
tradeIdColumn.setComparator(Comparator.comparing(o -> o.getId()));
dateColumn.setComparator(Comparator.comparing(o -> o.getDate()));
tradeIdColumn.setComparator(Comparator.comparing(Tradable::getId));
dateColumn.setComparator(Comparator.comparing(Tradable::getDate));
directionColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDirection()));
marketColumn.setComparator(Comparator.comparing(model::getMarketLabel));
priceColumn.setComparator(Comparator.comparing(model::getPrice, Comparator.nullsFirst(Comparator.naturalOrder())));
@ -229,7 +237,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
volumeColumn.setComparator(nullsFirstComparingAsTrade(TradeModel::getVolume));
amountColumn.setComparator(Comparator.comparing(model::getAmount, Comparator.nullsFirst(Comparator.naturalOrder())));
avatarColumn.setComparator(Comparator.comparing(
o -> model.dataModel.getNumPastTrades(o),
model.dataModel::getNumPastTrades,
Comparator.nullsFirst(Comparator.naturalOrder())
));
txFeeColumn.setComparator(nullsFirstComparing(o ->
@ -262,20 +270,9 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
tableView -> {
TableRow<Tradable> row = new TableRow<>();
ContextMenu rowMenu = new ContextMenu();
MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
editItem.setOnAction((event) -> {
try {
OfferPayload offerPayload = row.getItem().getOffer().getOfferPayload().orElseThrow();
if (offerPayload.getPubKeyRing().equals(keyRing.getPubKeyRing())) {
navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class);
} else {
new Popup().warning(Res.get("portfolio.context.notYourOffer")).show();
}
} catch (NullPointerException e) {
log.warn("Unable to get offerPayload - {}", e.toString());
}
});
rowMenu.getItems().add(editItem);
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
duplicateItem.setOnAction((ActionEvent event) -> onDuplicateOffer(row.getItem().getOffer()));
rowMenu.getItems().add(duplicateItem);
row.contextMenuProperty().bind(
Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
@ -579,6 +576,40 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
});
}
private void setDuplicateColumnCellFactory() {
duplicateColumn.getStyleClass().add("avatar-column");
duplicateColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
duplicateColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<Tradable, Tradable> call(TableColumn<Tradable, Tradable> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final Tradable item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty && isMyOfferAsMaker(item.getOffer().getOfferPayloadBase())) {
if (button == null) {
button = getRegularIconButton(MaterialDesignIcon.CONTENT_COPY);
button.setTooltip(new Tooltip(Res.get("shared.duplicateOffer")));
setGraphic(button);
}
button.setOnAction(event -> onDuplicateOffer(item.getOffer()));
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
}
@SuppressWarnings("UnusedReturnValue")
private TableColumn<Tradable, Tradable> setAvatarColumnCellFactory() {
avatarColumn.getStyleClass().addAll("last-column", "avatar-column");
@ -593,7 +624,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
public void updateItem(final Tradable item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty && item instanceof TradeModel) {
if (!empty && item instanceof TradeModel) {
TradeModel tradeModel = (TradeModel) item;
int numPastTrades = model.dataModel.getNumPastTrades(tradeModel);
NodeAddress tradingPeerNodeAddress = tradeModel.getTradingPeerNodeAddress();
@ -783,6 +814,23 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
});
}
private void onDuplicateOffer(Offer offer) {
try {
OfferPayloadBase offerPayloadBase = offer.getOfferPayloadBase();
if (isMyOfferAsMaker(offerPayloadBase)) {
PortfolioUtil.duplicateOffer(navigation, offerPayloadBase);
} else {
new Popup().warning(Res.get("portfolio.context.notYourOffer")).show();
}
} catch (NullPointerException e) {
log.warn("Unable to get offerPayload - {}", e.toString());
}
}
private boolean isMyOfferAsMaker(OfferPayloadBase offerPayloadBase) {
return offerPayloadBase.getPubKeyRing().equals(keyRing.getPubKeyRing());
}
private Tradable getDummyTradable() {
return new Tradable() {
@Override

View file

@ -25,10 +25,14 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.TradeCurrency;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.bisq_v1.CreateOfferService;
import bisq.core.payment.BsqSwapAccount;
import bisq.core.payment.PaymentAccount;
import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatisticsManager;
@ -46,23 +50,25 @@ import com.google.inject.Inject;
import javax.inject.Named;
import java.util.Optional;
class DuplicateOfferDataModel extends MutableOfferDataModel {
@Inject
DuplicateOfferDataModel(CreateOfferService createOfferService,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,
P2PService p2PService,
PriceFeedService priceFeedService,
AccountAgeWitnessService accountAgeWitnessService,
FeeService feeService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
TradeStatisticsManager tradeStatisticsManager,
Navigation navigation) {
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
BtcWalletService btcWalletService,
BsqWalletService bsqWalletService,
Preferences preferences,
User user,
P2PService p2PService,
PriceFeedService priceFeedService,
AccountAgeWitnessService accountAgeWitnessService,
FeeService feeService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
TradeStatisticsManager tradeStatisticsManager,
Navigation navigation) {
super(createOfferService,
openOfferManager,
@ -104,4 +110,25 @@ class DuplicateOfferDataModel extends MutableOfferDataModel {
return Math.min(offerBuyerSecurityDepositAsPercent,
Restrictions.getMaxBuyerSecurityDepositAsPercent());
}
@Override
protected PaymentAccount getPreselectedPaymentAccount() {
// If trade currency is BSQ don't use the BSQ swap payment account as it will automatically
// close the duplicate offer view
Optional<TradeCurrency> bsqOptional = CurrencyUtil.getTradeCurrency("BSQ");
if (bsqOptional.isPresent() && tradeCurrency.equals(bsqOptional.get()) && user.getPaymentAccounts() != null) {
Optional<PaymentAccount> firstBsqPaymentAccount = user.getPaymentAccounts().stream().filter(paymentAccount1 -> {
Optional<TradeCurrency> tradeCurrency = paymentAccount1.getTradeCurrency();
return tradeCurrency.isPresent() &&
tradeCurrency.get().equals(bsqOptional.get()) &&
!paymentAccount1.getId().equals(BsqSwapAccount.ID);
}).findFirst();
if (firstBsqPaymentAccount.isPresent()) {
return firstBsqPaymentAccount.get();
}
}
return super.getPreselectedPaymentAccount();
}
}

View file

@ -55,6 +55,7 @@
<TableColumn fx:id="deactivateItemColumn" minWidth="60" maxWidth="60" sortable="false"/>
<TableColumn fx:id="editItemColumn" minWidth="30" maxWidth="30" sortable="false"/>
<TableColumn fx:id="triggerIconColumn" minWidth="30" maxWidth="30" sortable="false"/>
<TableColumn fx:id="duplicateItemColumn" minWidth="30" maxWidth="30" sortable="false"/>
<TableColumn fx:id="removeItemColumn" minWidth="30" maxWidth="30" sortable="false"/>
</columns>
</TableView>

View file

@ -33,12 +33,11 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.BsqSwapOfferDetailsWindow;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView;
import bisq.desktop.main.portfolio.presentation.PortfolioUtil;
import bisq.desktop.util.GUIUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayloadBase;
import bisq.core.offer.OpenOffer;
import bisq.core.user.DontShowAgainLookup;
@ -98,7 +97,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
@FXML
TableColumn<OpenOfferListItem, OpenOfferListItem> priceColumn, deviationColumn, amountColumn, volumeColumn,
marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn,
removeItemColumn, editItemColumn, triggerPriceColumn, triggerIconColumn, paymentMethodColumn;
removeItemColumn, editItemColumn, triggerPriceColumn, triggerIconColumn, paymentMethodColumn, duplicateItemColumn;
@FXML
HBox searchBox;
@FXML
@ -152,6 +151,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
triggerPriceColumn.setGraphic(new AutoTooltipLabel(Res.get("openOffer.header.triggerPrice")));
deactivateItemColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.enabled")));
editItemColumn.setGraphic(new AutoTooltipLabel(""));
duplicateItemColumn.setGraphic(new AutoTooltipLabel(""));
removeItemColumn.setGraphic(new AutoTooltipLabel(""));
setOfferIdColumnCellFactory();
@ -167,6 +167,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
setEditColumnCellFactory();
setTriggerIconColumnCellFactory();
setTriggerPriceColumnCellFactory();
setDuplicateColumnCellFactory();
setRemoveColumnCellFactory();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -191,17 +192,9 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
tableView -> {
final TableRow<OpenOfferListItem> row = new TableRow<>();
final ContextMenu rowMenu = new ContextMenu();
MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
editItem.setOnAction((event) -> {
try {
OfferPayloadBase offerPayloadBase = row.getItem().getOffer().getOfferPayloadBase();
navigation.navigateToWithData(offerPayloadBase, MainView.class, PortfolioView.class,
DuplicateOfferView.class);
} catch (NullPointerException e) {
log.warn("Unable to get offerPayload - {}", e.toString());
}
});
rowMenu.getItems().add(editItem);
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
duplicateItem.setOnAction((event) -> onDuplicateOffer(row.getItem().getOffer()));
rowMenu.getItems().add(duplicateItem);
row.contextMenuProperty().bind(
Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
@ -215,7 +208,7 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
searchBox.setSpacing(5);
HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS);
selectToggleButton.setPadding(new Insets(0, 60, -20, 0));
selectToggleButton.setPadding(new Insets(0, 90, -20, 0));
selectToggleButton.setText(Res.get("shared.enabled"));
selectToggleButton.setDisable(true);
@ -356,11 +349,8 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
if (model.getDirectionLabel(item).contains(filterString)) {
return true;
}
if (offer.getOfferFeePaymentTxId() != null &&
offer.getOfferFeePaymentTxId().contains(filterString)) {
return true;
}
return false;
return offer.getOfferFeePaymentTxId() != null &&
offer.getOfferFeePaymentTxId().contains(filterString);
});
}
@ -440,6 +430,14 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
}
}
private void onDuplicateOffer(Offer offer) {
try {
PortfolioUtil.duplicateOffer(navigation, offer.getOfferPayloadBase());
} catch (NullPointerException e) {
log.warn("Unable to get offerPayload - {}", e.toString());
}
}
private void setOfferIdColumnCellFactory() {
offerIdColumn.setCellValueFactory((openOfferListItem) -> new ReadOnlyObjectWrapper<>(openOfferListItem.getValue()));
offerIdColumn.getStyleClass().addAll("number-column", "first-column");
@ -787,6 +785,40 @@ public class OpenOffersView extends ActivatableViewAndModel<VBox, OpenOffersView
});
}
private void setDuplicateColumnCellFactory() {
duplicateItemColumn.getStyleClass().add("avatar-column");
duplicateItemColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
duplicateItemColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<OpenOfferListItem, OpenOfferListItem> call(TableColumn<OpenOfferListItem, OpenOfferListItem> column) {
return new TableCell<>() {
Button button;
@Override
public void updateItem(final OpenOfferListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (button == null) {
button = getRegularIconButton(MaterialDesignIcon.CONTENT_COPY);
button.setTooltip(new Tooltip(Res.get("shared.duplicateOffer")));
setGraphic(button);
}
button.setOnAction(event -> onDuplicateOffer(item.getOffer()));
} else {
setGraphic(null);
if (button != null) {
button.setOnAction(null);
button = null;
}
}
}
};
}
});
}
private void setTriggerIconColumnCellFactory() {
triggerIconColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue()));
triggerIconColumn.setCellFactory(

View file

@ -26,8 +26,7 @@ import bisq.desktop.components.PeerInfoIconTrading;
import bisq.desktop.main.MainView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView;
import bisq.desktop.main.portfolio.presentation.PortfolioUtil;
import bisq.desktop.main.shared.ChatView;
import bisq.desktop.util.CssTheme;
import bisq.desktop.util.DisplayUtils;
@ -232,12 +231,12 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
tableView -> {
final TableRow<PendingTradesListItem> row = new TableRow<>();
final ContextMenu rowMenu = new ContextMenu();
MenuItem editItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
editItem.setOnAction((event) -> {
MenuItem duplicateItem = new MenuItem(Res.get("portfolio.context.offerLikeThis"));
duplicateItem.setOnAction((event) -> {
try {
OfferPayload offerPayload = row.getItem().getTrade().getOffer().getOfferPayload().orElseThrow();
if (offerPayload.getPubKeyRing().equals(keyRing.getPubKeyRing())) {
navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class);
PortfolioUtil.duplicateOffer(navigation, offerPayload);
} else {
new Popup().warning(Res.get("portfolio.context.notYourOffer")).show();
}
@ -245,7 +244,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
log.warn("Unable to get offerPayload - {}", e.toString());
}
});
rowMenu.getItems().add(editItem);
rowMenu.getItems().add(duplicateItem);
row.contextMenuProperty().bind(
Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)

View file

@ -0,0 +1,42 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.portfolio.presentation;
import bisq.desktop.Navigation;
import bisq.desktop.main.MainView;
import bisq.desktop.main.offer.BuyOfferView;
import bisq.desktop.main.offer.SellOfferView;
import bisq.desktop.main.offer.bsq_swap.create_offer.BsqSwapCreateOfferView;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.portfolio.duplicateoffer.DuplicateOfferView;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferPayloadBase;
import bisq.core.offer.bsq_swap.BsqSwapOfferPayload;
public class PortfolioUtil {
public static void duplicateOffer(Navigation navigation, OfferPayloadBase offerPayload) {
if (offerPayload instanceof BsqSwapOfferPayload) {
var offerViewClass = offerPayload.getDirection() == OfferDirection.BUY ? BuyOfferView.class : SellOfferView.class;
navigation.navigateToWithData(offerPayload, MainView.class, offerViewClass, BsqSwapCreateOfferView.class);
} else {
navigation.navigateToWithData(offerPayload, MainView.class, PortfolioView.class, DuplicateOfferView.class);
}
}
}