diff --git a/build.gradle b/build.gradle index 0ea7ee5d20..07f9f146cb 100644 --- a/build.gradle +++ b/build.gradle @@ -293,7 +293,7 @@ dependencies { implementation("io.projectreactor.addons:reactor-extra") - def commonsVersion = "f5a7832399" + def commonsVersion = "4a3facc824" implementation("com.github.FAForever.faf-java-commons:faf-commons-data:${commonsVersion}") { exclude module: 'guava' diff --git a/src/main/java/com/faforever/client/domain/ReviewsSummaryBean.java b/src/main/java/com/faforever/client/domain/ReviewsSummaryBean.java index 199e7316fe..acefdda3b8 100644 --- a/src/main/java/com/faforever/client/domain/ReviewsSummaryBean.java +++ b/src/main/java/com/faforever/client/domain/ReviewsSummaryBean.java @@ -17,6 +17,7 @@ public abstract class ReviewsSummaryBean { FloatProperty positive = new SimpleFloatProperty(); FloatProperty negative = new SimpleFloatProperty(); FloatProperty score = new SimpleFloatProperty(); + FloatProperty averageScore = new SimpleFloatProperty(); IntegerProperty numReviews = new SimpleIntegerProperty(); FloatProperty lowerBound = new SimpleFloatProperty(); @@ -64,6 +65,18 @@ public void setScore(float score) { this.score.set(score); } + public FloatProperty averageScoreProperty() { + return averageScore; + } + + public float getAverageScore() { + return averageScore.get(); + } + + public void setAverageScore(float averageScore) { + this.averageScore.set(averageScore); + } + public FloatProperty scoreProperty() { return score; } diff --git a/src/main/java/com/faforever/client/fx/JavaFxUtil.java b/src/main/java/com/faforever/client/fx/JavaFxUtil.java index 6eeb322093..7293b1de48 100644 --- a/src/main/java/com/faforever/client/fx/JavaFxUtil.java +++ b/src/main/java/com/faforever/client/fx/JavaFxUtil.java @@ -36,7 +36,6 @@ import javafx.util.StringConverter; import javafx.util.converter.NumberStringConverter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.tuple.Pair; import org.controlsfx.control.RangeSlider; import org.springframework.util.Assert; @@ -44,6 +43,8 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.nio.file.Path; +import java.text.DecimalFormat; +import java.text.ParseException; import java.util.Arrays; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -444,6 +445,13 @@ public static void bindManagedToVisible(Node... nodes) { public static void bindTextFieldAndRangeSlider(TextField lowValueTextField, TextField highValueTextField, RangeSlider rangeSlider) { + DecimalFormat numberFormat = (DecimalFormat) DecimalFormat.getInstance(); + numberFormat.setMaximumFractionDigits(0); + bindTextFieldAndRangeSlider(lowValueTextField, highValueTextField, rangeSlider, numberFormat); + } + + public static void bindTextFieldAndRangeSlider(TextField lowValueTextField, TextField highValueTextField, + RangeSlider rangeSlider, DecimalFormat format) { Map.of( lowValueTextField, Pair.of(rangeSlider.lowValueProperty(), rangeSlider.getMin()), highValueTextField, Pair.of(rangeSlider.highValueProperty(), rangeSlider.getMax()) @@ -454,7 +462,7 @@ public static void bindTextFieldAndRangeSlider(TextField lowValueTextField, Text @Override public String toString(Number number) { if (!number.equals(value)) { - return String.valueOf(number.intValue()); + return format.format(number); } else { return ""; } @@ -462,10 +470,14 @@ public String toString(Number number) { @Override public Number fromString(String string) { - if (NumberUtils.isParsable(string)) { - return Double.parseDouble(string); - } else { - if (!string.equals("-") && !string.equals(".")) { + String decimalSeparator = Character.toString(format.getDecimalFormatSymbols().getDecimalSeparator()); + try { + Number number = format.parse(string); + String decimalSeparatorSuffix = string.endsWith(decimalSeparator) && format.getMaximumFractionDigits() > 0 ? decimalSeparator : ""; + textField.setText(format.format(number) + decimalSeparatorSuffix); + return number; + } catch (ParseException e) { + if (!string.equals("-") && !string.equals(decimalSeparator)) { textField.setText(""); } return value; diff --git a/src/main/java/com/faforever/client/map/MapVaultController.java b/src/main/java/com/faforever/client/map/MapVaultController.java index 74609b94c1..0c13a7b753 100644 --- a/src/main/java/com/faforever/client/map/MapVaultController.java +++ b/src/main/java/com/faforever/client/map/MapVaultController.java @@ -96,7 +96,8 @@ protected void initSearchController() { searchController.addCategoryFilter("latestVersion.width", i18n.get("map.width"), mapSizeMap); searchController.addCategoryFilter("latestVersion.height", i18n.get("map.height"), mapSizeMap); - searchController.addRangeFilter("latestVersion.maxPlayers", i18n.get("map.maxPlayers"), 0, 16, 1, Double::intValue); + searchController.addRangeFilter("latestVersion.maxPlayers", i18n.get("map.maxPlayers"), 0, 16, 16, 0, 0); + searchController.addRangeFilter("reviewsSummary.averageScore", i18n.get("reviews.averageScore"), 0, 5, 10, 4, 1); searchController.addToggleFilter("latestVersion.ranked", i18n.get("map.onlyRanked"), "true"); } diff --git a/src/main/java/com/faforever/client/mod/ModVaultController.java b/src/main/java/com/faforever/client/mod/ModVaultController.java index 073f3132fc..66f8244a32 100644 --- a/src/main/java/com/faforever/client/mod/ModVaultController.java +++ b/src/main/java/com/faforever/client/mod/ModVaultController.java @@ -152,6 +152,8 @@ protected void initSearchController() { searchController.addTextFilter("author", i18n.get("mod.author"), false); searchController.addDateRangeFilter("latestVersion.updateTime", i18n.get("mod.uploadedDateTime"), 0); + searchController.addRangeFilter("reviewsSummary.averageScore", i18n.get("reviews.averageScore"), 0, 5, 10, 4, 1); + searchController.addBinaryFilter("latestVersion.type", i18n.get("mod.type"), ModType.UI.toString(), ModType.SIM.toString(), i18n.get("modType.ui"), i18n.get("modType.sim")); searchController.addToggleFilter("latestVersion.ranked", i18n.get("mod.onlyRanked"), "true"); diff --git a/src/main/java/com/faforever/client/query/RangeFilterController.java b/src/main/java/com/faforever/client/query/RangeFilterController.java index 94915c53ce..0aa172d26f 100644 --- a/src/main/java/com/faforever/client/query/RangeFilterController.java +++ b/src/main/java/com/faforever/client/query/RangeFilterController.java @@ -11,11 +11,14 @@ import javafx.scene.control.MenuButton; import javafx.scene.control.TextField; import lombok.Data; +import lombok.SneakyThrows; import org.controlsfx.control.RangeSlider; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.math.RoundingMode; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -35,26 +38,30 @@ public class RangeFilterController implements FilterNodeController { private String propertyName; private Function valueTransform; + private int numberOfFractionDigits; + private DecimalFormat numberFormat; public void initialize() { JavaFxUtil.bindManagedToVisible(menu); rangeSlider.setShowTickMarks(true); rangeSlider.setShowTickLabels(true); rangeSlider.setMinorTickCount(0); - valueTransform = (value) -> value; + valueTransform = Function.identity(); } public Optional> getCondition() { List conditions = new ArrayList<>(); - if (!lowValue.getText().isBlank() && rangeSlider.getLowValue() > rangeSlider.getMin()) { + double lowValueValue = normalizeToFormat(rangeSlider.getLowValue()); + if (!lowValue.getText().isBlank() && lowValueValue > rangeSlider.getMin()) { QBuilder qBuilderLow = new QBuilder<>(); DoubleProperty propertyLow = qBuilderLow.doubleNum(propertyName); - conditions.add(propertyLow.gte(valueTransform.apply(rangeSlider.getLowValue()))); + conditions.add(propertyLow.gte(valueTransform.apply(lowValueValue))); } - if (!highValue.getText().isBlank() && rangeSlider.getHighValue() < rangeSlider.getMax()) { + double highValueValue = normalizeToFormat(rangeSlider.getHighValue()); + if (!highValue.getText().isBlank() && highValueValue < rangeSlider.getMax()) { QBuilder qBuilderHigh = new QBuilder<>(); DoubleProperty propertyHigh = qBuilderHigh.doubleNum(propertyName); - conditions.add(propertyHigh.lte(valueTransform.apply(rangeSlider.getHighValue()))); + conditions.add(propertyHigh.lte(valueTransform.apply(highValueValue))); } if (!conditions.isEmpty()) { if (!menu.getStyleClass().contains("query-filter-selected")) { @@ -84,7 +91,7 @@ public void setTitle(String title) { menu.textProperty().bind(Bindings.createStringBinding(() -> i18n.get("query.rangeFilter", title, lowValue.getText(), highValue.getText()), lowValue.textProperty(), highValue.textProperty())); } - public void setMinMax(double min, double max) { + public void setRange(double min, double max, int majorTickCount, int interMajorTickCount) { rangeSlider.setMin(min); rangeSlider.setLowValue(min); lowValue.setText(""); @@ -93,7 +100,11 @@ public void setMinMax(double min, double max) { rangeSlider.setHighValue(max); highValue.setText(""); - JavaFxUtil.bindTextFieldAndRangeSlider(lowValue, highValue, rangeSlider); + int numberOfTicks = majorTickCount + majorTickCount * interMajorTickCount; + double range = max - min; + rangeSlider.setBlockIncrement(range / numberOfTicks); + rangeSlider.setMajorTickUnit(range / majorTickCount); + rangeSlider.setMinorTickCount(interMajorTickCount); } public void setIncrement(double increment) { @@ -112,8 +123,25 @@ public void setValueTransform(Function valueTransform) this.valueTransform = valueTransform; } + public void setNumberOfFractionDigits(int numberOfFractionDigits) { + this.numberOfFractionDigits = numberOfFractionDigits; + } + + public void bind() { + numberFormat = (DecimalFormat) DecimalFormat.getInstance(); + numberFormat.setRoundingMode(RoundingMode.HALF_UP); + numberFormat.setMinimumFractionDigits(0); + numberFormat.setMaximumFractionDigits(numberOfFractionDigits); + JavaFxUtil.bindTextFieldAndRangeSlider(lowValue, highValue, rangeSlider, numberFormat); + } + @Override public Node getRoot() { return menu; } + + @SneakyThrows + private double normalizeToFormat(double value) { + return numberFormat.parse(numberFormat.format(value)).doubleValue(); + } } diff --git a/src/main/java/com/faforever/client/query/SearchablePropertyMappings.java b/src/main/java/com/faforever/client/query/SearchablePropertyMappings.java index 29f06f13d6..ad211aff98 100644 --- a/src/main/java/com/faforever/client/query/SearchablePropertyMappings.java +++ b/src/main/java/com/faforever/client/query/SearchablePropertyMappings.java @@ -37,6 +37,7 @@ public class SearchablePropertyMappings { .put("mapVersion.height", new Property("map.heightPixels", false)) .put("mapVersion.folderName", new Property("game.map.folderName", false)) .put("mapVersion.map.author.login", new Property("game.map.author", false)) + .put("reviewsSummary.averageScore", new Property("reviews.averageScore", true)) .build(); diff --git a/src/main/java/com/faforever/client/replay/OnlineReplayVaultController.java b/src/main/java/com/faforever/client/replay/OnlineReplayVaultController.java index 86ef535379..4971bfddda 100644 --- a/src/main/java/com/faforever/client/replay/OnlineReplayVaultController.java +++ b/src/main/java/com/faforever/client/replay/OnlineReplayVaultController.java @@ -150,10 +150,12 @@ protected void initSearchController() { //TODO: Use rating rather than estimated mean with an assumed deviation of 300 when that is available searchController.addRangeFilter("playerStats.ratingChanges.meanBefore", i18n.get("game.rating"), - MIN_RATING, MAX_RATING, 100, (value) -> value + 300); + MIN_RATING, MAX_RATING, 10, 4, 0, value -> value + 300); + + searchController.addRangeFilter("reviewsSummary.averageScore", i18n.get("reviews.averageScore"),0, 5, 10, 4, 1); searchController.addDateRangeFilter("endTime", i18n.get("game.date"), 1); - searchController.addRangeFilter("replayTicks", i18n.get("game.duration"), 0, 60, 1, value -> (int) (value*60*10)); + searchController.addRangeFilter("replayTicks", i18n.get("game.duration"), 0, 60, 12, 4, 0, value -> value*60*10); searchController.addToggleFilter("validity", i18n.get("game.onlyRanked"), "VALID"); } diff --git a/src/main/java/com/faforever/client/vault/search/SearchController.java b/src/main/java/com/faforever/client/vault/search/SearchController.java index ae0d212c5c..ae2885305b 100644 --- a/src/main/java/com/faforever/client/vault/search/SearchController.java +++ b/src/main/java/com/faforever/client/vault/search/SearchController.java @@ -303,18 +303,23 @@ public CategoryFilterController addCategoryFilter(String propertyName, String ti return categoryFilterController; } - public RangeFilterController addRangeFilter(String propertyName, String title, double min, double max, - double tickUnit, Function valueTransform) { + public void addRangeFilter(String propertyName, String title, double min, double max, + int majorTickCount, int interMajorTickCount, int numberOfFractionDigits) { + addRangeFilter(propertyName, title, min, max, majorTickCount, interMajorTickCount, numberOfFractionDigits, Function.identity()); + } + + public void addRangeFilter(String propertyName, String title, double min, double max, + int majorTickCount, int interMajorTickCount, int numberOfFractionDigits, + Function valueTransform) { RangeFilterController rangeFilterController = uiService.loadFxml("theme/vault/search/rangeFilter.fxml"); rangeFilterController.setTitle(title); rangeFilterController.setPropertyName(propertyName); - rangeFilterController.setMinMax(min, max); - rangeFilterController.setIncrement(tickUnit); - rangeFilterController.setTickUnit(tickUnit); + rangeFilterController.setRange(min, max, majorTickCount, interMajorTickCount); rangeFilterController.setSnapToTicks(true); + rangeFilterController.setNumberOfFractionDigits(numberOfFractionDigits); rangeFilterController.setValueTransform(valueTransform); + rangeFilterController.bind(); addFilterNode(rangeFilterController); - return rangeFilterController; } public DateRangeFilterController addDateRangeFilter(String propertyName, String title, int initialYearsBefore) { diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 1504e75536..002774d8dd 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -1269,6 +1269,7 @@ game.create.generatedMap2 = Generate the map settings.fa.allowIpv6 = Allow the ICE adapter to use IPv6 settings.fa.allowIpv6.description = Ipv6 causes connection issues for some players. Turn this on if you do not have any IPv6 issues with connections home.directory.warning.cyrillic = Warning\: Cyrillic characters in the home directory path may cause problems. Please, avoid using them. +reviews.averageScore = Average reviews score ignoreWarning = Ignore warning replay.replayRunning = Replay could not be started because another replay is already running. teammatchmaking.queue.tmm3v3 = 3v3 diff --git a/src/test/java/com/faforever/client/query/RangeFilterControllerTest.java b/src/test/java/com/faforever/client/query/RangeFilterControllerTest.java index 4a470169ec..d3d789a09a 100644 --- a/src/test/java/com/faforever/client/query/RangeFilterControllerTest.java +++ b/src/test/java/com/faforever/client/query/RangeFilterControllerTest.java @@ -44,11 +44,12 @@ public void setUp() throws Exception { loadFxml("theme/vault/search/rangeFilter.fxml", clazz -> instance); instance.setPropertyName(propertyName); - instance.setMinMax(min, max); + instance.setRange(min, max, 10, 0); instance.setIncrement(increment); instance.setSnapToTicks(true); instance.setTickUnit(increment); instance.setValueTransform((value) -> value); + instance.bind(); } @Test @@ -88,7 +89,7 @@ public void testAddListener() throws Exception { instance.rangeSlider.setHighValue(90.0); instance.lowValue.setText("20"); instance.highValue.setText("80"); - verify(queryListener, times(12)).invalidated(any()); + verify(queryListener, times(20)).invalidated(any()); } @Test @@ -157,4 +158,13 @@ public void testGetConditionRange() throws Exception { assertEquals(result.get().get(1).query(new RSQLVisitor()), new QBuilder<>().doubleNum(propertyName).lte(50.0).query(new RSQLVisitor())); assertTrue(instance.menu.getStyleClass().contains("query-filter-selected")); } + + @Test + void testTicks() { + instance.setRange(-10.0, 90.0, 5, 1); + + assertEquals(20.0, instance.rangeSlider.getMajorTickUnit()); + assertEquals(1, instance.rangeSlider.getMinorTickCount()); + assertEquals(10.0, instance.rangeSlider.getBlockIncrement()); + } }