chore: etc
This commit is contained in:
parent
ecccebe7c5
commit
895aecba3d
19 changed files with 404 additions and 55 deletions
|
|
@ -128,6 +128,11 @@
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.5.20</version> <!-- or latest compatible version -->
|
<version>1.5.20</version> <!-- or latest compatible version -->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>2.0.17</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package client;
|
package client;
|
||||||
|
|
||||||
|
import client.model.ApplicationDataViewModel;
|
||||||
import client.scenes.FoodpalApplicationCtrl;
|
import client.scenes.FoodpalApplicationCtrl;
|
||||||
import client.scenes.SearchBarCtrl;
|
import client.scenes.SearchBarCtrl;
|
||||||
import client.scenes.nutrition.NutritionDetailsCtrl;
|
import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||||
|
|
@ -58,5 +59,6 @@ public class MyModule implements Module {
|
||||||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Ingredient>>() {}).toInstance(
|
binder.bind(new TypeLiteral<WebSocketDataService<Long, Ingredient>>() {}).toInstance(
|
||||||
new WebSocketDataService<>()
|
new WebSocketDataService<>()
|
||||||
);
|
);
|
||||||
|
binder.bind(ApplicationDataViewModel.class).in(Scopes.SINGLETON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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<RecipeViewModel> recipes = new SimpleListProperty<>();
|
||||||
|
private final ListProperty<IngredientViewModel> ingredients = new SimpleListProperty<>();
|
||||||
|
public ApplicationDataViewModel() {}
|
||||||
|
public ApplicationDataViewModel(ObservableList<RecipeViewModel> recipes, ObservableList<IngredientViewModel> ingredients) {
|
||||||
|
this.recipes.set(recipes);
|
||||||
|
this.ingredients.set(ingredients);
|
||||||
|
}
|
||||||
|
public Optional<RecipeViewModel> getRecipeViewById(Long id) {
|
||||||
|
return recipes.get().stream().filter(r -> r.getId().equals(id)).findFirst();
|
||||||
|
}
|
||||||
|
public Optional<IngredientViewModel> getIngredientViewById(Long id) {
|
||||||
|
return ingredients.get().stream().filter(i -> i.idProperty().get() == id).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<RecipeViewModel> getRecipes() {
|
||||||
|
return recipes.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<IngredientViewModel> getIngredients() {
|
||||||
|
return ingredients.get();
|
||||||
|
}
|
||||||
|
public void setRecipes(List<RecipeViewModel> recipes) {
|
||||||
|
this.recipes.setAll(recipes);
|
||||||
|
}
|
||||||
|
public void setIngredients(List<IngredientViewModel> ingredients) {
|
||||||
|
this.ingredients.setAll(ingredients);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
client/src/main/java/client/model/BaseViewModel.java
Normal file
6
client/src/main/java/client/model/BaseViewModel.java
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package client.model;
|
||||||
|
|
||||||
|
public abstract class BaseViewModel<T> {
|
||||||
|
public abstract void updateFrom(T item);
|
||||||
|
public abstract T into();
|
||||||
|
}
|
||||||
|
|
@ -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<FormalIngredient> {
|
||||||
|
private final LongProperty id = new SimpleLongProperty();
|
||||||
|
private final ObjectProperty<IngredientViewModel> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package client.Ingredient;
|
package client.model;
|
||||||
|
|
||||||
import commons.Ingredient;
|
import commons.Ingredient;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
|
@ -12,7 +12,7 @@ import javafx.beans.property.SimpleLongProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
public class IngredientViewModel {
|
public class IngredientViewModel extends BaseViewModel<Ingredient> {
|
||||||
private final LongProperty id = new SimpleLongProperty();
|
private final LongProperty id = new SimpleLongProperty();
|
||||||
private final StringProperty name = new SimpleStringProperty();
|
private final StringProperty name = new SimpleStringProperty();
|
||||||
private final DoubleProperty protein = new SimpleDoubleProperty();
|
private final DoubleProperty protein = new SimpleDoubleProperty();
|
||||||
|
|
@ -22,7 +22,7 @@ public class IngredientViewModel {
|
||||||
private final ReadOnlyDoubleWrapper kcal = new ReadOnlyDoubleWrapper();
|
private final ReadOnlyDoubleWrapper kcal = new ReadOnlyDoubleWrapper();
|
||||||
public IngredientViewModel() {
|
public IngredientViewModel() {
|
||||||
DoubleBinding computeKcal = Bindings.createDoubleBinding(
|
DoubleBinding computeKcal = Bindings.createDoubleBinding(
|
||||||
() -> toIngredient().kcalPer100g(),
|
() -> into().kcalPer100g(),
|
||||||
protein, carbs, fat
|
protein, carbs, fat
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ public class IngredientViewModel {
|
||||||
return carbs;
|
return carbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ingredient toIngredient() {
|
public Ingredient into() {
|
||||||
Ingredient i = new Ingredient();
|
Ingredient i = new Ingredient();
|
||||||
i.setId(id.get());
|
i.setId(id.get());
|
||||||
i.setName(name.get());
|
i.setName(name.get());
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package client.model;
|
||||||
|
|
||||||
|
import commons.RecipeIngredient;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
|
||||||
|
public abstract class RecipeIngredientViewModel<T>
|
||||||
|
extends BaseViewModel<T> implements ScaleAware<RecipeIngredient> {
|
||||||
|
public abstract DoubleProperty kcalProperty();
|
||||||
|
}
|
||||||
112
client/src/main/java/client/model/RecipeViewModel.java
Normal file
112
client/src/main/java/client/model/RecipeViewModel.java
Normal file
|
|
@ -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<Recipe> implements ScaleAware<Recipe> {
|
||||||
|
// Immutable
|
||||||
|
private final Long id;
|
||||||
|
|
||||||
|
// Mutable values
|
||||||
|
private final StringProperty name = new SimpleStringProperty();
|
||||||
|
private final StringProperty locale = new SimpleStringProperty();
|
||||||
|
private final ListProperty<RecipeIngredientViewModel<?>> ingredients = new SimpleListProperty<>(FXCollections.observableArrayList(
|
||||||
|
item -> new Observable[] { item.kcalProperty() }
|
||||||
|
));
|
||||||
|
private final ObjectProperty<ObservableList<String>> 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<RecipeIngredientViewModel<?>> 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<RecipeIngredientViewModel<?>> list = item.getIngredients().stream().<RecipeIngredientViewModel<?>>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<RecipeIngredientViewModel<?>> ingredientsProperty() {
|
||||||
|
return ingredients;
|
||||||
|
}
|
||||||
|
public ObjectProperty<ObservableList<String>> stepsProperty() {
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
public DoubleProperty kcalProperty() {
|
||||||
|
return kcal;
|
||||||
|
}
|
||||||
|
public DoubleProperty scaleProperty() {
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
client/src/main/java/client/model/ScaleAware.java
Normal file
12
client/src/main/java/client/model/ScaleAware.java
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package client.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface describing a value that has a real and scaled counterpart.
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public interface ScaleAware<T> {
|
||||||
|
T getReal();
|
||||||
|
T getScaled();
|
||||||
|
void setReal(T real);
|
||||||
|
void setScale(Number scale);
|
||||||
|
}
|
||||||
|
|
@ -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<VagueIngredient> {
|
||||||
|
private final SimpleObjectProperty<VagueIngredient> 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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,8 @@ import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import client.exception.InvalidModificationException;
|
import client.exception.InvalidModificationException;
|
||||||
|
import client.model.ApplicationDataViewModel;
|
||||||
|
import client.model.RecipeViewModel;
|
||||||
import client.scenes.nutrition.NutritionViewCtrl;
|
import client.scenes.nutrition.NutritionViewCtrl;
|
||||||
import client.scenes.recipe.RecipeDetailCtrl;
|
import client.scenes.recipe.RecipeDetailCtrl;
|
||||||
|
|
||||||
|
|
@ -51,7 +53,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
private final LocaleManager localeManager;
|
private final LocaleManager localeManager;
|
||||||
private final WebSocketDataService<Long, Recipe> dataService;
|
private final WebSocketDataService<Long, Recipe> dataService;
|
||||||
private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName());
|
private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName());
|
||||||
|
private final ApplicationDataViewModel applicationDataViewModel;
|
||||||
@FXML
|
@FXML
|
||||||
private RecipeDetailCtrl recipeDetailController;
|
private RecipeDetailCtrl recipeDetailController;
|
||||||
|
|
||||||
|
|
@ -98,7 +100,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
WebSocketUtils webSocketUtils,
|
WebSocketUtils webSocketUtils,
|
||||||
LocaleManager localeManager,
|
LocaleManager localeManager,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
WebSocketDataService<Long, Recipe> recipeDataService
|
WebSocketDataService<Long, Recipe> recipeDataService,
|
||||||
|
ApplicationDataViewModel applicationDataViewModel
|
||||||
) {
|
) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.webSocketUtils = webSocketUtils;
|
this.webSocketUtils = webSocketUtils;
|
||||||
|
|
@ -106,6 +109,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
|
|
||||||
this.configService = configService;
|
this.configService = configService;
|
||||||
this.dataService = recipeDataService;
|
this.dataService = recipeDataService;
|
||||||
|
this.applicationDataViewModel = applicationDataViewModel;
|
||||||
|
|
||||||
setupDataService();
|
setupDataService();
|
||||||
logger.info("WebSocket processor initialized.");
|
logger.info("WebSocket processor initialized.");
|
||||||
|
|
@ -290,6 +294,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
||||||
searchBarController.getFilter(),
|
searchBarController.getFilter(),
|
||||||
this.configService.getConfig().getRecipeLanguages()
|
this.configService.getConfig().getRecipeLanguages()
|
||||||
);
|
);
|
||||||
|
applicationDataViewModel.setRecipes(recipes.stream().map(RecipeViewModel::new).toList());
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
recipes = Collections.emptyList();
|
recipes = Collections.emptyList();
|
||||||
String msg = "Failed to load recipes: " + e.getMessage();
|
String msg = "Failed to load recipes: " + e.getMessage();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package client.scenes.nutrition;
|
package client.scenes.nutrition;
|
||||||
|
|
||||||
import client.Ingredient.IngredientViewModel;
|
import client.model.ApplicationDataViewModel;
|
||||||
|
import client.model.IngredientViewModel;
|
||||||
import client.exception.IllegalInputFormatException;
|
import client.exception.IllegalInputFormatException;
|
||||||
import client.utils.LocaleAware;
|
import client.utils.LocaleAware;
|
||||||
import client.utils.LocaleManager;
|
import client.utils.LocaleManager;
|
||||||
|
|
@ -32,6 +33,7 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
||||||
private final ServerUtils server;
|
private final ServerUtils server;
|
||||||
private final SimpleObjectProperty<IngredientViewModel> ingredient =
|
private final SimpleObjectProperty<IngredientViewModel> ingredient =
|
||||||
new SimpleObjectProperty<>(new IngredientViewModel());
|
new SimpleObjectProperty<>(new IngredientViewModel());
|
||||||
|
private ApplicationDataViewModel applicationDataViewModel;
|
||||||
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||||
|
|
||||||
public Label ingredientName;
|
public Label ingredientName;
|
||||||
|
|
@ -51,10 +53,12 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
||||||
@Inject
|
@Inject
|
||||||
public NutritionDetailsCtrl(
|
public NutritionDetailsCtrl(
|
||||||
LocaleManager manager,
|
LocaleManager manager,
|
||||||
ServerUtils server
|
ServerUtils server,
|
||||||
|
ApplicationDataViewModel applicationDataViewModel
|
||||||
) {
|
) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.applicationDataViewModel = applicationDataViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -77,6 +81,9 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
||||||
Ingredient newIngredient = server.updateIngredient(updateIngredient());
|
Ingredient newIngredient = server.updateIngredient(updateIngredient());
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
this.ingredient.get().updateFrom(newIngredient);
|
this.ingredient.get().updateFrom(newIngredient);
|
||||||
|
applicationDataViewModel.getIngredientViewById(newIngredient.getId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("error!"))
|
||||||
|
.updateFrom(newIngredient);
|
||||||
logger.info("Updated ingredient to " + newIngredient);
|
logger.info("Updated ingredient to " + newIngredient);
|
||||||
});
|
});
|
||||||
} catch (IllegalInputFormatException e) {
|
} catch (IllegalInputFormatException e) {
|
||||||
|
|
@ -106,7 +113,7 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
private Ingredient updateIngredient() throws IllegalInputFormatException {
|
private Ingredient updateIngredient() throws IllegalInputFormatException {
|
||||||
Ingredient current = this.ingredient.get().toIngredient();
|
Ingredient current = this.ingredient.get().into();
|
||||||
try {
|
try {
|
||||||
double f = Double.parseDouble(this.fatInputElement.getText());
|
double f = Double.parseDouble(this.fatInputElement.getText());
|
||||||
double p = Double.parseDouble(this.proteinInputElement.getText());
|
double p = Double.parseDouble(this.proteinInputElement.getText());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package client.scenes.recipe;
|
package client.scenes.recipe;
|
||||||
|
|
||||||
import client.exception.UpdateException;
|
import client.exception.UpdateException;
|
||||||
|
import client.model.ApplicationDataViewModel;
|
||||||
|
import client.model.RecipeViewModel;
|
||||||
import client.scenes.FoodpalApplicationCtrl;
|
import client.scenes.FoodpalApplicationCtrl;
|
||||||
import client.utils.Config;
|
import client.utils.Config;
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
|
|
@ -18,6 +20,8 @@ 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.property.SimpleObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
|
@ -32,8 +36,8 @@ import javafx.scene.text.Font;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the recipe detail view.
|
* Controller for the recipeViewModel detail view.
|
||||||
* Manages displaying and editing recipe details such as name, ingredients, and
|
* Manages displaying and editing recipeViewModel details such as name, ingredients, and
|
||||||
* steps.
|
* steps.
|
||||||
*/
|
*/
|
||||||
public class RecipeDetailCtrl implements LocaleAware {
|
public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
@ -42,6 +46,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 ApplicationDataViewModel applicationDataViewModel;
|
||||||
|
public Label kcalLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private IngredientListCtrl ingredientListController;
|
private IngredientListCtrl ingredientListController;
|
||||||
|
|
@ -49,7 +55,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
private RecipeStepListCtrl stepListController;
|
private RecipeStepListCtrl stepListController;
|
||||||
|
|
||||||
private Recipe recipe;
|
private SimpleObjectProperty<RecipeViewModel> recipeViewModel = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecipeDetailCtrl(
|
public RecipeDetailCtrl(
|
||||||
|
|
@ -57,12 +63,14 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
ServerUtils server,
|
ServerUtils server,
|
||||||
FoodpalApplicationCtrl appCtrl,
|
FoodpalApplicationCtrl appCtrl,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
WebSocketDataService<Long, Recipe> webSocketDataService) {
|
WebSocketDataService<Long, Recipe> webSocketDataService,
|
||||||
|
ApplicationDataViewModel applicationDataViewModel) {
|
||||||
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.applicationDataViewModel = applicationDataViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
|
@ -92,9 +100,9 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method for a frequently needed operation to retrieve the
|
* 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<Recipe> getSelectedRecipe() {
|
private Optional<Recipe> getSelectedRecipe() {
|
||||||
return Optional.ofNullable(
|
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.
|
* @throws InterruptedException Upon request interruption.
|
||||||
*
|
*
|
||||||
* @see FoodpalApplicationCtrl#refresh()
|
* @see FoodpalApplicationCtrl#refresh()
|
||||||
*/
|
*/
|
||||||
private void refresh() throws IOException, InterruptedException {
|
public void refresh() throws IOException, InterruptedException {
|
||||||
this.appCtrl.refresh();
|
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.
|
* @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) {
|
public void setCurrentlyViewedRecipe(Recipe recipe) {
|
||||||
if (recipe == null) {
|
if (recipe == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recipe = recipe;
|
this.recipeViewModel.set(new RecipeViewModel(recipe));
|
||||||
|
|
||||||
this.showName(recipe.getName());
|
this.showName(recipe.getName());
|
||||||
this.ingredientListController.refetchFromRecipe(recipe);
|
this.ingredientListController.refetchFromRecipe(recipe);
|
||||||
this.stepListController.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
|
* Create a callback that takes in the selected recipeViewModel and a helper type
|
||||||
* and updates the recipe based on that.
|
* and updates the recipeViewModel based on that.
|
||||||
* Also, posts the update to the server.
|
* 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
|
* how to update it
|
||||||
* @return The created callback to use in a setUpdateCallback or related
|
* @return The created callback to use in a setUpdateCallback or related
|
||||||
* function
|
* function
|
||||||
|
|
@ -165,7 +172,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
return recipes -> {
|
return recipes -> {
|
||||||
Recipe selectedRecipe = this.getSelectedRecipe().orElseThrow(
|
Recipe selectedRecipe = this.getSelectedRecipe().orElseThrow(
|
||||||
() -> new NullPointerException(
|
() -> new NullPointerException(
|
||||||
"Null recipe whereas ingredients are edited"));
|
"Null recipeViewModel whereas ingredients are edited"));
|
||||||
|
|
||||||
recipeConsumer.accept(selectedRecipe, recipes);
|
recipeConsumer.accept(selectedRecipe, recipes);
|
||||||
|
|
||||||
|
|
@ -174,9 +181,10 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
webSocketDataService.add(selectedRecipe.getId(), recipe -> {
|
webSocketDataService.add(selectedRecipe.getId(), recipe -> {
|
||||||
int idx = getParentRecipeList().getItems().indexOf(selectedRecipe);
|
int idx = getParentRecipeList().getItems().indexOf(selectedRecipe);
|
||||||
getParentRecipeList().getItems().set(idx, recipe);
|
getParentRecipeList().getItems().set(idx, recipe);
|
||||||
|
recipeViewModel.get().updateFrom(recipe);
|
||||||
});
|
});
|
||||||
} catch (IOException | InterruptedException e) {
|
} 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);
|
selectedRecipe);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -190,15 +198,16 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
// Initialize callback for ingredient list updates
|
// Initialize callback for ingredient list updates
|
||||||
this.ingredientListController.setUpdateCallback(
|
this.ingredientListController.setUpdateCallback(
|
||||||
this.createUpdateRecipeCallback(Recipe::setIngredients));
|
this.createUpdateRecipeCallback(Recipe::setIngredients)
|
||||||
|
);
|
||||||
// Ditto, for step list updates
|
// Ditto, for step list updates
|
||||||
this.stepListController.setUpdateCallback(
|
this.stepListController.setUpdateCallback(
|
||||||
this.createUpdateRecipeCallback(Recipe::setPreparationSteps));
|
this.createUpdateRecipeCallback(Recipe::setPreparationSteps));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revised edit recipe control flow, deprecates the use of a separate
|
* Revised edit recipeViewModel control flow, deprecates the use of a separate
|
||||||
* AddNameCtrl. This is automagically called when a new recipe is created,
|
* AddNameCtrl. This is automagically called when a new recipeViewModel is created,
|
||||||
* making for a more seamless UX.
|
* making for a more seamless UX.
|
||||||
* Public for reference by ApplicationCtrl.
|
* Public for reference by ApplicationCtrl.
|
||||||
*/
|
*/
|
||||||
|
|
@ -213,14 +222,15 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
String newName = edit.getText();
|
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 {
|
try {
|
||||||
server.updateRecipe(this.recipe);
|
server.updateRecipe(r);
|
||||||
// this.refresh();
|
// this.refresh();
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
// throw a nice blanket UpdateException
|
// 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());
|
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()}.
|
* Internally calls {@link FoodpalApplicationCtrl#removeSelectedRecipe()}.
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
|
|
@ -241,17 +251,17 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the favourite button state based on whether the currently viewed
|
* 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() {
|
public void refreshFavouriteButton() {
|
||||||
if (recipe == null) {
|
if (recipeViewModel == null || recipeViewModel.get() == null) {
|
||||||
favouriteButton.setDisable(true);
|
favouriteButton.setDisable(true);
|
||||||
favouriteButton.setText("☆");
|
favouriteButton.setText("☆");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
favouriteButton.setDisable(false);
|
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
|
@FXML
|
||||||
private void printRecipe() {
|
private void printRecipe() {
|
||||||
if (recipe == null) {
|
if (recipeViewModel == null) {
|
||||||
return; // Do nothing if no recipe selected
|
return; // Do nothing if no recipeViewModel selected
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open directory chooser
|
// Open directory chooser
|
||||||
|
|
@ -278,9 +288,9 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for filename
|
// Ask for filename
|
||||||
TextInputDialog dialog = new TextInputDialog(recipe.getName() + ".txt");
|
TextInputDialog dialog = new TextInputDialog(recipeViewModel.get().getReal().getName() + ".txt");
|
||||||
dialog.setTitle("Save Recipe");
|
dialog.setTitle("Save Recipe");
|
||||||
dialog.setHeaderText("Enter filename for the recipe");
|
dialog.setHeaderText("Enter filename for the recipeViewModel");
|
||||||
dialog.setContentText("Filename:");
|
dialog.setContentText("Filename:");
|
||||||
|
|
||||||
Optional<String> result = dialog.showAndWait();
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
|
@ -292,18 +302,18 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use PrintExportService methods
|
// Use PrintExportService methods
|
||||||
String recipeText = PrintExportService.buildRecipeText(recipe);
|
String recipeText = PrintExportService.buildRecipeText(recipeViewModel.get().getReal());
|
||||||
Path dirPath = selectedDirectory.toPath();
|
Path dirPath = selectedDirectory.toPath();
|
||||||
PrintExportService.exportToFile(recipeText, dirPath, filename);
|
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.
|
* application configuration and writes the changes to disk.
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void toggleFavourite() {
|
private void toggleFavourite() {
|
||||||
long id = this.recipe.getId();
|
long id = this.recipeViewModel.get().getReal().getId();
|
||||||
|
|
||||||
Config config = this.getConfig();
|
Config config = this.getConfig();
|
||||||
if (config.isFavourite(id)) {
|
if (config.isFavourite(id)) {
|
||||||
|
|
@ -337,16 +347,16 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch the recipe's language.
|
* Switch the recipeViewModel's language.
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
void changeLanguage() {
|
void changeLanguage() {
|
||||||
recipe.setLocale(this.langSelector.getValue());
|
recipeViewModel.get().getReal().setLocale(this.langSelector.getValue());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
server.updateRecipe(this.recipe);
|
server.updateRecipe(this.recipeViewModel.get().getReal());
|
||||||
} catch (IOException | InterruptedException e) {
|
} 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
|
@Override
|
||||||
public void initializeComponents() {
|
public void initializeComponents() {
|
||||||
initStepsIngredientsList();
|
initStepsIngredientsList();
|
||||||
|
recipeViewModel.addListener((_, ov, nv) -> {
|
||||||
|
if (nv != null) {
|
||||||
|
kcalLabel.textProperty().bind(recipeViewModel.get().kcalProperty().asString("%.1f kcal"));
|
||||||
|
}
|
||||||
|
});
|
||||||
langSelector.getItems().addAll("en", "nl", "pl");
|
langSelector.getItems().addAll("en", "nl", "pl");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,5 @@
|
||||||
<!-- Preparation -->
|
<!-- Preparation -->
|
||||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||||
|
<Label>Total energy in this recipe: </Label><Label fx:id="kcalLabel" /><Label>kcal</Label>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
||||||
|
|
@ -76,4 +76,9 @@ public class FormalIngredient extends RecipeIngredient implements Scalable<Forma
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(super.hashCode(), amount, unitSuffix);
|
return Objects.hash(super.hashCode(), amount, unitSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double kcal() {
|
||||||
|
return ingredient.kcalPer100g() * amountInBaseUnit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import java.util.Objects;
|
||||||
// TABLE named recipes
|
// TABLE named recipes
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "recipes")
|
@Table(name = "recipes")
|
||||||
public class Recipe {
|
public class Recipe implements Scalable<Recipe> {
|
||||||
|
|
||||||
// PRIMARY Key, unique id for recipe, generated automatically.
|
// PRIMARY Key, unique id for recipe, generated automatically.
|
||||||
@Id
|
@Id
|
||||||
|
|
@ -191,5 +191,16 @@ public class Recipe {
|
||||||
", preparationSteps=" + preparationSteps +
|
", preparationSteps=" + preparationSteps +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
public double kcal() {
|
||||||
|
return this.ingredients.stream().mapToDouble(RecipeIngredient::kcal).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Recipe scaleBy(double factor) {
|
||||||
|
return new Recipe(id, name, locale, ingredients.stream().map(ri -> switch (ri) {
|
||||||
|
case FormalIngredient f -> f.scaleBy(factor);
|
||||||
|
case VagueIngredient v -> v;
|
||||||
|
default -> throw new IllegalArgumentException("Unexpected child class of RecipeIngredient!");
|
||||||
|
}).toList(), preparationSteps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,4 +92,5 @@ public abstract class RecipeIngredient {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(id, ingredient);
|
return Objects.hash(id, ingredient);
|
||||||
}
|
}
|
||||||
|
public abstract double kcal();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package commons;
|
package commons;
|
||||||
|
|
||||||
public interface Scalable<IngredientType extends RecipeIngredient> {
|
public interface Scalable<T> {
|
||||||
default IngredientType scaleBy(int numPortions) {
|
default T scaleBy(int numPortions) {
|
||||||
return scaleBy((double) numPortions);
|
return scaleBy((double) numPortions);
|
||||||
};
|
};
|
||||||
IngredientType scaleBy(double factor);
|
T scaleBy(double factor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,4 +50,9 @@ public class VagueIngredient extends RecipeIngredient {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode(description);
|
return Objects.hashCode(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double kcal() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue