Skip to content

Commit

Permalink
Add toggle for filtering offers which can be taken with users accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
chimp1984 committed Jan 4, 2021
1 parent c3c1f95 commit 9e27504
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 24 deletions.
7 changes: 7 additions & 0 deletions core/src/main/java/bisq/core/user/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,11 @@ public void setIgnoreDustThreshold(int value) {
requestPersistence();
}

public void setShowOffersMatchingMyAccounts(boolean value) {
prefPayload.setShowOffersMatchingMyAccounts(value);
requestPersistence();
}


///////////////////////////////////////////////////////////////////////////////////////////
// Getter
Expand Down Expand Up @@ -1081,5 +1086,7 @@ private interface ExcludesDelegateMethods {
void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings);

void setHideNonAccountPaymentMethods(boolean hideNonAccountPaymentMethods);

void setShowOffersMatchingMyAccounts(boolean value);
}
}
8 changes: 6 additions & 2 deletions core/src/main/java/bisq/core/user/PreferencesPayload.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public final class PreferencesPayload implements PersistableEnvelope {

// Added in 1.5.5
private boolean hideNonAccountPaymentMethods;
private boolean showOffersMatchingMyAccounts;


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -195,7 +197,8 @@ public Message toProtoMessage() {
.addAllAutoConfirmSettings(autoConfirmSettingsList.stream()
.map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage()))
.collect(Collectors.toList()))
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods);
.setHideNonAccountPaymentMethods(hideNonAccountPaymentMethods)
.setShowOffersMatchingMyAccounts(showOffersMatchingMyAccounts);

Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
Expand Down Expand Up @@ -290,7 +293,8 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co
new ArrayList<>(proto.getAutoConfirmSettingsList().stream()
.map(AutoConfirmSettings::fromProto)
.collect(Collectors.toList())),
proto.getHideNonAccountPaymentMethods()
proto.getHideNonAccountPaymentMethods(),
proto.getShowOffersMatchingMyAccounts()
);
}
}
1 change: 1 addition & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ offerbook.offerersAcceptedBankSeats=Accepted seat of bank countries (taker):\n {
offerbook.availableOffers=Available offers
offerbook.filterByCurrency=Filter by currency
offerbook.filterByPaymentMethod=Filter by payment method
offerbook.matchingOffers=Offers matching my accounts
offerbook.timeSinceSigning=Account info
offerbook.timeSinceSigning.info=This account was verified and {0}
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peer accounts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipSlideToggleButton;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.AutocompleteComboBox;
import bisq.desktop.components.ColoredDecimalPlacesWithZerosText;
Expand Down Expand Up @@ -126,6 +127,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
private AutocompleteComboBox<TradeCurrency> currencyComboBox;
private AutocompleteComboBox<PaymentMethod> paymentMethodComboBox;
private AutoTooltipButton createOfferButton;
private AutoTooltipSlideToggleButton matchingOffersToggle;
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, marketColumn,
priceColumn, paymentMethodColumn, depositColumn, signingStateColumn, avatarColumn;
private TableView<OfferBookListItem> tableView;
Expand Down Expand Up @@ -174,22 +176,32 @@ public void initialize() {
hBox.setSpacing(35);
hBox.setPadding(new Insets(10, 0, 0, 0));

final Tuple3<VBox, Label, AutocompleteComboBox<TradeCurrency>> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox(
Tuple3<VBox, Label, AutocompleteComboBox<TradeCurrency>> currencyBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox(
Res.get("offerbook.filterByCurrency"));
final Tuple3<VBox, Label, AutocompleteComboBox<PaymentMethod>> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox(
currencyComboBox = currencyBoxTuple.third;
currencyComboBox.setPrefWidth(270);

Tuple3<VBox, Label, AutocompleteComboBox<PaymentMethod>> paymentBoxTuple = FormBuilder.addTopLabelAutocompleteComboBox(
Res.get("offerbook.filterByPaymentMethod"));
paymentMethodComboBox = paymentBoxTuple.third;
paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory());
paymentMethodComboBox.setPrefWidth(270);

matchingOffersToggle = new AutoTooltipSlideToggleButton();
matchingOffersToggle.setText(Res.get("offerbook.matchingOffers"));
HBox.setMargin(matchingOffersToggle, new Insets(7, 0, -9, -15));

hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, matchingOffersToggle);
AnchorPane.setLeftAnchor(hBox, 0d);
AnchorPane.setTopAnchor(hBox, 0d);
AnchorPane.setBottomAnchor(hBox, 0d);

createOfferButton = new AutoTooltipButton();
createOfferButton.setMinHeight(40);
createOfferButton.setGraphicTextGap(10);
AnchorPane.setRightAnchor(createOfferButton, 0d);
AnchorPane.setBottomAnchor(createOfferButton, 0d);

hBox.getChildren().addAll(currencyBoxTuple.first, paymentBoxTuple.first, createOfferButton);
AnchorPane.setLeftAnchor(hBox, 0d);
AnchorPane.setTopAnchor(hBox, 0d);
AnchorPane.setBottomAnchor(hBox, 0d);

AnchorPane anchorPane = new AnchorPane();
anchorPane.getChildren().addAll(hBox, createOfferButton);

Expand All @@ -199,11 +211,6 @@ public void initialize() {
GridPane.setMargin(anchorPane, new Insets(Layout.FIRST_ROW_DISTANCE, 0, 0, 0));
root.getChildren().add(anchorPane);

currencyComboBox = currencyBoxTuple.third;

paymentMethodComboBox = paymentBoxTuple.third;
paymentMethodComboBox.setCellFactory(GUIUtil.getPaymentMethodCellFactory());

tableView = new TableView<>();

GridPane.setRowIndex(tableView, ++gridRow);
Expand Down Expand Up @@ -328,6 +335,10 @@ protected void activate() {

currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem()));

matchingOffersToggle.setSelected(model.useOffersMatchingMyAccountsFilter);
matchingOffersToggle.disableProperty().bind(model.disableMatchToggle);
matchingOffersToggle.setOnAction(e -> model.onShowOffersMatchingMyAccounts(matchingOffersToggle.isSelected()));

volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not());
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());

