Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
28e1d9a206 |
|||
|
676b8dbee0 |
46 changed files with 211 additions and 1409 deletions
|
|
@ -21,13 +21,7 @@
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Source: https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
<version>2.0.17</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public class IngredientController {
|
||||||
private final RestTemplate restTemplate = new RestTemplate(); // Simplified REST client
|
private final RestTemplate restTemplate = new RestTemplate(); // Simplified REST client
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void handleDeleteIngredient(ActionEvent event) {
|
private void handleDeleteIngredient(ActionEvent event) {
|
||||||
// Get selected ingredient
|
// Get selected ingredient
|
||||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||||
if (selectedIngredient == null) {
|
if (selectedIngredient == null) {
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,6 @@ import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||||
import client.scenes.nutrition.NutritionViewCtrl;
|
import client.scenes.nutrition.NutritionViewCtrl;
|
||||||
import client.scenes.recipe.IngredientListCtrl;
|
import client.scenes.recipe.IngredientListCtrl;
|
||||||
import client.scenes.recipe.RecipeStepListCtrl;
|
import client.scenes.recipe.RecipeStepListCtrl;
|
||||||
import client.scenes.shopping.ShoppingListCtrl;
|
|
||||||
import client.scenes.shopping.ShoppingListNewItemPromptCtrl;
|
|
||||||
import client.service.ShoppingListService;
|
|
||||||
import client.service.ShoppingListServiceImpl;
|
|
||||||
import client.service.ShoppingListViewModel;
|
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
import client.utils.LocaleManager;
|
import client.utils.LocaleManager;
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
|
|
@ -62,10 +57,6 @@ public class MyModule implements Module {
|
||||||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Recipe>>() {}).toInstance(
|
binder.bind(new TypeLiteral<WebSocketDataService<Long, Recipe>>() {}).toInstance(
|
||||||
new WebSocketDataService<>()
|
new WebSocketDataService<>()
|
||||||
);
|
);
|
||||||
binder.bind(ShoppingListNewItemPromptCtrl.class).in(Scopes.SINGLETON);
|
|
||||||
binder.bind(ShoppingListCtrl.class).in(Scopes.SINGLETON);
|
|
||||||
binder.bind(ShoppingListViewModel.class).toInstance(new ShoppingListViewModel());
|
|
||||||
binder.bind(ShoppingListService.class).to(ShoppingListServiceImpl.class);
|
|
||||||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Ingredient>>() {}).toInstance(
|
binder.bind(new TypeLiteral<WebSocketDataService<Long, Ingredient>>() {}).toInstance(
|
||||||
new WebSocketDataService<>()
|
new WebSocketDataService<>()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package client;
|
||||||
|
|
||||||
import client.scenes.FoodpalApplicationCtrl;
|
import client.scenes.FoodpalApplicationCtrl;
|
||||||
import client.scenes.MainCtrl;
|
import client.scenes.MainCtrl;
|
||||||
import client.scenes.ServerConnectionDialogCtrl;
|
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
|
@ -28,15 +27,10 @@ public class UI extends Application {
|
||||||
|
|
||||||
var serverUtils = INJECTOR.getInstance(ServerUtils.class);
|
var serverUtils = INJECTOR.getInstance(ServerUtils.class);
|
||||||
if (!serverUtils.isServerAvailable()) {
|
if (!serverUtils.isServerAvailable()) {
|
||||||
var connectionHandler = INJECTOR.getInstance(ServerConnectionDialogCtrl.class);
|
var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down.";
|
||||||
boolean serverConnected = connectionHandler.promptForURL();
|
System.err.println(msg);
|
||||||
|
|
||||||
if(!serverConnected){
|
|
||||||
var msg = "User Cancelled Server connection. Shutting down";
|
|
||||||
System.err.print(msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.fxml");
|
var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.fxml");
|
||||||
|
|
||||||
var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
|
var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package client.exception;
|
|
||||||
|
|
||||||
public class DuplicateIngredientException extends Exception{
|
|
||||||
private final String ingredientName;
|
|
||||||
|
|
||||||
public DuplicateIngredientException(String ingredientName){
|
|
||||||
super("An ingredient with name " + ingredientName + " already exists, please provide a different name");
|
|
||||||
this.ingredientName = ingredientName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplicateIngredientException(String ingredientName, String message){
|
|
||||||
super(message);
|
|
||||||
this.ingredientName = ingredientName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIngredientName() {
|
|
||||||
return ingredientName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -2,17 +2,17 @@ package client.scenes;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import client.UI;
|
|
||||||
import client.exception.InvalidModificationException;
|
import client.exception.InvalidModificationException;
|
||||||
import client.scenes.nutrition.NutritionViewCtrl;
|
import client.scenes.nutrition.NutritionViewCtrl;
|
||||||
import client.scenes.recipe.RecipeDetailCtrl;
|
import client.scenes.recipe.RecipeDetailCtrl;
|
||||||
|
|
||||||
import client.scenes.shopping.ShoppingListCtrl;
|
|
||||||
import client.utils.Config;
|
import client.utils.Config;
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
import client.utils.DefaultValueFactory;
|
import client.utils.DefaultValueFactory;
|
||||||
|
|
@ -21,6 +21,7 @@ import client.utils.LocaleManager;
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import client.utils.WebSocketDataService;
|
import client.utils.WebSocketDataService;
|
||||||
import client.utils.WebSocketUtils;
|
import client.utils.WebSocketUtils;
|
||||||
|
import commons.Ingredient;
|
||||||
import commons.Recipe;
|
import commons.Recipe;
|
||||||
|
|
||||||
import commons.ws.Topics;
|
import commons.ws.Topics;
|
||||||
|
|
@ -31,20 +32,14 @@ import commons.ws.messages.Message;
|
||||||
import commons.ws.messages.UpdateRecipeMessage;
|
import commons.ws.messages.UpdateRecipeMessage;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ListProperty;
|
|
||||||
import javafx.beans.property.SimpleListProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.TextInputDialog;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
@ -70,7 +65,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
public ListView<Recipe> recipeList;
|
public ListView<Recipe> recipeList;
|
||||||
|
|
||||||
private final ListProperty<Recipe> favouriteRecipeList = new SimpleListProperty<>();
|
@FXML
|
||||||
|
private ListView<Ingredient> ingredientListView;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button addRecipeButton;
|
private Button addRecipeButton;
|
||||||
|
|
@ -88,8 +84,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton favouritesOnlyToggle;
|
private ToggleButton favouritesOnlyToggle;
|
||||||
|
|
||||||
@FXML
|
private List<Recipe> allRecipes = new ArrayList<>();
|
||||||
private Button manageIngredientsButton;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label updatedBadge;
|
private Label updatedBadge;
|
||||||
|
|
@ -205,12 +200,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
} //gives star in front fav items
|
} //gives star in front fav items
|
||||||
boolean fav = config.isFavourite(item.getId());
|
boolean fav = config.isFavourite(item.getId());
|
||||||
setText((fav ? "★ " : "") + item.getName());
|
setText((fav ? "★ " : "") + item.getName());
|
||||||
|
|
||||||
if(fav){
|
|
||||||
setTextFill(Color.BLUE);
|
|
||||||
}else{
|
|
||||||
setTextFill(Color.BLACK);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// When your selection changes, update details in the panel
|
// When your selection changes, update details in the panel
|
||||||
|
|
@ -288,13 +277,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
openSelectedRecipe();
|
openSelectedRecipe();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
recipeList.getItems().addListener((ListChangeListener.Change<? extends Recipe> c) -> {
|
|
||||||
favouriteRecipeList.set(
|
|
||||||
FXCollections.observableList(
|
|
||||||
recipeList.getItems().stream().filter(r -> config.isFavourite(r.getId())).toList()
|
|
||||||
));
|
|
||||||
System.out.println(favouriteRecipeList);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.initializeSearchBar();
|
this.initializeSearchBar();
|
||||||
refresh();
|
refresh();
|
||||||
|
|
@ -308,9 +290,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
removeRecipeButton.setText(getLocaleString("menu.button.remove.recipe"));
|
removeRecipeButton.setText(getLocaleString("menu.button.remove.recipe"));
|
||||||
cloneRecipeButton.setText(getLocaleString("menu.button.clone"));
|
cloneRecipeButton.setText(getLocaleString("menu.button.clone"));
|
||||||
recipesLabel.setText(getLocaleString("menu.label.recipes"));
|
recipesLabel.setText(getLocaleString("menu.label.recipes"));
|
||||||
|
|
||||||
favouritesOnlyToggle.setText(getLocaleString("menu.button.favourites"));
|
|
||||||
manageIngredientsButton.setText(getLocaleString("menu.button.ingredients"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -333,7 +312,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
logger.severe(msg);
|
logger.severe(msg);
|
||||||
printError(msg);
|
printError(msg);
|
||||||
}
|
}
|
||||||
recipeList.getItems().setAll(recipes);
|
|
||||||
|
allRecipes = new ArrayList<>(recipes);
|
||||||
applyRecipeFilterAndKeepSelection();
|
applyRecipeFilterAndKeepSelection();
|
||||||
|
|
||||||
showUpdatedBadge();
|
showUpdatedBadge();
|
||||||
|
|
@ -429,20 +409,16 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
public void applyRecipeFilterAndKeepSelection() {
|
public void applyRecipeFilterAndKeepSelection() {
|
||||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||||
Long selectedId = selected == null ? null : selected.getId();
|
Long selectedId = selected == null ? null : selected.getId();
|
||||||
List<Recipe> view = recipeList.getItems().stream().toList();
|
|
||||||
|
List<Recipe> view = allRecipes;
|
||||||
|
|
||||||
if (favouritesOnlyToggle != null && favouritesOnlyToggle.isSelected()) {
|
if (favouritesOnlyToggle != null && favouritesOnlyToggle.isSelected()) {
|
||||||
view = favouriteRecipeList.get();
|
view = allRecipes.stream()
|
||||||
|
.filter(r -> config.isFavourite(r.getId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
recipeList.getItems().setAll(view);
|
recipeList.getItems().setAll(view);
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (favouritesOnlyToggle != null && !favouritesOnlyToggle.isSelected()) {
|
|
||||||
recipeList.getItems().setAll(server.getRecipes(config.getRecipeLanguages()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException | InterruptedException e) {
|
|
||||||
logger.severe(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore selection if possible
|
// restore selection if possible
|
||||||
if (selectedId != null) {
|
if (selectedId != null) {
|
||||||
|
|
@ -463,6 +439,114 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
this.recipeDetailController.refreshFavouriteButton();
|
this.recipeDetailController.refreshFavouriteButton();
|
||||||
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Delete Ingredient button click
|
||||||
|
@FXML
|
||||||
|
private void handleDeleteIngredient() {
|
||||||
|
// Get selected ingredient
|
||||||
|
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
if (selectedIngredient == null) {
|
||||||
|
// Show an error message if no ingredient is selected
|
||||||
|
showError("No ingredient selected", "Please select an ingredient to delete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the ingredient is used in any recipe
|
||||||
|
checkIngredientUsage(selectedIngredient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ingredient is used in any recipe before deleting
|
||||||
|
private void checkIngredientUsage(Ingredient ingredient) {
|
||||||
|
try {
|
||||||
|
long usageCount = server.getIngredientUsage(ingredient.getId()); // Check ingredient usage via ServerUtils
|
||||||
|
|
||||||
|
if (usageCount > 0) {
|
||||||
|
// If ingredient is used, show a warning dialog
|
||||||
|
showWarningDialog(ingredient, usageCount);
|
||||||
|
} else {
|
||||||
|
// If not used, delete
|
||||||
|
deleteIngredient(ingredient);
|
||||||
|
}
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
showError("Error", "Failed to check ingredient usage: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteIngredient(Ingredient ingredient) {
|
||||||
|
try {
|
||||||
|
server.deleteIngredient(ingredient.getId()); // Call ServerUtils to delete the ingredient
|
||||||
|
showConfirmation("Success", "Ingredient '" + ingredient.getName() + "' has been deleted.");
|
||||||
|
refreshIngredientList(); // refresh the ingredient list
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
showError("Error", "Failed to delete ingredient: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showWarningDialog(Ingredient ingredient, long usedInRecipes) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(String title, String message) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showConfirmation(String title, String message) {
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle(title);
|
||||||
|
alert.setHeaderText(null);
|
||||||
|
alert.setContentText(message);
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshIngredientList() {
|
||||||
|
// Refresh
|
||||||
|
ingredientListView.getItems().clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void handleAddIngredient() {
|
||||||
|
//ask the user for the ingredient name
|
||||||
|
TextInputDialog dialog = new TextInputDialog();
|
||||||
|
dialog.setTitle("Add Ingredient");
|
||||||
|
dialog.setHeaderText("Enter the ingredient name:");
|
||||||
|
dialog.setContentText("Ingredient:");
|
||||||
|
|
||||||
|
// Wait for the user to enter a value
|
||||||
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
|
||||||
|
result.ifPresent(name -> {
|
||||||
|
// Create a new Ingredient object
|
||||||
|
Ingredient newIngredient = new Ingredient();
|
||||||
|
newIngredient.setName(name);
|
||||||
|
|
||||||
|
// Add the new ingredient to the ListView
|
||||||
|
ingredientListView.getItems().add(newIngredient);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// display ingredient name
|
||||||
|
@FXML
|
||||||
|
private void initializeIngredients() {
|
||||||
|
ingredientListView.setCellFactory(list -> new ListCell<Ingredient>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Ingredient item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (empty || item == null) {
|
||||||
|
setText(null);
|
||||||
|
} else {
|
||||||
|
setText(item.getName()); // Display the ingredient name in the ListView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void openIngredientsPopup() {
|
private void openIngredientsPopup() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -474,7 +558,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
var root = pair.getValue();
|
var root = pair.getValue();
|
||||||
|
|
||||||
var stage = new javafx.stage.Stage();
|
var stage = new javafx.stage.Stage();
|
||||||
stage.setTitle(getLocaleString("menu.ingredients.title"));
|
stage.setTitle("Nutrition values view");
|
||||||
stage.initModality(javafx.stage.Modality.APPLICATION_MODAL);
|
stage.initModality(javafx.stage.Modality.APPLICATION_MODAL);
|
||||||
stage.setScene(new javafx.scene.Scene(root));
|
stage.setScene(new javafx.scene.Scene(root));
|
||||||
stage.showAndWait();
|
stage.showAndWait();
|
||||||
|
|
@ -514,14 +598,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
updatedBadgeTimer.playFromStart();
|
updatedBadgeTimer.playFromStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openShoppingListWindow() throws IOException {
|
|
||||||
var root = UI.getFXML().load(ShoppingListCtrl.class, "client", "scenes", "shopping", "ShoppingList.fxml");
|
|
||||||
Stage stage = new Stage();
|
|
||||||
stage.setTitle(this.getLocaleString("menu.shopping.title"));
|
|
||||||
stage.setScene(new Scene(root.getValue()));
|
|
||||||
stage.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
package client.scenes.Ingredient;
|
package client.scenes.Ingredient;
|
||||||
|
|
||||||
import client.exception.DuplicateIngredientException;
|
|
||||||
import client.scenes.nutrition.NutritionDetailsCtrl;
|
import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||||
import client.utils.LocaleAware;
|
|
||||||
import client.utils.LocaleManager;
|
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import commons.Ingredient;
|
import commons.Ingredient;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.control.TextInputDialog;
|
import javafx.scene.control.TextInputDialog;
|
||||||
|
|
@ -25,18 +19,10 @@ import java.util.logging.Logger;
|
||||||
//TODO and check for capital letter milk and MILK are seen as different
|
//TODO and check for capital letter milk and MILK are seen as different
|
||||||
|
|
||||||
|
|
||||||
public class IngredientListCtrl implements LocaleAware {
|
public class IngredientListCtrl {
|
||||||
|
|
||||||
private final ServerUtils server;
|
private final ServerUtils server;
|
||||||
private final LocaleManager localeManager;
|
|
||||||
private final Logger logger = Logger.getLogger(IngredientListCtrl.class.getName());
|
private final Logger logger = Logger.getLogger(IngredientListCtrl.class.getName());
|
||||||
|
|
||||||
@FXML
|
|
||||||
public Label ingredientsLabel;
|
|
||||||
public Button addButton;
|
|
||||||
public Button refreshButton;
|
|
||||||
public Button deleteButton;
|
|
||||||
public Button closeButton;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ListView<Ingredient> ingredientListView;
|
private ListView<Ingredient> ingredientListView;
|
||||||
@FXML
|
@FXML
|
||||||
|
|
@ -45,30 +31,14 @@ public class IngredientListCtrl implements LocaleAware {
|
||||||
@Inject
|
@Inject
|
||||||
public IngredientListCtrl(
|
public IngredientListCtrl(
|
||||||
ServerUtils server,
|
ServerUtils server,
|
||||||
LocaleManager localeManager,
|
|
||||||
NutritionDetailsCtrl nutritionDetailsCtrl
|
NutritionDetailsCtrl nutritionDetailsCtrl
|
||||||
) {
|
) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.localeManager = localeManager;
|
|
||||||
this.nutritionDetailsCtrl = nutritionDetailsCtrl;
|
this.nutritionDetailsCtrl = nutritionDetailsCtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@FXML
|
||||||
public void updateText() {
|
public void initialize() {
|
||||||
ingredientsLabel.setText(getLocaleString("menu.label.ingredients"));
|
|
||||||
addButton.setText(getLocaleString("menu.button.add"));
|
|
||||||
refreshButton.setText(getLocaleString("menu.button.refresh"));
|
|
||||||
deleteButton.setText(getLocaleString("menu.button.delete"));
|
|
||||||
closeButton.setText(getLocaleString("menu.button.close"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LocaleManager getLocaleManager() {
|
|
||||||
return this.localeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initializeComponents() {
|
|
||||||
ingredientListView.setCellFactory(list -> new ListCell<>() {
|
ingredientListView.setCellFactory(list -> new ListCell<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Ingredient item, boolean empty) {
|
protected void updateItem(Ingredient item, boolean empty) {
|
||||||
|
|
@ -90,7 +60,6 @@ public class IngredientListCtrl implements LocaleAware {
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void addIngredient() {
|
private void addIngredient() {
|
||||||
TextInputDialog dialog = new TextInputDialog();
|
TextInputDialog dialog = new TextInputDialog();
|
||||||
|
|
@ -114,11 +83,10 @@ public class IngredientListCtrl implements LocaleAware {
|
||||||
refresh(); // reload list from server
|
refresh(); // reload list from server
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
showError("Failed to create ingredient: " + e.getMessage());
|
showError("Failed to create ingredient: " + e.getMessage());
|
||||||
} catch (DuplicateIngredientException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package client.scenes;
|
package client.scenes;
|
||||||
|
|
||||||
import client.utils.Config;
|
|
||||||
import client.utils.LocaleAware;
|
import client.utils.LocaleAware;
|
||||||
import client.utils.LocaleManager;
|
import client.utils.LocaleManager;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
@ -42,12 +41,11 @@ public class LangSelectMenuCtrl implements LocaleAware {
|
||||||
String lang = langSelectMenu.getSelectionModel().getSelectedItem();
|
String lang = langSelectMenu.getSelectionModel().getSelectedItem();
|
||||||
logger.info("Switching locale to " + lang);
|
logger.info("Switching locale to " + lang);
|
||||||
manager.setLocale(Locale.of(lang));
|
manager.setLocale(Locale.of(lang));
|
||||||
initializeComponents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initializeComponents() {
|
public void initializeComponents() {
|
||||||
langSelectMenu.getItems().setAll(Config.languages);
|
langSelectMenu.getItems().setAll("en", "pl", "nl", "zht", "zhc", "tok", "tr");
|
||||||
langSelectMenu.setValue(manager.getLocale().getLanguage());
|
langSelectMenu.setValue(manager.getLocale().getLanguage());
|
||||||
langSelectMenu.setConverter(new StringConverter<String>() {
|
langSelectMenu.setConverter(new StringConverter<String>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package client.scenes;
|
package client.scenes;
|
||||||
|
|
||||||
import client.utils.Config;
|
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
import client.utils.LocaleAware;
|
import client.utils.LocaleAware;
|
||||||
import client.utils.LocaleManager;
|
import client.utils.LocaleManager;
|
||||||
|
|
@ -55,7 +54,7 @@ public class LanguageFilterCtrl implements LocaleAware {
|
||||||
public void initializeComponents() {
|
public void initializeComponents() {
|
||||||
var items = this.langFilterMenu.getItems();
|
var items = this.langFilterMenu.getItems();
|
||||||
|
|
||||||
final List<String> languages = List.of(Config.languages);
|
final List<String> languages = List.of("en", "nl", "pl", "tok", "tr");
|
||||||
this.selectedLanguages = this.configService.getConfig().getRecipeLanguages();
|
this.selectedLanguages = this.configService.getConfig().getRecipeLanguages();
|
||||||
this.updateMenuButtonDisplay();
|
this.updateMenuButtonDisplay();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,11 @@ import javafx.animation.PauseTransition;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the search bar component.
|
* Controller for the search bar component.
|
||||||
|
|
@ -103,11 +99,7 @@ public class SearchBarCtrl implements LocaleAware {
|
||||||
currentSearchTask = new Task<>() {
|
currentSearchTask = new Task<>() {
|
||||||
@Override
|
@Override
|
||||||
protected List<Recipe> call() throws IOException, InterruptedException {
|
protected List<Recipe> call() throws IOException, InterruptedException {
|
||||||
var recipes = serverUtils.getRecipesFiltered(
|
return serverUtils.getRecipesFiltered(filter, configService.getConfig().getRecipeLanguages());
|
||||||
"",
|
|
||||||
configService.getConfig().getRecipeLanguages()
|
|
||||||
);
|
|
||||||
return applyMultiTermAndFilter(recipes, filter);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -154,61 +146,8 @@ public class SearchBarCtrl implements LocaleAware {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.searchField.setOnKeyReleased(event -> {
|
this.searchField.setOnKeyReleased(event -> {
|
||||||
if (event.getCode() == KeyCode.ESCAPE) {
|
|
||||||
searchField.clear();
|
|
||||||
this.onSearch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// This cancels the current debounce timer and restarts it.
|
// This cancels the current debounce timer and restarts it.
|
||||||
this.searchDebounce.playFromStart();
|
this.searchDebounce.playFromStart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private List<Recipe> applyMultiTermAndFilter(List<Recipe> 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
package client.scenes;
|
|
||||||
|
|
||||||
|
|
||||||
import client.utils.ConfigService;
|
|
||||||
import client.utils.server.ServerUtils;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.TextInputDialog;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
|
|
||||||
public class ServerConnectionDialogCtrl {
|
|
||||||
|
|
||||||
private final ConfigService configService;
|
|
||||||
private final ServerUtils serverUtils;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ServerConnectionDialogCtrl(ConfigService configService, ServerUtils serverUtils) {
|
|
||||||
this.configService = configService;
|
|
||||||
this.serverUtils = serverUtils;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return a boolean for if the user got connected to server or
|
|
||||||
*/
|
|
||||||
public boolean promptForURL(){
|
|
||||||
Alert error = new Alert(Alert.AlertType.ERROR); //creates an error alert
|
|
||||||
error.setTitle("Server is Unavailable");
|
|
||||||
error.setHeaderText("Unable to connect to Server");
|
|
||||||
error.setContentText("The server at " + configService.getConfig().getServerUrl()
|
|
||||||
+ " is not available\n\n Would you like to try a different Server URL?");
|
|
||||||
|
|
||||||
error.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
|
||||||
Optional<ButtonType> userChoice = error.showAndWait(); //asks if user wants to input server URL
|
|
||||||
|
|
||||||
if(userChoice.isEmpty() || userChoice.get() == ButtonType.NO){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true){ // Keeps asking the user until either a valid url is provided or the user exits
|
|
||||||
TextInputDialog dialog =
|
|
||||||
new TextInputDialog(configService.getConfig().getServerUrl());
|
|
||||||
dialog.setTitle("Enter new server URL");
|
|
||||||
dialog.setContentText("Server URL:");
|
|
||||||
Optional<String> userRes = dialog.showAndWait();
|
|
||||||
if(userRes.isEmpty()){
|
|
||||||
return false; //user cancelled the operation
|
|
||||||
}
|
|
||||||
String newServer = userRes.get().trim();
|
|
||||||
|
|
||||||
if(newServer.isEmpty()){
|
|
||||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
|
||||||
alert.setTitle("Invalid Input");
|
|
||||||
alert.setHeaderText("Invalid server URL");
|
|
||||||
alert.setContentText("Please enter a valid URL");
|
|
||||||
alert.showAndWait();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
configService.getConfig().setServerUrl(newServer);
|
|
||||||
if(serverUtils.isServerAvailable()){
|
|
||||||
configService.save();
|
|
||||||
Alert success = new Alert(Alert.AlertType.INFORMATION);
|
|
||||||
success.setTitle("Success");
|
|
||||||
success.setHeaderText("Connected to Server");
|
|
||||||
success.setContentText("Successfully connected to the server!");
|
|
||||||
success.showAndWait();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
Alert retry = new Alert(Alert.AlertType.ERROR);
|
|
||||||
retry.setTitle("Failed");
|
|
||||||
retry.setHeaderText("Failed to connect to Server");
|
|
||||||
retry.setContentText("Would you like to try another URL?");
|
|
||||||
retry.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
|
||||||
Optional<ButtonType> result = retry.showAndWait();
|
|
||||||
if(result.isEmpty() || result.get() == ButtonType.NO){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -66,7 +66,7 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
||||||
this.proteinInputElement.textProperty().bindBidirectional(vm.proteinProperty(), new NumberStringConverter());
|
this.proteinInputElement.textProperty().bindBidirectional(vm.proteinProperty(), new NumberStringConverter());
|
||||||
this.carbInputElement.textProperty().bindBidirectional(vm.carbsProperty(), new NumberStringConverter());
|
this.carbInputElement.textProperty().bindBidirectional(vm.carbsProperty(), new NumberStringConverter());
|
||||||
this.estimatedKcalLabel.textProperty().bind(Bindings.createStringBinding(
|
this.estimatedKcalLabel.textProperty().bind(Bindings.createStringBinding(
|
||||||
() -> String.format("Estimated energy value: %.1f kcal/100g", vm.getKcal()), vm.kcalProperty()
|
() -> String.format("Estimated energy value: %.1f", vm.getKcal()), vm.kcalProperty()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
this.nutritionValueContainer.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
this.nutritionValueContainer.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import com.google.inject.Inject;
|
||||||
import commons.FormalIngredient;
|
import commons.FormalIngredient;
|
||||||
import commons.Recipe;
|
import commons.Recipe;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|
@ -79,13 +78,7 @@ public class IngredientListCtrl implements LocaleAware {
|
||||||
if (recipe == null) {
|
if (recipe == null) {
|
||||||
this.ingredients = FXCollections.observableArrayList(new ArrayList<>());
|
this.ingredients = FXCollections.observableArrayList(new ArrayList<>());
|
||||||
} else {
|
} else {
|
||||||
List<RecipeIngredient> ingredientList = recipe
|
List<RecipeIngredient> ingredientList = recipe.getIngredients();
|
||||||
.getIngredients()
|
|
||||||
.stream()
|
|
||||||
.sorted(Comparator.comparing(ingredient -> ingredient
|
|
||||||
.getIngredient()
|
|
||||||
.getName()))
|
|
||||||
.toList();
|
|
||||||
this.ingredients = FXCollections.observableArrayList(ingredientList);
|
this.ingredients = FXCollections.observableArrayList(ingredientList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package client.scenes.recipe;
|
package client.scenes.recipe;
|
||||||
|
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import client.exception.DuplicateIngredientException;
|
|
||||||
import commons.Ingredient;
|
import commons.Ingredient;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
|
@ -68,15 +67,8 @@ public class IngredientsPopupCtrl {
|
||||||
server.createIngredient(name); // calls POST /api/ingredients
|
server.createIngredient(name); // calls POST /api/ingredients
|
||||||
refresh(); // reload list from server
|
refresh(); // reload list from server
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
|
|
||||||
showError("Failed to create ingredient: " + e.getMessage());
|
showError("Failed to create ingredient: " + e.getMessage());
|
||||||
} catch (DuplicateIngredientException e) {
|
|
||||||
showError("An ingredient with the name " + name + " already exists." +
|
|
||||||
" Please provide a different name."); //checks if error received has the DUPLICATE string and creates a showError instance with an appropriate error message and description
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package client.scenes.recipe;
|
||||||
|
|
||||||
import client.exception.UpdateException;
|
import client.exception.UpdateException;
|
||||||
import client.scenes.FoodpalApplicationCtrl;
|
import client.scenes.FoodpalApplicationCtrl;
|
||||||
import client.service.ShoppingListService;
|
|
||||||
import client.utils.Config;
|
import client.utils.Config;
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
import client.utils.LocaleAware;
|
import client.utils.LocaleAware;
|
||||||
|
|
@ -11,7 +10,6 @@ import client.utils.PrintExportService;
|
||||||
import client.utils.server.ServerUtils;
|
import client.utils.server.ServerUtils;
|
||||||
import client.utils.WebSocketDataService;
|
import client.utils.WebSocketDataService;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import commons.FormalIngredient;
|
|
||||||
import commons.Recipe;
|
import commons.Recipe;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -20,9 +18,6 @@ import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
|
|
@ -49,12 +44,8 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
private final FoodpalApplicationCtrl appCtrl;
|
private final FoodpalApplicationCtrl appCtrl;
|
||||||
private final ConfigService configService;
|
private final ConfigService configService;
|
||||||
private final WebSocketDataService<Long, Recipe> webSocketDataService;
|
private final WebSocketDataService<Long, Recipe> webSocketDataService;
|
||||||
private final ShoppingListService shoppingListService;
|
|
||||||
|
|
||||||
public Spinner<Double> scaleSpinner;
|
public Spinner<Double> scaleSpinner;
|
||||||
public Label inferredKcalLabel;
|
|
||||||
public Spinner<Integer> servingsSpinner;
|
|
||||||
public Label inferredServeSizeLabel;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private IngredientListCtrl ingredientListController;
|
private IngredientListCtrl ingredientListController;
|
||||||
|
|
@ -71,14 +62,12 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
ServerUtils server,
|
ServerUtils server,
|
||||||
FoodpalApplicationCtrl appCtrl,
|
FoodpalApplicationCtrl appCtrl,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
ShoppingListService listService,
|
|
||||||
WebSocketDataService<Long, Recipe> webSocketDataService) {
|
WebSocketDataService<Long, Recipe> webSocketDataService) {
|
||||||
this.localeManager = localeManager;
|
this.localeManager = localeManager;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.appCtrl = appCtrl;
|
this.appCtrl = appCtrl;
|
||||||
this.configService = configService;
|
this.configService = configService;
|
||||||
this.webSocketDataService = webSocketDataService;
|
this.webSocketDataService = webSocketDataService;
|
||||||
this.shoppingListService = listService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|
@ -165,20 +154,12 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
// If there is a scale
|
// If there is a scale
|
||||||
// Prevents issues from first startup
|
// Prevents issues from first startup
|
||||||
if (scaleSpinner.getValue() != null && servingsSpinner.getValue() != null) {
|
if (scaleSpinner.getValue() != null) {
|
||||||
Double scale = scaleSpinner.getValue();
|
Double scale = scaleSpinner.getValue();
|
||||||
|
|
||||||
// see impl. creates a scaled context for the recipe such that its non-scaled value is kept as a reference.
|
// see impl. creates a scaled context for the recipe such that its non-scaled value is kept as a reference.
|
||||||
this.recipeView = new ScalableRecipeView(recipe, scale);
|
this.recipeView = new ScalableRecipeView(recipe, scale);
|
||||||
// TODO i18n
|
|
||||||
inferredKcalLabel.textProperty().bind(Bindings.createStringBinding(() ->
|
|
||||||
String.format("Inferred %.1f kcal/100g for this recipe",
|
|
||||||
Double.isNaN(this.recipeView.scaledKcalProperty().get()) ?
|
|
||||||
0.0 : this.recipeView.scaledKcalProperty().get())
|
|
||||||
, this.recipeView.scaledKcalProperty()));
|
|
||||||
recipeView.servingsProperty().set(servingsSpinner.getValue());
|
|
||||||
inferredServeSizeLabel.textProperty().bind(Bindings.createStringBinding(
|
|
||||||
() -> String.format("Inferred size per serving: %.1f g", recipeView.servingSizeProperty().get()),
|
|
||||||
recipeView.servingSizeProperty()));
|
|
||||||
// expose the scaled view to list controllers
|
// expose the scaled view to list controllers
|
||||||
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
|
|
@ -405,7 +386,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
public void initializeComponents() {
|
public void initializeComponents() {
|
||||||
initStepsIngredientsList();
|
initStepsIngredientsList();
|
||||||
// creates a new scale spinner with an arbitrary max scale
|
// creates a new scale spinner with an arbitrary max scale
|
||||||
scaleSpinner.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(1, Double.MAX_VALUE, 1));
|
scaleSpinner.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, 1));
|
||||||
scaleSpinner.setEditable(true);
|
scaleSpinner.setEditable(true);
|
||||||
scaleSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
scaleSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (newValue == null) {
|
if (newValue == null) {
|
||||||
|
|
@ -414,23 +395,6 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
// triggers a UI update each time the spinner changes to a different value.
|
// triggers a UI update each time the spinner changes to a different value.
|
||||||
setCurrentlyViewedRecipe(recipe);
|
setCurrentlyViewedRecipe(recipe);
|
||||||
});
|
});
|
||||||
servingsSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1));
|
langSelector.getItems().addAll("en", "nl", "pl", "tok");
|
||||||
servingsSpinner.setEditable(true);
|
|
||||||
servingsSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
if (newValue == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCurrentlyViewedRecipe(recipe);
|
|
||||||
});
|
|
||||||
langSelector.getItems().addAll(Config.languages);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleAddAllToShoppingList(ActionEvent actionEvent) {
|
|
||||||
System.out.println("handleAddAllToShoppingList");
|
|
||||||
// TODO BACKLOG Add overview screen
|
|
||||||
recipe.getIngredients().stream()
|
|
||||||
.filter(x -> x.getClass().equals(FormalIngredient.class))
|
|
||||||
.map(FormalIngredient.class::cast)
|
|
||||||
.forEach(x -> shoppingListService.putIngredient(x, recipe));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,14 @@ import commons.Recipe;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.DoubleProperty;
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
public class ScalableRecipeView {
|
public class ScalableRecipeView {
|
||||||
private final ObjectProperty<Recipe> recipe = new SimpleObjectProperty<>();
|
private final ObjectProperty<Recipe> recipe = new SimpleObjectProperty<>();
|
||||||
private final ObjectProperty<Recipe> scaled = new SimpleObjectProperty<>();
|
private final ObjectProperty<Recipe> scaled = new SimpleObjectProperty<>();
|
||||||
private final DoubleProperty scale = new SimpleDoubleProperty();
|
private final DoubleProperty scale = new SimpleDoubleProperty();
|
||||||
private final DoubleProperty scaledKcal = new SimpleDoubleProperty();
|
|
||||||
private final IntegerProperty servings = new SimpleIntegerProperty();
|
|
||||||
private final DoubleProperty servingSize = new SimpleDoubleProperty();
|
|
||||||
public ScalableRecipeView(
|
public ScalableRecipeView(
|
||||||
Recipe recipe,
|
Recipe recipe,
|
||||||
Double scale
|
Double scale
|
||||||
|
|
@ -27,11 +22,10 @@ public class ScalableRecipeView {
|
||||||
() -> Recipe.getScaled(this.recipe.get(), this.scale.get()),
|
() -> Recipe.getScaled(this.recipe.get(), this.scale.get()),
|
||||||
this.recipe, this.scale);
|
this.recipe, this.scale);
|
||||||
this.scaled.bind(binding);
|
this.scaled.bind(binding);
|
||||||
this.scaledKcal.bind(Bindings.createDoubleBinding(() -> this.scaled.get().kcal(), this.scaled));
|
}
|
||||||
this.servingSize.bind(Bindings.createDoubleBinding(
|
|
||||||
() -> this.scaled.get().weight() * ( 1.0 / this.servings.get()),
|
public double getScale() {
|
||||||
this.servings)
|
return scale.get();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Recipe getRecipe() {
|
public Recipe getRecipe() {
|
||||||
|
|
@ -42,14 +36,15 @@ public class ScalableRecipeView {
|
||||||
return scaled.get();
|
return scaled.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DoubleProperty scaledKcalProperty() {
|
public DoubleProperty scaleProperty() {
|
||||||
return scaledKcal;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty servingsProperty() {
|
public ObjectProperty<Recipe> scaledProperty() {
|
||||||
return servings;
|
return scaled;
|
||||||
}
|
}
|
||||||
public DoubleProperty servingSizeProperty() {
|
|
||||||
return servingSize;
|
public ObjectProperty<Recipe> recipeProperty() {
|
||||||
|
return recipe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package client.scenes.shopping;
|
|
||||||
|
|
||||||
import client.scenes.recipe.OrderedEditableListCell;
|
|
||||||
import commons.FormalIngredient;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.Spinner;
|
|
||||||
import javafx.scene.control.SpinnerValueFactory;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.util.Pair;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ShoppingListCell extends OrderedEditableListCell<Pair<FormalIngredient, Optional<String>>> {
|
|
||||||
private Node makeEditor() {
|
|
||||||
HBox editor = new HBox();
|
|
||||||
Spinner<Double> amountInput = new Spinner<>();
|
|
||||||
FormalIngredient ingredient = getItem().getKey();
|
|
||||||
amountInput.setEditable(true);
|
|
||||||
amountInput.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, ingredient.getAmount()));
|
|
||||||
Label textLabel = new Label(getItem().getKey().getUnitSuffix() + " of " + getItem().getKey().getIngredient().getName());
|
|
||||||
editor.getChildren().addAll(amountInput, textLabel);
|
|
||||||
editor.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
|
|
||||||
if (e.getCode() != KeyCode.ENTER) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Pair<FormalIngredient, Optional<String>> pair = getItem();
|
|
||||||
pair.getKey().setAmount(amountInput.getValue());
|
|
||||||
commitEdit(pair);
|
|
||||||
});
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void startEdit() {
|
|
||||||
super.startEdit();
|
|
||||||
this.setText("");
|
|
||||||
this.setGraphic(makeEditor());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Pair<FormalIngredient, Optional<String>> item, boolean empty) {
|
|
||||||
super.updateItem(item, empty);
|
|
||||||
|
|
||||||
if (empty) {
|
|
||||||
this.setText("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String display = item.getKey().toString() +
|
|
||||||
item.getValue().map(recipe -> {
|
|
||||||
return " (" + recipe + ")";
|
|
||||||
}).orElse("");
|
|
||||||
|
|
||||||
this.setText(display);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void cancelEdit() {
|
|
||||||
super.cancelEdit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commitEdit(Pair<FormalIngredient, Optional<String>> newValue) {
|
|
||||||
super.commitEdit(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package client.scenes.shopping;
|
|
||||||
|
|
||||||
import client.UI;
|
|
||||||
import client.service.ShoppingListService;
|
|
||||||
import client.utils.LocaleAware;
|
|
||||||
import client.utils.LocaleManager;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import commons.FormalIngredient;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.ListView;
|
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.util.Pair;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ShoppingListCtrl implements LocaleAware {
|
|
||||||
ShoppingListService shopping;
|
|
||||||
|
|
||||||
LocaleManager localeManager;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ListView<Pair<FormalIngredient, Optional<String>>> shoppingListView;
|
|
||||||
@Inject
|
|
||||||
public ShoppingListCtrl(
|
|
||||||
ShoppingListService shopping,
|
|
||||||
LocaleManager localeManager
|
|
||||||
) {
|
|
||||||
this.shopping = shopping;
|
|
||||||
this.localeManager = localeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateText() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LocaleManager getLocaleManager() {
|
|
||||||
return this.localeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializeComponents() {
|
|
||||||
this.shoppingListView.setEditable(true);
|
|
||||||
this.shoppingListView.setCellFactory(l -> new ShoppingListCell());
|
|
||||||
this.shoppingListView.getItems().setAll(
|
|
||||||
this.shopping.getItems()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private void refreshList() {
|
|
||||||
this.shoppingListView.getItems().setAll(
|
|
||||||
this.shopping.getItems()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleAddItem(ActionEvent actionEvent) {
|
|
||||||
Stage stage = new Stage();
|
|
||||||
Pair<ShoppingListNewItemPromptCtrl, Parent> root = UI.getFXML().load(ShoppingListNewItemPromptCtrl.class,
|
|
||||||
"client", "scenes", "shopping", "ShoppingListItemAddModal.fxml");
|
|
||||||
root.getKey().setNewValueConsumer(fi -> {
|
|
||||||
this.shopping.putIngredient(fi);
|
|
||||||
refreshList();
|
|
||||||
});
|
|
||||||
stage.setScene(new Scene(root.getValue()));
|
|
||||||
stage.setTitle("My modal window");
|
|
||||||
stage.initModality(Modality.WINDOW_MODAL);
|
|
||||||
stage.initOwner(
|
|
||||||
((Node)actionEvent.getSource()).getScene().getWindow() );
|
|
||||||
stage.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleRemoveItem(ActionEvent actionEvent) {
|
|
||||||
var x = this.shoppingListView.getSelectionModel().getSelectedItem();
|
|
||||||
this.shopping.getItems().remove(x);
|
|
||||||
refreshList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
package client.scenes.shopping;
|
|
||||||
|
|
||||||
import client.utils.LocaleAware;
|
|
||||||
import client.utils.LocaleManager;
|
|
||||||
import client.utils.server.ServerUtils;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import commons.FormalIngredient;
|
|
||||||
import commons.Ingredient;
|
|
||||||
import commons.Unit;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.scene.control.MenuButton;
|
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.control.Spinner;
|
|
||||||
import javafx.scene.control.SpinnerValueFactory;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class ShoppingListNewItemPromptCtrl implements LocaleAware {
|
|
||||||
public MenuButton ingredientSelection;
|
|
||||||
private final ObjectProperty<Ingredient> selected = new SimpleObjectProperty<>();
|
|
||||||
private final ObjectProperty<Unit> selectedUnit = new SimpleObjectProperty<>();
|
|
||||||
private final ServerUtils server;
|
|
||||||
private final LocaleManager localeManager;
|
|
||||||
private Consumer<FormalIngredient> newValueConsumer;
|
|
||||||
public MenuButton unitSelect;
|
|
||||||
public Spinner<Double> amountSelect;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ShoppingListNewItemPromptCtrl(ServerUtils server, LocaleManager localeManager) {
|
|
||||||
this.server = server;
|
|
||||||
this.localeManager = localeManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNewValueConsumer(Consumer<FormalIngredient> consumer) {
|
|
||||||
this.newValueConsumer = consumer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void confirmAdd(ActionEvent actionEvent) {
|
|
||||||
if (selected.get() == null || selectedUnit.get() == null) {
|
|
||||||
System.err.println("You must select both an ingredient and an unit");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FormalIngredient fi = new FormalIngredient(selected.get(), amountSelect.getValue(), selectedUnit.get().suffix);
|
|
||||||
newValueConsumer.accept(fi);
|
|
||||||
Stage stage = (Stage) ingredientSelection.getScene().getWindow();
|
|
||||||
stage.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancelAdd(ActionEvent actionEvent) {
|
|
||||||
}
|
|
||||||
private <T> void makeMenuItems(
|
|
||||||
MenuButton menu,
|
|
||||||
Iterable<T> items,
|
|
||||||
Function<T, String> labelMapper,
|
|
||||||
Consumer<T> onSelect) {
|
|
||||||
// Iterates over the list of items and applies the label and onSelect handlers.
|
|
||||||
for (T item : items) {
|
|
||||||
MenuItem mi = new MenuItem();
|
|
||||||
mi.setText(labelMapper.apply(item));
|
|
||||||
mi.setOnAction(_ -> {
|
|
||||||
menu.setText(labelMapper.apply(item));
|
|
||||||
onSelect.accept(item);
|
|
||||||
});
|
|
||||||
menu.getItems().add(mi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void updateText() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void initializeComponents() {
|
|
||||||
try {
|
|
||||||
amountSelect.setValueFactory(
|
|
||||||
new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, 0));
|
|
||||||
amountSelect.setEditable(true);
|
|
||||||
makeMenuItems(ingredientSelection, server.getIngredients(), Ingredient::getName, selected::set);
|
|
||||||
makeMenuItems(unitSelect,
|
|
||||||
Arrays.stream(Unit.values()).filter(u -> u.formal).toList(),
|
|
||||||
Unit::toString, selectedUnit::set);
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
System.err.println(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LocaleManager getLocaleManager() {
|
|
||||||
return localeManager;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package client.service;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import commons.FormalIngredient;
|
|
||||||
import commons.Recipe;
|
|
||||||
import javafx.util.Pair;
|
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class NonFunctionalShoppingListService extends ShoppingListService {
|
|
||||||
@Inject
|
|
||||||
public NonFunctionalShoppingListService(ShoppingListViewModel viewModel) {
|
|
||||||
super(viewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putIngredient(FormalIngredient ingredient) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putIngredient(FormalIngredient ingredient, Recipe recipe) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putIngredient(FormalIngredient ingredient, String recipeName) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putArbitraryItem(String name) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FormalIngredient purgeIngredient(Long id) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FormalIngredient purgeIngredient(String ingredientName) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Pair<FormalIngredient, Optional<String>>> getItems() {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String makePrintable() {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue