diff --git a/client/pom.xml b/client/pom.xml
index b9eb186..ebbef30 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -21,7 +21,13 @@
commons
0.0.1-SNAPSHOT
-
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.17
+ compile
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/client/src/main/java/client/MyModule.java b/client/src/main/java/client/MyModule.java
index e40e613..14eaa3e 100644
--- a/client/src/main/java/client/MyModule.java
+++ b/client/src/main/java/client/MyModule.java
@@ -21,8 +21,10 @@ import client.scenes.nutrition.NutritionDetailsCtrl;
import client.scenes.nutrition.NutritionViewCtrl;
import client.scenes.recipe.IngredientListCtrl;
import client.scenes.recipe.RecipeStepListCtrl;
-import client.service.NonFunctionalShoppingListService;
+import client.scenes.shopping.ShoppingListCtrl;
+import client.scenes.shopping.ShoppingListNewItemPromptCtrl;
import client.service.ShoppingListService;
+import client.service.ShoppingListServiceImpl;
import client.service.ShoppingListViewModel;
import client.utils.ConfigService;
import client.utils.LocaleManager;
@@ -60,8 +62,10 @@ public class MyModule implements Module {
binder.bind(new TypeLiteral>() {}).toInstance(
new WebSocketDataService<>()
);
+ binder.bind(ShoppingListNewItemPromptCtrl.class).in(Scopes.SINGLETON);
+ binder.bind(ShoppingListCtrl.class).in(Scopes.SINGLETON);
binder.bind(ShoppingListViewModel.class).toInstance(new ShoppingListViewModel());
- binder.bind(ShoppingListService.class).to(NonFunctionalShoppingListService.class);
+ binder.bind(ShoppingListService.class).to(ShoppingListServiceImpl.class);
binder.bind(new TypeLiteral>() {}).toInstance(
new WebSocketDataService<>()
);
diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java
index ab60161..181279b 100644
--- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java
+++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java
@@ -7,10 +7,12 @@ import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;
+import client.UI;
import client.exception.InvalidModificationException;
import client.scenes.nutrition.NutritionViewCtrl;
import client.scenes.recipe.RecipeDetailCtrl;
+import client.scenes.shopping.ShoppingListCtrl;
import client.utils.Config;
import client.utils.ConfigService;
import client.utils.DefaultValueFactory;
@@ -34,8 +36,6 @@ import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
@@ -515,11 +515,10 @@ public class FoodpalApplicationCtrl implements LocaleAware {
}
public void openShoppingListWindow() throws IOException {
- FXMLLoader loader = new FXMLLoader(getClass().getResource("shopping/ShoppingList.fxml"));
- Parent root = (Parent)loader.load();
+ var root = UI.getFXML().load(ShoppingListCtrl.class, "client", "scenes", "shopping", "ShoppingList.fxml");
Stage stage = new Stage();
stage.setTitle(this.getLocaleString("menu.shopping.title"));
- stage.setScene(new Scene(root));
+ stage.setScene(new Scene(root.getValue()));
stage.show();
}
diff --git a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
index f1eeb5d..04140d1 100644
--- a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
+++ b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
@@ -2,6 +2,7 @@ package client.scenes.recipe;
import client.exception.UpdateException;
import client.scenes.FoodpalApplicationCtrl;
+import client.service.ShoppingListService;
import client.utils.Config;
import client.utils.ConfigService;
import client.utils.LocaleAware;
@@ -10,6 +11,7 @@ import client.utils.PrintExportService;
import client.utils.server.ServerUtils;
import client.utils.WebSocketDataService;
import com.google.inject.Inject;
+import commons.FormalIngredient;
import commons.Recipe;
import java.io.File;
@@ -20,6 +22,7 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javafx.beans.binding.Bindings;
+import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
@@ -46,6 +49,7 @@ public class RecipeDetailCtrl implements LocaleAware {
private final FoodpalApplicationCtrl appCtrl;
private final ConfigService configService;
private final WebSocketDataService webSocketDataService;
+ private final ShoppingListService shoppingListService;
public Spinner scaleSpinner;
public Label inferredKcalLabel;
@@ -67,12 +71,14 @@ public class RecipeDetailCtrl implements LocaleAware {
ServerUtils server,
FoodpalApplicationCtrl appCtrl,
ConfigService configService,
+ ShoppingListService listService,
WebSocketDataService webSocketDataService) {
this.localeManager = localeManager;
this.server = server;
this.appCtrl = appCtrl;
this.configService = configService;
this.webSocketDataService = webSocketDataService;
+ this.shoppingListService = listService;
}
@FXML
@@ -418,4 +424,13 @@ public class RecipeDetailCtrl implements LocaleAware {
});
langSelector.getItems().addAll("en", "nl", "pl", "tok");
}
+
+ public void handleAddAllToShoppingList(ActionEvent actionEvent) {
+ System.out.println("handleAddAllToShoppingList");
+ // TODO BACKLOG Add overview screen
+ recipe.getIngredients().stream()
+ .filter(x -> x.getClass().equals(FormalIngredient.class))
+ .map(FormalIngredient.class::cast)
+ .forEach(x -> shoppingListService.putIngredient(x, recipe));
+ }
}
diff --git a/client/src/main/java/client/scenes/shopping/ShoppingListCell.java b/client/src/main/java/client/scenes/shopping/ShoppingListCell.java
new file mode 100644
index 0000000..d684435
--- /dev/null
+++ b/client/src/main/java/client/scenes/shopping/ShoppingListCell.java
@@ -0,0 +1,67 @@
+package client.scenes.shopping;
+
+import client.scenes.recipe.OrderedEditableListCell;
+import commons.FormalIngredient;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.control.Spinner;
+import javafx.scene.control.SpinnerValueFactory;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.HBox;
+import javafx.util.Pair;
+
+import java.util.Optional;
+
+public class ShoppingListCell extends OrderedEditableListCell>> {
+ private Node makeEditor() {
+ HBox editor = new HBox();
+ Spinner amountInput = new Spinner<>();
+ FormalIngredient ingredient = getItem().getKey();
+ amountInput.setEditable(true);
+ amountInput.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, ingredient.getAmount()));
+ Label textLabel = new Label(getItem().getKey().getUnitSuffix() + " of " + getItem().getKey().getIngredient().getName());
+ editor.getChildren().addAll(amountInput, textLabel);
+ editor.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
+ if (e.getCode() != KeyCode.ENTER) {
+ return;
+ }
+ Pair> pair = getItem();
+ pair.getKey().setAmount(amountInput.getValue());
+ commitEdit(pair);
+ });
+ return editor;
+ }
+ @Override
+ public void startEdit() {
+ super.startEdit();
+ this.setText("");
+ this.setGraphic(makeEditor());
+ }
+ @Override
+ protected void updateItem(Pair> item, boolean empty) {
+ super.updateItem(item, empty);
+
+ if (empty) {
+ this.setText("");
+ return;
+ }
+
+ String display = item.getKey().toString() +
+ item.getValue().map(recipe -> {
+ return " (" + recipe + ")";
+ }).orElse("");
+
+ this.setText(display);
+ }
+ @Override
+ public void cancelEdit() {
+ super.cancelEdit();
+ }
+
+ @Override
+ public void commitEdit(Pair> newValue) {
+ super.commitEdit(newValue);
+ }
+
+}
diff --git a/client/src/main/java/client/scenes/shopping/ShoppingListCtrl.java b/client/src/main/java/client/scenes/shopping/ShoppingListCtrl.java
index b73ec9d..ec08c62 100644
--- a/client/src/main/java/client/scenes/shopping/ShoppingListCtrl.java
+++ b/client/src/main/java/client/scenes/shopping/ShoppingListCtrl.java
@@ -1,13 +1,19 @@
package client.scenes.shopping;
+import client.UI;
import client.service.ShoppingListService;
import client.utils.LocaleAware;
import client.utils.LocaleManager;
import com.google.inject.Inject;
import commons.FormalIngredient;
+import javafx.event.ActionEvent;
import javafx.fxml.FXML;
-import javafx.scene.control.ListCell;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
import javafx.scene.control.ListView;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
import javafx.util.Pair;
import java.util.Optional;
@@ -19,7 +25,6 @@ public class ShoppingListCtrl implements LocaleAware {
@FXML
private ListView>> shoppingListView;
-
@Inject
public ShoppingListCtrl(
ShoppingListService shopping,
@@ -40,27 +45,37 @@ public class ShoppingListCtrl implements LocaleAware {
}
public void initializeComponents() {
- this.shoppingListView.setCellFactory(l -> new ListCell<>() {
- @Override
- protected void updateItem(Pair> item, boolean empty) {
- super.updateItem(item, empty);
-
- if (empty) {
- this.setText("");
- return;
- }
-
- String display = item.getKey().toString() +
- item.getValue().map(recipe -> {
- return " (" + recipe + ")";
- }).orElse("");
-
- this.setText(display);
- }
- });
-
+ this.shoppingListView.setEditable(true);
+ this.shoppingListView.setCellFactory(l -> new ShoppingListCell());
this.shoppingListView.getItems().setAll(
this.shopping.getItems()
);
}
+ private void refreshList() {
+ this.shoppingListView.getItems().setAll(
+ this.shopping.getItems()
+ );
+ }
+
+ public void handleAddItem(ActionEvent actionEvent) {
+ Stage stage = new Stage();
+ Pair root = UI.getFXML().load(ShoppingListNewItemPromptCtrl.class,
+ "client", "scenes", "shopping", "ShoppingListItemAddModal.fxml");
+ root.getKey().setNewValueConsumer(fi -> {
+ this.shopping.putIngredient(fi);
+ refreshList();
+ });
+ stage.setScene(new Scene(root.getValue()));
+ stage.setTitle("My modal window");
+ stage.initModality(Modality.WINDOW_MODAL);
+ stage.initOwner(
+ ((Node)actionEvent.getSource()).getScene().getWindow() );
+ stage.show();
+ }
+
+ public void handleRemoveItem(ActionEvent actionEvent) {
+ var x = this.shoppingListView.getSelectionModel().getSelectedItem();
+ this.shopping.getItems().remove(x);
+ refreshList();
+ }
}
diff --git a/client/src/main/java/client/scenes/shopping/ShoppingListNewItemPromptCtrl.java b/client/src/main/java/client/scenes/shopping/ShoppingListNewItemPromptCtrl.java
new file mode 100644
index 0000000..09bd44f
--- /dev/null
+++ b/client/src/main/java/client/scenes/shopping/ShoppingListNewItemPromptCtrl.java
@@ -0,0 +1,96 @@
+package client.scenes.shopping;
+
+import client.utils.LocaleAware;
+import client.utils.LocaleManager;
+import client.utils.server.ServerUtils;
+import com.google.inject.Inject;
+import commons.FormalIngredient;
+import commons.Ingredient;
+import commons.Unit;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.event.ActionEvent;
+import javafx.scene.control.MenuButton;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.Spinner;
+import javafx.scene.control.SpinnerValueFactory;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class ShoppingListNewItemPromptCtrl implements LocaleAware {
+ public MenuButton ingredientSelection;
+ private final ObjectProperty selected = new SimpleObjectProperty<>();
+ private final ObjectProperty selectedUnit = new SimpleObjectProperty<>();
+ private final ServerUtils server;
+ private final LocaleManager localeManager;
+ private Consumer newValueConsumer;
+ public MenuButton unitSelect;
+ public Spinner amountSelect;
+
+ @Inject
+ public ShoppingListNewItemPromptCtrl(ServerUtils server, LocaleManager localeManager) {
+ this.server = server;
+ this.localeManager = localeManager;
+ }
+
+ public void setNewValueConsumer(Consumer consumer) {
+ this.newValueConsumer = consumer;
+ }
+
+ public void confirmAdd(ActionEvent actionEvent) {
+ if (selected.get() == null || selectedUnit.get() == null) {
+ System.err.println("You must select both an ingredient and an unit");
+ return;
+ }
+ FormalIngredient fi = new FormalIngredient(selected.get(), amountSelect.getValue(), selectedUnit.get().suffix);
+ newValueConsumer.accept(fi);
+ Stage stage = (Stage) ingredientSelection.getScene().getWindow();
+ stage.close();
+ }
+
+ public void cancelAdd(ActionEvent actionEvent) {
+ }
+ private void makeMenuItems(
+ MenuButton menu,
+ Iterable items,
+ Function labelMapper,
+ Consumer onSelect) {
+ // Iterates over the list of items and applies the label and onSelect handlers.
+ for (T item : items) {
+ MenuItem mi = new MenuItem();
+ mi.setText(labelMapper.apply(item));
+ mi.setOnAction(_ -> {
+ menu.setText(labelMapper.apply(item));
+ onSelect.accept(item);
+ });
+ menu.getItems().add(mi);
+ }
+ }
+ @Override
+ public void updateText() {
+
+ }
+ @Override
+ public void initializeComponents() {
+ try {
+ amountSelect.setValueFactory(
+ new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, 0));
+ amountSelect.setEditable(true);
+ makeMenuItems(ingredientSelection, server.getIngredients(), Ingredient::getName, selected::set);
+ makeMenuItems(unitSelect,
+ Arrays.stream(Unit.values()).filter(u -> u.formal).toList(),
+ Unit::toString, selectedUnit::set);
+ } catch (IOException | InterruptedException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ @Override
+ public LocaleManager getLocaleManager() {
+ return localeManager;
+ }
+}
diff --git a/client/src/main/java/client/service/ShoppingListItem.java b/client/src/main/java/client/service/ShoppingListItem.java
new file mode 100644
index 0000000..91dd98a
--- /dev/null
+++ b/client/src/main/java/client/service/ShoppingListItem.java
@@ -0,0 +1,6 @@
+package client.service;
+
+import commons.FormalIngredient;
+public class ShoppingListItem {
+ private FormalIngredient i;
+}
diff --git a/client/src/main/java/client/service/ShoppingListService.java b/client/src/main/java/client/service/ShoppingListService.java
index f757165..04ef26b 100644
--- a/client/src/main/java/client/service/ShoppingListService.java
+++ b/client/src/main/java/client/service/ShoppingListService.java
@@ -14,6 +14,15 @@ public abstract class ShoppingListService {
public ShoppingListService(ShoppingListViewModel viewModel) {
this.viewModel = viewModel;
}
+
+ public ShoppingListViewModel getViewModel() {
+ return viewModel;
+ }
+
+ public void setViewModel(ShoppingListViewModel viewModel) {
+ this.viewModel = viewModel;
+ }
+
public abstract void putIngredient(FormalIngredient ingredient);
public abstract void putIngredient(FormalIngredient ingredient, Recipe recipe);
public abstract void putIngredient(FormalIngredient ingredient, String recipeName);
diff --git a/client/src/main/java/client/service/ShoppingListServiceImpl.java b/client/src/main/java/client/service/ShoppingListServiceImpl.java
new file mode 100644
index 0000000..9941ee6
--- /dev/null
+++ b/client/src/main/java/client/service/ShoppingListServiceImpl.java
@@ -0,0 +1,71 @@
+package client.service;
+
+import com.google.inject.Inject;
+import commons.FormalIngredient;
+import commons.Recipe;
+import javafx.util.Pair;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+public class ShoppingListServiceImpl extends ShoppingListService {
+ private final Logger logger = Logger.getLogger(ShoppingListServiceImpl.class.getName());
+ @Inject
+ public ShoppingListServiceImpl(
+ ShoppingListViewModel model
+ ) {
+ super(model);
+ }
+
+ @Override
+ public void putIngredient(FormalIngredient ingredient) {
+ getViewModel().getListItems().add(new Pair<>(ingredient, Optional.empty()));
+ }
+
+ @Override
+ public void putIngredient(FormalIngredient ingredient, Recipe recipe) {
+ Pair> val = new Pair<>(ingredient, Optional.of(recipe.getName()));
+ logger.info("putting ingredients into shopping list: " + val);
+ getViewModel().getListItems().add(val);
+ }
+
+ @Override
+ public void putIngredient(FormalIngredient ingredient, String recipeName) {
+ getViewModel().getListItems().add(new Pair<>(ingredient, Optional.of(recipeName)));
+ }
+
+ @Override
+ public void putArbitraryItem(String name) {
+
+ }
+
+ @Override
+ public FormalIngredient purgeIngredient(Long id) {
+ return null;
+ }
+
+ @Override
+ public FormalIngredient purgeIngredient(String ingredientName) {
+ FormalIngredient fi = getViewModel().getListItems().stream()
+ .filter(i ->
+ i.getKey().getIngredient().getName().equals(ingredientName))
+ .findFirst().orElseThrow(NullPointerException::new).getKey();
+ return null;
+ }
+
+ @Override
+ public void reset() {
+ getViewModel().getListItems().clear();
+ }
+
+ @Override
+ public List>> getItems() {
+ return getViewModel().getListItems();
+ }
+
+ @Override
+ public String makePrintable() {
+ return "TODO";
+ }
+}
diff --git a/client/src/main/java/client/service/ShoppingListViewModel.java b/client/src/main/java/client/service/ShoppingListViewModel.java
index 30e03df..aaaa237 100644
--- a/client/src/main/java/client/service/ShoppingListViewModel.java
+++ b/client/src/main/java/client/service/ShoppingListViewModel.java
@@ -4,6 +4,7 @@ import commons.FormalIngredient;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
import javafx.util.Pair;
import org.apache.commons.lang3.NotImplementedException;
@@ -19,4 +20,7 @@ public class ShoppingListViewModel {
throw new NotImplementedException();
}
+ public ObservableList>> getListItems() {
+ return listItems.get();
+ }
}
diff --git a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
index 28ba793..602b827 100644
--- a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
+++ b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
@@ -25,6 +25,7 @@
+
diff --git a/client/src/main/resources/client/scenes/shopping/ShoppingList.fxml b/client/src/main/resources/client/scenes/shopping/ShoppingList.fxml
index 314caf3..272e30d 100644
--- a/client/src/main/resources/client/scenes/shopping/ShoppingList.fxml
+++ b/client/src/main/resources/client/scenes/shopping/ShoppingList.fxml
@@ -4,12 +4,20 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/main/resources/client/scenes/shopping/ShoppingListItemAddModal.fxml b/client/src/main/resources/client/scenes/shopping/ShoppingListItemAddModal.fxml
new file mode 100644
index 0000000..aee4c20
--- /dev/null
+++ b/client/src/main/resources/client/scenes/shopping/ShoppingListItemAddModal.fxml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Unit...
+ Your ingredient...
+
+
+
+
+
+
+