diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/BisqEasyViewUtils.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/BisqEasyViewUtils.java index 50e370b0f2..6f25729bf7 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/BisqEasyViewUtils.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/BisqEasyViewUtils.java @@ -17,18 +17,25 @@ package bisq.desktop.main.content.bisq_easy; +import bisq.account.payment_method.BitcoinPaymentMethod; +import bisq.account.payment_method.FiatPaymentMethod; +import bisq.account.payment_method.PaymentMethod; import bisq.common.data.Quadruple; import bisq.desktop.common.Layout; import bisq.desktop.common.utils.ImageUtil; +import bisq.desktop.components.controls.BisqTooltip; import bisq.desktop.components.table.BisqTableView; import bisq.security.DigestUtil; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.effect.ColorAdjust; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import java.math.BigInteger; +import java.util.List; public class BisqEasyViewUtils { private static final String[] customPaymentIconIds = { @@ -70,4 +77,37 @@ public static StackPane getCustomPaymentMethodIcon(String customPaymentMethod) { stackPane.setAlignment(Pos.CENTER); return stackPane; } + + public static HBox getPaymentAndSettlementMethodsBox(List paymentMethods, + List settlementMethods) { + HBox hBox = new HBox(8); + for (FiatPaymentMethod paymentMethod : paymentMethods) { + hBox.getChildren().add(createMethodLabel(paymentMethod)); + } + + ImageView icon = ImageUtil.getImageViewById("interchangeable-grey"); + Label interchangeableIcon = new Label(); + interchangeableIcon.setGraphic(icon); + interchangeableIcon.setPadding(new Insets(0, 0, 1, 0)); + hBox.getChildren().add(interchangeableIcon); + + for (BitcoinPaymentMethod settlementMethod : settlementMethods) { + Label label = createMethodLabel(settlementMethod); + ColorAdjust colorAdjust = new ColorAdjust(); + colorAdjust.setBrightness(-0.2); + label.setEffect(colorAdjust); + hBox.getChildren().add(label); + } + return hBox; + } + + private static Label createMethodLabel(PaymentMethod paymentMethod) { + Node icon = !paymentMethod.isCustomPaymentMethod() + ? ImageUtil.getImageViewById(paymentMethod.getName()) + : BisqEasyViewUtils.getCustomPaymentMethodIcon(paymentMethod.getDisplayString()); + Label label = new Label(); + label.setGraphic(icon); + label.setTooltip(new BisqTooltip(paymentMethod.getDisplayString())); + return label; + } } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListItem.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListItem.java index e25033bc7d..4afb85018d 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListItem.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/offerbook/offerbook_list/OfferbookListItem.java @@ -34,7 +34,7 @@ import bisq.offer.price.OfferPriceFormatter; import bisq.offer.price.PriceUtil; import bisq.offer.price.spec.FixPriceSpec; -import bisq.offer.price.spec.FloatPriceSpec; +import bisq.offer.price.spec.PriceSpecFormatter; import bisq.presentation.formatters.PercentageFormatter; import bisq.user.profile.UserProfile; import bisq.user.reputation.ReputationScore; @@ -60,7 +60,7 @@ public class OfferbookListItem { private final ReputationService reputationService; private final UserProfile senderUserProfile; private final String userNickname, formattedRangeQuoteAmount, bitcoinPaymentMethodsAsString, - fiatPaymentMethodsAsString, authorUserProfileId; + fiatPaymentMethodsAsString, authorUserProfileId, marketCurrencyCode, offerType; private final ReputationScore reputationScore; private final List fiatPaymentMethods; private final List bitcoinPaymentMethods; @@ -68,13 +68,13 @@ public class OfferbookListItem { private final Monetary quoteSideMinAmount; private final long totalScore; private double priceSpecAsPercent; - private String formattedPercentagePrice, priceTooltipText; private final Pin marketPriceByCurrencyMapPin; + private String formattedPercentagePrice, priceTooltipText; - OfferbookListItem(BisqEasyOfferbookMessage bisqEasyOfferbookMessage, - UserProfile senderUserProfile, - ReputationService reputationService, - MarketPriceService marketPriceService) { + public OfferbookListItem(BisqEasyOfferbookMessage bisqEasyOfferbookMessage, + UserProfile senderUserProfile, + ReputationService reputationService, + MarketPriceService marketPriceService) { this.bisqEasyOfferbookMessage = bisqEasyOfferbookMessage; bisqEasyOffer = bisqEasyOfferbookMessage.getBisqEasyOffer().orElseThrow(); @@ -94,6 +94,10 @@ public class OfferbookListItem { formattedRangeQuoteAmount = OfferAmountFormatter.formatQuoteAmount(marketPriceService, bisqEasyOffer, false); isFixPrice = bisqEasyOffer.getPriceSpec() instanceof FixPriceSpec; authorUserProfileId = bisqEasyOfferbookMessage.getAuthorUserProfileId(); + marketCurrencyCode = bisqEasyOffer.getMarket().getQuoteCurrencyCode(); + offerType = bisqEasyOffer.getDirection().isBuy() + ? Res.get("bisqEasy.offerbook.offerList.table.columns.offerType.buy") + : Res.get("bisqEasy.offerbook.offerList.table.columns.offerType.sell"); reputationScore = reputationService.getReputationScore(senderUserProfile); totalScore = reputationScore.getTotalScore(); @@ -113,15 +117,9 @@ boolean isBuyOffer() { private void updatePriceSpecAsPercent() { priceSpecAsPercent = PriceUtil.findPercentFromMarketPrice(marketPriceService, bisqEasyOffer).orElseThrow(); - formattedPercentagePrice = PercentageFormatter.formatToPercentWithSymbol(priceSpecAsPercent); - String price = OfferPriceFormatter.formatQuote(marketPriceService, bisqEasyOffer); - if (bisqEasyOffer.getPriceSpec() instanceof FixPriceSpec) { - priceTooltipText = Res.get("bisqEasy.offerbook.offerList.table.columns.price.tooltip.fixPrice", price, formattedPercentagePrice); - } else if (bisqEasyOffer.getPriceSpec() instanceof FloatPriceSpec) { - priceTooltipText = Res.get("bisqEasy.offerbook.offerList.table.columns.price.tooltip.floatPrice", formattedPercentagePrice, price); - } else { - priceTooltipText = Res.get("bisqEasy.offerbook.offerList.table.columns.price.tooltip.marketPrice", price); - } + formattedPercentagePrice = PercentageFormatter.formatToPercentWithSignAndSymbol(priceSpecAsPercent); + String offerPrice = OfferPriceFormatter.formatQuote(marketPriceService, bisqEasyOffer); + priceTooltipText = PriceSpecFormatter.getFormattedPriceSpecWithOfferPrice(bisqEasyOffer.getPriceSpec(), offerPrice); } private List retrieveAndSortFiatPaymentMethods() { diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessageListCellFactory.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessageListCellFactory.java index b79f4ad6a5..e17ae941d2 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessageListCellFactory.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/ChatMessageListCellFactory.java @@ -127,7 +127,7 @@ private MessageBox createMessage(ChatMessageListItem>> list) { if (item.isLeaveChatMessage()) { if (item.getChatMessage() instanceof BisqEasyOpenTradeMessage) { - return new TradePeerLefMessageBox(item, controller); + return new TradePeerLeftMessageBox(item, controller); } else { return new PeerLeftMessageBox(item, controller); } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java index 78dcb434c4..ea0745f0dc 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/BubbleMessageBox.java @@ -17,9 +17,6 @@ package bisq.desktop.main.content.chat.message_container.list.message_box; -import bisq.account.payment_method.BitcoinPaymentMethod; -import bisq.account.payment_method.FiatPaymentMethod; -import bisq.account.payment_method.PaymentMethod; import bisq.chat.ChatChannel; import bisq.chat.ChatMessage; import bisq.chat.Citation; @@ -47,13 +44,10 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Cursor; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.effect.BlurType; -import javafx.scene.effect.ColorAdjust; import javafx.scene.effect.DropShadow; -import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -273,38 +267,12 @@ private HBox createAndGetAmountAndPriceBox() { } private HBox createAndGetPaymentAndSettlementMethodsBox() { - HBox hBox = new HBox(8); if (item.isBisqEasyPublicChatMessageWithOffer()) { - for (FiatPaymentMethod fiatPaymentMethod : item.getBisqEasyOfferPaymentMethods()) { - hBox.getChildren().add(createMethodLabel(fiatPaymentMethod)); - } - - ImageView icon = ImageUtil.getImageViewById("interchangeable-grey"); - Label interchangeableIcon = new Label(); - interchangeableIcon.setGraphic(icon); - interchangeableIcon.setPadding(new Insets(0, 0, 1, 0)); - hBox.getChildren().add(interchangeableIcon); - - for (BitcoinPaymentMethod bitcoinPaymentMethod : item.getBisqEasyOfferSettlementMethods()) { - Label label = createMethodLabel(bitcoinPaymentMethod); - ColorAdjust colorAdjust = new ColorAdjust(); - colorAdjust.setBrightness(-0.2); - label.setEffect(colorAdjust); - hBox.getChildren().add(label); - } + HBox hBox = BisqEasyViewUtils.getPaymentAndSettlementMethodsBox(item.getBisqEasyOfferPaymentMethods(), item.getBisqEasyOfferSettlementMethods()); + hBox.setAlignment(Pos.BOTTOM_LEFT); + return hBox; } - hBox.setAlignment(Pos.BOTTOM_LEFT); - return hBox; - } - - private Label createMethodLabel(PaymentMethod paymentMethod) { - Node icon = !paymentMethod.isCustomPaymentMethod() - ? ImageUtil.getImageViewById(paymentMethod.getName()) - : BisqEasyViewUtils.getCustomPaymentMethodIcon(paymentMethod.getDisplayString()); - Label label = new Label(); - label.setGraphic(icon); - label.setTooltip(new BisqTooltip(paymentMethod.getDisplayString())); - return label; + return new HBox(); } private VBox createAndGetQuotedMessageBox() { diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLefMessageBox.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLeftMessageBox.java similarity index 88% rename from apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLefMessageBox.java rename to apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLeftMessageBox.java index 12d8961f0a..aa15cb69f4 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLefMessageBox.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/chat/message_container/list/message_box/TradePeerLeftMessageBox.java @@ -30,9 +30,9 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; -public final class TradePeerLefMessageBox extends PeerProtocolLogMessageBox { - public TradePeerLefMessageBox(ChatMessageListItem> item, - ChatMessagesListController controller) { +public final class TradePeerLeftMessageBox extends PeerProtocolLogMessageBox { + public TradePeerLeftMessageBox(ChatMessageListItem> item, + ChatMessagesListController controller) { super(item); BisqEasyOpenTradeMessage bisqEasyOpenTradeMessage = (BisqEasyOpenTradeMessage) item.getChatMessage(); diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardController.java index 2ec368b219..fe0d628895 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardController.java @@ -29,6 +29,7 @@ import bisq.desktop.common.view.TabController; import bisq.desktop.main.content.components.ReportToModeratorWindow; import bisq.desktop.main.content.user.profile_card.details.ProfileCardDetailsController; +import bisq.desktop.main.content.user.profile_card.offers.ProfileCardOffersController; import bisq.desktop.main.content.user.profile_card.overview.ProfileCardOverviewController; import bisq.desktop.main.content.user.profile_card.reputation.ProfileCardReputationController; import bisq.desktop.overlay.OverlayController; @@ -83,6 +84,7 @@ public InitData(UserProfile userProfile) { private final ProfileCardOverviewController profileCardOverviewController; private final ProfileCardDetailsController profileCardDetailsController; private final ProfileCardReputationController profileCardReputationController; + private final ProfileCardOffersController profileCardOffersController; private Optional> selectedChannel; private Optional ignoreUserStateHandler, closeHandler; private Subscription userProfilePin; @@ -98,6 +100,7 @@ public ProfileCardController(ServiceProvider serviceProvider) { profileCardOverviewController = new ProfileCardOverviewController(serviceProvider); profileCardDetailsController = new ProfileCardDetailsController(serviceProvider); profileCardReputationController = new ProfileCardReputationController(serviceProvider); + profileCardOffersController = new ProfileCardOffersController(serviceProvider); view = new ProfileCardView(model, this); } @@ -108,6 +111,7 @@ public void onActivate() { profileCardOverviewController.updateUserProfileData(userProfile); profileCardDetailsController.updateUserProfileData(userProfile); profileCardReputationController.updateUserProfileData(userProfile); + profileCardOffersController.updateUserProfileData(userProfile); boolean isMyProfile = userIdentityService.isUserIdentityPresent(userProfile.getId()); model.getShouldShowReportButton().set(!isMyProfile && selectedChannel.isPresent()); model.getShouldShowUserActionsMenu().set(!isMyProfile); @@ -124,7 +128,7 @@ protected Optional createController(NavigationTarget navig return switch (navigationTarget) { case PROFILE_CARD_OVERVIEW -> Optional.of(profileCardOverviewController); case PROFILE_CARD_DETAILS -> Optional.of(profileCardDetailsController); -// case PROFILE_CARD_OFFERS -> Optional.of(); + case PROFILE_CARD_OFFERS -> Optional.of(profileCardOffersController); case PROFILE_CARD_REPUTATION -> Optional.of(profileCardReputationController); default -> Optional.empty(); }; diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardView.java index 4ad492262a..e7fe960251 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/ProfileCardView.java @@ -58,7 +58,7 @@ public ProfileCardView(ProfileCardModel model, ProfileCardController controller) addTab(Res.get("user.profileCard.tab.overview"), NavigationTarget.PROFILE_CARD_OVERVIEW); addTab(Res.get("user.profileCard.tab.details"), NavigationTarget.PROFILE_CARD_DETAILS); -// addTab(Res.get("user.profileCard.tab.offers"), NavigationTarget.PROFILE_CARD_OFFERS); + addTab(Res.get("user.profileCard.tab.offers"), NavigationTarget.PROFILE_CARD_OFFERS); addTab(Res.get("user.profileCard.tab.reputation"), NavigationTarget.PROFILE_CARD_REPUTATION); } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersController.java new file mode 100644 index 0000000000..b3b72794c9 --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersController.java @@ -0,0 +1,71 @@ +/* + * 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 . + */ + +package bisq.desktop.main.content.user.profile_card.offers; + +import bisq.bonded_roles.market_price.MarketPriceService; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannel; +import bisq.chat.bisqeasy.offerbook.BisqEasyOfferbookChannelService; +import bisq.desktop.ServiceProvider; +import bisq.desktop.common.view.Controller; +import bisq.desktop.main.content.bisq_easy.offerbook.offerbook_list.OfferbookListItem; +import bisq.user.profile.UserProfile; +import bisq.user.reputation.ReputationService; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class ProfileCardOffersController implements Controller { + @Getter + private final ProfileCardOffersView view; + private final ProfileCardOffersModel model; + private final BisqEasyOfferbookChannelService bisqEasyOfferbookChannelService; + private final ReputationService reputationService; + private final MarketPriceService marketPriceService; + + public ProfileCardOffersController(ServiceProvider serviceProvider) { + model = new ProfileCardOffersModel(); + view = new ProfileCardOffersView(model, this); + bisqEasyOfferbookChannelService = serviceProvider.getChatService().getBisqEasyOfferbookChannelService(); + reputationService = serviceProvider.getUserService().getReputationService(); + marketPriceService = serviceProvider.getBondedRolesService().getMarketPriceService(); + } + + @Override + public void onActivate() { + } + + @Override + public void onDeactivate() { + } + + public void updateUserProfileData(UserProfile userProfile) { + model.getListItems().clear(); + + List userOffers = new ArrayList<>(); + for (BisqEasyOfferbookChannel market : bisqEasyOfferbookChannelService.getChannels()) { + userOffers.addAll(market.getChatMessages().stream() + .filter(chatMessage -> chatMessage.hasBisqEasyOffer() + && chatMessage.getAuthorUserProfileId().equals(userProfile.getId())) + .map(userChatMessageWithOffer -> new OfferbookListItem( + userChatMessageWithOffer, userProfile, reputationService, marketPriceService)) + .toList()); + } + model.getListItems().addAll(userOffers); + } +} diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersModel.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersModel.java new file mode 100644 index 0000000000..836945d22a --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersModel.java @@ -0,0 +1,31 @@ +/* + * 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 . + */ + +package bisq.desktop.main.content.user.profile_card.offers; + +import bisq.desktop.common.view.Model; +import bisq.desktop.main.content.bisq_easy.offerbook.offerbook_list.OfferbookListItem; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class ProfileCardOffersModel implements Model { + private final ObservableList listItems = FXCollections.observableArrayList(); +} diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersView.java new file mode 100644 index 0000000000..427aa1f972 --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/offers/ProfileCardOffersView.java @@ -0,0 +1,182 @@ +/* + * 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 . + */ + +package bisq.desktop.main.content.user.profile_card.offers; + +import bisq.desktop.common.view.View; +import bisq.desktop.components.controls.BisqTooltip; +import bisq.desktop.components.table.BisqTableColumn; +import bisq.desktop.components.table.BisqTableView; +import bisq.desktop.main.content.bisq_easy.BisqEasyViewUtils; +import bisq.desktop.main.content.bisq_easy.offerbook.offerbook_list.OfferbookListItem; +import bisq.desktop.main.content.components.MarketImageComposition; +import bisq.i18n.Res; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.CacheHint; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import lombok.extern.slf4j.Slf4j; + +import java.util.Comparator; + +@Slf4j +public class ProfileCardOffersView extends View { + private final BisqTableView tableView; + + public ProfileCardOffersView(ProfileCardOffersModel model, + ProfileCardOffersController controller) { + super(new VBox(), model, controller); + + VBox vBox = new VBox(); + vBox.setFillWidth(true); + vBox.getStyleClass().add("header"); + tableView = new BisqTableView<>(model.getListItems()); + tableView.getStyleClass().addAll("reputation-table", "rich-table-view"); + tableView.allowVerticalScrollbar(); + configTableView(); + root.getChildren().addAll(vBox, tableView); + root.setPadding(new Insets(20, 0, 0, 0)); + root.getStyleClass().add("reputation"); + } + + @Override + protected void onViewAttached() { + tableView.initialize(); + } + + @Override + protected void onViewDetached() { + tableView.dispose(); + } + + private void configTableView() { + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("user.profileCard.offers.table.columns.market")) + .left() + .comparator(Comparator.comparing(OfferbookListItem::getMarketCurrencyCode)) + .setCellFactory(getMarketCellFactory()) + .build()); + + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("user.profileCard.offers.table.columns.offerType")) + .left() + .comparator(Comparator.comparing(OfferbookListItem::getOfferType)) + .valueSupplier(OfferbookListItem::getOfferType) + .build()); + + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("user.profileCard.offers.table.columns.amount")) + .left() + .comparator(Comparator.comparing(OfferbookListItem::getQuoteSideMinAmount)) + .valueSupplier(OfferbookListItem::getFormattedRangeQuoteAmount) + .build()); + + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("user.profileCard.offers.table.columns.price")) + .right() + .comparator(Comparator.comparing(OfferbookListItem::getPriceSpecAsPercent)) + .setCellFactory(getPriceCellFactory()) + .build()); + + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("user.profileCard.offers.table.columns.paymentMethods")) + .left() + .isSortable(false) + .setCellFactory(getPaymentMethodsCellFactory()) + .build()); + } + + private Callback, + TableCell> getMarketCellFactory() { + return column -> new TableCell<>() { + private final HBox marketLogoAndCodeBox = new HBox(10); + private final Label marketCodeLabel = new Label(); + + { + marketLogoAndCodeBox.setAlignment(Pos.CENTER_LEFT); + } + + @Override + protected void updateItem(OfferbookListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + Node marketLogo = MarketImageComposition.createMarketLogo(item.getMarketCurrencyCode()); + marketLogo.setCache(true); + marketLogo.setCacheHint(CacheHint.SPEED); + marketCodeLabel.setText(item.getMarketCurrencyCode()); + marketLogoAndCodeBox.getChildren().setAll(marketLogo, marketCodeLabel); + setGraphic(marketLogoAndCodeBox); + } else { + marketCodeLabel.setText(""); + marketLogoAndCodeBox.getChildren().clear(); + setGraphic(null); + } + } + }; + } + + private Callback, + TableCell> getPriceCellFactory() { + return column -> new TableCell<>() { + private final Label percentagePriceLabel = new Label(); + private final BisqTooltip tooltip = new BisqTooltip(); + + @Override + protected void updateItem(OfferbookListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + tooltip.setText(item.getPriceTooltipText()); + percentagePriceLabel.setText(item.getFormattedPercentagePrice()); + percentagePriceLabel.setTooltip(tooltip); + setGraphic(percentagePriceLabel); + } else { + percentagePriceLabel.setText(""); + percentagePriceLabel.setTooltip(null); + setGraphic(null); + } + } + }; + } + + private Callback, + TableCell> getPaymentMethodsCellFactory() { + return column -> new TableCell<>() { + @Override + protected void updateItem(OfferbookListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + HBox paymentMethodsBox = BisqEasyViewUtils.getPaymentAndSettlementMethodsBox( + item.getFiatPaymentMethods(), item.getBitcoinPaymentMethods()); + paymentMethodsBox.setAlignment(Pos.CENTER_LEFT); + paymentMethodsBox.setPadding(new Insets(0, 10, 0, 0)); + setGraphic(paymentMethodsBox); + } else { + setGraphic(null); + } + } + }; + } +} diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationView.java index 7394228ef3..0e04cf0b70 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationView.java @@ -29,7 +29,6 @@ import bisq.presentation.formatters.TimeFormatter; import bisq.user.reputation.ReputationSource; import javafx.geometry.Insets; -import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/apps/desktop/desktop/src/main/resources/css/user.css b/apps/desktop/desktop/src/main/resources/css/user.css index 3dac1a1854..6a54266a6c 100644 --- a/apps/desktop/desktop/src/main/resources/css/user.css +++ b/apps/desktop/desktop/src/main/resources/css/user.css @@ -178,6 +178,11 @@ -fx-pref-height: 25; } +.profile-card .reputation .reputation-table.table-view .column-header-background { + -fx-background-color: -bisq-dark-grey-30; + -fx-padding: 0 0 3 0; +} + .profile-card .reputation .reputation-table.table-view .table-row-cell { -fx-border-width: 0; } diff --git a/i18n/src/main/java/bisq/i18n/Res.java b/i18n/src/main/java/bisq/i18n/Res.java index 7a71b43be2..c29aa7634c 100644 --- a/i18n/src/main/java/bisq/i18n/Res.java +++ b/i18n/src/main/java/bisq/i18n/Res.java @@ -46,7 +46,8 @@ public class Res { "settings", "wallet", "authorized_role", - "payment_method" + "payment_method", + "offer" ); private static final List bundles = new ArrayList<>(); diff --git a/i18n/src/main/resources/bisq_easy.properties b/i18n/src/main/resources/bisq_easy.properties index 756797ab57..78e7e19cb7 100644 --- a/i18n/src/main/resources/bisq_easy.properties +++ b/i18n/src/main/resources/bisq_easy.properties @@ -557,6 +557,8 @@ bisqEasy.offerbook.offerList.table.columns.price=Price bisqEasy.offerbook.offerList.table.columns.fiatAmount={0} amount bisqEasy.offerbook.offerList.table.columns.paymentMethod=Payment bisqEasy.offerbook.offerList.table.columns.settlementMethod=Settlement +bisqEasy.offerbook.offerList.table.columns.offerType.buy=Buying +bisqEasy.offerbook.offerList.table.columns.offerType.sell=Selling bisqEasy.offerbook.offerList.table.filters.offerDirection.buyFrom=Buy from bisqEasy.offerbook.offerList.table.filters.offerDirection.sellTo=Sell to bisqEasy.offerbook.offerList.table.filters.paymentMethods.title=Payments ({0}) @@ -565,10 +567,6 @@ bisqEasy.offerbook.offerList.table.filters.paymentMethods.customPayments=Custom bisqEasy.offerbook.offerList.table.filters.paymentMethods.clearFilters=Clear filters bisqEasy.offerbook.offerList.table.filters.showMyOffersOnly=My offers only -bisqEasy.offerbook.offerList.table.columns.price.tooltip.fixPrice=Fixed price: {0}\nPercentage from current market price: {1} -bisqEasy.offerbook.offerList.table.columns.price.tooltip.marketPrice=Market price: {0} -bisqEasy.offerbook.offerList.table.columns.price.tooltip.floatPrice=Percentage price {0}\nWith current market price: {1} - ###################################################### # Open trades diff --git a/i18n/src/main/resources/offer.properties b/i18n/src/main/resources/offer.properties new file mode 100644 index 0000000000..03ed11cfb0 --- /dev/null +++ b/i18n/src/main/resources/offer.properties @@ -0,0 +1,18 @@ +################################################################################ +# +# Offer +# +################################################################################ + +################################################################################ +# Price Spec Formatter +################################################################################ + +offer.priceSpecFormatter.fixPrice=Offer with fixed price\ + \n{0} +offer.priceSpecFormatter.marketPrice=At market price\ + \n{0} +offer.priceSpecFormatter.floatPrice.above={0} above market price\ + \n{1} +offer.priceSpecFormatter.floatPrice.below={0} below market price\ + \n{1} diff --git a/i18n/src/main/resources/user.properties b/i18n/src/main/resources/user.properties index dd064b381e..86aa949900 100644 --- a/i18n/src/main/resources/user.properties +++ b/i18n/src/main/resources/user.properties @@ -153,3 +153,8 @@ user.profileCard.details.totalReputationScore=Total reputation score user.profileCard.details.profileAge=Profile age user.profileCard.details.lastUserActivity=Last user activity user.profileCard.details.version=Software version +user.profileCard.offers.table.columns.market=Market +user.profileCard.offers.table.columns.offerType=Offer +user.profileCard.offers.table.columns.amount=Amount +user.profileCard.offers.table.columns.price=Price +user.profileCard.offers.table.columns.paymentMethods=Payment methods diff --git a/offer/src/main/java/bisq/offer/price/spec/PriceSpecFormatter.java b/offer/src/main/java/bisq/offer/price/spec/PriceSpecFormatter.java index 2350e1035f..7500d9d30f 100644 --- a/offer/src/main/java/bisq/offer/price/spec/PriceSpecFormatter.java +++ b/offer/src/main/java/bisq/offer/price/spec/PriceSpecFormatter.java @@ -46,4 +46,21 @@ public static String getFormattedPriceSpec(PriceSpec priceSpec, boolean abbrevia } return priceInfo; } + + public static String getFormattedPriceSpecWithOfferPrice(PriceSpec priceSpec, String offerPrice) { + if (priceSpec instanceof FixPriceSpec fixPriceSpec) { + String price = PriceFormatter.formatWithCode(fixPriceSpec.getPriceQuote()); + return Res.get("offer.priceSpecFormatter.fixPrice", price); + } + if (priceSpec instanceof FloatPriceSpec floatPriceSpec) { + String percent = PercentageFormatter.formatToPercentWithSymbol(Math.abs(floatPriceSpec.getPercentage())); + return Res.get(floatPriceSpec.getPercentage() >= 0 + ? "offer.priceSpecFormatter.floatPrice.above" + : "offer.priceSpecFormatter.floatPrice.below", + percent, + offerPrice); + } + // market price + return Res.get("offer.priceSpecFormatter.marketPrice", offerPrice); + } } diff --git a/presentation/src/main/java/bisq/presentation/formatters/PercentageFormatter.java b/presentation/src/main/java/bisq/presentation/formatters/PercentageFormatter.java index bdb14f5e4f..bcdb2191da 100644 --- a/presentation/src/main/java/bisq/presentation/formatters/PercentageFormatter.java +++ b/presentation/src/main/java/bisq/presentation/formatters/PercentageFormatter.java @@ -40,6 +40,12 @@ public static String formatToPercentWithSymbol(double value) { return formatToPercent(value) + "%"; } + public static String formatToPercentWithSignAndSymbol(double value) { + return value > 0 + ? "+" + formatToPercentWithSymbol(value) + : formatToPercentWithSymbol(value); + } + /** * @param value to be represented as percentage. 1 = 100 %. We show 2 fraction digits and use RoundingMode.HALF_UP * @return The formatted percentage value without the '%' sign