Expand Down Expand Up @@ -424,6 +435,8 @@ private void updateSigningStateColumn() {
@Override
protected void deactivate() {
createOfferButton.setOnAction(null);
matchingOffersToggle.setOnAction(null);
matchingOffersToggle.disableProperty().unbind();
model.getOfferList().comparatorProperty().unbind();

volumeColumn.sortableProperty().unbind();
Expand Down Expand Up @@ -1024,6 +1037,10 @@ public void updateItem(final OfferBookListItem item, boolean empty) {
final Offer offer = item.getOffer();
boolean myOffer = model.isMyOffer(offer);
if (tableRow != null) {
// this code is duplicated in model.getOffersMatchingMyAccountsPredicate but as
// we want to pass the results for displaying relevant info in popups we
// cannot simply replace it with the predicate. If there are any changes we
// need to maintain both.
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
isInsufficientCounterpartyTradeLimit = model.isInsufficientCounterpartyTradeLimit(offer);
hasSameProtocolVersion = model.hasSameProtocolVersion(offer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,19 @@
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;

import java.text.DecimalFormat;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -126,11 +129,15 @@ class OfferBookViewModel extends ActivatableViewModel {

private boolean isTabSelected;
final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(true);
final BooleanProperty disableMatchToggle = new SimpleBooleanProperty();
final IntegerProperty maxPlacesForAmount = new SimpleIntegerProperty();
final IntegerProperty maxPlacesForVolume = new SimpleIntegerProperty();
final IntegerProperty maxPlacesForPrice = new SimpleIntegerProperty();
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
boolean showAllPaymentMethods = true;
boolean useOffersMatchingMyAccountsFilter;
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
private final Map<String, Boolean> insufficientCounterpartyTradeLimitCache = new HashMap<>();


///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -206,6 +213,10 @@ public OfferBookViewModel(User user,

highestMarketPriceMarginOffer.ifPresent(offerBookListItem -> maxPlacesForMarketPriceMargin.set(formatMarketPriceMargin(offerBookListItem.getOffer(), false).length()));
};

// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
myInsufficientTradeLimitCache.clear());
}

@Override
Expand All @@ -223,10 +234,13 @@ protected void activate() {
}
tradeCurrencyCode.set(selectedTradeCurrency.getCode());

disableMatchToggle.set(user.getPaymentAccounts() == null || user.getPaymentAccounts().isEmpty());
useOffersMatchingMyAccountsFilter = !disableMatchToggle.get() && isShowOffersMatchingMyAccounts();

fillAllTradeCurrencies();
preferences.getTradeCurrenciesAsObservable().addListener(tradeCurrencyListChangeListener);
offerBook.fillOfferBookListItems();
applyFilterPredicate();
filterOffers();
setMarketPriceFeedCurrency();

priceUtil.recalculateBsq30DayAveragePrice();
Expand All @@ -238,6 +252,7 @@ protected void deactivate() {
preferences.getTradeCurrenciesAsObservable().removeListener(tradeCurrencyListChangeListener);
}


///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -268,7 +283,7 @@ else if (!showAllEntry) {
}

setMarketPriceFeedCurrency();
applyFilterPredicate();
filterOffers();

if (direction == OfferPayload.Direction.BUY)
preferences.setBuyScreenCurrencyCode(code);
Expand All @@ -294,17 +309,28 @@ void onSetPaymentMethod(PaymentMethod paymentMethod) {
this.selectedPaymentMethod = getShowAllEntryForPaymentMethod();
}

applyFilterPredicate();
filterOffers();
}

void onRemoveOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
openOfferManager.removeOffer(offer, resultHandler, errorMessageHandler);
}

void onShowOffersMatchingMyAccounts(boolean isSelected) {
useOffersMatchingMyAccountsFilter = isSelected;
preferences.setShowOffersMatchingMyAccounts(useOffersMatchingMyAccountsFilter);
filterOffers();
}


