Merge branch 'client/feature-ingredient-nutrition-view' into 'main'
feat(client/nutrition): Nutrition & Ingredient list view Closes #54 and #56 See merge request cse1105/2025-2026/teams/csep-team-76!54
This commit is contained in:
commit
1abbeee1ae
15 changed files with 403 additions and 110 deletions
|
|
@ -111,14 +111,13 @@ public class IngredientController {
|
|||
public static class IngredientUsageResponse {
|
||||
private long ingredientId;
|
||||
private long usedInRecipes;
|
||||
|
||||
public long getIngredientId() {
|
||||
return ingredientId;
|
||||
}
|
||||
|
||||
public void setIngredientId(long ingredientId) {
|
||||
this.ingredientId = ingredientId;
|
||||
}
|
||||
// public long getIngredientId() {
|
||||
// return ingredientId;
|
||||
// }
|
||||
//
|
||||
// public void setIngredientId(long ingredientId) {
|
||||
// this.ingredientId = ingredientId;
|
||||
// }
|
||||
|
||||
public long getUsedInRecipes() {
|
||||
return usedInRecipes;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package client.Ingredient;
|
||||
|
||||
import commons.Ingredient;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
public class IngredientViewModel {
|
||||
private final LongProperty id = new SimpleLongProperty();
|
||||
private final StringProperty name = new SimpleStringProperty();
|
||||
private final DoubleProperty protein = new SimpleDoubleProperty();
|
||||
private final DoubleProperty fat = new SimpleDoubleProperty();
|
||||
private final DoubleProperty carbs = new SimpleDoubleProperty();
|
||||
|
||||
private final ReadOnlyDoubleWrapper kcal = new ReadOnlyDoubleWrapper();
|
||||
public IngredientViewModel() {
|
||||
DoubleBinding computeKcal = Bindings.createDoubleBinding(
|
||||
() -> toIngredient().kcalPer100g(),
|
||||
protein, carbs, fat
|
||||
);
|
||||
|
||||
// bind the read-only wrapper to the binding
|
||||
kcal.bind(computeKcal);
|
||||
|
||||
}
|
||||
|
||||
public IngredientViewModel(Ingredient ing) {
|
||||
updateFrom(ing);
|
||||
}
|
||||
|
||||
public void updateFrom(Ingredient ing) {
|
||||
if (ing == null) return;
|
||||
id.set(ing.getId());
|
||||
name.set(ing.getName());
|
||||
protein.set(ing.getProteinPer100g());
|
||||
fat.set(ing.getFatPer100g());
|
||||
carbs.set(ing.getCarbsPer100g());
|
||||
}
|
||||
|
||||
// property getters
|
||||
public LongProperty idProperty() {
|
||||
return id;
|
||||
}
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
public DoubleProperty proteinProperty() {
|
||||
return protein;
|
||||
}
|
||||
public DoubleProperty fatProperty() {
|
||||
return fat;
|
||||
}
|
||||
public DoubleProperty carbsProperty() {
|
||||
return carbs;
|
||||
}
|
||||
|
||||
public Ingredient toIngredient() {
|
||||
Ingredient i = new Ingredient();
|
||||
i.setId(id.get());
|
||||
i.setName(name.get());
|
||||
i.setProteinPer100g(protein.get());
|
||||
i.setFatPer100g(fat.get());
|
||||
i.setCarbsPer100g(carbs.get());
|
||||
return i;
|
||||
}
|
||||
public ReadOnlyDoubleProperty kcalProperty() {
|
||||
return kcal.getReadOnlyProperty(); }
|
||||
public double getKcal() {
|
||||
return kcal.get(); }
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +17,8 @@ package client;
|
|||
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import client.scenes.SearchBarCtrl;
|
||||
import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||
import client.scenes.nutrition.NutritionViewCtrl;
|
||||
import client.scenes.recipe.IngredientListCtrl;
|
||||
import client.scenes.recipe.RecipeStepListCtrl;
|
||||
import client.utils.ConfigService;
|
||||
|
|
@ -47,7 +49,8 @@ public class MyModule implements Module {
|
|||
binder.bind(LocaleManager.class).in(Scopes.SINGLETON);
|
||||
binder.bind(ServerUtils.class).in(Scopes.SINGLETON);
|
||||
binder.bind(WebSocketUtils.class).in(Scopes.SINGLETON);
|
||||
|
||||
binder.bind(NutritionDetailsCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(NutritionViewCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(ConfigService.class).toInstance(new ConfigService(Path.of("config.json")));
|
||||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Recipe>>() {}).toInstance(
|
||||
new WebSocketDataService<>()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package client.exception;
|
||||
|
||||
public class IllegalInputFormatException extends RuntimeException {
|
||||
public IllegalInputFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import java.util.logging.Logger;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import client.exception.InvalidModificationException;
|
||||
import client.scenes.recipe.IngredientsPopupCtrl;
|
||||
import client.scenes.nutrition.NutritionViewCtrl;
|
||||
import client.scenes.recipe.RecipeDetailCtrl;
|
||||
|
||||
import client.utils.Config;
|
||||
|
|
@ -524,14 +524,14 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
private void openIngredientsPopup() {
|
||||
try {
|
||||
var pair = client.UI.getFXML().load(
|
||||
IngredientsPopupCtrl.class,
|
||||
"client", "scenes", "recipe", "IngredientsPopup.fxml"
|
||||
NutritionViewCtrl.class,
|
||||
"client", "scenes", "nutrition", "NutritionView.fxml"
|
||||
);
|
||||
|
||||
var root = pair.getValue();
|
||||
|
||||
var stage = new javafx.stage.Stage();
|
||||
stage.setTitle("Ingredients");
|
||||
stage.setTitle("Nutrition values view");
|
||||
stage.initModality(javafx.stage.Modality.APPLICATION_MODAL);
|
||||
stage.setScene(new javafx.scene.Scene(root));
|
||||
stage.showAndWait();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
package client.scenes.Ingredient;
|
||||
|
||||
import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Ingredient;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
//TODO and check for capital letter milk and MILK are seen as different
|
||||
|
||||
|
||||
public class IngredientListCtrl {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final Logger logger = Logger.getLogger(IngredientListCtrl.class.getName());
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
@FXML
|
||||
private NutritionDetailsCtrl nutritionDetailsCtrl;
|
||||
|
||||
@Inject
|
||||
public IngredientListCtrl(
|
||||
ServerUtils server,
|
||||
NutritionDetailsCtrl nutritionDetailsCtrl
|
||||
) {
|
||||
this.server = server;
|
||||
this.nutritionDetailsCtrl = nutritionDetailsCtrl;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
ingredientListView.setCellFactory(list -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(Ingredient item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
ingredientListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
logger.info("Selected ingredient " + newValue.getName() + ", propagating to Nutrition Details controller...");
|
||||
nutritionDetailsCtrl.setItem(newValue);
|
||||
});
|
||||
|
||||
refresh();
|
||||
}
|
||||
@FXML
|
||||
private void addIngredient() {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Add Ingredient");
|
||||
dialog.setHeaderText("Create a new ingredient");
|
||||
dialog.setContentText("Name:");
|
||||
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
if (result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = result.get().trim();
|
||||
if (name.isEmpty()) {
|
||||
showError("Ingredient name cannot be empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
server.createIngredient(name); // calls POST /api/ingredients
|
||||
refresh(); // reload list from server
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to create ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void refresh() {
|
||||
try {
|
||||
List<Ingredient> ingredients = server.getIngredients();
|
||||
ingredientListView.getItems().setAll(ingredients);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to load ingredients: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void deleteSelected() {
|
||||
Ingredient selected = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
showError("No ingredient selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long usageCount = server.getIngredientUsage(selected.getId());
|
||||
if (usageCount > 0) {
|
||||
boolean proceed = confirmDeleteUsed(selected.getName(), usageCount);
|
||||
if (!proceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
server.deleteIngredient(selected.getId());
|
||||
refresh();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to delete ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void close() {
|
||||
Stage stage = (Stage) ingredientListView.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
private boolean confirmDeleteUsed(String name, long usedInRecipes) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("Warning");
|
||||
alert.setHeaderText("Ingredient in use");
|
||||
alert.setContentText("Ingredient '" + name + "' is used in " + usedInRecipes
|
||||
+ " recipe(s). Delete anyway?");
|
||||
|
||||
var delete = new javafx.scene.control.ButtonType("Delete Anyway");
|
||||
var cancel = new javafx.scene.control.ButtonType("Cancel");
|
||||
alert.getButtonTypes().setAll(delete, cancel);
|
||||
|
||||
return alert.showAndWait().orElse(cancel) == delete;
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(msg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,38 @@
|
|||
package client.scenes.nutrition;
|
||||
|
||||
import client.Ingredient.IngredientViewModel;
|
||||
import client.exception.IllegalInputFormatException;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Ingredient;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class NutritionDetailsCtrl implements LocaleAware {
|
||||
@FXML
|
||||
public GridPane nutritionValueContainer;
|
||||
|
||||
private boolean visible;
|
||||
private final LocaleManager manager;
|
||||
private final ServerUtils server;
|
||||
private final SimpleObjectProperty<IngredientViewModel> ingredient =
|
||||
new SimpleObjectProperty<>(new IngredientViewModel());
|
||||
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||
|
||||
public Label ingredientName;
|
||||
public Label fatInputLabel;
|
||||
|
|
@ -20,19 +45,87 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
|||
public TextField proteinInputElement;
|
||||
public TextField carbInputElement;
|
||||
|
||||
@FXML
|
||||
public VBox nutritionDetails;
|
||||
|
||||
@Inject
|
||||
public NutritionDetailsCtrl(
|
||||
LocaleManager manager
|
||||
LocaleManager manager,
|
||||
ServerUtils server
|
||||
) {
|
||||
this.manager = manager;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeComponents() {
|
||||
Platform.runLater(() -> {
|
||||
IngredientViewModel vm = this.ingredient.get();
|
||||
this.ingredientName.textProperty().bind(vm.nameProperty());
|
||||
this.fatInputElement.textProperty().bindBidirectional(vm.fatProperty(), new NumberStringConverter());
|
||||
this.proteinInputElement.textProperty().bindBidirectional(vm.proteinProperty(), new NumberStringConverter());
|
||||
this.carbInputElement.textProperty().bindBidirectional(vm.carbsProperty(), new NumberStringConverter());
|
||||
this.estimatedKcalLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Estimated energy value: %.1f", vm.getKcal()), vm.kcalProperty()
|
||||
));
|
||||
});
|
||||
this.nutritionValueContainer.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
||||
if (event.getCode() != KeyCode.ENTER) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Ingredient newIngredient = server.updateIngredient(updateIngredient());
|
||||
Platform.runLater(() -> {
|
||||
this.ingredient.get().updateFrom(newIngredient);
|
||||
logger.info("Updated ingredient to " + newIngredient);
|
||||
});
|
||||
} catch (IllegalInputFormatException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
|
||||
}
|
||||
|
||||
public void setVisible(boolean isVisible) {
|
||||
nutritionDetails.setVisible(isVisible);
|
||||
}
|
||||
public void toggleVisible() {
|
||||
nutritionDetails.setVisible(!visible);
|
||||
visible = !visible;
|
||||
}
|
||||
public void setItem(Ingredient ingredient) {
|
||||
this.ingredient.get().updateFrom(ingredient);
|
||||
setVisible(true);
|
||||
}
|
||||
private Ingredient updateIngredient() throws IllegalInputFormatException {
|
||||
Ingredient current = this.ingredient.get().toIngredient();
|
||||
try {
|
||||
double f = Double.parseDouble(this.fatInputElement.getText());
|
||||
double p = Double.parseDouble(this.proteinInputElement.getText());
|
||||
double c = Double.parseDouble(this.carbInputElement.getText());
|
||||
current.setCarbsPer100g(c);
|
||||
current.setProteinPer100g(p);
|
||||
current.setFatPer100g(f);
|
||||
return current;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalInputFormatException("Invalid F/P/C value");
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return manager;
|
||||
}
|
||||
public void handleNutritionSaveClick(ActionEvent actionEvent) throws IOException, InterruptedException {
|
||||
Ingredient newIngredient = updateIngredient();
|
||||
this.ingredient.get().updateFrom(server.updateIngredient(newIngredient));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,71 +1,10 @@
|
|||
package client.scenes.nutrition;
|
||||
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Ingredient;
|
||||
import commons.Recipe;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ListView;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class NutritionViewCtrl {
|
||||
private ObservableList<Recipe> recipes;
|
||||
private HashMap<Ingredient, Integer> ingredientStats;
|
||||
public ListView<Ingredient> nutritionIngredientsView;
|
||||
private final NutritionDetailsCtrl nutritionDetailsCtrl;
|
||||
|
||||
// TODO into Ingredient class definition
|
||||
// FIXME MOST LIKELY CURRENTLY BROKEN. TO BE FIXED.
|
||||
/**
|
||||
* Comedically verbose function to count unique appearances of an ingredient by name in each recipe.
|
||||
* For each recipe:
|
||||
* 1. Collect unique ingredients that appeared in that recipe.
|
||||
* 2. For each unique ingredient in said recipe:
|
||||
* 1. Initialize the appearance for that ingredient to 0.
|
||||
* 2. For each recipe in list:
|
||||
* 1. If the name of the ingredient exists in the recipe list, increment the statistic by 1.
|
||||
* 2. Else maintain the same value for that statistic.
|
||||
* @param recipeList The recipe list
|
||||
*/
|
||||
private void updateIngredientStats(
|
||||
List<Recipe> recipeList
|
||||
) {
|
||||
recipeList.forEach(recipe -> {
|
||||
Set<Ingredient> uniqueIngredients = new HashSet<>(
|
||||
recipe.getIngredients().stream().map(
|
||||
ingredient -> ingredient.ingredient).toList());
|
||||
nutritionIngredientsView.getItems().setAll(uniqueIngredients);
|
||||
uniqueIngredients.forEach(ingredient -> {
|
||||
ingredientStats.put(ingredient, 0);
|
||||
recipeList.forEach(r ->
|
||||
ingredientStats.put(
|
||||
ingredient,
|
||||
ingredientStats.get(ingredient) + (
|
||||
(r.getIngredients().contains(ingredient))
|
||||
? 1 : 0
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@Inject
|
||||
public NutritionViewCtrl(
|
||||
FoodpalApplicationCtrl foodpalApplicationCtrl,
|
||||
NutritionDetailsCtrl nutritionDetailsCtrl
|
||||
) {
|
||||
this.recipes = foodpalApplicationCtrl.recipeList.getItems();
|
||||
this.recipes.addListener((ListChangeListener<? super Recipe>) _ -> {
|
||||
updateIngredientStats(this.recipes);
|
||||
});
|
||||
this.nutritionDetailsCtrl = nutritionDetailsCtrl;
|
||||
this.nutritionIngredientsView.selectionModelProperty().addListener((observable, oldValue, newValue) -> {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,9 +216,13 @@ public class ServerUtils {
|
|||
updateRecipe(recipe);
|
||||
}
|
||||
|
||||
|
||||
// how many ingredients are getting used in recipes
|
||||
|
||||
/**
|
||||
* Gets the amount of recipes this ingredient is being used in.
|
||||
* @param ingredientId The queried ingredient's ID.
|
||||
* @return The amount of recipes the ingredient is used in.
|
||||
* @throws IOException if server query failed.
|
||||
* @throws InterruptedException if operation is interrupted.
|
||||
*/
|
||||
public long getIngredientUsage(long ingredientId) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients/" + ingredientId + "/usage"))
|
||||
|
|
@ -292,6 +296,22 @@ public class ServerUtils {
|
|||
return objectMapper.readValue(response.body(), Ingredient.class);
|
||||
}
|
||||
|
||||
public Ingredient updateIngredient(Ingredient newIngredient) throws IOException, InterruptedException {
|
||||
logger.info("PATCH ingredient with id: " + newIngredient.getId());
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/ingredients/" + newIngredient.getId()))
|
||||
.header("Content-Type", "application/json")
|
||||
.method(
|
||||
"PATCH",
|
||||
HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(newIngredient)))
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to update ingredient with id: " + newIngredient.getId() + " body: " + response.body());
|
||||
}
|
||||
return objectMapper.readValue(response.body(), Ingredient.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="client.scenes.recipe.IngredientsPopupCtrl"
|
||||
fx:controller="client.scenes.Ingredient.IngredientListCtrl"
|
||||
spacing="10" prefWidth="420" prefHeight="520">
|
||||
|
||||
<padding>
|
||||
|
|
@ -7,26 +7,21 @@
|
|||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import javafx.scene.shape.Line?>
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="client.scenes.nutrition.NutritionDetailsCtrl"
|
||||
prefHeight="400.0" prefWidth="600.0">
|
||||
<VBox visible="false">
|
||||
fx:id="nutritionDetails"
|
||||
visible="false">
|
||||
<Label fx:id="ingredientName" />
|
||||
<HBox>
|
||||
<Label fx:id="fatInputLabel">Fat: </Label>
|
||||
<TextField fx:id="fatInputElement" />
|
||||
</HBox>
|
||||
<HBox>
|
||||
<Label fx:id="proteinInputLabel">Protein: </Label>
|
||||
<TextField fx:id="proteinInputElement" />
|
||||
</HBox>
|
||||
<HBox>
|
||||
<Label fx:id="carbInputLabel">Carbohydrates: </Label>
|
||||
<TextField fx:id="carbInputElement" />
|
||||
</HBox>
|
||||
<GridPane fx:id="nutritionValueContainer">
|
||||
<Label GridPane.columnIndex="0" GridPane.rowIndex="0" fx:id="fatInputLabel">Fat: </Label>
|
||||
<TextField GridPane.columnIndex="1" GridPane.rowIndex="0" fx:id="fatInputElement" />
|
||||
<Label GridPane.columnIndex="0" GridPane.rowIndex="1" fx:id="proteinInputLabel">Protein: </Label>
|
||||
<TextField GridPane.columnIndex="1" GridPane.rowIndex="1" fx:id="proteinInputElement" />
|
||||
<Label GridPane.columnIndex="0" GridPane.rowIndex="2" fx:id="carbInputLabel">Carbohydrates: </Label>
|
||||
<TextField GridPane.columnIndex="1" GridPane.rowIndex="2" fx:id="carbInputElement" />
|
||||
</GridPane>
|
||||
<Button onAction="#handleNutritionSaveClick">Save values</Button>
|
||||
<Label fx:id="estimatedKcalLabel">Estimated: 0kcal</Label>
|
||||
<Label fx:id="usageLabel">Not used in any recipes</Label>
|
||||
</VBox>
|
||||
|
||||
</AnchorPane>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@
|
|||
fx:controller="client.scenes.nutrition.NutritionViewCtrl"
|
||||
prefHeight="400.0" prefWidth="600.0">
|
||||
<SplitPane>
|
||||
<ListView fx:id="nutritionIngredientsView" />
|
||||
<AnchorPane>
|
||||
<fx:include source="IngredientList.fxml" />
|
||||
<fx:include source="NutritionDetails.fxml" />
|
||||
</AnchorPane>
|
||||
</SplitPane>
|
||||
</AnchorPane>
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@
|
|||
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
||||
|
||||
<!-- Ingredients -->
|
||||
<fx:include source="IngredientList.fxml" fx:id="ingredientList"
|
||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||
<fx:include source="RecipeIngredientList.fxml" fx:id="ingredientList" />
|
||||
|
||||
<!-- Preparation -->
|
||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||
|
|
|
|||
|
|
@ -84,10 +84,21 @@ public class Ingredient {
|
|||
public double getCarbsPer100g() {
|
||||
return carbsPer100g;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
public void setCarbsPer100g(double carbsPer100g) {
|
||||
this.carbsPer100g = carbsPer100g;
|
||||
}
|
||||
|
||||
public void setFatPer100g(double fatPer100g) {
|
||||
this.fatPer100g = fatPer100g;
|
||||
}
|
||||
|
||||
public void setProteinPer100g(double proteinPer100g) {
|
||||
this.proteinPer100g = proteinPer100g;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
|
@ -101,7 +112,7 @@ public class Ingredient {
|
|||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Ingredient that = (Ingredient) o;
|
||||
return id == that.id && Double.compare(proteinPer100g, that.proteinPer100g) == 0 && Double.compare(fatPer100g, that.fatPer100g) == 0 && Double.compare(carbsPer100g, that.carbsPer100g) == 0 && Objects.equals(name, that.name);
|
||||
return Objects.equals(id, that.id) && Double.compare(proteinPer100g, that.proteinPer100g) == 0 && Double.compare(fatPer100g, that.fatPer100g) == 0 && Double.compare(carbsPer100g, that.carbsPer100g) == 0 && Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue