diff --git a/client/src/main/java/client/scenes/SearchBarCtrl.java b/client/src/main/java/client/scenes/SearchBarCtrl.java index 8b87a42..c37f188 100644 --- a/client/src/main/java/client/scenes/SearchBarCtrl.java +++ b/client/src/main/java/client/scenes/SearchBarCtrl.java @@ -14,8 +14,11 @@ import javafx.scene.input.KeyCode; import javafx.util.Duration; import java.io.IOException; +import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Controller for the search bar component. @@ -100,7 +103,11 @@ public class SearchBarCtrl implements LocaleAware { currentSearchTask = new Task<>() { @Override protected List call() throws IOException, InterruptedException { - return serverUtils.getRecipesFiltered(filter, configService.getConfig().getRecipeLanguages()); + var recipes = serverUtils.getRecipesFiltered( + "", + configService.getConfig().getRecipeLanguages() + ); + return applyMultiTermAndFilter(recipes, filter); } }; @@ -156,4 +163,52 @@ public class SearchBarCtrl implements LocaleAware { this.searchDebounce.playFromStart(); }); } + private List applyMultiTermAndFilter(List recipes, String query) { + if (recipes == null) { + return List.of(); + } + if (query == null || query.isBlank()) { + return recipes; + } + + var tokens = Arrays.stream(query.toLowerCase(Locale.ROOT).split("[\\s,]+")) + .map(String::trim) + .filter(s -> !s.isBlank()) + .filter(s -> !s.equals("and")) + .toList(); + + if (tokens.isEmpty()) { + return recipes; + } + + return recipes.stream() + .filter(r -> { + var sb = new StringBuilder(); + + if (r.getName() != null) { + sb.append(r.getName()).append(' '); + } + + if (r.getIngredients() != null) { + r.getIngredients().forEach(i -> { + if (i != null) { + sb.append(i).append(' '); + } + }); + } + + if (r.getPreparationSteps() != null) { + r.getPreparationSteps().forEach(s -> { + if (s != null) { + sb.append(s).append(' '); + } + }); + } + + var haystack = sb.toString().toLowerCase(Locale.ROOT); + return tokens.stream().allMatch(haystack::contains); + }) + .collect(Collectors.toList()); + } + } diff --git a/client/src/main/java/client/utils/server/Endpoints.java b/client/src/main/java/client/utils/server/Endpoints.java index 5b31304..6c23870 100644 --- a/client/src/main/java/client/utils/server/Endpoints.java +++ b/client/src/main/java/client/utils/server/Endpoints.java @@ -4,7 +4,9 @@ import client.utils.ConfigService; import com.google.inject.Inject; import java.net.URI; +import java.net.URLEncoder; import java.net.http.HttpRequest; +import java.nio.charset.StandardCharsets; import java.util.List; public class Endpoints { @@ -81,9 +83,23 @@ public class Endpoints { } public HttpRequest.Builder getRecipesWith(String params) { + if (params != null && params.contains("search=")) { + int start = params.indexOf("search=") + "search=".length(); + int end = params.indexOf('&', start); + if (end == -1) { + end = params.length(); + } + + String rawValue = params.substring(start, end); + String encodedValue = URLEncoder.encode(rawValue, StandardCharsets.UTF_8); + + params = params.substring(0, start) + encodedValue + params.substring(end); + } + return this.http(this.createApiUrl("/recipes?" + params)).GET(); } + public HttpRequest.Builder createIngredient(HttpRequest.BodyPublisher body) { String url = this.createApiUrl("/ingredients");