///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////

boolean isShowOffersMatchingMyAccounts() {
return preferences.isShowOffersMatchingMyAccounts();
}

SortedList<OfferBookListItem> getOfferList() {
return sortedItems;
}
Expand Down Expand Up @@ -561,8 +587,15 @@ boolean canCreateOrTakeOffer() {
// Filters
///////////////////////////////////////////////////////////////////////////////////////////

private void applyFilterPredicate() {
filteredItems.setPredicate(offerBookListItem -> {
private void filterOffers() {
Predicate<OfferBookListItem> predicate = useOffersMatchingMyAccountsFilter ?
getCurrencyAndMethodPredicate().and(getOffersMatchingMyAccountsPredicate()) :
getCurrencyAndMethodPredicate();
filteredItems.setPredicate(predicate);
}

private Predicate<OfferBookListItem> getCurrencyAndMethodPredicate() {
return offerBookListItem -> {
Offer offer = offerBookListItem.getOffer();
boolean directionResult = offer.getDirection() != direction;
boolean currencyResult = (showAllTradeCurrenciesProperty.get()) ||
Expand All @@ -571,7 +604,37 @@ private void applyFilterPredicate() {
offer.getPaymentMethod().equals(selectedPaymentMethod);
boolean notMyOfferOrShowMyOffersActivated = !isMyOffer(offerBookListItem.getOffer()) || preferences.isShowOwnOffersInOfferBook();
return directionResult && currencyResult && paymentMethodResult && notMyOfferOrShowMyOffersActivated;
});
};
}

private Predicate<OfferBookListItem> getOffersMatchingMyAccountsPredicate() {
// This code duplicates code in the view at the button column. We need there the different results for
// display in popups so we cannot replace that with the predicate. Any change need to be applied in both
// places.
return offerBookListItem -> {
Offer offer = offerBookListItem.getOffer();
boolean isPaymentAccountValidForOffer = isAnyPaymentAccountValidForOffer(offer);
boolean isInsufficientCounterpartyTradeLimit = isInsufficientCounterpartyTradeLimit(offer);
boolean hasSameProtocolVersion = hasSameProtocolVersion(offer);
boolean isIgnored = isIgnored(offer);
boolean isOfferBanned = isOfferBanned(offer);
boolean isCurrencyBanned = isCurrencyBanned(offer);
boolean isPaymentMethodBanned = isPaymentMethodBanned(offer);
boolean isNodeAddressBanned = isNodeAddressBanned(offer);
boolean requireUpdateToNewVersion = requireUpdateToNewVersion();
boolean isMyInsufficientTradeLimit = isMyInsufficientTradeLimit(offer);
boolean isTradable = isPaymentAccountValidForOffer &&
!isInsufficientCounterpartyTradeLimit &&
hasSameProtocolVersion &&
!isIgnored &&
!isOfferBanned &&
!isCurrencyBanned &&
!isPaymentMethodBanned &&
!isNodeAddressBanned &&
!requireUpdateToNewVersion &&
!isMyInsufficientTradeLimit;
return isTradable;
};
}

boolean isIgnored(Offer offer) {
Expand Down Expand Up @@ -599,13 +662,28 @@ boolean requireUpdateToNewVersion() {
return filterManager.requireUpdateToNewVersionForTrading();
}

// This call is a bit expensive so we cache results
boolean isInsufficientCounterpartyTradeLimit(Offer offer) {
return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
!accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(), errorMessage -> {
});
String offerId = offer.getId();
if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) {
return insufficientCounterpartyTradeLimitCache.get(offerId);
}

boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
!accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(),
errorMessage -> {
});
insufficientCounterpartyTradeLimitCache.put(offerId, result);
return result;
}

// This call is a bit expensive so we cache results
boolean isMyInsufficientTradeLimit(Offer offer) {
String offerId = offer.getId();
if (myInsufficientTradeLimitCache.containsKey(offerId)) {
return myInsufficientTradeLimitCache.get(offerId);
}

Optional<PaymentAccount> accountOptional = getMostMaturePaymentAccountForOffer(offer);
long myTradeLimit = accountOptional
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
Expand All @@ -616,9 +694,11 @@ boolean isMyInsufficientTradeLimit(Offer offer) {
accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null",
Coin.valueOf(myTradeLimit).toFriendlyString(),
Coin.valueOf(offerMinAmount).toFriendlyString());
return CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
accountOptional.isPresent() &&
myTradeLimit < offerMinAmount;
myInsufficientTradeLimitCache.put(offerId, result);
return result;
}

boolean hasSameProtocolVersion(Offer offer) {
Expand Down
1 change: 1 addition & 0 deletions proto/src/main/proto/pb.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,7 @@ message PreferencesPayload {
repeated AutoConfirmSettings auto_confirm_settings = 56;
double bsq_average_trim_threshold = 57;
bool hide_non_account_payment_methods = 58;
bool show_offers_matching_my_accounts = 59;
}

message AutoConfirmSettings {
Expand Down

0 comments on commit 9e27504

Please sign in to comment.