diff --git a/client/pom.xml b/client/pom.xml index b9eb186..753a004 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -128,6 +128,11 @@ logback-classic 1.5.20 + + org.slf4j + slf4j-api + 2.0.17 + diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java index 661b2df..6cd6fb9 100644 --- a/client/src/main/java/client/MyModule.java +++ b/client/src/main/java/client/MyModule.java @@ -15,6 +15,7 @@ */ package client; +import client.model.ApplicationDataViewModel; import client.scenes.FoodpalApplicationCtrl; import client.scenes.SearchBarCtrl; import client.scenes.nutrition.NutritionDetailsCtrl; @@ -58,5 +59,6 @@ public class MyModule implements Module { binder.bind(new TypeLiteral>() {}).toInstance( new WebSocketDataService<>() ); + binder.bind(ApplicationDataViewModel.class).in(Scopes.SINGLETON); } } \ No newline at end of file diff --git a/client/src/main/java/client/model/ApplicationDataViewModel.java b/client/src/main/java/client/model/ApplicationDataViewModel.java new file mode 100644 index 0000000..0fd0ceb --- /dev/null +++ b/client/src/main/java/client/model/ApplicationDataViewModel.java @@ -0,0 +1,41 @@ +package client.model; + +import commons.Recipe; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; + +import java.util.List; +import java.util.Optional; + +public class ApplicationDataViewModel { + private final ListProperty recipes = new SimpleListProperty<>(); + private final ListProperty ingredients = new SimpleListProperty<>(); + public ApplicationDataViewModel() {} + public ApplicationDataViewModel(ObservableList recipes, ObservableList ingredients) { + this.recipes.set(recipes); + this.ingredients.set(ingredients); + } + public Optional getRecipeViewById(Long id) { + return recipes.get().stream().filter(r -> r.getId().equals(id)).findFirst(); + } + public Optional getIngredientViewById(Long id) { + return ingredients.get().stream().filter(i -> i.idProperty().get() == id).findFirst(); + } + + public ObservableList getRecipes() { + return recipes.get(); + } + + public ObservableList getIngredients() { + return ingredients.get(); + } + public void setRecipes(List recipes) { + this.recipes.setAll(recipes); + } + public void setIngredients(List ingredients) { + this.ingredients.setAll(ingredients); + } +} diff --git a/client/src/main/java/client/model/BaseViewModel.java b/client/src/main/java/client/model/BaseViewModel.java new file mode 100644 index 0000000..0eda2b8 --- /dev/null +++ b/client/src/main/java/client/model/BaseViewModel.java @@ -0,0 +1,6 @@ +package client.model; + +public abstract class BaseViewModel { + public abstract void updateFrom(T item); + public abstract T into(); +} diff --git a/client/src/main/java/client/model/FormalIngredientViewModel.java b/client/src/main/java/client/model/FormalIngredientViewModel.java new file mode 100644 index 0000000..f012655 --- /dev/null +++ b/client/src/main/java/client/model/FormalIngredientViewModel.java @@ -0,0 +1,66 @@ +package client.model; + +import commons.FormalIngredient; +import commons.RecipeIngredient; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class FormalIngredientViewModel extends RecipeIngredientViewModel { + private final LongProperty id = new SimpleLongProperty(); + private final ObjectProperty ingredientView = new SimpleObjectProperty<>(); + private final DoubleProperty amount = new SimpleDoubleProperty(); + private final StringProperty unitSuffix = new SimpleStringProperty(); + private final DoubleProperty kcal = new SimpleDoubleProperty(); + public FormalIngredientViewModel(FormalIngredient ingredient) { + updateFrom(ingredient); + kcal.bind(Bindings.createDoubleBinding(() -> { + System.out.println(ingredientView.get().getKcal()); + System.out.println("evaluating kcal: " + ingredientView.get().getKcal() * amount.get()); + return ingredientView.get().getKcal() * amount.get(); + })); + } + @Override + public void updateFrom(FormalIngredient item) { + id.set(item.getId()); + ingredientView.set(new IngredientViewModel(item.getIngredient())); + amount.set(item.getAmount()); + unitSuffix.set(item.getUnitSuffix()); + } + + @Override + public FormalIngredient into() { + return new FormalIngredient(); + } + + @Override + public RecipeIngredient getReal() { + return null; + } + + @Override + public RecipeIngredient getScaled() { + return null; + } + + @Override + public void setReal(RecipeIngredient real) { + + } + + @Override + public void setScale(Number scale) { + + } + + @Override + public DoubleProperty kcalProperty() { + return kcal; + } +} diff --git a/client/src/main/java/client/Ingredient/IngredientViewModel.java b/client/src/main/java/client/model/IngredientViewModel.java similarity index 93% rename from client/src/main/java/client/Ingredient/IngredientViewModel.java rename to client/src/main/java/client/model/IngredientViewModel.java index 799c69f..6f87311 100644 --- a/client/src/main/java/client/Ingredient/IngredientViewModel.java +++ b/client/src/main/java/client/model/IngredientViewModel.java @@ -1,4 +1,4 @@ -package client.Ingredient; +package client.model; import commons.Ingredient; import javafx.beans.binding.Bindings; @@ -12,7 +12,7 @@ import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -public class IngredientViewModel { +public class IngredientViewModel extends BaseViewModel { private final LongProperty id = new SimpleLongProperty(); private final StringProperty name = new SimpleStringProperty(); private final DoubleProperty protein = new SimpleDoubleProperty(); @@ -22,7 +22,7 @@ public class IngredientViewModel { private final ReadOnlyDoubleWrapper kcal = new ReadOnlyDoubleWrapper(); public IngredientViewModel() { DoubleBinding computeKcal = Bindings.createDoubleBinding( - () -> toIngredient().kcalPer100g(), + () -> into().kcalPer100g(), protein, carbs, fat ); @@ -61,7 +61,7 @@ public class IngredientViewModel { return carbs; } - public Ingredient toIngredient() { + public Ingredient into() { Ingredient i = new Ingredient(); i.setId(id.get()); i.setName(name.get()); diff --git a/client/src/main/java/client/model/RecipeIngredientViewModel.java b/client/src/main/java/client/model/RecipeIngredientViewModel.java new file mode 100644 index 0000000..c1f7f64 --- /dev/null +++ b/client/src/main/java/client/model/RecipeIngredientViewModel.java @@ -0,0 +1,9 @@ +package client.model; + +import commons.RecipeIngredient; +import javafx.beans.property.DoubleProperty; + +public abstract class RecipeIngredientViewModel + extends BaseViewModel implements ScaleAware { + public abstract DoubleProperty kcalProperty(); +} diff --git a/client/src/main/java/client/model/RecipeViewModel.java b/client/src/main/java/client/model/RecipeViewModel.java new file mode 100644 index 0000000..6640c47 --- /dev/null +++ b/client/src/main/java/client/model/RecipeViewModel.java @@ -0,0 +1,112 @@ +package client.model; + +import commons.FormalIngredient; +import commons.Recipe; +import commons.RecipeIngredient; +import commons.VagueIngredient; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.util.List; + +public class RecipeViewModel extends BaseViewModel implements ScaleAware { + // Immutable + private final Long id; + + // Mutable values + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty locale = new SimpleStringProperty(); + private final ListProperty> ingredients = new SimpleListProperty<>(FXCollections.observableArrayList( + item -> new Observable[] { item.kcalProperty() } + )); + private final ObjectProperty> steps = new SimpleObjectProperty<>(); + private final DoubleProperty kcal = new SimpleDoubleProperty(); + private final DoubleProperty scale = new SimpleDoubleProperty(); + public RecipeViewModel(Recipe recipe) { + this.id = recipe.getId(); + this.kcal.bind(Bindings.createDoubleBinding(() -> { + List> list = ingredients.get(); + System.out.println(list); + if (list == null || list.isEmpty()) { + return 0.0; + } + System.out.println("Compiled kcal double binding"); + return list.stream().mapToDouble(i -> i.kcalProperty().get()).sum(); + }, ingredients)); + updateFrom(recipe); + } + @Override + public void updateFrom(Recipe item) { + name.setValue(item.getName()); + locale.setValue(item.getLocale()); + List> list = item.getIngredients().stream().>map(recipeIngredient -> switch (recipeIngredient) { + case FormalIngredient f -> new FormalIngredientViewModel(f); + case VagueIngredient v -> new VagueIngredientViewModel(v); + default -> throw new IllegalStateException("Unexpected value: " + recipeIngredient); + }).toList(); + ingredients.setAll(list); + steps.setValue(FXCollections.observableList(item.getPreparationSteps())); + } + + @Override + public Recipe into() { + return new Recipe( + id, name.getValue(), locale.getValue(), + ingredients.stream().map(i -> (RecipeIngredient) i.into()).toList(), + steps.getValue()); + } + + @Override + public Recipe getReal() { + return into(); + } + + public Long getId() { + return id; + } + + @Override + public Recipe getScaled() { + return into().scaleBy(scale.get()); + } + + @Override + public void setReal(Recipe real) { + updateFrom(real); + } + + @Override + public void setScale(Number scale) { + this.scale.setValue(scale); + } + + public StringProperty nameProperty() { + return name; + } + + public StringProperty localeProperty() { + return locale; + } + public ListProperty> ingredientsProperty() { + return ingredients; + } + public ObjectProperty> stepsProperty() { + return steps; + } + public DoubleProperty kcalProperty() { + return kcal; + } + public DoubleProperty scaleProperty() { + return scale; + } +} diff --git a/client/src/main/java/client/model/ScaleAware.java b/client/src/main/java/client/model/ScaleAware.java new file mode 100644 index 0000000..c5e7bf9 --- /dev/null +++ b/client/src/main/java/client/model/ScaleAware.java @@ -0,0 +1,12 @@ +package client.model; + +/** + * An interface describing a value that has a real and scaled counterpart. + * @param + */ +public interface ScaleAware { + T getReal(); + T getScaled(); + void setReal(T real); + void setScale(Number scale); +} diff --git a/client/src/main/java/client/model/VagueIngredientViewModel.java b/client/src/main/java/client/model/VagueIngredientViewModel.java new file mode 100644 index 0000000..98f24c3 --- /dev/null +++ b/client/src/main/java/client/model/VagueIngredientViewModel.java @@ -0,0 +1,47 @@ +package client.model; + +import commons.RecipeIngredient; +import commons.VagueIngredient; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class VagueIngredientViewModel extends RecipeIngredientViewModel { + private final SimpleObjectProperty vagueIngredient = new SimpleObjectProperty<>(); + public VagueIngredientViewModel(VagueIngredient ingredient) { + this.vagueIngredient.set(ingredient); + } + @Override + public void updateFrom(VagueIngredient item) { + + } + + @Override + public VagueIngredient into() { + return vagueIngredient.get(); + } + + @Override + public DoubleProperty kcalProperty() { + return null; + } + + @Override + public RecipeIngredient getReal() { + return null; + } + + @Override + public RecipeIngredient getScaled() { + return null; + } + + @Override + public void setReal(RecipeIngredient real) { + + } + + @Override + public void setScale(Number scale) { + + } +} diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index d276137..9abcf9f 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -10,6 +10,8 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import client.exception.InvalidModificationException; +import client.model.ApplicationDataViewModel; +import client.model.RecipeViewModel; import client.scenes.nutrition.NutritionViewCtrl; import client.scenes.recipe.RecipeDetailCtrl; @@ -51,7 +53,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { private final LocaleManager localeManager; private final WebSocketDataService dataService; private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName()); - + private final ApplicationDataViewModel applicationDataViewModel; @FXML private RecipeDetailCtrl recipeDetailController; @@ -98,7 +100,8 @@ public class FoodpalApplicationCtrl implements LocaleAware { WebSocketUtils webSocketUtils, LocaleManager localeManager, ConfigService configService, - WebSocketDataService recipeDataService + WebSocketDataService recipeDataService, + ApplicationDataViewModel applicationDataViewModel ) { this.server = server; this.webSocketUtils = webSocketUtils; @@ -106,6 +109,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { this.configService = configService; this.dataService = recipeDataService; + this.applicationDataViewModel = applicationDataViewModel; setupDataService(); logger.info("WebSocket processor initialized."); @@ -290,6 +294,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { searchBarController.getFilter(), this.configService.getConfig().getRecipeLanguages() ); + applicationDataViewModel.setRecipes(recipes.stream().map(RecipeViewModel::new).toList()); } catch (IOException | InterruptedException e) { recipes = Collections.emptyList(); String msg = "Failed to load recipes: " + e.getMessage(); diff --git a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java index 25b7bfb..28afbdb 100644 --- a/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java +++ b/client/src/main/java/client/scenes/nutrition/NutritionDetailsCtrl.java @@ -1,6 +1,7 @@ package client.scenes.nutrition; -import client.Ingredient.IngredientViewModel; +import client.model.ApplicationDataViewModel; +import client.model.IngredientViewModel; import client.exception.IllegalInputFormatException; import client.utils.LocaleAware; import client.utils.LocaleManager; @@ -32,6 +33,7 @@ public class NutritionDetailsCtrl implements LocaleAware { private final ServerUtils server; private final SimpleObjectProperty ingredient = new SimpleObjectProperty<>(new IngredientViewModel()); + private ApplicationDataViewModel applicationDataViewModel; private final Logger logger = Logger.getLogger(this.getClass().getName()); public Label ingredientName; @@ -51,10 +53,12 @@ public class NutritionDetailsCtrl implements LocaleAware { @Inject public NutritionDetailsCtrl( LocaleManager manager, - ServerUtils server + ServerUtils server, + ApplicationDataViewModel applicationDataViewModel ) { this.manager = manager; this.server = server; + this.applicationDataViewModel = applicationDataViewModel; } @Override @@ -77,6 +81,9 @@ public class NutritionDetailsCtrl implements LocaleAware { Ingredient newIngredient = server.updateIngredient(updateIngredient()); Platform.runLater(() -> { this.ingredient.get().updateFrom(newIngredient); + applicationDataViewModel.getIngredientViewById(newIngredient.getId()) + .orElseThrow(() -> new RuntimeException("error!")) + .updateFrom(newIngredient); logger.info("Updated ingredient to " + newIngredient); }); } catch (IllegalInputFormatException e) { @@ -106,7 +113,7 @@ public class NutritionDetailsCtrl implements LocaleAware { setVisible(true); } private Ingredient updateIngredient() throws IllegalInputFormatException { - Ingredient current = this.ingredient.get().toIngredient(); + Ingredient current = this.ingredient.get().into(); try { double f = Double.parseDouble(this.fatInputElement.getText()); double p = Double.parseDouble(this.proteinInputElement.getText()); diff --git a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java index 608ac44..ec1cb58 100644 --- a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java +++ b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java @@ -1,6 +1,8 @@ package client.scenes.recipe; import client.exception.UpdateException; +import client.model.ApplicationDataViewModel; +import client.model.RecipeViewModel; import client.scenes.FoodpalApplicationCtrl; import client.utils.Config; import client.utils.ConfigService; @@ -18,6 +20,8 @@ import java.nio.file.Path; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; + +import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -32,8 +36,8 @@ import javafx.scene.text.Font; import javafx.stage.DirectoryChooser; /** - * Controller for the recipe detail view. - * Manages displaying and editing recipe details such as name, ingredients, and + * Controller for the recipeViewModel detail view. + * Manages displaying and editing recipeViewModel details such as name, ingredients, and * steps. */ public class RecipeDetailCtrl implements LocaleAware { @@ -42,6 +46,8 @@ public class RecipeDetailCtrl implements LocaleAware { private final FoodpalApplicationCtrl appCtrl; private final ConfigService configService; private final WebSocketDataService webSocketDataService; + private final ApplicationDataViewModel applicationDataViewModel; + public Label kcalLabel; @FXML private IngredientListCtrl ingredientListController; @@ -49,7 +55,7 @@ public class RecipeDetailCtrl implements LocaleAware { @FXML private RecipeStepListCtrl stepListController; - private Recipe recipe; + private SimpleObjectProperty recipeViewModel = new SimpleObjectProperty<>(); @Inject public RecipeDetailCtrl( @@ -57,12 +63,14 @@ public class RecipeDetailCtrl implements LocaleAware { ServerUtils server, FoodpalApplicationCtrl appCtrl, ConfigService configService, - WebSocketDataService webSocketDataService) { + WebSocketDataService webSocketDataService, + ApplicationDataViewModel applicationDataViewModel) { this.localeManager = localeManager; this.server = server; this.appCtrl = appCtrl; this.configService = configService; this.webSocketDataService = webSocketDataService; + this.applicationDataViewModel = applicationDataViewModel; } @FXML @@ -92,9 +100,9 @@ public class RecipeDetailCtrl implements LocaleAware { /** * Convenience method for a frequently needed operation to retrieve the - * currently selected recipe. + * currently selected recipeViewModel. * - * @return The currently selected recipe in the parent recipe list. + * @return The currently selected recipeViewModel in the parent recipeViewModel list. */ private Optional getSelectedRecipe() { return Optional.ofNullable( @@ -112,19 +120,19 @@ public class RecipeDetailCtrl implements LocaleAware { } /** - * Refreshes the recipe list from the server. + * Refreshes the recipeViewModel list from the server. * - * @throws IOException Upon invalid recipe response. + * @throws IOException Upon invalid recipeViewModel response. * @throws InterruptedException Upon request interruption. * * @see FoodpalApplicationCtrl#refresh() */ - private void refresh() throws IOException, InterruptedException { + public void refresh() throws IOException, InterruptedException { this.appCtrl.refresh(); } /** - * Toggles the visibility of the recipe details screen. + * Toggles the visibility of the recipeViewModel details screen. * * @param visible Whether the details screen should be visible. */ @@ -133,17 +141,16 @@ public class RecipeDetailCtrl implements LocaleAware { } /** - * Sets the currently viewed recipe in the detail view. + * Sets the currently viewed recipeViewModel in the detail view. * - * @param recipe The recipe to view. + * @param recipe The recipeViewModel to view. */ public void setCurrentlyViewedRecipe(Recipe recipe) { if (recipe == null) { return; } - this.recipe = recipe; - + this.recipeViewModel.set(new RecipeViewModel(recipe)); this.showName(recipe.getName()); this.ingredientListController.refetchFromRecipe(recipe); this.stepListController.refetchFromRecipe(recipe); @@ -152,11 +159,11 @@ public class RecipeDetailCtrl implements LocaleAware { } /** - * Create a callback that takes in the selected recipe and a helper type - * and updates the recipe based on that. + * Create a callback that takes in the selected recipeViewModel and a helper type + * and updates the recipeViewModel based on that. * Also, posts the update to the server. * - * @param recipeConsumer The helper function to use when updating the recipe - + * @param recipeConsumer The helper function to use when updating the recipeViewModel - * how to update it * @return The created callback to use in a setUpdateCallback or related * function @@ -165,7 +172,7 @@ public class RecipeDetailCtrl implements LocaleAware { return recipes -> { Recipe selectedRecipe = this.getSelectedRecipe().orElseThrow( () -> new NullPointerException( - "Null recipe whereas ingredients are edited")); + "Null recipeViewModel whereas ingredients are edited")); recipeConsumer.accept(selectedRecipe, recipes); @@ -174,9 +181,10 @@ public class RecipeDetailCtrl implements LocaleAware { webSocketDataService.add(selectedRecipe.getId(), recipe -> { int idx = getParentRecipeList().getItems().indexOf(selectedRecipe); getParentRecipeList().getItems().set(idx, recipe); + recipeViewModel.get().updateFrom(recipe); }); } catch (IOException | InterruptedException e) { - throw new UpdateException("Unable to update recipe to server for " + + throw new UpdateException("Unable to update recipeViewModel to server for " + selectedRecipe); } }; @@ -190,15 +198,16 @@ public class RecipeDetailCtrl implements LocaleAware { // Initialize callback for ingredient list updates this.ingredientListController.setUpdateCallback( - this.createUpdateRecipeCallback(Recipe::setIngredients)); + this.createUpdateRecipeCallback(Recipe::setIngredients) + ); // Ditto, for step list updates this.stepListController.setUpdateCallback( this.createUpdateRecipeCallback(Recipe::setPreparationSteps)); } /** - * Revised edit recipe control flow, deprecates the use of a separate - * AddNameCtrl. This is automagically called when a new recipe is created, + * Revised edit recipeViewModel control flow, deprecates the use of a separate + * AddNameCtrl. This is automagically called when a new recipeViewModel is created, * making for a more seamless UX. * Public for reference by ApplicationCtrl. */ @@ -213,14 +222,15 @@ public class RecipeDetailCtrl implements LocaleAware { } String newName = edit.getText(); - this.recipe.setName(newName); - + this.recipeViewModel.get().nameProperty().setValue(newName); + Recipe r = this.recipeViewModel.get().into(); + System.out.println(r.toDetailedString()); try { - server.updateRecipe(this.recipe); + server.updateRecipe(r); // this.refresh(); } catch (IOException | InterruptedException e) { // throw a nice blanket UpdateException - throw new UpdateException("Error occurred when updating recipe name!"); + throw new UpdateException("Error occurred when updating recipeViewModel name!"); } this.showName(edit.getText()); @@ -231,7 +241,7 @@ public class RecipeDetailCtrl implements LocaleAware { } /** - * Remove the selected recipe from the server and refresh the recipe list. + * Remove the selected recipeViewModel from the server and refresh the recipeViewModel list. * Internally calls {@link FoodpalApplicationCtrl#removeSelectedRecipe()}. */ @FXML @@ -241,17 +251,17 @@ public class RecipeDetailCtrl implements LocaleAware { /** * Refreshes the favourite button state based on whether the currently viewed - * recipe is marked as a favourite in the application configuration. + * recipeViewModel is marked as a favourite in the application configuration. */ public void refreshFavouriteButton() { - if (recipe == null) { + if (recipeViewModel == null || recipeViewModel.get() == null) { favouriteButton.setDisable(true); favouriteButton.setText("☆"); return; } favouriteButton.setDisable(false); - favouriteButton.setText(this.getConfig().isFavourite(recipe.getId()) ? "★" + favouriteButton.setText(this.getConfig().isFavourite(recipeViewModel.get().getReal().getId()) ? "★" : "☆"); } @@ -262,8 +272,8 @@ public class RecipeDetailCtrl implements LocaleAware { */ @FXML private void printRecipe() { - if (recipe == null) { - return; // Do nothing if no recipe selected + if (recipeViewModel == null) { + return; // Do nothing if no recipeViewModel selected } // Open directory chooser @@ -278,9 +288,9 @@ public class RecipeDetailCtrl implements LocaleAware { } // Ask for filename - TextInputDialog dialog = new TextInputDialog(recipe.getName() + ".txt"); + TextInputDialog dialog = new TextInputDialog(recipeViewModel.get().getReal().getName() + ".txt"); dialog.setTitle("Save Recipe"); - dialog.setHeaderText("Enter filename for the recipe"); + dialog.setHeaderText("Enter filename for the recipeViewModel"); dialog.setContentText("Filename:"); Optional result = dialog.showAndWait(); @@ -292,18 +302,18 @@ public class RecipeDetailCtrl implements LocaleAware { } // Use PrintExportService methods - String recipeText = PrintExportService.buildRecipeText(recipe); + String recipeText = PrintExportService.buildRecipeText(recipeViewModel.get().getReal()); Path dirPath = selectedDirectory.toPath(); PrintExportService.exportToFile(recipeText, dirPath, filename); } } /** - * Toggles the favourite status of the currently viewed recipe in the + * Toggles the favourite status of the currently viewed recipeViewModel in the * application configuration and writes the changes to disk. */ @FXML private void toggleFavourite() { - long id = this.recipe.getId(); + long id = this.recipeViewModel.get().getReal().getId(); Config config = this.getConfig(); if (config.isFavourite(id)) { @@ -337,16 +347,16 @@ public class RecipeDetailCtrl implements LocaleAware { } /** - * Switch the recipe's language. + * Switch the recipeViewModel's language. */ @FXML void changeLanguage() { - recipe.setLocale(this.langSelector.getValue()); + recipeViewModel.get().getReal().setLocale(this.langSelector.getValue()); try { - server.updateRecipe(this.recipe); + server.updateRecipe(this.recipeViewModel.get().getReal()); } catch (IOException | InterruptedException e) { - throw new UpdateException("Error occurred when updating recipe locale!"); + throw new UpdateException("Error occurred when updating recipeViewModel locale!"); } } @@ -365,7 +375,11 @@ public class RecipeDetailCtrl implements LocaleAware { @Override public void initializeComponents() { initStepsIngredientsList(); - + recipeViewModel.addListener((_, ov, nv) -> { + if (nv != null) { + kcalLabel.textProperty().bind(recipeViewModel.get().kcalProperty().asString("%.1f kcal")); + } + }); langSelector.getItems().addAll("en", "nl", "pl"); } } diff --git a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml index dd5f2ba..639e823 100644 --- a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml +++ b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml @@ -35,4 +35,5 @@ +