Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add favourites system to offerbook markets #1817

Merged
merged 18 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import bisq.chat.bisqeasy.open_trades.BisqEasyOpenTradeChannel;
import bisq.common.currency.Market;
import bisq.common.observable.Pin;
import bisq.common.observable.collection.CollectionObserver;
import bisq.common.observable.collection.ObservableArray;
import bisq.common.util.ProtobufUtils;
import bisq.desktop.ServiceProvider;
Expand All @@ -41,6 +42,7 @@
import bisq.settings.CookieKey;
import bisq.settings.SettingsService;
import javafx.collections.ListChangeListener;
import javafx.collections.SetChangeListener;
import javafx.scene.layout.StackPane;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
Expand All @@ -54,11 +56,15 @@

@Slf4j
public final class BisqEasyOfferbookController extends ChatController<BisqEasyOfferbookView, BisqEasyOfferbookModel> {
private static final double MARKET_SELECTION_LIST_CELL_HEIGHT = 53;

private final SettingsService settingsService;
private final MarketPriceService marketPriceService;
private final BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService;
private final BisqEasyOfferbookModel bisqEasyOfferbookModel;
private Pin offerOnlySettingsPin, bisqEasyPrivateTradeChatChannelsPin, selectedChannelPin, marketPriceByCurrencyMapPin;
private final SetChangeListener<Market> favouriteMarketsListener;
private Pin offerOnlySettingsPin, bisqEasyPrivateTradeChatChannelsPin, selectedChannelPin,
marketPriceByCurrencyMapPin, favouriteMarketsPin;
private Subscription marketSelectorSearchPin, selectedMarketFilterPin, selectedOfferDirectionOrOwnerFilterPin,
selectedPeerReputationFilterPin, selectedMarketSortTypePin;

Expand All @@ -69,6 +75,24 @@ public BisqEasyOfferbookController(ServiceProvider serviceProvider) {
settingsService = serviceProvider.getSettingsService();
marketPriceService = serviceProvider.getBondedRolesService().getMarketPriceService();
bisqEasyOfferbookModel = getModel();
favouriteMarketsListener = change -> {
if (change.wasAdded()) {
Market market = change.getElementAdded();
model.getMarketChannelItems().forEach(item -> item.getIsFavourite().set(market.equals(item.getMarket())));
}

if (change.wasRemoved()) {
Market market = change.getElementRemoved();
model.getMarketChannelItems().forEach(item -> {
if (market.equals(item.getMarket()) && item.getIsFavourite().get()) {
item.getIsFavourite().set(false);
}
});
}

updateFilteredMarketChannelItems();
updateFavouriteMarketChannelItems();
};

createMarketChannels();
}
Expand Down Expand Up @@ -169,8 +193,32 @@ public void onActivate() {
}
});

CollectionObserver<Market> favouriteMarketsObserver = new CollectionObserver<>() {
@Override
public void add(Market market) {
model.getFavouriteMarkets().add(market);
}

@Override
public void remove(Object element) {
if (element instanceof Market) {
model.getFavouriteMarkets().remove((Market) element);
}
}

@Override
public void clear() {
model.getFavouriteMarkets().clear();
}
};
favouriteMarketsPin = settingsService.getFavouriteMarkets().addObserver(favouriteMarketsObserver);

model.getFavouriteMarkets().addListener(favouriteMarketsListener);

model.getSortedMarketChannelItems().setComparator(model.getSelectedMarketSortType().get().getComparator());

updateFilteredMarketChannelItems();
updateFavouriteMarketChannelItems();
maybeSelectFirst();
}

Expand All @@ -187,10 +235,10 @@ public void onDeactivate() {
selectedPeerReputationFilterPin.unsubscribe();
marketPriceByCurrencyMapPin.unbind();
selectedMarketSortTypePin.unsubscribe();
favouriteMarketsPin.unbind();
model.getFavouriteMarkets().removeListener(favouriteMarketsListener);

resetSelectedChildTarget();

model.getMarketChannelItems().forEach(MarketChannelItem::cleanUp);
}

@Override
Expand All @@ -210,10 +258,7 @@ protected void selectedChannelChanged(ChatChannel<? extends ChatMessage> chatCha
model.getMarketChannelItems().stream()
.filter(item -> item.getChannel().equals(channel))
.findAny()
.ifPresent(item -> {
model.getSelectedMarketChannelItem().set(item);
updateSelectedMarketChannelItem(item);
});
.ifPresent(item -> model.getSelectedMarketChannelItem().set(item));

model.getSearchText().set("");
resetSelectedChildTarget();
Expand Down Expand Up @@ -269,7 +314,15 @@ private void updateFilteredMarketChannelItems() {
model.getFilteredMarketChannelItems().setPredicate(item ->
model.getMarketFilterPredicate().test(item) &&
model.getMarketSearchTextPredicate().test(item) &&
model.getMarketPricePredicate().test(item));
model.getMarketPricePredicate().test(item) &&
!model.getFavouriteMarkets().contains(item.getMarket()));
}

