diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/ServiceProvider.java b/apps/desktop/desktop/src/main/java/bisq/desktop/ServiceProvider.java index 9f1bed8d78..47c7c24371 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/ServiceProvider.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/ServiceProvider.java @@ -3,20 +3,16 @@ * * 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 + * 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 + * 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 . + * along with Bisq. If not, see . */ package bisq.desktop; 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 aa652341e7..2ec368b219 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 @@ -30,6 +30,7 @@ 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.overview.ProfileCardOverviewController; +import bisq.desktop.main.content.user.profile_card.reputation.ProfileCardReputationController; import bisq.desktop.overlay.OverlayController; import bisq.user.banned.BannedUserService; import bisq.user.identity.UserIdentityService; @@ -81,6 +82,7 @@ public InitData(UserProfile userProfile) { protected final UserIdentityService userIdentityService; private final ProfileCardOverviewController profileCardOverviewController; private final ProfileCardDetailsController profileCardDetailsController; + private final ProfileCardReputationController profileCardReputationController; private Optional> selectedChannel; private Optional ignoreUserStateHandler, closeHandler; private Subscription userProfilePin; @@ -95,6 +97,7 @@ public ProfileCardController(ServiceProvider serviceProvider) { userIdentityService = serviceProvider.getUserService().getUserIdentityService(); profileCardOverviewController = new ProfileCardOverviewController(serviceProvider); profileCardDetailsController = new ProfileCardDetailsController(serviceProvider); + profileCardReputationController = new ProfileCardReputationController(serviceProvider); view = new ProfileCardView(model, this); } @@ -102,8 +105,9 @@ public ProfileCardController(ServiceProvider serviceProvider) { public void onActivate() { userProfilePin = EasyBind.subscribe(model.getUserProfile(), userProfile -> { model.getReputationScore().set(reputationService.getReputationScore(userProfile)); - profileCardDetailsController.updateUserProfileData(userProfile); profileCardOverviewController.updateUserProfileData(userProfile); + profileCardDetailsController.updateUserProfileData(userProfile); + profileCardReputationController.updateUserProfileData(userProfile); boolean isMyProfile = userIdentityService.isUserIdentityPresent(userProfile.getId()); model.getShouldShowReportButton().set(!isMyProfile && selectedChannel.isPresent()); model.getShouldShowUserActionsMenu().set(!isMyProfile); @@ -120,8 +124,8 @@ protected Optional createController(NavigationTarget navig return switch (navigationTarget) { case PROFILE_CARD_OVERVIEW -> Optional.of(profileCardOverviewController); case PROFILE_CARD_DETAILS -> Optional.of(profileCardDetailsController); -// case USER_DETAILS_OFFERS -> Optional.of(); -// case USER_DETAILS_REPUTATION -> Optional.of(); +// case PROFILE_CARD_OFFERS -> Optional.of(); + 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 657307f528..6737abeceb 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,8 +58,8 @@ 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.USER_CARD_OFFERS); -// addTab(Res.get("user.profileCard.tab.reputation"), NavigationTarget.USER_CARD_REPUTATION); +// addTab(Res.get("user.profileCard.tab.offers"), NavigationTarget.PROFILE_CARD_OFFERS); + addTab(Res.get("user.profileCard.tab.reputation"), NavigationTarget.PROFILE_CARD_REPUTATION); } @Override diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationController.java new file mode 100644 index 0000000000..6e3b6c6157 --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationController.java @@ -0,0 +1,100 @@ +/* + * 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.reputation; + +import bisq.desktop.ServiceProvider; +import bisq.desktop.common.view.Controller; +import bisq.user.profile.UserProfile; +import bisq.user.reputation.AccountAgeService; +import bisq.user.reputation.BondedReputationService; +import bisq.user.reputation.ProfileAgeService; +import bisq.user.reputation.ProofOfBurnService; +import bisq.user.reputation.ReputationService; +import bisq.user.reputation.ReputationSource; +import bisq.user.reputation.SignedWitnessService; +import lombok.Getter; + +import java.util.Optional; + +public class ProfileCardReputationController implements Controller { + @Getter + private final ProfileCardReputationView view; + private final ProfileCardReputationModel model; + private final ReputationService reputationService; + + public ProfileCardReputationController(ServiceProvider serviceProvider) { + model = new ProfileCardReputationModel(); + view = new ProfileCardReputationView(model, this); + reputationService = serviceProvider.getUserService().getReputationService(); + } + + @Override + public void onActivate() { + } + + @Override + public void onDeactivate() { + } + + public void updateUserProfileData(UserProfile userProfile) { + model.getListItems().clear(); + + ProofOfBurnService proofOfBurnService = reputationService.getProofOfBurnService(); + Optional.ofNullable(proofOfBurnService.getDataSetByHash().get(userProfile.getProofOfBurnKey())) + .ifPresent(dataSet -> model.getListItems().addAll(dataSet.stream() + .map(data -> new ProfileCardReputationView.ListItem(ReputationSource.BURNED_BSQ, + data.getBlockTime(), + proofOfBurnService.calculateScore(data), + data.getAmount())) + .toList())); + + BondedReputationService bondedReputationService = reputationService.getBondedReputationService(); + Optional.ofNullable(bondedReputationService.getDataSetByHash().get(userProfile.getBondedReputationKey())) + .ifPresent(dataSet -> model.getListItems().addAll(dataSet.stream() + .map(data -> new ProfileCardReputationView.ListItem(ReputationSource.BSQ_BOND, + data.getBlockTime(), + bondedReputationService.calculateScore(data), + Optional.of(data.getAmount()), + Optional.of(data.getLockTime()))) + .toList())); + + AccountAgeService accountAgeService = reputationService.getAccountAgeService(); + Optional.ofNullable(accountAgeService.getDataSetByHash().get(userProfile.getAccountAgeKey())) + .ifPresent(dataSet -> model.getListItems().addAll(dataSet.stream() + .map(data -> new ProfileCardReputationView.ListItem(ReputationSource.BISQ1_ACCOUNT_AGE, + data.getDate(), + accountAgeService.calculateScore(data))) + .toList())); + + SignedWitnessService signedWitnessService = reputationService.getSignedWitnessService(); + Optional.ofNullable(signedWitnessService.getDataSetByHash().get(userProfile.getSignedWitnessKey())) + .ifPresent(dataSet -> model.getListItems().addAll(dataSet.stream() + .map(data -> new ProfileCardReputationView.ListItem(ReputationSource.BISQ1_SIGNED_ACCOUNT_AGE_WITNESS, + data.getWitnessSignDate(), + signedWitnessService.calculateScore(data))) + .toList())); + + ProfileAgeService profileAgeService = reputationService.getProfileAgeService(); + Optional.ofNullable(profileAgeService.getDataSetByHash().get(userProfile.getProfileAgeKey())) + .ifPresent(dataSet -> model.getListItems().addAll(dataSet.stream() + .map(data -> new ProfileCardReputationView.ListItem(ReputationSource.PROFILE_AGE, + data.getDate(), + profileAgeService.calculateScore(data))) + .toList())); + } +} diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationModel.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationModel.java new file mode 100644 index 0000000000..84518fb5f9 --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationModel.java @@ -0,0 +1,30 @@ +/* + * 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.reputation; + +import bisq.desktop.common.view.Model; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Getter +public class ProfileCardReputationModel implements Model { + private final ObservableList listItems = FXCollections.observableArrayList(); +} 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 new file mode 100644 index 0000000000..7394228ef3 --- /dev/null +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/profile_card/reputation/ProfileCardReputationView.java @@ -0,0 +1,144 @@ +/* + * 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.reputation; + +import bisq.common.monetary.Coin; +import bisq.desktop.common.view.View; +import bisq.desktop.components.table.BisqTableColumn; +import bisq.desktop.components.table.BisqTableView; +import bisq.desktop.components.table.DateColumnUtil; +import bisq.desktop.components.table.DateTableItem; +import bisq.i18n.Res; +import bisq.presentation.formatters.AmountFormatter; +import bisq.presentation.formatters.DateFormatter; +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; +import lombok.extern.slf4j.Slf4j; + +import java.util.Comparator; +import java.util.Optional; + +@Slf4j +public class ProfileCardReputationView extends View { + private final BisqTableView tableView; + + public ProfileCardReputationView(ProfileCardReputationModel model, + ProfileCardReputationController 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() { + BisqTableColumn dateColumn = DateColumnUtil.getDateColumn(tableView.getSortOrder()); + dateColumn.setMinWidth(100); + tableView.getColumns().add(dateColumn); + + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("reputation.details.table.columns.source")) + .left() + .comparator(Comparator.comparing(ListItem::getReputationSource)) + .valueSupplier(ListItem::getSourceString) + .build()); + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("reputation.details.table.columns.score")) + .comparator(Comparator.comparing(ListItem::getScore)) + .valueSupplier(ListItem::getScoreString) + .build()); + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("temporal.age")) + .comparator(Comparator.comparing(ListItem::getAge)) + .valueSupplier(ListItem::getAgeString) + .build()); + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("offer.amount")) + .comparator(Comparator.comparing(ListItem::getAmount)) + .valueSupplier(ListItem::getAmountString) + .build()); + tableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("reputation.details.table.columns.lockTime")) + .comparator(Comparator.comparing(ListItem::getLockTime)) + .valueSupplier(ListItem::getLockTimeString) + .build()); + } + + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + @Getter + static class ListItem implements DateTableItem { + @EqualsAndHashCode.Include + private final ReputationSource reputationSource; + @EqualsAndHashCode.Include + private final long date, score, amount, lockTime; + + private final long age; + private final String dateString, timeString, sourceString, ageString, amountString, scoreString, lockTimeString; + + public ListItem(ReputationSource reputationSource, long blockTime, long score) { + this(reputationSource, blockTime, score, Optional.empty(), Optional.empty()); + } + + public ListItem(ReputationSource reputationSource, long blockTime, long score, long amount) { + this(reputationSource, blockTime, score, Optional.of(amount), Optional.empty()); + } + + public ListItem(ReputationSource reputationSource, + long blockTime, + long score, + Optional optionalAmount, + Optional optionalLockTime) { + this.reputationSource = reputationSource; + this.date = blockTime; + this.score = score; + this.amount = optionalAmount.orElse(0L); + this.lockTime = optionalLockTime.orElse(0L); + + dateString = DateFormatter.formatDate(blockTime); + timeString = DateFormatter.formatTime(blockTime); + age = TimeFormatter.getAgeInDays(blockTime); + ageString = TimeFormatter.formatAgeInDays(blockTime); + sourceString = reputationSource.getDisplayString(); + amountString = optionalAmount.map(amount -> AmountFormatter.formatAmountWithCode(Coin.fromValue(amount, "BSQ"))).orElse("-"); + scoreString = String.valueOf(score); + lockTimeString = optionalLockTime.map(String::valueOf).orElse("-"); + } + } +} diff --git a/apps/desktop/desktop/src/main/resources/css/user.css b/apps/desktop/desktop/src/main/resources/css/user.css index 86795018c4..ccb2d34057 100644 --- a/apps/desktop/desktop/src/main/resources/css/user.css +++ b/apps/desktop/desktop/src/main/resources/css/user.css @@ -143,3 +143,30 @@ .profile-card .bisq-common-bg { -fx-padding: 35 !important; } + +.profile-card .reputation .header { + -fx-pref-height: 8; + -fx-background-color: -bisq-dark-grey-30; + -fx-background-radius: 8 8 0 0; +} + +.profile-card .reputation .reputation-table.table-view { + -fx-pref-height: 300; + -fx-background-radius: 0 0 8 8; + -fx-background-insets: 0; + -fx-padding: 0; + -fx-border-width: 0; +} + +.profile-card .reputation .reputation-table.table-view .table-cell .text { + -fx-font-size: 0.9em; + -fx-padding: 0; +} + +.profile-card .reputation .reputation-table.table-view .column-header { + -fx-pref-height: 25; +} + +.profile-card .reputation .reputation-table.table-view .table-row-cell { + -fx-border-width: 0; +}