private void updateFavouriteMarketChannelItems() {
model.getFavouriteMarketChannelItems().setPredicate(item -> model.getFavouriteMarkets().contains(item.getMarket()));
double padding = 15;
double tableViewHeight = (model.getFavouriteMarketChannelItems().size() * MARKET_SELECTION_LIST_CELL_HEIGHT) + padding;
model.getFavouritesTableViewHeight().set(tableViewHeight);
}

private boolean isMaker(BisqEasyOffer bisqEasyOffer) {
Expand All @@ -292,7 +345,7 @@ private void maybeSelectFirst() {
}
}

private void updateSelectedMarketChannelItem(MarketChannelItem selectedItem) {
model.getMarketChannelItems().forEach(item -> item.getSelected().set(item == selectedItem));
double getMarketSelectionListCellHeight() {
return MARKET_SELECTION_LIST_CELL_HEIGHT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
package bisq.desktop.main.content.bisq_easy.offerbook;

import bisq.chat.ChatChannelDomain;
import bisq.common.currency.Market;
import bisq.desktop.main.content.chat.ChatModel;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import lombok.Getter;
Expand All @@ -47,13 +49,17 @@ public final class BisqEasyOfferbookModel extends ChatModel {
private final ObjectProperty<Filters.PeerReputation> selectedPeerReputationFilter = new SimpleObjectProperty<>();
private final ObjectProperty<MarketSortType> selectedMarketSortType = new SimpleObjectProperty<>(MarketSortType.NUM_OFFERS);
private final StringProperty marketPrice = new SimpleStringProperty();
private final ObservableSet<Market> favouriteMarkets = FXCollections.observableSet();
private final FilteredList<MarketChannelItem> favouriteMarketChannelItems = new FilteredList<>(marketChannelItems);

@Setter
private Predicate<MarketChannelItem> marketPricePredicate = marketChannelItem -> true;
@Setter
private Predicate<MarketChannelItem> marketSearchTextPredicate = marketChannelItem -> true;
@Setter
private Predicate<MarketChannelItem> marketFilterPredicate = marketChannelItem -> true;
@Setter
private DoubleProperty favouritesTableViewHeight = new SimpleDoubleProperty(0);

public BisqEasyOfferbookModel(ChatChannelDomain chatChannelDomain) {
super(chatChannelDomain);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,101 @@

import bisq.common.currency.Market;
import bisq.common.currency.MarketRepository;
import bisq.desktop.common.utils.ImageUtil;
import bisq.desktop.components.containers.Spacer;
import bisq.desktop.components.controls.BisqTooltip;
import bisq.i18n.Res;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringExpression;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.Tooltip;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;

import java.util.Comparator;
import java.util.List;

public class BisqEasyOfferbookUtil {
private static final List<Market> majorMarkets = MarketRepository.getMajorMarkets();
static final List<Market> majorMarkets = MarketRepository.getMajorMarkets();

public static Comparator<MarketChannelItem> sortByNumOffers() {
static Comparator<MarketChannelItem> sortByNumOffers() {
return (lhs, rhs) -> Integer.compare(rhs.getNumOffers().get(), lhs.getNumOffers().get());
}

public static Comparator<MarketChannelItem> sortByMajorMarkets() {
static Comparator<MarketChannelItem> sortByMajorMarkets() {
return (lhs, rhs) -> {
int index1 = majorMarkets.indexOf(lhs.getMarket());
int index2 = majorMarkets.indexOf(rhs.getMarket());
return Integer.compare(index1, index2);
};
}

public static Comparator<MarketChannelItem> sortByMarketNameAsc() {
return Comparator.comparing(MarketChannelItem::getMarketString);
static Comparator<MarketChannelItem> sortByMarketNameAsc() {
return Comparator.comparing(MarketChannelItem::toString);
}

public static Comparator<MarketChannelItem> sortByMarketNameDesc() {
return Comparator.comparing(MarketChannelItem::getMarketString).reversed();
static Comparator<MarketChannelItem> sortByMarketNameDesc() {
return Comparator.comparing(MarketChannelItem::toString).reversed();
}

public static Comparator<MarketChannelItem> sortByMarketActivity() {
static Comparator<MarketChannelItem> sortByMarketActivity() {
return (lhs, rhs) -> BisqEasyOfferbookUtil.sortByNumOffers()
.thenComparing(BisqEasyOfferbookUtil.sortByMajorMarkets())
.thenComparing(BisqEasyOfferbookUtil.sortByMarketNameAsc())
.compare(lhs, rhs);
}

public static Callback<TableColumn<MarketChannelItem, MarketChannelItem>,
TableCell<MarketChannelItem, MarketChannelItem>> getMarketLabelCellFactory() {
static Callback<TableColumn<MarketChannelItem, MarketChannelItem>,
TableCell<MarketChannelItem, MarketChannelItem>> getMarketLabelCellFactory(boolean isFavouritesTableView) {
return column -> new TableCell<>() {
private final Label marketName = new Label();
private final Label marketCode = new Label();
private final Label numOffers = new Label();
private final Label favouriteLabel = new Label();
private final ImageView star;
private final HBox hBox = new HBox(10, marketCode, numOffers);
private final VBox vBox = new VBox(0, marketName, hBox);
private final Tooltip tooltip = new BisqTooltip();
private final HBox container = new HBox(0, vBox, Spacer.fillHBox(), favouriteLabel);
private final Tooltip marketDetailsTooltip = new BisqTooltip();
private final Tooltip favouriteTooltip = new BisqTooltip();
private Subscription selectedPin;

{
setCursor(Cursor.HAND);
marketName.getStyleClass().add("market-name");
hBox.setAlignment(Pos.CENTER_LEFT);
vBox.setAlignment(Pos.CENTER_LEFT);
Tooltip.install(vBox, tooltip);
Tooltip.install(vBox, marketDetailsTooltip);
marketDetailsTooltip.setStyle("-fx-text-fill: -fx-dark-text-color;");

favouriteTooltip.textProperty().set(isFavouritesTableView
? Res.get("bisqEasy.offerbook.marketListCell.favourites.tooltip.removeFromFavourites")
: Res.get("bisqEasy.offerbook.marketListCell.favourites.tooltip.addToFavourites"));
favouriteTooltip.setStyle("-fx-text-fill: -fx-dark-text-color;");
star = ImageUtil.getImageViewById(isFavouritesTableView
? "favourites-star-yellow"
: "favourites-star-grey-hollow");
favouriteLabel.setGraphic(star);
favouriteLabel.getStyleClass().add("favourite-label");
Tooltip.install(favouriteLabel, favouriteTooltip);

container.setAlignment(Pos.CENTER_LEFT);
}

@Override
protected void updateItem(MarketChannelItem item, boolean empty) {
super.updateItem(item, empty);

// Clean up previous row
if (getTableRow() != null && selectedPin != null) {
selectedPin.unsubscribe();
}

if (item != null && !empty) {
marketName.setText(item.getMarket().getQuoteCurrencyName());
marketCode.setText(item.getMarket().getQuoteCurrencyCode());
Expand All @@ -79,21 +105,30 @@ protected void updateItem(MarketChannelItem item, boolean empty) {
numOffers.textProperty().bind(formattedNumOffers);
StringExpression formattedTooltip = Bindings.createStringBinding(() ->
BisqEasyOfferbookUtil.getFormattedTooltip(item.getNumOffers().get(), item.getMarket().getQuoteCurrencyName()), item.getNumOffers());
tooltip.textProperty().bind(formattedTooltip);
tooltip.setStyle("-fx-text-fill: -fx-dark-text-color;");
marketDetailsTooltip.textProperty().bind(formattedTooltip);

// Set up new row
TableRow<MarketChannelItem> newRow = getTableRow();
if (newRow != null) {
selectedPin = EasyBind.subscribe(newRow.selectedProperty(), item::updateMarketLogoEffect);
}

setGraphic(vBox);
favouriteLabel.setOnMouseClicked(e -> item.toggleFavourite());

setGraphic(container);
} else {
numOffers.textProperty().unbind();
tooltip.textProperty().unbind();
marketDetailsTooltip.textProperty().unbind();

favouriteLabel.setOnMouseClicked(null);

setGraphic(null);
}
}
};
}

public static Callback<TableColumn<MarketChannelItem, MarketChannelItem>,
static Callback<TableColumn<MarketChannelItem, MarketChannelItem>,
TableCell<MarketChannelItem, MarketChannelItem>> getMarketLogoCellFactory() {
return column -> new TableCell<>() {
{
Expand Down
Loading
Loading